Compare commits

...

39 commits

Author SHA1 Message Date
github-actions[bot]
1c217cbbd4 build(webui): update Browserslist db
Some checks failed
Tests / Test server - macos-latest (push) Has been cancelled
Tests / Test server - ubuntu-latest (push) Has been cancelled
Tests / Test server - windows-latest (push) Has been cancelled
Tests / Test webui builds (push) Has been cancelled
2026-07-02 12:11:40 +08:00
github-actions
f35f362265 chore(release): 1.25.0 [skip ci] 2026-06-30 07:05:43 +00:00
Gauthier Roebroeck
4efb161ef0 build(openapi): increase timeout for openapi generation
(cherry picked from commit cea20f9cfa)
2026-06-30 14:41:03 +08:00
Gauthier Roebroeck
275cb9251a fix(openapi): flatten hierchical schemas
(cherry picked from commit eb2aa972d5)
2026-06-30 14:40:50 +08:00
Gauthier Roebroeck
bd8b7731dc fix(kobo): enfore content restriction
Some checks failed
Tests / Test server - macos-latest (push) Has been cancelled
Tests / Test server - ubuntu-latest (push) Has been cancelled
Tests / Test server - windows-latest (push) Has been cancelled
Tests / Test webui builds (push) Has been cancelled
thanks to @5ud0er and @hoangperry
2026-06-25 10:57:45 +08:00
dependabot[bot]
5da5226b35 deps(ci): bump actions/checkout from 6 to 7
Some checks failed
Tests / Test server - macos-latest (push) Has been cancelled
Tests / Test server - ubuntu-latest (push) Has been cancelled
Tests / Test server - windows-latest (push) Has been cancelled
Tests / Test webui builds (push) Has been cancelled
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-23 10:01:18 +08:00
dependabot[bot]
910512c73c deps(webui): bump launch-editor from 2.6.0 to 2.14.1 in /komga-webui
Some checks failed
Tests / Test server - macos-latest (push) Has been cancelled
Tests / Test server - ubuntu-latest (push) Has been cancelled
Tests / Test server - windows-latest (push) Has been cancelled
Tests / Test webui builds (push) Has been cancelled
Bumps [launch-editor](https://github.com/vitejs/launch-editor) from 2.6.0 to 2.14.1.
- [Commits](https://github.com/vitejs/launch-editor/compare/v2.6.0...v2.14.1)

---
updated-dependencies:
- dependency-name: launch-editor
  dependency-version: 2.14.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-04 09:48:04 +08:00
dependabot[bot]
86362b1ae8 deps(webui): bump axios from 1.15.2 to 1.16.0 in /komga-webui
Some checks failed
Tests / Test server - macos-latest (push) Has been cancelled
Tests / Test server - ubuntu-latest (push) Has been cancelled
Tests / Test server - windows-latest (push) Has been cancelled
Tests / Test webui builds (push) Has been cancelled
Bumps [axios](https://github.com/axios/axios) from 1.15.2 to 1.16.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.15.2...v1.16.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.16.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-01 11:39:09 +08:00
dependabot[bot]
f9f402ed55 deps(webui): bump qs from 6.14.2 to 6.15.2 in /komga-webui
Bumps [qs](https://github.com/ljharb/qs) from 6.14.2 to 6.15.2.
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.2...v6.15.2)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.15.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-01 11:38:05 +08:00
github-actions[bot]
9a3bbdd878 build(webui): update Browserslist db 2026-06-01 11:37:50 +08:00
dependabot[bot]
3e9a0eaf2c deps(webui): bump js-cookie from 3.0.5 to 3.0.7 in /komga-webui
Some checks failed
Tests / Test webui builds (push) Has been cancelled
Tests / Test server - macos-latest (push) Has been cancelled
Tests / Test server - ubuntu-latest (push) Has been cancelled
Tests / Test server - windows-latest (push) Has been cancelled
Bumps [js-cookie](https://github.com/js-cookie/js-cookie) from 3.0.5 to 3.0.7.
- [Release notes](https://github.com/js-cookie/js-cookie/releases)
- [Commits](https://github.com/js-cookie/js-cookie/compare/v3.0.5...v3.0.7)

---
updated-dependencies:
- dependency-name: js-cookie
  dependency-version: 3.0.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-22 09:13:36 +08:00
Gauthier Roebroeck
5682a37d0d feat(komga): add support for solid rar4 archives
Some checks failed
Tests / Test server - macos-latest (push) Has been cancelled
Tests / Test server - ubuntu-latest (push) Has been cancelled
Tests / Test server - windows-latest (push) Has been cancelled
Tests / Test webui builds (push) Has been cancelled
2026-05-13 10:38:43 +08:00
Gauthier Roebroeck
ba628f9b4e deps(komga): bump junrar to 7.6.0 2026-05-13 10:38:43 +08:00
dependabot[bot]
be5f88c50e deps(webui): bump fast-uri from 3.1.0 to 3.1.2 in /komga-webui
Some checks failed
Tests / Test server - macos-latest (push) Has been cancelled
Tests / Test server - ubuntu-latest (push) Has been cancelled
Tests / Test server - windows-latest (push) Has been cancelled
Tests / Test webui builds (push) Has been cancelled
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.0 to 3.1.2.
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.0...v3.1.2)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-11 09:16:48 +08:00
dependabot[bot]
0bd8edba9d deps(webui): bump @babel/plugin-transform-modules-systemjs
Bumps [@babel/plugin-transform-modules-systemjs](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-modules-systemjs) from 7.22.11 to 7.29.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.29.4/packages/babel-plugin-transform-modules-systemjs)

---
updated-dependencies:
- dependency-name: "@babel/plugin-transform-modules-systemjs"
  dependency-version: 7.29.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-11 09:16:34 +08:00
dependabot[bot]
737cc56d03 deps(webui): bump axios from 1.15.0 to 1.15.2 in /komga-webui
Bumps [axios](https://github.com/axios/axios) from 1.15.0 to 1.15.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.15.0...v1.15.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.15.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-11 09:14:51 +08:00
github-actions[bot]
ba6cb76b71 build(webui): update Browserslist db
Some checks failed
Tests / Test server - macos-latest (push) Has been cancelled
Tests / Test server - ubuntu-latest (push) Has been cancelled
Tests / Test server - windows-latest (push) Has been cancelled
Tests / Test webui builds (push) Has been cancelled
2026-05-04 10:04:00 +08:00
Gauthier Roebroeck
3d7615e7b4 deps: upgrade Spring Boot to 3.5.14
Some checks failed
Tests / Test server - macos-latest (push) Has been cancelled
Tests / Test server - ubuntu-latest (push) Has been cancelled
Tests / Test server - windows-latest (push) Has been cancelled
Tests / Test webui builds (push) Has been cancelled
2026-04-27 17:16:34 +08:00
github-actions
9c84690a07 chore(release): 1.24.4 [skip ci] 2026-04-24 01:52:15 +00:00
Óliver García Albertos
5d14057736
build(docker): update base image to Ubuntu 26.04 for amd64 and arm64
Some checks failed
Tests / Test server - macos-latest (push) Has been cancelled
Tests / Test server - ubuntu-latest (push) Has been cancelled
Tests / Test server - windows-latest (push) Has been cancelled
Tests / Test webui builds (push) Has been cancelled
2026-04-24 09:14:45 +08:00
James Ward
e3a8cc6b01
fix(api): omit UserDto.ageRestriction instead of returning null
Some checks are pending
Tests / Test server - macos-latest (push) Waiting to run
Tests / Test server - ubuntu-latest (push) Waiting to run
Tests / Test server - windows-latest (push) Waiting to run
Tests / Test webui builds (push) Waiting to run
Closes: 2296
2026-04-23 14:34:05 +08:00
dependabot[bot]
bb60a797e8 deps(webui): bump @xmldom/xmldom from 0.8.12 to 0.8.13 in /komga-webui
Bumps [@xmldom/xmldom](https://github.com/xmldom/xmldom) from 0.8.12 to 0.8.13.
- [Release notes](https://github.com/xmldom/xmldom/releases)
- [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/xmldom/xmldom/compare/0.8.12...0.8.13)

---
updated-dependencies:
- dependency-name: "@xmldom/xmldom"
  dependency-version: 0.8.13
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-23 14:22:19 +08:00
Gauthier Roebroeck
ddfe65dde1 fix(koreader): also accept application/json on Accept header
Some checks failed
Tests / Test server - macos-latest (push) Has been cancelled
Tests / Test server - ubuntu-latest (push) Has been cancelled
Tests / Test server - windows-latest (push) Has been cancelled
Tests / Test webui builds (push) Has been cancelled
2026-04-21 16:33:45 +08:00
Gauthier Roebroeck
4a7d9a6510 fix(kobo): proxy raw request body to kobo store
Some checks failed
Tests / Test server - macos-latest (push) Has been cancelled
Tests / Test server - ubuntu-latest (push) Has been cancelled
Tests / Test server - windows-latest (push) Has been cancelled
Tests / Test webui builds (push) Has been cancelled
Closes: #2289
2026-04-17 16:45:52 +08:00
dependabot[bot]
85edd8b63f deps(webui): bump axios from 1.13.5 to 1.15.0 in /komga-webui
Some checks failed
Tests / Test server - macos-latest (push) Has been cancelled
Tests / Test server - ubuntu-latest (push) Has been cancelled
Tests / Test server - windows-latest (push) Has been cancelled
Tests / Test webui builds (push) Has been cancelled
Bumps [axios](https://github.com/axios/axios) from 1.13.5 to 1.15.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.13.5...v1.15.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.15.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 14:37:46 +08:00
dependabot[bot]
63de6aa9b1 deps(webui): bump follow-redirects in /komga-webui
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.11 to 1.16.0.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.11...v1.16.0)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-version: 1.16.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 14:37:26 +08:00
Gauthier Roebroeck
717ef82a3d fix(opds2): incorrect latest series navigation links
Closes: #2285
2026-04-14 14:28:54 +08:00
Gauthier Roebroeck
7c00661365 fix(opds2): auth logo issue when using base url
Closes: #2285
2026-04-14 14:28:38 +08:00
Gauthier Roebroeck
6c2621b538 ci: update homebrew action
Some checks are pending
Tests / Test server - macos-latest (push) Waiting to run
Tests / Test server - ubuntu-latest (push) Waiting to run
Tests / Test server - windows-latest (push) Waiting to run
Tests / Test webui builds (push) Waiting to run
2026-04-13 13:22:48 +08:00
Gauthier Roebroeck
5fc0b7ef30 fix(epub): some TOC may not be parsed correctly
Some checks are pending
Tests / Test server - macos-latest (push) Waiting to run
Tests / Test server - ubuntu-latest (push) Waiting to run
Tests / Test server - windows-latest (push) Waiting to run
Tests / Test webui builds (push) Waiting to run
2026-04-13 10:13:35 +08:00
Gauthier Roebroeck
dd9c3facd7 deps: bump gradle-git-properties plugin from 2.5.2 to 2.5.7 2026-04-13 10:13:35 +08:00
dependabot[bot]
b77554fa17 deps(webui): bump lodash from 4.17.23 to 4.18.1 in /komga-webui
Some checks failed
Tests / Test server - macos-latest (push) Has been cancelled
Tests / Test server - ubuntu-latest (push) Has been cancelled
Tests / Test server - windows-latest (push) Has been cancelled
Tests / Test webui builds (push) Has been cancelled
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.23 to 4.18.1.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.23...4.18.1)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.18.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-10 11:44:23 +08:00
github-actions[bot]
7e0d680bdf build(webui): update Browserslist db
Some checks failed
Tests / Test server - macos-latest (push) Has been cancelled
Tests / Test server - ubuntu-latest (push) Has been cancelled
Tests / Test server - windows-latest (push) Has been cancelled
Tests / Test webui builds (push) Has been cancelled
2026-04-01 13:57:47 +08:00
dependabot[bot]
5620e3fd67 deps(webui): bump @xmldom/xmldom from 0.8.10 to 0.8.12 in /komga-webui
Bumps [@xmldom/xmldom](https://github.com/xmldom/xmldom) from 0.8.10 to 0.8.12.
- [Release notes](https://github.com/xmldom/xmldom/releases)
- [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/xmldom/xmldom/compare/0.8.10...0.8.12)

---
updated-dependencies:
- dependency-name: "@xmldom/xmldom"
  dependency-version: 0.8.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-01 09:21:46 +08:00
dependabot[bot]
3ca9806e86 deps(ci): bump gradle/actions from 5 to 6
Some checks failed
Tests / Test server - macos-latest (push) Has been cancelled
Tests / Test server - ubuntu-latest (push) Has been cancelled
Tests / Test server - windows-latest (push) Has been cancelled
Tests / Test webui builds (push) Has been cancelled
Bumps [gradle/actions](https://github.com/gradle/actions) from 5 to 6.
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/v5...v6)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 15:51:54 +08:00
dependabot[bot]
263d3dd893 deps(webui): bump brace-expansion from 1.1.12 to 1.1.13 in /komga-webui
Some checks are pending
Tests / Test server - macos-latest (push) Waiting to run
Tests / Test server - ubuntu-latest (push) Waiting to run
Tests / Test server - windows-latest (push) Waiting to run
Tests / Test webui builds (push) Waiting to run
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.12 to 1.1.13.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v1.1.12...v1.1.13)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.13
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 10:50:15 +08:00
github-actions
328a22064d chore(release): 1.24.3 [skip ci] 2026-03-27 08:39:06 +00:00
dependabot[bot]
40dadcb8ca deps(webui): bump node-forge from 1.3.2 to 1.4.0 in /komga-webui
Bumps [node-forge](https://github.com/digitalbazaar/forge) from 1.3.2 to 1.4.0.
- [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md)
- [Commits](https://github.com/digitalbazaar/forge/compare/v1.3.2...v1.4.0)

---
updated-dependencies:
- dependency-name: node-forge
  dependency-version: 1.4.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-27 16:25:37 +08:00
Gauthier Roebroeck
b8ea79eaf8 fix(webui): epub reader would not load 2026-03-27 16:17:14 +08:00
31 changed files with 1660 additions and 1971 deletions

View file

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Configure git

View file

@ -43,12 +43,12 @@ jobs:
version_next: ${{ steps.versions.outputs.version_next }}
should_release: ${{ steps.versions.outputs.should_release }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
- name: Install svu
run: brew install --cask caarlos0/tap/svu
- name: Compute next version for release
@ -72,7 +72,7 @@ jobs:
sudo rm -rf /usr/share/dotnet
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
@ -117,7 +117,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
uses: gradle/actions/setup-gradle@v6
- name: Build
run: ./gradlew :komga:prepareThymeLeaf :komga:bootJar :komga-tray:jar

View file

@ -19,7 +19,7 @@ jobs:
fail-fast: false
name: Test server - ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Setup Java 21
uses: actions/setup-java@v5
with:
@ -28,7 +28,7 @@ jobs:
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
uses: gradle/actions/setup-gradle@v6
- name: Build
run: ./gradlew build :komga-tray:jar
@ -78,7 +78,7 @@ jobs:
runs-on: ubuntu-latest
name: Test webui builds
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'

View file

@ -1,3 +1,94 @@
# [1.25.0](https://github.com/gotson/komga/compare/1.24.4...1.25.0) (2026-06-30)
## 🚀 Features
**komga**
- add support for solid rar4 archives ([5682a37](https://github.com/gotson/komga/commits/5682a37))
## 🐛 Fixes
**kobo**
- enfore content restriction ([bd8b773](https://github.com/gotson/komga/commits/bd8b773))
**openapi**
- flatten hierchical schemas ([275cb92](https://github.com/gotson/komga/commits/275cb92))
## 🛠 Build
**openapi**
- increase timeout for openapi generation ([4efb161](https://github.com/gotson/komga/commits/4efb161))
**webui**
- update Browserslist db ([9a3bbdd](https://github.com/gotson/komga/commits/9a3bbdd))
- update Browserslist db ([ba6cb76](https://github.com/gotson/komga/commits/ba6cb76))
## ⚙️ Dependencies
**ci**
- bump actions/checkout from 6 to 7 ([5da5226](https://github.com/gotson/komga/commits/5da5226))
**komga**
- bump junrar to 7.6.0 ([ba628f9](https://github.com/gotson/komga/commits/ba628f9))
**webui**
- bump launch-editor from 2.6.0 to 2.14.1 in /komga-webui ([910512c](https://github.com/gotson/komga/commits/910512c))
- bump axios from 1.15.2 to 1.16.0 in /komga-webui ([86362b1](https://github.com/gotson/komga/commits/86362b1))
- bump qs from 6.14.2 to 6.15.2 in /komga-webui ([f9f402e](https://github.com/gotson/komga/commits/f9f402e))
- bump js-cookie from 3.0.5 to 3.0.7 in /komga-webui ([3e9a0ea](https://github.com/gotson/komga/commits/3e9a0ea))
- bump fast-uri from 3.1.0 to 3.1.2 in /komga-webui ([be5f88c](https://github.com/gotson/komga/commits/be5f88c))
- bump @babel/plugin-transform-modules-systemjs ([0bd8edb](https://github.com/gotson/komga/commits/0bd8edb))
- bump axios from 1.15.0 to 1.15.2 in /komga-webui ([737cc56](https://github.com/gotson/komga/commits/737cc56))
**unscoped**
- upgrade Spring Boot to 3.5.14 ([3d7615e](https://github.com/gotson/komga/commits/3d7615e))
# [1.24.4](https://github.com/gotson/komga/compare/1.24.3...1.24.4) (2026-04-24)
## 🐛 Fixes
**api**
- omit UserDto.ageRestriction instead of returning null ([e3a8cc6](https://github.com/gotson/komga/commits/e3a8cc6))
**epub**
- some TOC may not be parsed correctly ([5fc0b7e](https://github.com/gotson/komga/commits/5fc0b7e))
**kobo**
- proxy raw request body to kobo store ([4a7d9a6](https://github.com/gotson/komga/commits/4a7d9a6)), closes [#2289](https://github.com/gotson/komga/issues/2289)
**koreader**
- also accept application/json on Accept header ([ddfe65d](https://github.com/gotson/komga/commits/ddfe65d))
**opds2**
- incorrect latest series navigation links ([717ef82](https://github.com/gotson/komga/commits/717ef82)), closes [#2285](https://github.com/gotson/komga/issues/2285)
- auth logo issue when using base url ([7c00661](https://github.com/gotson/komga/commits/7c00661)), closes [#2285](https://github.com/gotson/komga/issues/2285)
## 🛠 Build
**docker**
- update base image to Ubuntu 26.04 for amd64 and arm64 ([5d14057](https://github.com/gotson/komga/commits/5d14057))
**webui**
- update Browserslist db ([7e0d680](https://github.com/gotson/komga/commits/7e0d680))
**unscoped**
- update homebrew action ([6c2621b](https://github.com/gotson/komga/commits/6c2621b))
## ⚙️ Dependencies
**ci**
- bump gradle/actions from 5 to 6 ([3ca9806](https://github.com/gotson/komga/commits/3ca9806))
**webui**
- bump @xmldom/xmldom from 0.8.12 to 0.8.13 in /komga-webui ([bb60a79](https://github.com/gotson/komga/commits/bb60a79))
- bump axios from 1.13.5 to 1.15.0 in /komga-webui ([85edd8b](https://github.com/gotson/komga/commits/85edd8b))
- bump follow-redirects in /komga-webui ([63de6aa](https://github.com/gotson/komga/commits/63de6aa))
- bump lodash from 4.17.23 to 4.18.1 in /komga-webui ([b77554f](https://github.com/gotson/komga/commits/b77554f))
- bump @xmldom/xmldom from 0.8.10 to 0.8.12 in /komga-webui ([5620e3f](https://github.com/gotson/komga/commits/5620e3f))
- bump brace-expansion from 1.1.12 to 1.1.13 in /komga-webui ([263d3dd](https://github.com/gotson/komga/commits/263d3dd))
**unscoped**
- bump gradle-git-properties plugin from 2.5.2 to 2.5.7 ([dd9c3fa](https://github.com/gotson/komga/commits/dd9c3fa))
# [1.24.3](https://github.com/gotson/komga/compare/1.24.2...1.24.3) (2026-03-27)
## 🐛 Fixes
**webui**
- epub reader would not load ([b8ea79e](https://github.com/gotson/komga/commits/b8ea79e))
## ⚙️ Dependencies
**webui**
- bump node-forge from 1.3.2 to 1.4.0 in /komga-webui ([40dadcb](https://github.com/gotson/komga/commits/40dadcb))
# [1.24.2](https://github.com/gotson/komga/compare/1.24.1...1.24.2) (2026-03-26)
## 🐛 Fixes
**docker**

View file

@ -1,2 +1,2 @@
version=1.24.2
version=1.25.0
org.gradle.jvmargs=-Xmx2G

View file

@ -2,9 +2,9 @@
sqliteJdbc = "3.50.2.0"
nightmonkeys = "1.0.0"
twelvemonkeys = "3.12.0"
springboot = "3.5.4"
springboot = "3.5.14"
lucene = "9.9.1" # v10 requires JDK 21
jooq = "3.19.24" # should be aligned with the version provided by Spring Boot
jooq = "3.19.32" # should be aligned with the version provided by Spring Boot
[plugins]
gradleGitProperties = {id = "com.gorylenko.gradle-git-properties", version = "2.5.2"}
gradleGitProperties = {id = "com.gorylenko.gradle-git-properties", version = "2.5.7"}

File diff suppressed because it is too large Load diff

View file

@ -11,16 +11,16 @@
"dependencies": {
"@d-i-t-a/reader": "github:gotson/R2D2BC#fork",
"@w0s/isbn-verify": "^3.1.2",
"axios": "^1.13.5",
"axios": "^1.16.0",
"chart.js": "^2.9.4",
"core-js": "^3.8.3",
"date-fns": "^2.30.0",
"filesize": "^10.0.12",
"js-file-downloader": "^1.1.25",
"language-tags": "^1.0.9",
"lodash": "^4.17.23",
"lodash": "^4.18.1",
"marked": "^15.0.4",
"qs": "^6.14.2",
"qs": "^6.15.2",
"screenfull": "^5.2.0",
"vue": "^2.6.14",
"vue-chartkick": "^0.6.1",

View file

@ -751,7 +751,7 @@ export default Vue.extend({
const fontFamiliesInjectables = this.fontFamiliesAdditional.map(x => ({
type: 'style',
url: new URL(`${urls.origin}api/v1/fonts/resource/${x}/css`, import.meta.url).toString(),
url: new URL(`${urls.origin}api/v1/fonts/resource/${x}/css`).toString(),
fontFamily: x,
}))

View file

@ -78,7 +78,7 @@ dependencies {
implementation("org.apache.tika:tika-core:2.9.1")
implementation("org.apache.commons:commons-compress:1.27.1")
implementation("com.github.junrar:junrar:7.5.5")
implementation("com.github.junrar:junrar:7.6.0")
implementation("com.github.gotson.nightcompress:nightcompress:1.1.1")
implementation("org.apache.pdfbox:pdfbox:3.0.5")
implementation("net.grey-panther:natural-comparator:1.1")
@ -394,6 +394,7 @@ openApi {
args.add("--spring.profiles.active=claim,generate-openapi")
args.add("--server.port=8080")
}
waitTimeInSeconds.set(60)
}
tasks.jacocoTestReport {

View file

@ -5,12 +5,11 @@ COPY assembly/${JAR} application.jar
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted
# amd64 builder
FROM ubuntu:24.10 AS build-amd64
FROM ubuntu:26.04 AS build-amd64
ENV JAVA_HOME=/opt/java/openjdk
COPY --from=eclipse-temurin:23-jre $JAVA_HOME $JAVA_HOME
ENV PATH="${JAVA_HOME}/bin:${PATH}"
RUN sed -i -re 's/([a-z]{2}\.)?archive.ubuntu.com|security.ubuntu.com/old-releases.ubuntu.com/g' /etc/apt/sources.list.d/ubuntu.sources && \
apt -y update && \
RUN apt -y update && \
apt -y install ca-certificates locales libjxl-dev libheif-dev libwebp-dev libarchive-dev wget curl && \
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen && \
locale-gen en_US.UTF-8 && \
@ -20,12 +19,11 @@ RUN sed -i -re 's/([a-z]{2}\.)?archive.ubuntu.com|security.ubuntu.com/old-releas
ENV LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:/usr/lib/x86_64-linux-gnu"
# arm64 builder
FROM ubuntu:24.10 AS build-arm64
FROM ubuntu:26.04 AS build-arm64
ENV JAVA_HOME=/opt/java/openjdk
COPY --from=eclipse-temurin:23-jre $JAVA_HOME $JAVA_HOME
ENV PATH="${JAVA_HOME}/bin:${PATH}"
RUN sed -i -re 's/([a-z]{2}\.)?ports.ubuntu.com\/ubuntu-ports/old-releases.ubuntu.com\/ubuntu/g' /etc/apt/sources.list.d/ubuntu.sources && \
apt -y update && \
RUN apt -y update && \
apt -y install ca-certificates locales libjxl-dev libheif-dev libwebp-dev libarchive-dev wget curl && \
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen && \
locale-gen en_US.UTF-8 && \

File diff suppressed because it is too large Load diff

View file

@ -3,14 +3,24 @@ package org.gotson.komga.domain.model
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.annotation.JsonTypeInfo
import io.swagger.v3.oas.annotations.media.Schema
class SearchCondition {
@Schema(
name = "SearchConditionBook",
oneOf = [AnyOfBook::class, AllOfBook::class, LibraryId::class, ReadListId::class, SeriesId::class, Deleted::class, OneShot::class, Title::class, ReleaseDate::class, Tag::class, NumberSort::class, ReadStatus::class, MediaStatus::class, MediaProfile::class, Author::class, Poster::class],
)
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
sealed interface Book
@Schema(
name = "SearchConditionSeries",
oneOf = [AnyOfSeries::class, AllOfSeries::class, LibraryId::class, CollectionId::class, Deleted::class, Complete::class, OneShot::class, Title::class, TitleSort::class, ReleaseDate::class, Tag::class, SharingLabel::class, Publisher::class, Language::class, Genre::class, AgeRating::class, ReadStatus::class, SeriesStatus::class, Author::class],
)
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
sealed interface Series
@Schema(name = "SearchConditionAnyOfBook")
data class AnyOfBook(
@JsonProperty("anyOf")
val conditions: List<Book>,
@ -18,6 +28,7 @@ class SearchCondition {
constructor(vararg args: Book) : this(args.toList())
}
@Schema(name = "SearchConditionAllOfBook")
data class AllOfBook(
@JsonProperty("allOf")
val conditions: List<Book>,
@ -25,6 +36,7 @@ class SearchCondition {
constructor(vararg args: Book) : this(args.toList())
}
@Schema(name = "SearchConditionAnyOfSeries")
data class AnyOfSeries(
@JsonProperty("anyOf")
val conditions: List<Series>,
@ -32,6 +44,7 @@ class SearchCondition {
constructor(vararg args: Series) : this(args.toList())
}
@Schema(name = "SearchConditionAllOfSeries")
data class AllOfSeries(
@JsonProperty("allOf")
val conditions: List<Series>,
@ -39,118 +52,140 @@ class SearchCondition {
constructor(vararg args: Series) : this(args.toList())
}
@Schema(name = "SearchConditionLibraryId")
data class LibraryId(
@JsonProperty("libraryId")
val operator: SearchOperator.Equality<String>,
) : Book,
Series
@Schema(name = "SearchConditionCollectionId")
data class CollectionId(
@JsonProperty("collectionId")
val operator: SearchOperator.Equality<String>,
) : Series
@Schema(name = "SearchConditionReadListId")
data class ReadListId(
@JsonProperty("readListId")
val operator: SearchOperator.Equality<String>,
) : Book
@Schema(name = "SearchConditionSeriesId")
data class SeriesId(
@JsonProperty("seriesId")
val operator: SearchOperator.Equality<String>,
) : Book
@Schema(name = "SearchConditionDeleted")
data class Deleted(
@JsonProperty("deleted")
val operator: SearchOperator.Boolean,
) : Book,
Series
@Schema(name = "SearchConditionComplete")
data class Complete(
@JsonProperty("complete")
val operator: SearchOperator.Boolean,
) : Series
@Schema(name = "SearchConditionOneShot")
data class OneShot(
@JsonProperty("oneShot")
val operator: SearchOperator.Boolean,
) : Book,
Series
@Schema(name = "SearchConditionTitle")
data class Title(
@JsonProperty("title")
val operator: SearchOperator.StringOp,
) : Book,
Series
@Schema(name = "SearchConditionTitleSort")
data class TitleSort(
@JsonProperty("titleSort")
val operator: SearchOperator.StringOp,
) : Series
@Schema(name = "SearchConditionReleaseDate")
data class ReleaseDate(
@JsonProperty("releaseDate")
val operator: SearchOperator.Date,
) : Book,
Series
@Schema(name = "SearchConditionNumberSort")
data class NumberSort(
@JsonProperty("numberSort")
val operator: SearchOperator.Numeric<Float>,
) : Book
@Schema(name = "SearchConditionTag")
data class Tag(
@JsonProperty("tag")
val operator: SearchOperator.EqualityNullable<String>,
) : Book,
Series
@Schema(name = "SearchConditionSharingLabel")
data class SharingLabel(
@JsonProperty("sharingLabel")
val operator: SearchOperator.EqualityNullable<String>,
) : Series
@Schema(name = "SearchConditionPublisher")
data class Publisher(
@JsonProperty("publisher")
val operator: SearchOperator.Equality<String>,
) : Series
@Schema(name = "SearchConditionLanguage")
data class Language(
@JsonProperty("language")
val operator: SearchOperator.Equality<String>,
) : Series
@Schema(name = "SearchConditionGenre")
data class Genre(
@JsonProperty("genre")
val operator: SearchOperator.EqualityNullable<String>,
) : Series
@Schema(name = "SearchConditionAgeRating")
data class AgeRating(
@JsonProperty("ageRating")
val operator: SearchOperator.NumericNullable<Int>,
) : Series
@Schema(name = "SearchConditionReadStatus")
data class ReadStatus(
@JsonProperty("readStatus")
val operator: SearchOperator.Equality<org.gotson.komga.domain.model.ReadStatus>,
) : Book,
Series
@Schema(name = "SearchConditionMediaStatus")
data class MediaStatus(
@JsonProperty("mediaStatus")
val operator: SearchOperator.Equality<Media.Status>,
) : Book
@Schema(name = "SearchConditionSeriesStatus")
data class SeriesStatus(
@JsonProperty("seriesStatus")
val operator: SearchOperator.Equality<SeriesMetadata.Status>,
) : Series
@Schema(name = "SearchConditionMediaProfile")
data class MediaProfile(
@JsonProperty("mediaProfile")
val operator: SearchOperator.Equality<org.gotson.komga.domain.model.MediaProfile>,
) : Book
@Schema(name = "SearchConditionAuthor")
data class Author(
@JsonProperty("author")
val operator: SearchOperator.Equality<AuthorMatch>,
@ -163,6 +198,7 @@ class SearchCondition {
val role: String? = null,
)
@Schema(name = "SearchConditionPoster")
data class Poster(
@JsonProperty("poster")
val operator: SearchOperator.Equality<PosterMatch>,

View file

@ -2,10 +2,21 @@ package org.gotson.komga.domain.model
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.annotation.JsonTypeName
import io.swagger.v3.oas.annotations.media.DiscriminatorMapping
import io.swagger.v3.oas.annotations.media.Schema
import java.time.Duration
import java.time.ZonedDateTime
class SearchOperator {
@Schema(
name = "SearchOperatorEquality",
discriminatorProperty = "operator",
oneOf = [ Is::class, IsNot::class],
discriminatorMapping = [
DiscriminatorMapping("is", Is::class),
DiscriminatorMapping("isNot", IsNot::class),
],
)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
@ -13,6 +24,17 @@ class SearchOperator {
)
sealed interface Equality<T>
@Schema(
name = "SearchOperatorEqualityNullable",
discriminatorProperty = "operator",
oneOf = [ Is::class, IsNot::class, IsNullT::class, IsNotNullT::class],
discriminatorMapping = [
DiscriminatorMapping("is", Is::class),
DiscriminatorMapping("isNot", IsNot::class),
DiscriminatorMapping("isNull", IsNullT::class),
DiscriminatorMapping("isNotNull", IsNotNullT::class),
],
)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
@ -20,6 +42,21 @@ class SearchOperator {
)
sealed interface EqualityNullable<T>
@Schema(
name = "SearchOperatorString",
discriminatorProperty = "operator",
oneOf = [BeginsWith::class, DoesNotBeginWith::class, Contains::class, DoesNotContain::class, EndsWith::class, DoesNotEndWith::class, Is::class, IsNot::class],
discriminatorMapping = [
DiscriminatorMapping("beginsWith", BeginsWith::class),
DiscriminatorMapping("doesNotBeginWith", DoesNotBeginWith::class),
DiscriminatorMapping("contains", Contains::class),
DiscriminatorMapping("doesNotContain", DoesNotContain::class),
DiscriminatorMapping("endsWith", EndsWith::class),
DiscriminatorMapping("doesNotEndWith", DoesNotEndWith::class),
DiscriminatorMapping("is", Is::class),
DiscriminatorMapping("isNot", IsNot::class),
],
)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
@ -27,6 +64,17 @@ class SearchOperator {
)
sealed interface StringOp
@Schema(
name = "SearchOperatorNumericT",
discriminatorProperty = "operator",
oneOf = [GreaterThan::class, LessThan::class, Is::class, IsNot::class],
discriminatorMapping = [
DiscriminatorMapping("greaterThan", GreaterThan::class),
DiscriminatorMapping("lessThan", LessThan::class),
DiscriminatorMapping("is", Is::class),
DiscriminatorMapping("isNot", IsNot::class),
],
)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
@ -34,6 +82,19 @@ class SearchOperator {
)
sealed interface Numeric<T>
@Schema(
name = "SearchOperatorNumericNullable",
discriminatorProperty = "operator",
oneOf = [GreaterThan::class, LessThan::class, IsNullT::class, IsNotNullT::class, Is::class, IsNot::class],
discriminatorMapping = [
DiscriminatorMapping("greaterThan", GreaterThan::class),
DiscriminatorMapping("lessThan", LessThan::class),
DiscriminatorMapping("isNull", IsNullT::class),
DiscriminatorMapping("isNotNull", IsNotNullT::class),
DiscriminatorMapping("is", Is::class),
DiscriminatorMapping("isNot", IsNot::class),
],
)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
@ -41,6 +102,19 @@ class SearchOperator {
)
sealed interface NumericNullable<T>
@Schema(
name = "SearchOperatorDate",
discriminatorProperty = "operator",
oneOf = [Before::class, After::class, IsInTheLast::class, IsNotInTheLast::class, IsNull::class, IsNotNull::class],
discriminatorMapping = [
DiscriminatorMapping("before", Before::class),
DiscriminatorMapping("after", After::class),
DiscriminatorMapping("isInTheLast", IsInTheLast::class),
DiscriminatorMapping("isNotInTheLast", IsNotInTheLast::class),
DiscriminatorMapping("isNull", IsNull::class),
DiscriminatorMapping("isNotNull", IsNotNull::class),
],
)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
@ -48,6 +122,15 @@ class SearchOperator {
)
sealed interface Date
@Schema(
name = "SearchOperatorBoolean",
discriminatorProperty = "operator",
oneOf = [IsTrue::class, IsFalse::class],
discriminatorMapping = [
DiscriminatorMapping("isTrue", IsTrue::class),
DiscriminatorMapping("isFalse", IsFalse::class),
],
)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
@ -55,6 +138,7 @@ class SearchOperator {
)
sealed interface Boolean
@Schema(name = "SearchOperatorIs")
@JsonTypeName("is")
data class Is<T>(
val value: T,
@ -64,6 +148,7 @@ class SearchOperator {
Numeric<T>,
NumericNullable<T>
@Schema(name = "SearchOperatorIsNot")
@JsonTypeName("isNot")
data class IsNot<T>(
val value: T,
@ -73,80 +158,97 @@ class SearchOperator {
Numeric<T>,
NumericNullable<T>
@Schema(name = "SearchOperatorContains")
@JsonTypeName("contains")
data class Contains(
val value: String,
) : StringOp
@Schema(name = "SearchOperatorDoesNotContain")
@JsonTypeName("doesNotContain")
data class DoesNotContain(
val value: String,
) : StringOp
@Schema(name = "SearchOperatorBeginsWith")
@JsonTypeName("beginsWith")
data class BeginsWith(
val value: String,
) : StringOp
@Schema(name = "SearchOperatorDoesNotBeginWith")
@JsonTypeName("doesNotBeginWith")
data class DoesNotBeginWith(
val value: String,
) : StringOp
@Schema(name = "SearchOperatorEndsWith")
@JsonTypeName("endsWith")
data class EndsWith(
val value: String,
) : StringOp
@Schema(name = "SearchOperatorDoesNotEndWith")
@JsonTypeName("doesNotEndWith")
data class DoesNotEndWith(
val value: String,
) : StringOp
@Schema(name = "SearchOperatorGreaterThan")
@JsonTypeName("greaterThan")
data class GreaterThan<T>(
val value: T,
) : Numeric<T>,
NumericNullable<T>
@Schema(name = "SearchOperatorLessThan")
@JsonTypeName("lessThan")
data class LessThan<T>(
val value: T,
) : Numeric<T>,
NumericNullable<T>
@Schema(name = "SearchOperatorBefore")
@JsonTypeName("before")
data class Before(
val dateTime: ZonedDateTime,
) : Date
@Schema(name = "SearchOperatorAfter")
@JsonTypeName("after")
data class After(
val dateTime: ZonedDateTime,
) : Date
@Schema(name = "SearchOperatorIsInTheLast")
@JsonTypeName("isInTheLast")
data class IsInTheLast(
val duration: Duration,
) : Date
@Schema(name = "SearchOperatorIsNotInTheLast")
@JsonTypeName("isNotInTheLast")
data class IsNotInTheLast(
val duration: Duration,
) : Date
@Schema(name = "SearchOperatorIsTrue")
@JsonTypeName("isTrue")
data object IsTrue : Boolean
@Schema(name = "SearchOperatorIsFalse")
@JsonTypeName("isFalse")
data object IsFalse : Boolean
@Schema(name = "SearchOperatorIsNull")
@JsonTypeName("isNull")
data object IsNull : Date
@Schema(name = "SearchOperatorIsNotNull")
@JsonTypeName("isNotNull")
data object IsNotNull : Date
@Schema(name = "SearchOperatorIsNullT")
@JsonTypeName("isNull")
class IsNullT<T> :
NumericNullable<T>,
@ -160,6 +262,7 @@ class SearchOperator {
override fun hashCode(): Int = javaClass.hashCode()
}
@Schema(name = "SearchOperatorIsNotNullT")
@JsonTypeName("isNotNull")
class IsNotNullT<T> :
NumericNullable<T>,

View file

@ -21,6 +21,10 @@ interface ThumbnailBookRepository {
fun existsById(thumbnailId: String): Boolean
fun getLibraryIdOrNull(thumbnailId: String): String?
fun getSeriesIdOrNull(thumbnailId: String): String?
fun insert(thumbnail: ThumbnailBook)
fun update(thumbnail: ThumbnailBook)

View file

@ -22,6 +22,7 @@ class ThumbnailBookDao(
) : SplitDslDaoBase(dslRW, dslRO),
ThumbnailBookRepository {
private val tb = Tables.THUMBNAIL_BOOK
private val b = Tables.BOOK
override fun findAllByBookId(bookId: String): Collection<ThumbnailBook> =
dslRO
@ -72,6 +73,24 @@ class ThumbnailBookDao(
override fun existsById(thumbnailId: String): Boolean = dslRO.fetchExists(tb, tb.ID.eq(thumbnailId))
override fun getLibraryIdOrNull(thumbnailId: String): String? =
dslRO
.select(b.LIBRARY_ID)
.from(tb)
.leftJoin(b)
.on(tb.BOOK_ID.eq(b.ID))
.where(tb.ID.eq(thumbnailId))
.fetchOne(b.LIBRARY_ID)
override fun getSeriesIdOrNull(thumbnailId: String): String? =
dslRO
.select(b.SERIES_ID)
.from(tb)
.leftJoin(b)
.on(tb.BOOK_ID.eq(b.ID))
.where(tb.ID.eq(thumbnailId))
.fetchOne(b.SERIES_ID)
override fun insert(thumbnail: ThumbnailBook) {
dslRW
.insertInto(tb)

View file

@ -74,7 +74,7 @@ class KoboProxy(
* raw Kobo sync token returned, and added to the response headers.
*/
fun proxyCurrentRequest(
body: Any? = null,
body: ByteArray? = null,
includeSyncToken: Boolean = false,
): ResponseEntity<JsonNode> {
if (!komgaSettingsProvider.koboProxy) throw IllegalStateException("kobo proxying is disabled")

View file

@ -28,7 +28,6 @@ class RarExtractor(
): List<MediaContainerEntry> =
Archive(path.toFile()).use { rar ->
if (rar.isPasswordProtected) throw MediaUnsupportedException("Encrypted RAR archives are not supported", "ERR_1002")
if (rar.mainHeader.isSolid) throw MediaUnsupportedException("Solid RAR archives are not supported", "ERR_1003")
if (rar.mainHeader.isMultiVolume) throw MediaUnsupportedException("Multi-Volume RAR archives are not supported", "ERR_1004")
rar.fileHeaders
.filter { !it.isDirectory }

View file

@ -4,6 +4,7 @@ import org.gotson.komga.domain.model.EpubTocEntry
import org.gotson.komga.infrastructure.util.getEntryBytes
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import org.jsoup.parser.Parser
import java.net.URLDecoder
import java.nio.file.Path
import kotlin.io.path.Path
@ -20,7 +21,7 @@ fun processNav(
document: ResourceContent,
navElement: Epub3Nav,
): List<EpubTocEntry> {
val doc = Jsoup.parse(document.content)
val doc = Jsoup.parse(document.content, "", Parser.xmlParser())
val nav =
doc
.select("nav")

View file

@ -0,0 +1,44 @@
package org.gotson.komga.infrastructure.openapi
import org.springdoc.core.customizers.OpenApiCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
/**
* The generated schema for sealed classes is somehow wrong.
* This customizer will correct the issues.
*/
@Configuration
@Order(Ordered.LOWEST_PRECEDENCE)
class InheritanceFlattenerConfiguration {
private val schemaPrefix = listOf("SearchOperator", "SearchCondition")
@Bean
fun flattenInheritedSchemasCustomizer(): OpenApiCustomizer =
OpenApiCustomizer { openApi ->
openApi.components
?.schemas
?.values
?.filter { schema -> schemaPrefix.any { prefix -> schema.name.startsWith(prefix, false) } }
?.forEach { schema ->
// Swagger models inheritance as an allOf list with exactly two items:
// 1. The $ref to the parent interface
// 2. An inline schema containing the child's actual properties
if (schema.allOf != null) {
val refSchema = schema.allOf.filter { it.`$ref` != null }
val inlineSchema = schema.allOf.find { it.properties != null }
// If both are found, we know this is an inherited schema wrapper
if (refSchema.isNotEmpty() && inlineSchema != null) {
// Move the properties up to the root schema
schema.properties = inlineSchema.properties
// Delete the allOf array entirely
schema.allOf = null
}
}
}
}
}

View file

@ -4,6 +4,7 @@ import org.gotson.komga.domain.model.Book
import org.gotson.komga.domain.model.KomgaUser
import org.gotson.komga.domain.persistence.BookRepository
import org.gotson.komga.domain.persistence.SeriesMetadataRepository
import org.gotson.komga.domain.persistence.ThumbnailBookRepository
import org.gotson.komga.interfaces.api.rest.dto.BookDto
import org.gotson.komga.interfaces.api.rest.dto.SeriesDto
import org.springframework.http.HttpStatus
@ -14,6 +15,7 @@ import org.springframework.web.server.ResponseStatusException
class ContentRestrictionChecker(
private val seriesMetadataRepository: SeriesMetadataRepository,
private val bookRepository: BookRepository,
private val thumbnailBookRepository: ThumbnailBookRepository,
) {
/**
* Convenience function to check for content restriction.
@ -84,4 +86,26 @@ class ContentRestrictionChecker(
if (!komgaUser.canAccessLibrary(series.libraryId)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
if (!komgaUser.isContentAllowed(series.metadata.ageRating, series.metadata.sharingLabels)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
}
/**
* Convenience function to check for content restriction.
*
* @throws[ResponseStatusException] if the user cannot access the content
*/
fun checkContentRestrictionBookThumbnail(
komgaUser: KomgaUser,
thumbnailId: String,
) {
if (!komgaUser.canAccessAllLibraries()) {
thumbnailBookRepository.getLibraryIdOrNull(thumbnailId)?.let {
if (!komgaUser.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
}
if (komgaUser.restrictions.isRestricted)
thumbnailBookRepository.getSeriesIdOrNull(thumbnailId)?.let { seriesId ->
seriesMetadataRepository.findById(seriesId).let {
if (!komgaUser.isContentAllowed(it.ageRating, it.sharingLabels)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
}
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
}
}

View file

@ -92,7 +92,7 @@ class OpdsGenerator(
links =
listOf(
WPLinkDto(rel = "help", href = "https://komga.org"),
WPLinkDto(rel = "logo", href = ServletUriComponentsBuilder.fromCurrentContextPath().path("android-chrome-512x512.png").toUriString()),
WPLinkDto(rel = "logo", href = ServletUriComponentsBuilder.fromCurrentContextPath().pathSegment("android-chrome-512x512.png").toUriString()),
),
authentication =
listOf(

View file

@ -3,6 +3,7 @@ package org.gotson.komga.interfaces.api.kobo
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.treeToValue
import com.github.benmanes.caffeine.cache.Caffeine
import io.github.oshai.kotlinlogging.KotlinLogging
@ -174,6 +175,7 @@ class KoboController(
private val imageConverter: ImageConverter,
private val mediaRepository: MediaRepository,
private val contentRestrictionChecker: ContentRestrictionChecker,
private val objectMapper: ObjectMapper,
) {
private val cachedKepub =
Caffeine
@ -230,14 +232,16 @@ class KoboController(
*/
@PostMapping("v1/auth/device")
fun authDevice(
@RequestBody body: JsonNode,
@RequestBody rawBody: ByteArray,
): Any {
try {
return koboProxy.proxyCurrentRequest(body)
return koboProxy.proxyCurrentRequest(rawBody)
} catch (_: Exception) {
logger.warn { "Failed to get response from Kobo /v1/auth/device, fallback to noproxy" }
}
val body = objectMapper.readTree(rawBody)
/**
* Komga does not use the /v1/auth/device API call for authentication/authorization.
* Return dummy data to keep the device happy.
@ -512,13 +516,17 @@ class KoboController(
*/
@GetMapping("/v1/library/{bookId}/metadata")
fun getBookMetadata(
@AuthenticationPrincipal principal: KomgaPrincipal,
@PathVariable authToken: String,
@PathVariable bookId: String,
): ResponseEntity<*> =
if (!bookRepository.existsById(bookId) && koboProxy.isEnabled())
if (!bookRepository.existsById(bookId) && koboProxy.isEnabled()) {
koboProxy.proxyCurrentRequest()
else
} else {
contentRestrictionChecker.checkContentRestriction(principal.user, bookId)
ResponseEntity.ok(koboDtoRepository.findBookMetadataByIds(listOf(bookId)).map { it.withDownloadUrls(getDownloadUrlBuilder(authToken)) })
}
/**
* @return an array of [ReadingStateDto]
@ -535,6 +543,8 @@ class KoboController(
else
throw ResponseStatusException(HttpStatus.NOT_FOUND)
contentRestrictionChecker.checkContentRestriction(principal.user, book)
val response = readProgressRepository.findByBookIdAndUserIdOrNull(bookId, principal.user.id)?.toDto() ?: getEmptyReadProgressForBook(book)
return ResponseEntity.ok(listOf(response))
}
@ -546,16 +556,20 @@ class KoboController(
fun updateState(
@AuthenticationPrincipal principal: KomgaPrincipal,
@PathVariable bookId: String,
@RequestBody body: ReadingStateStateUpdateDto,
@RequestBody rawBody: ByteArray,
@RequestHeader(name = X_KOBO_DEVICEID, required = false) koboDeviceId: String = "unknown",
): ResponseEntity<*> {
val body = objectMapper.readValue<ReadingStateStateUpdateDto>(rawBody)
val book =
bookRepository.findByIdOrNull(bookId)
?: if (koboProxy.isEnabled())
return koboProxy.proxyCurrentRequest(body)
return koboProxy.proxyCurrentRequest(rawBody)
else
throw ResponseStatusException(HttpStatus.NOT_FOUND)
contentRestrictionChecker.checkContentRestriction(principal.user, book)
val koboUpdate = body.readingStates.firstOrNull() ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST)
if (koboUpdate.currentBookmark.location == null || koboUpdate.currentBookmark.contentSourceProgressPercent == null) throw ResponseStatusException(HttpStatus.BAD_REQUEST)
@ -719,6 +733,8 @@ class KoboController(
.location(UriComponentsBuilder.fromUriString(koboProxy.imageHostUrl).buildAndExpand(thumbnailId, width, height).toUri())
.build()
} else {
contentRestrictionChecker.checkContentRestrictionBookThumbnail(principal.user, thumbnailId)
val poster = bookLifecycle.getThumbnailBytesByThumbnailId(thumbnailId) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
val posterBytes =
if (poster.mediaType != ImageType.JPEG.mediaType)
@ -733,7 +749,7 @@ class KoboController(
method = [RequestMethod.GET, RequestMethod.PUT, RequestMethod.POST, RequestMethod.DELETE, RequestMethod.PATCH],
)
fun catchAll(
@RequestBody body: Any?,
@RequestBody body: ByteArray?,
): ResponseEntity<JsonNode> =
if (koboProxy.isEnabled())
koboProxy.proxyCurrentRequest(body)

View file

@ -14,6 +14,7 @@ import org.gotson.komga.infrastructure.security.KomgaPrincipal
import org.gotson.komga.interfaces.api.kosync.dto.DocumentProgressDto
import org.gotson.komga.interfaces.api.kosync.dto.UserAuthenticationDto
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
@ -29,7 +30,7 @@ import java.time.ZonedDateTime
private val logger = KotlinLogging.logger {}
@RestController
@RequestMapping("/koreader", produces = ["application/vnd.koreader.v1+json"])
@RequestMapping("/koreader", produces = [MediaType.APPLICATION_JSON_VALUE, "application/vnd.koreader.v1+json"])
class KoreaderSyncController(
private val bookRepository: BookRepository,
private val mediaRepository: MediaRepository,

View file

@ -435,7 +435,7 @@ class Opds2Controller(
PageRequest.of(page.pageNumber, page.pageSize, Sort.by(Sort.Order.desc("lastModified"))),
).map { it.toWPLinkDto() }
val uriBuilder = uriBuilder("libraries${if (library != null) "/${library.id}" else ""}/books/latest")
val uriBuilder = uriBuilder("libraries${if (library != null) "/${library.id}" else ""}/series/latest")
return FeedDto(
metadata =

View file

@ -112,10 +112,12 @@ class UserController(
restrictions =
ContentRestrictions(
ageRestriction =
if (ageRestriction == null || ageRestriction.restriction == AllowExcludeDto.NONE)
null
else
AgeRestriction(ageRestriction.age, ageRestriction.restriction.toDomain()),
ageRestriction.let {
if (it == null || it.restriction == AllowExcludeDto.NONE)
null
else
AgeRestriction(it.age, it.restriction.toDomain())
},
labelsAllow = labelsAllow ?: emptySet(),
labelsExclude = labelsExclude ?: emptySet(),
),

View file

@ -1,10 +1,12 @@
package org.gotson.komga.interfaces.api.rest.dto
import com.fasterxml.jackson.annotation.JsonInclude
import org.gotson.komga.domain.model.AgeRestriction
import org.gotson.komga.domain.model.AllowExclude
import org.gotson.komga.domain.model.KomgaUser
import org.gotson.komga.infrastructure.security.KomgaPrincipal
@JsonInclude(JsonInclude.Include.NON_NULL)
data class UserDto(
val id: String,
val email: String,

View file

@ -59,14 +59,21 @@ class BookAnalyzerTest(
assertThat(media.pages).hasSize(3)
}
@ParameterizedTest
@ValueSource(
strings = [
"rar4-solid.rar", "rar4-encrypted.rar",
],
)
fun `given rar4 solid or encrypted archive when analyzing then media status is UNSUPPORTED`(fileName: String) {
val file = ClassPathResource("archives/$fileName")
@Test
fun `given rar4 solid archive when analyzing then media status is READY`() {
val file = ClassPathResource("archives/rar4-solid.rar")
val book = Book("book", file.url, LocalDateTime.now())
val media = bookAnalyzer.analyze(book, false)
assertThat(media.mediaType).isEqualTo("application/x-rar-compressed; version=4")
assertThat(media.status).isEqualTo(Media.Status.READY)
assertThat(media.pages).hasSize(3)
}
@Test
fun `given rar4 encrypted archive when analyzing then media status is UNSUPPORTED`() {
val file = ClassPathResource("archives/rar4-encrypted.rar")
val book = Book("book", file.url, LocalDateTime.now())
val media = bookAnalyzer.analyze(book, false)

View file

@ -3,6 +3,7 @@ package org.gotson.komga.infrastructure.mediacontainer.epub
import org.assertj.core.api.Assertions.assertThat
import org.gotson.komga.domain.model.EpubTocEntry
import org.jsoup.Jsoup
import org.jsoup.parser.Parser
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import org.springframework.core.io.ClassPathResource
@ -15,7 +16,7 @@ class OpfTest {
// given
val opfResource = ClassPathResource("epub/clash.opf")
val opfString = opfResource.inputStream.readAllBytes().decodeToString()
val opfDoc = Jsoup.parse(opfString)
val opfDoc = Jsoup.parse(opfString, "", Parser.xmlParser())
// when
val opfLandmarks = processOpfGuide(opfDoc, if (prefix.isBlank()) null else Path.of(prefix))

View file

@ -7,9 +7,13 @@ import org.gotson.komga.domain.service.KomgaUserLifecycle
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.post
@ -68,4 +72,17 @@ class KoreaderSyncControllerTest(
jsonPath("authorized") { value("OK") }
}
}
@ParameterizedTest
@ValueSource(strings = [MediaType.APPLICATION_JSON_VALUE, "application/vnd.koreader.v1+json"])
fun `given accept header when calling API user then returns OK`(acceptHeader: String) {
mockMvc
.get("/koreader/users/auth") {
header("x-auth-user", apiKey)
header(HttpHeaders.ACCEPT, acceptHeader)
}.andExpect {
status { isOk() }
jsonPath("authorized") { value("OK") }
}
}
}

View file

@ -113,7 +113,7 @@ class UserControllerTest(
jsonPath("$.sharedLibrariesId") { doesNotExist() }
jsonPath("$.labelsAllow") { isEmpty() }
jsonPath("$.labelsExclude") { isEmpty() }
jsonPath("$.ageRestriction") { isEmpty() }
jsonPath("$.ageRestriction") { doesNotExist() }
}
}
}