mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-17 01:33:34 -05:00
Compare commits
38 Commits
76686db6a1
...
renovate/m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5561761020 | ||
|
|
a210153ed1 | ||
|
|
b40447c4f9 | ||
|
|
193dd1ee84 | ||
|
|
6ee3348f50 | ||
|
|
c98899d501 | ||
|
|
7654653a25 | ||
|
|
0c1a0ef408 | ||
|
|
e7f2192579 | ||
|
|
5c3b1e0b07 | ||
|
|
6ad59f2e2b | ||
|
|
03dd778fac | ||
|
|
6b833a38d1 | ||
|
|
a83885353c | ||
|
|
10d7c7c06d | ||
|
|
3b575271cb | ||
|
|
5ad92413f3 | ||
|
|
7fbdb39319 | ||
|
|
92abdf7fb7 | ||
|
|
ea0c666cfe | ||
|
|
b5e395a039 | ||
|
|
e530072a07 | ||
|
|
f85cbe1ca5 | ||
|
|
5f9126eb2f | ||
|
|
d77c57ede0 | ||
|
|
02f9a0d1d7 | ||
|
|
53192f56ca | ||
|
|
01d89cbb48 | ||
|
|
be55cb974b | ||
|
|
34c394ed19 | ||
|
|
1433a21abd | ||
|
|
ec28794655 | ||
|
|
72122b7cbf | ||
|
|
392a7990d2 | ||
|
|
0bdcf8b4ba | ||
|
|
8295440bfd | ||
|
|
e52aa6daf4 | ||
|
|
ef067ef5b9 |
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -42,7 +42,7 @@ body:
|
|||||||
label: Suwayomi-Server version
|
label: Suwayomi-Server version
|
||||||
description: You can find your Suwayomi-Server version in **More → About**.
|
description: You can find your Suwayomi-Server version in **More → About**.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example: "v2.1.1867"
|
Example: "v2.2.2100"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
36
.github/workflows/build_push.yml
vendored
36
.github/workflows/build_push.yml
vendored
@@ -54,14 +54,14 @@ jobs:
|
|||||||
run: ./gradlew :server:shadowJar --stacktrace
|
run: ./gradlew :server:shadowJar --stacktrace
|
||||||
|
|
||||||
- name: Upload Jar
|
- name: Upload Jar
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
path: master/server/build/*.jar
|
path: master/server/build/*.jar
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload icons
|
- name: Upload icons
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: icon
|
name: icon
|
||||||
path: master/server/src/main/resources/icon
|
path: master/server/src/main/resources/icon
|
||||||
@@ -71,7 +71,7 @@ jobs:
|
|||||||
run: tar -cvzf scripts.tar.gz -C master/ scripts/
|
run: tar -cvzf scripts.tar.gz -C master/ scripts/
|
||||||
|
|
||||||
- name: Upload scripts.tar.gz
|
- name: Upload scripts.tar.gz
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: scripts
|
name: scripts
|
||||||
path: scripts.tar.gz
|
path: scripts.tar.gz
|
||||||
@@ -103,7 +103,7 @@ jobs:
|
|||||||
run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs,jdk.accessibility --output suwa --strip-debug --no-man-pages --no-header-files --compress=2
|
run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs,jdk.accessibility --output suwa --strip-debug --no-man-pages --no-header-files --compress=2
|
||||||
|
|
||||||
- name: Upload JRE package
|
- name: Upload JRE package
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.name }}-jre
|
name: ${{ matrix.name }}-jre
|
||||||
path: suwa
|
path: suwa
|
||||||
@@ -134,26 +134,26 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Download Jar
|
- name: Download Jar
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
path: server/build
|
path: server/build
|
||||||
|
|
||||||
- name: Download JRE
|
- name: Download JRE
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
if: matrix.name != 'linux-assets' && matrix.name != 'debian-all'
|
if: matrix.name != 'linux-assets' && matrix.name != 'debian-all'
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.jre }}-jre
|
name: ${{ matrix.jre }}-jre
|
||||||
path: jre
|
path: jre
|
||||||
|
|
||||||
- name: Download icons
|
- name: Download icons
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: icon
|
name: icon
|
||||||
path: server/src/main/resources/icon
|
path: server/src/main/resources/icon
|
||||||
|
|
||||||
- name: Download scripts.tar.gz
|
- name: Download scripts.tar.gz
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: scripts
|
name: scripts
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ jobs:
|
|||||||
scripts/bundler.sh -o upload/ ${{ matrix.name }}
|
scripts/bundler.sh -o upload/ ${{ matrix.name }}
|
||||||
|
|
||||||
- name: Upload ${{ matrix.name }} release
|
- name: Upload ${{ matrix.name }} release
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.name }}
|
name: ${{ matrix.name }}
|
||||||
path: upload/*
|
path: upload/*
|
||||||
@@ -174,35 +174,35 @@ jobs:
|
|||||||
needs: bundle
|
needs: bundle
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: debian-all
|
name: debian-all
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: appimage
|
name: appimage
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: linux-assets
|
name: linux-assets
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: linux-x64
|
name: linux-x64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: macOS-x64
|
name: macOS-x64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: macOS-arm64
|
name: macOS-arm64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: windows-x64
|
name: windows-x64
|
||||||
path: release
|
path: release
|
||||||
@@ -240,7 +240,7 @@ jobs:
|
|||||||
git push origin $TAG
|
git push origin $TAG
|
||||||
|
|
||||||
- name: Upload Preview Release
|
- name: Upload Preview Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
|
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
|
||||||
repository: "Suwayomi/Suwayomi-Server-preview"
|
repository: "Suwayomi/Suwayomi-Server-preview"
|
||||||
|
|||||||
36
.github/workflows/publish.yml
vendored
36
.github/workflows/publish.yml
vendored
@@ -56,14 +56,14 @@ jobs:
|
|||||||
run: ./gradlew :server:downloadWebUI :server:shadowJar --stacktrace
|
run: ./gradlew :server:downloadWebUI :server:shadowJar --stacktrace
|
||||||
|
|
||||||
- name: Upload Jar
|
- name: Upload Jar
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
path: master/server/build/*.jar
|
path: master/server/build/*.jar
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload icons
|
- name: Upload icons
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: icon
|
name: icon
|
||||||
path: master/server/src/main/resources/icon
|
path: master/server/src/main/resources/icon
|
||||||
@@ -73,7 +73,7 @@ jobs:
|
|||||||
run: tar -cvzf scripts.tar.gz -C master/ scripts/
|
run: tar -cvzf scripts.tar.gz -C master/ scripts/
|
||||||
|
|
||||||
- name: Upload scripts.tar.gz
|
- name: Upload scripts.tar.gz
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: scripts
|
name: scripts
|
||||||
path: scripts.tar.gz
|
path: scripts.tar.gz
|
||||||
@@ -105,7 +105,7 @@ jobs:
|
|||||||
run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs,jdk.accessibility --output suwa --strip-debug --no-man-pages --no-header-files --compress=2
|
run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs,jdk.accessibility --output suwa --strip-debug --no-man-pages --no-header-files --compress=2
|
||||||
|
|
||||||
- name: Upload JDK package
|
- name: Upload JDK package
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.name }}-jre
|
name: ${{ matrix.name }}-jre
|
||||||
path: suwa
|
path: suwa
|
||||||
@@ -136,26 +136,26 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Download Jar
|
- name: Download Jar
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
path: server/build
|
path: server/build
|
||||||
|
|
||||||
- name: Download JRE
|
- name: Download JRE
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
if: matrix.name != 'linux-assets' && matrix.name != 'debian-all'
|
if: matrix.name != 'linux-assets' && matrix.name != 'debian-all'
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.jre }}-jre
|
name: ${{ matrix.jre }}-jre
|
||||||
path: jre
|
path: jre
|
||||||
|
|
||||||
- name: Download icons
|
- name: Download icons
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: icon
|
name: icon
|
||||||
path: server/src/main/resources/icon
|
path: server/src/main/resources/icon
|
||||||
|
|
||||||
- name: Download scripts.tar.gz
|
- name: Download scripts.tar.gz
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: scripts
|
name: scripts
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ jobs:
|
|||||||
scripts/bundler.sh -o upload/ ${{ matrix.name }}
|
scripts/bundler.sh -o upload/ ${{ matrix.name }}
|
||||||
|
|
||||||
- name: Upload ${{ matrix.name }} files
|
- name: Upload ${{ matrix.name }} files
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.name }}
|
name: ${{ matrix.name }}
|
||||||
path: upload/*
|
path: upload/*
|
||||||
@@ -177,35 +177,35 @@ jobs:
|
|||||||
needs: bundle
|
needs: bundle
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: debian-all
|
name: debian-all
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: appimage
|
name: appimage
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: linux-assets
|
name: linux-assets
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: linux-x64
|
name: linux-x64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: macOS-x64
|
name: macOS-x64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: macOS-arm64
|
name: macOS-arm64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: windows-x64
|
name: windows-x64
|
||||||
path: release
|
path: release
|
||||||
@@ -214,7 +214,7 @@ jobs:
|
|||||||
run: cd release && sha256sum * > Checksums.sha256
|
run: cd release && sha256sum * > Checksums.sha256
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.DEPLOY_RELEASE_TOKEN }}
|
token: ${{ secrets.DEPLOY_RELEASE_TOKEN }}
|
||||||
draft: true
|
draft: true
|
||||||
|
|||||||
35
CHANGELOG.md
35
CHANGELOG.md
@@ -6,6 +6,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||||||
|
|
||||||
## [Unreleased] (Preview)
|
## [Unreleased] (Preview)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- .
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- .
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- (WebUI) Handle serving non-default webui with "bundled"
|
||||||
|
|
||||||
|
## [v2.2.2100] + [WebUI: v20260508.01] - 2026-05-08
|
||||||
|
|
||||||
|
### Major Changes
|
||||||
|
|
||||||
|
#### Added UI Login Auth Method!
|
||||||
|
We now have another alternative login type! This is our most flexable option as it allows clients and the server to handle it together, it's not an all or nothing authentication method like the others. For example the web-based clients can now use thier own login pages, and the server can add data to the tokens it gives to the clients, improving performance while keeping the security standard high.
|
||||||
|
|
||||||
|
#### PostgreSQL Support!
|
||||||
|
We finally support alternative database types! H2 is a great database, but it has its issues. Using a more widespread database like PostgreSQL can avoid them while keeping the performance high! Note that PostgreSQL will need to be run externally. See our example [docker-compose.yml](https://github.com/Suwayomi/Suwayomi-Server-docker/blob/ffc7f6990e889d0a257efe3d94db085d5e73f420/docker-compose-postgresql.yml)
|
||||||
|
|
||||||
|
#### Remote Image Modification/Conversion
|
||||||
|
We now support remote image modification! This will allow you to use an external server that can change your images however you want, such as converting to JXL or WebP, or applying some upscaling or sharpening to them! This pairs well with our new option to convert images before it serves them to the client.
|
||||||
|
See our [conversion server](https://github.com/Suwayomi/Suwayomi-converter) for an example.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- (**Source/API**) Expose "baseUrl" (TYPE "SourceType")
|
- (**Source/API**) Expose "baseUrl" (TYPE "SourceType")
|
||||||
- (**Extension**) Support author notes
|
- (**Extension**) Support author notes
|
||||||
@@ -65,6 +88,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||||||
- (**Library update**) Fix stale manga data in update subscription status events
|
- (**Library update**) Fix stale manga data in update subscription status events
|
||||||
- (**Cloudflare/flaresolverr**) Fix sending POST requests as GET to flaresolverr
|
- (**Cloudflare/flaresolverr**) Fix sending POST requests as GET to flaresolverr
|
||||||
|
|
||||||
|
### WebUI
|
||||||
|
- [See WebUI changes here][WebUI: v20260508.01]
|
||||||
|
|
||||||
|
### Contributors
|
||||||
|
@cpiber, @Syer10, @lamaxama, @schroda, @AwkwardPeak7, @ItsGlassPlus1, @manti-X, @Youwes09, @renovate[bot], @D-Brox, @weblate, @Micka149, @TheRay82, @UnknownSkyrimPasserby, @KaceyKoo-gif, @333fred, @KolbyML, @Robonau, @ornaras, @SpicyCatGames, @FadedSociety, @ginocic, @zeedif, @CzechuPL, @mrintrepide, @renjfk, @thiagoalcr, @Smileskun, @dejavui, @allrobot
|
||||||
|
|
||||||
## [v2.1.1867] + [WebUI: v20250703.01] - 2025-07-31
|
## [v2.1.1867] + [WebUI: v20250703.01] - 2025-07-31
|
||||||
|
|
||||||
### Webview!
|
### Webview!
|
||||||
@@ -166,7 +195,7 @@ This release has been working on the long awaited tracking support! With the las
|
|||||||
|
|
||||||
@schroda, @Syer10, @RatCornu, @FumoVite
|
@schroda, @Syer10, @RatCornu, @FumoVite
|
||||||
|
|
||||||
## [v1.0.0] + [WebUI: r1409] - 2024-02-23
|
## [v1.0.0] + [WebUI: v1.0.0] - 2024-02-23
|
||||||
|
|
||||||
We've done a lot since our last release over a year ago.
|
We've done a lot since our last release over a year ago.
|
||||||
|
|
||||||
@@ -379,6 +408,7 @@ Huge thanks to @martinek who pulled the most of the weight this release!
|
|||||||
|
|
||||||
<!-- WEBUI LINKS -->
|
<!-- WEBUI LINKS -->
|
||||||
|
|
||||||
|
[WebUI: v20260508.01]: https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#2026050801-r3136---2026-05-08
|
||||||
[WebUI: v20251230.01]: https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#2025123001-r2937---2025-12-30
|
[WebUI: v20251230.01]: https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#2025123001-r2937---2025-12-30
|
||||||
[WebUI: v20250801.01]: https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#2025080101-r2717---2025-08-01
|
[WebUI: v20250801.01]: https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#2025080101-r2717---2025-08-01
|
||||||
[WebUI: v20250731.01]: https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#2025073101-r2715---2025-07-31
|
[WebUI: v20250731.01]: https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#2025073101-r2715---2025-07-31
|
||||||
@@ -404,7 +434,8 @@ Huge thanks to @martinek who pulled the most of the weight this release!
|
|||||||
|
|
||||||
<!-- SERVER LINKS -->
|
<!-- SERVER LINKS -->
|
||||||
|
|
||||||
[unreleased]: https://github.com/suwayomi/suwayomi-server/compare/v2.1.1867...HEAD
|
[unreleased]: https://github.com/suwayomi/suwayomi-server/compare/v2.2.2100...HEAD
|
||||||
|
[v2.2.2100]: https://github.com/suwayomi/suwayomi-server/compare/v2.1.1867...v2.2.2100
|
||||||
[v2.1.1867]: https://github.com/suwayomi/suwayomi-server/compare/v2.0.1727...v2.1.1867
|
[v2.1.1867]: https://github.com/suwayomi/suwayomi-server/compare/v2.0.1727...v2.1.1867
|
||||||
[v2.0.1727]: https://github.com/suwayomi/suwayomi-server/compare/v1.1.1...v2.0.1727
|
[v2.0.1727]: https://github.com/suwayomi/suwayomi-server/compare/v1.1.1...v2.0.1727
|
||||||
[v1.1.1]: https://github.com/suwayomi/suwayomi-server/compare/v1.1.0...v1.1.1
|
[v1.1.1]: https://github.com/suwayomi/suwayomi-server/compare/v1.1.0...v1.1.1
|
||||||
|
|||||||
@@ -76,9 +76,9 @@ These clients are built-in options, and the server can keep them automatically u
|
|||||||
- [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI): Web app, PWA
|
- [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI): Web app, PWA
|
||||||
- [Suwayomi-VUI](https://github.com/Suwayomi/Suwayomi-VUI): Web app, PWA
|
- [Suwayomi-VUI](https://github.com/Suwayomi/Suwayomi-VUI): Web app, PWA
|
||||||
|
|
||||||
##### Other clients (potentially inactive or abondend)
|
##### Other clients (potentially inactive or abandoned)
|
||||||
- [Tachidesk-VaadinUI](https://github.com/Suwayomi/Tachidesk-VaadinUI): Desktop app (windows, linux, mac); UI in the browser, manages its own suwayomi server instance
|
- [Tachidesk-VaadinUI](https://github.com/Suwayomi/Tachidesk-VaadinUI): Desktop app (windows, linux, mac); UI in the browser, manages its own suwayomi server instance
|
||||||
- [Moku](https://github.com/Youwes09/Moku): Desktop app (windows, linux, mac), requires access to a running server
|
- [Moku](https://github.com/Youwes09/Moku): Desktop app (windows, linux, mac), can manage its own suwayomi server instance
|
||||||
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): Desktop app (windows, linux, mac); can manage its own suwayomi server instance
|
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): Desktop app (windows, linux, mac); can manage its own suwayomi server instance
|
||||||
- [Tachidesk-Sorayomi](https://github.com/Suwayomi/Tachidesk-Sorayomi): Web app; Desktop app (windows, linux, mac); Android app; requires access to a running server
|
- [Tachidesk-Sorayomi](https://github.com/Suwayomi/Tachidesk-Sorayomi): Web app; Desktop app (windows, linux, mac); Android app; requires access to a running server
|
||||||
- [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): Android app; iOS app Desktop app (linux); requires access to a running server
|
- [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): Android app; iOS app Desktop app (linux); requires access to a running server
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ subprojects {
|
|||||||
}
|
}
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
jvmTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get())
|
jvmTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get())
|
||||||
freeCompilerArgs.add("-Xcontext-receivers")
|
freeCompilerArgs.add("-Xcontext-parameters")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import java.io.BufferedReader
|
|||||||
const val MainClass = "suwayomi.tachidesk.MainKt"
|
const val MainClass = "suwayomi.tachidesk.MainKt"
|
||||||
|
|
||||||
// should be bumped with each stable release
|
// should be bumped with each stable release
|
||||||
val getTachideskVersion = { "v2.1.${getCommitCount()}" }
|
val getTachideskVersion = { "v2.2.${getCommitCount()}" }
|
||||||
|
|
||||||
val webUIRevisionTag = "r2643"
|
val webUIRevisionTag = "r3136"
|
||||||
|
|
||||||
private val getCommitCount = {
|
private val getCommitCount = {
|
||||||
runCatching {
|
runCatching {
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
[versions]
|
[versions]
|
||||||
kotlin = "2.3.10"
|
kotlin = "2.3.21"
|
||||||
coroutines = "1.10.2"
|
coroutines = "1.11.0"
|
||||||
serialization = "1.10.0"
|
serialization = "1.11.0"
|
||||||
jvmTarget = "21"
|
jvmTarget = "21"
|
||||||
okhttp = "5.3.2" # Major version is locked by Tachiyomi extensions
|
okhttp = "5.3.2" # Major version is locked by Tachiyomi extensions
|
||||||
javalin = "6.7.0"
|
javalin = "7.2.0"
|
||||||
jte = "3.2.3"
|
jte = "3.2.4"
|
||||||
jackson = "2.18.3" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
|
jackson = "3.1.2" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
|
||||||
exposed = "0.61.0"
|
exposed = "0.61.0"
|
||||||
dex2jar = "2.4.34"
|
dex2jar = "2.4.36"
|
||||||
polyglot = "24.2.2"
|
polyglot = "25.0.3"
|
||||||
settings = "1.3.0"
|
settings = "1.3.0"
|
||||||
twelvemonkeys = "3.13.0"
|
twelvemonkeys = "3.13.1"
|
||||||
graphqlkotlin = "8.8.1"
|
graphqlkotlin = "10.0.0-alpha.3"
|
||||||
xmlserialization = "0.91.3"
|
xmlserialization = "0.91.3"
|
||||||
ktlint = "1.8.0"
|
ktlint = "1.8.0"
|
||||||
koin = "4.1.1"
|
koin = "4.2.1"
|
||||||
moko = "0.26.0"
|
moko = "0.26.4"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
# Kotlin
|
# Kotlin
|
||||||
@@ -39,22 +39,22 @@ serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization-jvm", v
|
|||||||
# Logging
|
# Logging
|
||||||
slf4japi = "org.slf4j:slf4j-api:2.0.17"
|
slf4japi = "org.slf4j:slf4j-api:2.0.17"
|
||||||
logback = "ch.qos.logback:logback-classic:1.5.32"
|
logback = "ch.qos.logback:logback-classic:1.5.32"
|
||||||
kotlinlogging = "io.github.oshai:kotlin-logging-jvm:8.0.01"
|
kotlinlogging = "io.github.oshai:kotlin-logging-jvm:8.0.02"
|
||||||
|
|
||||||
# OkHttp
|
# OkHttp
|
||||||
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
||||||
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
||||||
okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" }
|
okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" }
|
||||||
okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" }
|
okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" }
|
||||||
okio = "com.squareup.okio:okio:3.16.4"
|
okio = "com.squareup.okio:okio:3.17.0"
|
||||||
|
|
||||||
# Javalin api
|
# Javalin api
|
||||||
javalin-core = { module = "io.javalin:javalin", version.ref = "javalin" }
|
javalin-core = { module = "io.javalin:javalin", version.ref = "javalin" }
|
||||||
javalin-openapi = { module = "io.javalin:javalin-openapi", version.ref = "javalin" }
|
javalin-openapi = { module = "io.javalin:javalin-openapi", version.ref = "javalin" }
|
||||||
javalin-rendering = { module = "io.javalin:javalin-rendering", version.ref = "javalin" }
|
javalin-rendering = { module = "io.javalin:javalin-rendering-jte", version.ref = "javalin" }
|
||||||
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
|
jackson-databind = { module = "tools.jackson.core:jackson-databind", version.ref = "jackson" }
|
||||||
jackson-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
|
jackson-kotlin = { module = "tools.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
|
||||||
jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations", version.ref = "jackson" }
|
jackson-annotations = "com.fasterxml.jackson.core:jackson-annotations:2.20"
|
||||||
jte = { module = "gg.jte:jte", version.ref = "jte" }
|
jte = { module = "gg.jte:jte", version.ref = "jte" }
|
||||||
kte = { module = "gg.jte:jte-kotlin", version.ref = "jte" }
|
kte = { module = "gg.jte:jte-kotlin", version.ref = "jte" }
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "e
|
|||||||
exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" }
|
exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" }
|
||||||
exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
|
exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
|
||||||
exposed-javatime = { module = "org.jetbrains.exposed:exposed-java-time", version.ref = "exposed" }
|
exposed-javatime = { module = "org.jetbrains.exposed:exposed-java-time", version.ref = "exposed" }
|
||||||
postgres = "org.postgresql:postgresql:42.7.10"
|
postgres = "org.postgresql:postgresql:42.7.11"
|
||||||
h2 = "com.h2database:h2:1.4.200" # current database driver, can't update to h2 v2 without sql migration
|
h2 = "com.h2database:h2:1.4.200" # current database driver, can't update to h2 v2 without sql migration
|
||||||
hikaricp = "com.zaxxer:HikariCP:7.0.2"
|
hikaricp = "com.zaxxer:HikariCP:7.0.2"
|
||||||
|
|
||||||
@@ -86,10 +86,10 @@ systemtray-desktop = "com.dorkbox:Desktop:1.1" # version locked by SystemTray
|
|||||||
# dependencies of Tachiyomi extensions
|
# dependencies of Tachiyomi extensions
|
||||||
injekt = "com.github.null2264:injekt-koin:ee267b2e27"
|
injekt = "com.github.null2264:injekt-koin:ee267b2e27"
|
||||||
rxjava = "io.reactivex:rxjava:1.3.8"
|
rxjava = "io.reactivex:rxjava:1.3.8"
|
||||||
jsoup = "org.jsoup:jsoup:1.22.1"
|
jsoup = "org.jsoup:jsoup:1.22.2"
|
||||||
|
|
||||||
# Config
|
# Config
|
||||||
config = "com.typesafe:config:1.4.5"
|
config = "com.typesafe:config:1.4.8"
|
||||||
config4k = "io.github.config4k:config4k:0.7.0"
|
config4k = "io.github.config4k:config4k:0.7.0"
|
||||||
|
|
||||||
# Sort
|
# Sort
|
||||||
@@ -105,7 +105,7 @@ dex2jar-tools = { module = "de.femtopedia.dex2jar:dex-tools", version.ref = "dex
|
|||||||
|
|
||||||
# APK
|
# APK
|
||||||
apk-parser = "net.dongliu:apk-parser:2.6.10"
|
apk-parser = "net.dongliu:apk-parser:2.6.10"
|
||||||
apksig = "com.android.tools.build:apksig:9.0.1"
|
apksig = "com.android.tools.build:apksig:9.2.1"
|
||||||
|
|
||||||
# Xml
|
# Xml
|
||||||
xmlpull = "xmlpull:xmlpull:1.1.3.4a"
|
xmlpull = "xmlpull:xmlpull:1.1.3.4a"
|
||||||
@@ -115,13 +115,13 @@ appdirs = "ca.gosyer:kotlin-multiplatform-appdirs:2.0.0"
|
|||||||
cache4k = "io.github.reactivecircus.cache4k:cache4k:0.14.0"
|
cache4k = "io.github.reactivecircus.cache4k:cache4k:0.14.0"
|
||||||
zip4j = "net.lingala.zip4j:zip4j:2.11.6"
|
zip4j = "net.lingala.zip4j:zip4j:2.11.6"
|
||||||
commonscompress = "org.apache.commons:commons-compress:1.28.0"
|
commonscompress = "org.apache.commons:commons-compress:1.28.0"
|
||||||
junrar = "com.github.junrar:junrar:7.5.7"
|
junrar = "com.github.junrar:junrar:7.5.10"
|
||||||
|
|
||||||
# AES/CBC/PKCS7Padding Cypher provider
|
# AES/CBC/PKCS7Padding Cypher provider
|
||||||
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.83"
|
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.84"
|
||||||
|
|
||||||
# AndroidX annotations
|
# AndroidX annotations
|
||||||
android-annotations = "androidx.annotation:annotation:1.9.1"
|
android-annotations = "androidx.annotation:annotation:1.10.0"
|
||||||
|
|
||||||
# Substitute for duktape-android
|
# Substitute for duktape-android
|
||||||
polyglot-core = { module = "org.graalvm.polyglot:polyglot", version.ref = "polyglot" }
|
polyglot-core = { module = "org.graalvm.polyglot:polyglot", version.ref = "polyglot" }
|
||||||
@@ -132,7 +132,7 @@ settings-core = { module = "com.russhwolf:multiplatform-settings-jvm", version.r
|
|||||||
settings-serialization = { module = "com.russhwolf:multiplatform-settings-serialization-jvm", version.ref = "settings" }
|
settings-serialization = { module = "com.russhwolf:multiplatform-settings-serialization-jvm", version.ref = "settings" }
|
||||||
|
|
||||||
# ICU4J
|
# ICU4J
|
||||||
icu4j = "com.ibm.icu:icu4j:78.2"
|
icu4j = "com.ibm.icu:icu4j:78.3"
|
||||||
|
|
||||||
# Image Decoding implementation provider
|
# Image Decoding implementation provider
|
||||||
twelvemonkeys-common-lang = { module = "com.twelvemonkeys.common:common-lang", version.ref = "twelvemonkeys" }
|
twelvemonkeys-common-lang = { module = "com.twelvemonkeys.common:common-lang", version.ref = "twelvemonkeys" }
|
||||||
@@ -158,7 +158,7 @@ cronUtils = "com.cronutils:cron-utils:9.2.1"
|
|||||||
kcef = "dev.datlag:kcef:2024.04.20.4"
|
kcef = "dev.datlag:kcef:2024.04.20.4"
|
||||||
|
|
||||||
# User
|
# User
|
||||||
jwt = "com.auth0:java-jwt:4.5.1"
|
jwt = "com.auth0:java-jwt:4.5.2"
|
||||||
|
|
||||||
# lint - used for renovate to update ktlint version
|
# lint - used for renovate to update ktlint version
|
||||||
ktlint = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" }
|
ktlint = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" }
|
||||||
@@ -173,16 +173,16 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi
|
|||||||
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin"}
|
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin"}
|
||||||
|
|
||||||
# Linter
|
# Linter
|
||||||
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "14.0.1"}
|
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "14.2.0"}
|
||||||
|
|
||||||
# Build config
|
# Build config
|
||||||
buildconfig = { id = "com.github.gmazzo.buildconfig", version = "6.0.7"}
|
buildconfig = { id = "com.github.gmazzo.buildconfig", version = "6.0.9"}
|
||||||
|
|
||||||
# Download
|
# Download
|
||||||
download = { id = "de.undercouch.download", version = "5.7.0"}
|
download = { id = "de.undercouch.download", version = "5.7.0"}
|
||||||
|
|
||||||
# ShadowJar
|
# ShadowJar
|
||||||
shadowjar = { id = "com.gradleup.shadow", version = "8.3.9"}
|
shadowjar = { id = "com.gradleup.shadow", version = "8.3.10"}
|
||||||
|
|
||||||
# Moko
|
# Moko
|
||||||
moko = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko" }
|
moko = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko" }
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,7 +1,9 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
|
retries=0
|
||||||
|
retryBackOffMs=500
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
2
gradlew
vendored
2
gradlew
vendored
@@ -57,7 +57,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
|||||||
31
gradlew.bat
vendored
31
gradlew.bat
vendored
@@ -23,8 +23,8 @@
|
|||||||
@rem
|
@rem
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
setlocal EnableExtensions
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
@@ -51,7 +51,7 @@ echo. 1>&2
|
|||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
:findJavaFromJavaHome
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
@@ -65,7 +65,7 @@ echo. 1>&2
|
|||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
@@ -73,21 +73,10 @@ goto fail
|
|||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
||||||
|
@rem which allows us to clear the local environment before executing the java command
|
||||||
|
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
||||||
|
|
||||||
:end
|
:exitWithErrorLevel
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||||
|
|
||||||
:fail
|
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
|
||||||
rem the _cmd.exe /c_ return code!
|
|
||||||
set EXIT_CODE=%ERRORLEVEL%
|
|
||||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
|
||||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
|
||||||
exit /b %EXIT_CODE%
|
|
||||||
|
|
||||||
:mainEnd
|
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
|
||||||
|
|
||||||
:omega
|
|
||||||
|
|||||||
@@ -140,17 +140,16 @@ fun OkHttpClient.newCachelessCallWithProgress(
|
|||||||
return progressClient.newCall(request)
|
return progressClient.newCall(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
context(Json)
|
context(_: Json)
|
||||||
inline fun <reified T> Response.parseAs(): T = decodeFromJsonResponse(serializer(), this)
|
inline fun <reified T> Response.parseAs(): T = decodeFromJsonResponse(serializer(), this)
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
context(json: Json)
|
||||||
context(Json)
|
|
||||||
fun <T> decodeFromJsonResponse(
|
fun <T> decodeFromJsonResponse(
|
||||||
deserializer: DeserializationStrategy<T>,
|
deserializer: DeserializationStrategy<T>,
|
||||||
response: Response,
|
response: Response,
|
||||||
): T =
|
): T =
|
||||||
response.body.source().use {
|
response.body.source().use {
|
||||||
decodeFromBufferedSource(deserializer, it)
|
json.decodeFromBufferedSource(deserializer, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
class HttpException(
|
class HttpException(
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import io.javalin.websocket.WsMessageContext
|
|||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.eclipse.jetty.websocket.api.CloseStatus
|
import org.eclipse.jetty.websocket.core.CloseStatus
|
||||||
import suwayomi.tachidesk.manga.impl.update.Websocket
|
import suwayomi.tachidesk.manga.impl.update.Websocket
|
||||||
|
|
||||||
object WebView : Websocket<String>() {
|
object WebView : Websocket<String>() {
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
package suwayomi.tachidesk.graphql
|
|
||||||
|
|
||||||
import com.expediagroup.graphql.server.extensions.toGraphQLError
|
|
||||||
import graphql.execution.DataFetcherResult
|
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
|
||||||
|
|
||||||
val logger = KotlinLogging.logger { }
|
|
||||||
|
|
||||||
inline fun <T> asDataFetcherResult(block: () -> T): DataFetcherResult<T?> {
|
|
||||||
val result =
|
|
||||||
runCatching {
|
|
||||||
block()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.isFailure) {
|
|
||||||
logger.error(result.exceptionOrNull()) { "asDataFetcherResult: failed due to" }
|
|
||||||
return DataFetcherResult
|
|
||||||
.newResult<T?>()
|
|
||||||
.error(result.exceptionOrNull()?.toGraphQLError())
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
return DataFetcherResult
|
|
||||||
.newResult<T?>()
|
|
||||||
.data(result.getOrNull())
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
@@ -3,12 +3,8 @@ package suwayomi.tachidesk.graphql.cache
|
|||||||
import org.dataloader.CacheMap
|
import org.dataloader.CacheMap
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
class CustomCacheMap<K, V> : CacheMap<K, V> {
|
class CustomCacheMap<K : Any, V : Any> : CacheMap<K, V> {
|
||||||
private val cache: MutableMap<K, CompletableFuture<V>>
|
private val cache: MutableMap<K, CompletableFuture<V>> = HashMap()
|
||||||
|
|
||||||
init {
|
|
||||||
cache = HashMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun containsKey(key: K): Boolean = cache.containsKey(key)
|
override fun containsKey(key: K): Boolean = cache.containsKey(key)
|
||||||
|
|
||||||
@@ -18,12 +14,12 @@ class CustomCacheMap<K, V> : CacheMap<K, V> {
|
|||||||
|
|
||||||
override fun getAll(): Collection<CompletableFuture<V>> = cache.values
|
override fun getAll(): Collection<CompletableFuture<V>> = cache.values
|
||||||
|
|
||||||
override fun set(
|
override fun putIfAbsentAtomically(
|
||||||
key: K,
|
key: K,
|
||||||
value: CompletableFuture<V>,
|
value: CompletableFuture<V>,
|
||||||
): CacheMap<K, V> {
|
): CompletableFuture<V> {
|
||||||
cache[key] = value
|
cache[key] = value
|
||||||
return this
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun delete(key: K): CacheMap<K, V> {
|
override fun delete(key: K): CacheMap<K, V> {
|
||||||
@@ -35,4 +31,6 @@ class CustomCacheMap<K, V> : CacheMap<K, V> {
|
|||||||
cache.clear()
|
cache.clear()
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun size(): Int = cache.size
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ import suwayomi.tachidesk.graphql.types.ChapterType
|
|||||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||||
|
|
||||||
class ChapterDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
class ChapterDataLoader : KotlinDataLoader<Int, ChapterType> {
|
||||||
override val dataLoaderName = "ChapterDataLoader"
|
override val dataLoaderName = "ChapterDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
|
||||||
DataLoaderFactory.newDataLoader<Int, ChapterType> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -48,7 +48,7 @@ class ChaptersForMangaDataLoader : KotlinDataLoader<Int, ChapterNodeList> {
|
|||||||
override val dataLoaderName = "ChaptersForMangaDataLoader"
|
override val dataLoaderName = "ChaptersForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterNodeList> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterNodeList> =
|
||||||
DataLoaderFactory.newDataLoader<Int, ChapterNodeList> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -68,7 +68,7 @@ class DownloadedChapterCountForMangaDataLoader : KotlinDataLoader<Int, Int> {
|
|||||||
override val dataLoaderName = "DownloadedChapterCountForMangaDataLoader"
|
override val dataLoaderName = "DownloadedChapterCountForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, Int> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, Int> =
|
||||||
DataLoaderFactory.newDataLoader<Int, Int> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -90,7 +90,7 @@ class UnreadChapterCountForMangaDataLoader : KotlinDataLoader<Int, Int> {
|
|||||||
override val dataLoaderName = "UnreadChapterCountForMangaDataLoader"
|
override val dataLoaderName = "UnreadChapterCountForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, Int> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, Int> =
|
||||||
DataLoaderFactory.newDataLoader<Int, Int> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -112,7 +112,7 @@ class BookmarkedChapterCountForMangaDataLoader : KotlinDataLoader<Int, Int> {
|
|||||||
override val dataLoaderName = "BookmarkedChapterCountForMangaDataLoader"
|
override val dataLoaderName = "BookmarkedChapterCountForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, Int> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, Int> =
|
||||||
DataLoaderFactory.newDataLoader<Int, Int> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -157,11 +157,11 @@ class HasDuplicateChaptersForMangaDataLoader : KotlinDataLoader<Int, Boolean> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LastReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
class LastReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType> {
|
||||||
override val dataLoaderName = "LastReadChapterForMangaDataLoader"
|
override val dataLoaderName = "LastReadChapterForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
|
||||||
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -177,11 +177,11 @@ class LastReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LatestReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
class LatestReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType> {
|
||||||
override val dataLoaderName = "LatestReadChapterForMangaDataLoader"
|
override val dataLoaderName = "LatestReadChapterForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
|
||||||
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -197,11 +197,11 @@ class LatestReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LatestFetchedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
class LatestFetchedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType> {
|
||||||
override val dataLoaderName = "LatestFetchedChapterForMangaDataLoader"
|
override val dataLoaderName = "LatestFetchedChapterForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
|
||||||
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -217,11 +217,11 @@ class LatestFetchedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LatestUploadedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
class LatestUploadedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType> {
|
||||||
override val dataLoaderName = "LatestUploadedChapterForMangaDataLoader"
|
override val dataLoaderName = "LatestUploadedChapterForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
|
||||||
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -237,11 +237,11 @@ class LatestUploadedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterTyp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FirstUnreadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
class FirstUnreadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType> {
|
||||||
override val dataLoaderName = "FirstUnreadChapterForMangaDataLoader"
|
override val dataLoaderName = "FirstUnreadChapterForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
|
||||||
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -257,11 +257,11 @@ class FirstUnreadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HighestNumberedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
class HighestNumberedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType> {
|
||||||
override val dataLoaderName = "HighestNumberedChapterForMangaDataLoader"
|
override val dataLoaderName = "HighestNumberedChapterForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
|
||||||
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
|||||||
import suwayomi.tachidesk.manga.model.table.SourceTable
|
import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||||
|
|
||||||
class ExtensionDataLoader : KotlinDataLoader<String, ExtensionType?> {
|
class ExtensionDataLoader : KotlinDataLoader<String, ExtensionType> {
|
||||||
override val dataLoaderName = "ExtensionDataLoader"
|
override val dataLoaderName = "ExtensionDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, ExtensionType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, ExtensionType> =
|
||||||
DataLoaderFactory.newDataLoader { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
@@ -40,10 +40,10 @@ class ExtensionDataLoader : KotlinDataLoader<String, ExtensionType?> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExtensionForSourceDataLoader : KotlinDataLoader<Long, ExtensionType?> {
|
class ExtensionForSourceDataLoader : KotlinDataLoader<Long, ExtensionType> {
|
||||||
override val dataLoaderName = "ExtensionForSourceDataLoader"
|
override val dataLoaderName = "ExtensionForSourceDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Long, ExtensionType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Long, ExtensionType> =
|
||||||
DataLoaderFactory.newDataLoader { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ import suwayomi.tachidesk.manga.model.table.CategoryMangaTable
|
|||||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||||
|
|
||||||
class MangaDataLoader : KotlinDataLoader<Int, MangaType?> {
|
class MangaDataLoader : KotlinDataLoader<Int, MangaType> {
|
||||||
override val dataLoaderName = "MangaDataLoader"
|
override val dataLoaderName = "MangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, MangaType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, MangaType> =
|
||||||
DataLoaderFactory.newDataLoader { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
@@ -122,6 +122,6 @@ class MangaForIdsDataLoader : KotlinDataLoader<List<Int>, MangaNodeList> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DataLoaderOptions.newOptions().setCacheMap(CustomCacheMap<List<Int>, MangaNodeList>()),
|
DataLoaderOptions.newOptions().setCacheMap(CustomCacheMap<List<Int>, MangaNodeList>()).build(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ import suwayomi.tachidesk.manga.model.table.MangaMetaTable
|
|||||||
import suwayomi.tachidesk.manga.model.table.SourceMetaTable
|
import suwayomi.tachidesk.manga.model.table.SourceMetaTable
|
||||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||||
|
|
||||||
class GlobalMetaDataLoader : KotlinDataLoader<String, GlobalMetaType?> {
|
class GlobalMetaDataLoader : KotlinDataLoader<String, GlobalMetaType> {
|
||||||
override val dataLoaderName = "GlobalMetaDataLoader"
|
override val dataLoaderName = "GlobalMetaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, GlobalMetaType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, GlobalMetaType> =
|
||||||
DataLoaderFactory.newDataLoader<String, GlobalMetaType?> { ids ->
|
DataLoaderFactory.newDataLoader<String, GlobalMetaType> { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
|||||||
import suwayomi.tachidesk.manga.model.table.SourceTable
|
import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||||
|
|
||||||
class SourceDataLoader : KotlinDataLoader<Long, SourceType?> {
|
class SourceDataLoader : KotlinDataLoader<Long, SourceType> {
|
||||||
override val dataLoaderName = "SourceDataLoader"
|
override val dataLoaderName = "SourceDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Long, SourceType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Long, SourceType> =
|
||||||
DataLoaderFactory.newDataLoader { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated
|
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
import graphql.execution.DataFetcherResult
|
||||||
@@ -15,7 +17,6 @@ import org.jetbrains.exposed.sql.or
|
|||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.CategoryMetaType
|
import suwayomi.tachidesk.graphql.types.CategoryMetaType
|
||||||
import suwayomi.tachidesk.graphql.types.CategoryType
|
import suwayomi.tachidesk.graphql.types.CategoryType
|
||||||
@@ -42,14 +43,13 @@ class CategoryMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setCategoryMeta(input: SetCategoryMetaInput): DataFetcherResult<SetCategoryMetaPayload?> =
|
fun setCategoryMeta(input: SetCategoryMetaInput): SetCategoryMetaPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, meta) = input
|
||||||
val (clientMutationId, meta) = input
|
|
||||||
|
|
||||||
Category.modifyMeta(meta.categoryId, meta.key, meta.value)
|
Category.modifyMeta(meta.categoryId, meta.key, meta.value)
|
||||||
|
|
||||||
SetCategoryMetaPayload(clientMutationId, meta)
|
return SetCategoryMetaPayload(clientMutationId, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteCategoryMetaInput(
|
data class DeleteCategoryMetaInput(
|
||||||
val clientMutationId: String? = null,
|
val clientMutationId: String? = null,
|
||||||
@@ -64,34 +64,33 @@ class CategoryMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteCategoryMeta(input: DeleteCategoryMetaInput): DataFetcherResult<DeleteCategoryMetaPayload?> =
|
fun deleteCategoryMeta(input: DeleteCategoryMetaInput): DeleteCategoryMetaPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, categoryId, key) = input
|
||||||
val (clientMutationId, categoryId, key) = input
|
|
||||||
|
|
||||||
val (meta, category) =
|
val (meta, category) =
|
||||||
transaction {
|
transaction {
|
||||||
val meta =
|
val meta =
|
||||||
CategoryMetaTable
|
CategoryMetaTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
.where { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
|
|
||||||
CategoryMetaTable.deleteWhere { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
CategoryMetaTable.deleteWhere { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
||||||
|
|
||||||
val category =
|
val category =
|
||||||
transaction {
|
transaction {
|
||||||
CategoryType(CategoryTable.selectAll().where { CategoryTable.id eq categoryId }.first())
|
CategoryType(CategoryTable.selectAll().where { CategoryTable.id eq categoryId }.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meta != null) {
|
if (meta != null) {
|
||||||
CategoryMetaType(meta)
|
CategoryMetaType(meta)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
} to category
|
} to category
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteCategoryMetaPayload(clientMutationId, meta, category)
|
return DeleteCategoryMetaPayload(clientMutationId, meta, category)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SetCategoryMetasItem(
|
data class SetCategoryMetasItem(
|
||||||
val categoryIds: List<Int>,
|
val categoryIds: List<Int>,
|
||||||
@@ -110,43 +109,42 @@ class CategoryMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setCategoryMetas(input: SetCategoryMetasInput): DataFetcherResult<SetCategoryMetasPayload?> =
|
fun setCategoryMetas(input: SetCategoryMetasInput): SetCategoryMetasPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, items) = input
|
||||||
val (clientMutationId, items) = input
|
|
||||||
|
|
||||||
val metaByCategoryId =
|
val metaByCategoryId =
|
||||||
items
|
items
|
||||||
.flatMap { item ->
|
.flatMap { item ->
|
||||||
val metaMap = item.metas.associate { it.key to it.value }
|
val metaMap = item.metas.associate { it.key to it.value }
|
||||||
item.categoryIds.map { categoryId -> categoryId to metaMap }
|
item.categoryIds.map { categoryId -> categoryId to metaMap }
|
||||||
}.groupBy({ it.first }, { it.second })
|
}.groupBy({ it.first }, { it.second })
|
||||||
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
||||||
|
|
||||||
Category.modifyCategoriesMetas(metaByCategoryId)
|
Category.modifyCategoriesMetas(metaByCategoryId)
|
||||||
|
|
||||||
val allCategoryIds = metaByCategoryId.keys
|
val allCategoryIds = metaByCategoryId.keys
|
||||||
val allMetaKeys = metaByCategoryId.values.flatMap { item -> item.keys }.distinct()
|
val allMetaKeys = metaByCategoryId.values.flatMap { item -> item.keys }.distinct()
|
||||||
|
|
||||||
val (updatedMetas, categories) =
|
val (updatedMetas, categories) =
|
||||||
transaction {
|
transaction {
|
||||||
val updatedMetas =
|
val updatedMetas =
|
||||||
CategoryMetaTable
|
CategoryMetaTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { (CategoryMetaTable.ref inList allCategoryIds) and (CategoryMetaTable.key inList allMetaKeys) }
|
.where { (CategoryMetaTable.ref inList allCategoryIds) and (CategoryMetaTable.key inList allMetaKeys) }
|
||||||
.map { CategoryMetaType(it) }
|
.map { CategoryMetaType(it) }
|
||||||
|
|
||||||
val categories =
|
val categories =
|
||||||
CategoryTable
|
CategoryTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { CategoryTable.id inList allCategoryIds }
|
.where { CategoryTable.id inList allCategoryIds }
|
||||||
.map { CategoryType(it) }
|
.map { CategoryType(it) }
|
||||||
.distinctBy { it.id }
|
.distinctBy { it.id }
|
||||||
|
|
||||||
updatedMetas to categories
|
updatedMetas to categories
|
||||||
}
|
}
|
||||||
|
|
||||||
SetCategoryMetasPayload(clientMutationId, updatedMetas, categories)
|
return SetCategoryMetasPayload(clientMutationId, updatedMetas, categories)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteCategoryMetasItem(
|
data class DeleteCategoryMetasItem(
|
||||||
val categoryIds: List<Int>,
|
val categoryIds: List<Int>,
|
||||||
@@ -166,64 +164,63 @@ class CategoryMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteCategoryMetas(input: DeleteCategoryMetasInput): DataFetcherResult<DeleteCategoryMetasPayload?> =
|
fun deleteCategoryMetas(input: DeleteCategoryMetasInput): DeleteCategoryMetasPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, items) = input
|
||||||
val (clientMutationId, items) = input
|
|
||||||
|
|
||||||
items.forEach { item ->
|
items.forEach { item ->
|
||||||
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
||||||
"Either 'keys' or 'prefixes' must be provided for each item"
|
"Either 'keys' or 'prefixes' must be provided for each item"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val (allDeletedMetas, allCategoryIds) =
|
||||||
|
transaction {
|
||||||
|
val deletedMetas = mutableListOf<CategoryMetaType>()
|
||||||
|
val categoryIds = mutableSetOf<Int>()
|
||||||
|
|
||||||
|
items.forEach { item ->
|
||||||
|
val keyCondition: Op<Boolean>? =
|
||||||
|
item.keys?.takeIf { it.isNotEmpty() }?.let { CategoryMetaTable.key inList it }
|
||||||
|
|
||||||
|
val prefixCondition: Op<Boolean>? =
|
||||||
|
item.prefixes
|
||||||
|
?.filter { it.isNotEmpty() }
|
||||||
|
?.map { (CategoryMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
||||||
|
?.reduceOrNull { acc, op -> acc or op }
|
||||||
|
|
||||||
|
val metaKeyCondition =
|
||||||
|
if (keyCondition != null && prefixCondition != null) {
|
||||||
|
keyCondition or prefixCondition
|
||||||
|
} else {
|
||||||
|
keyCondition ?: prefixCondition!!
|
||||||
|
}
|
||||||
|
|
||||||
|
val condition = (CategoryMetaTable.ref inList item.categoryIds) and metaKeyCondition
|
||||||
|
|
||||||
|
deletedMetas +=
|
||||||
|
CategoryMetaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { condition }
|
||||||
|
.map { CategoryMetaType(it) }
|
||||||
|
|
||||||
|
CategoryMetaTable.deleteWhere { condition }
|
||||||
|
categoryIds += item.categoryIds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deletedMetas to categoryIds
|
||||||
}
|
}
|
||||||
|
|
||||||
val (allDeletedMetas, allCategoryIds) =
|
val categories =
|
||||||
transaction {
|
transaction {
|
||||||
val deletedMetas = mutableListOf<CategoryMetaType>()
|
CategoryTable
|
||||||
val categoryIds = mutableSetOf<Int>()
|
.selectAll()
|
||||||
|
.where { CategoryTable.id inList allCategoryIds }
|
||||||
|
.map { CategoryType(it) }
|
||||||
|
.distinctBy { it.id }
|
||||||
|
}
|
||||||
|
|
||||||
items.forEach { item ->
|
return DeleteCategoryMetasPayload(clientMutationId, allDeletedMetas, categories)
|
||||||
val keyCondition: Op<Boolean>? =
|
}
|
||||||
item.keys?.takeIf { it.isNotEmpty() }?.let { CategoryMetaTable.key inList it }
|
|
||||||
|
|
||||||
val prefixCondition: Op<Boolean>? =
|
|
||||||
item.prefixes
|
|
||||||
?.filter { it.isNotEmpty() }
|
|
||||||
?.map { (CategoryMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
|
||||||
?.reduceOrNull { acc, op -> acc or op }
|
|
||||||
|
|
||||||
val metaKeyCondition =
|
|
||||||
if (keyCondition != null && prefixCondition != null) {
|
|
||||||
keyCondition or prefixCondition
|
|
||||||
} else {
|
|
||||||
keyCondition ?: prefixCondition!!
|
|
||||||
}
|
|
||||||
|
|
||||||
val condition = (CategoryMetaTable.ref inList item.categoryIds) and metaKeyCondition
|
|
||||||
|
|
||||||
deletedMetas +=
|
|
||||||
CategoryMetaTable
|
|
||||||
.selectAll()
|
|
||||||
.where { condition }
|
|
||||||
.map { CategoryMetaType(it) }
|
|
||||||
|
|
||||||
CategoryMetaTable.deleteWhere { condition }
|
|
||||||
categoryIds += item.categoryIds
|
|
||||||
}
|
|
||||||
|
|
||||||
deletedMetas to categoryIds
|
|
||||||
}
|
|
||||||
|
|
||||||
val categories =
|
|
||||||
transaction {
|
|
||||||
CategoryTable
|
|
||||||
.selectAll()
|
|
||||||
.where { CategoryTable.id inList allCategoryIds }
|
|
||||||
.map { CategoryType(it) }
|
|
||||||
.distinctBy { it.id }
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteCategoryMetasPayload(clientMutationId, allDeletedMetas, categories)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class UpdateCategoryPatch(
|
data class UpdateCategoryPatch(
|
||||||
val name: String? = null,
|
val name: String? = null,
|
||||||
@@ -291,40 +288,38 @@ class CategoryMutation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateCategory(input: UpdateCategoryInput): DataFetcherResult<UpdateCategoryPayload?> =
|
fun updateCategory(input: UpdateCategoryInput): UpdateCategoryPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, id, patch) = input
|
||||||
val (clientMutationId, id, patch) = input
|
|
||||||
|
|
||||||
updateCategories(listOf(id), patch)
|
updateCategories(listOf(id), patch)
|
||||||
|
|
||||||
val category =
|
val category =
|
||||||
transaction {
|
transaction {
|
||||||
CategoryType(CategoryTable.selectAll().where { CategoryTable.id eq id }.first())
|
CategoryType(CategoryTable.selectAll().where { CategoryTable.id eq id }.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateCategoryPayload(
|
return UpdateCategoryPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
category = category,
|
category = category,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateCategories(input: UpdateCategoriesInput): DataFetcherResult<UpdateCategoriesPayload?> =
|
fun updateCategories(input: UpdateCategoriesInput): UpdateCategoriesPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, ids, patch) = input
|
||||||
val (clientMutationId, ids, patch) = input
|
|
||||||
|
|
||||||
updateCategories(ids, patch)
|
updateCategories(ids, patch)
|
||||||
|
|
||||||
val categories =
|
val categories =
|
||||||
transaction {
|
transaction {
|
||||||
CategoryTable.selectAll().where { CategoryTable.id inList ids }.map { CategoryType(it) }
|
CategoryTable.selectAll().where { CategoryTable.id inList ids }.map { CategoryType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateCategoriesPayload(
|
return UpdateCategoriesPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
categories = categories,
|
categories = categories,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class UpdateCategoryOrderPayload(
|
data class UpdateCategoryOrderPayload(
|
||||||
val clientMutationId: String?,
|
val clientMutationId: String?,
|
||||||
@@ -338,50 +333,49 @@ class CategoryMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateCategoryOrder(input: UpdateCategoryOrderInput): DataFetcherResult<UpdateCategoryOrderPayload?> =
|
fun updateCategoryOrder(input: UpdateCategoryOrderInput): UpdateCategoryOrderPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, categoryId, position) = input
|
||||||
val (clientMutationId, categoryId, position) = input
|
require(position > 0) {
|
||||||
require(position > 0) {
|
"'order' must not be <= 0"
|
||||||
"'order' must not be <= 0"
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction {
|
|
||||||
val currentOrder =
|
|
||||||
CategoryTable
|
|
||||||
.selectAll()
|
|
||||||
.where { CategoryTable.id eq categoryId }
|
|
||||||
.first()[CategoryTable.order]
|
|
||||||
|
|
||||||
if (currentOrder != position) {
|
|
||||||
if (position < currentOrder) {
|
|
||||||
CategoryTable.update({ CategoryTable.order greaterEq position }) {
|
|
||||||
it[CategoryTable.order] = CategoryTable.order + 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
CategoryTable.update({ CategoryTable.order lessEq position }) {
|
|
||||||
it[CategoryTable.order] = CategoryTable.order - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CategoryTable.update({ CategoryTable.id eq categoryId }) {
|
|
||||||
it[CategoryTable.order] = position
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Category.normalizeCategories()
|
|
||||||
|
|
||||||
val categories =
|
|
||||||
transaction {
|
|
||||||
CategoryTable.selectAll().orderBy(CategoryTable.order).map { CategoryType(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateCategoryOrderPayload(
|
|
||||||
clientMutationId = clientMutationId,
|
|
||||||
categories = categories,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction {
|
||||||
|
val currentOrder =
|
||||||
|
CategoryTable
|
||||||
|
.selectAll()
|
||||||
|
.where { CategoryTable.id eq categoryId }
|
||||||
|
.first()[CategoryTable.order]
|
||||||
|
|
||||||
|
if (currentOrder != position) {
|
||||||
|
if (position < currentOrder) {
|
||||||
|
CategoryTable.update({ CategoryTable.order greaterEq position }) {
|
||||||
|
it[CategoryTable.order] = CategoryTable.order + 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CategoryTable.update({ CategoryTable.order lessEq position }) {
|
||||||
|
it[CategoryTable.order] = CategoryTable.order - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CategoryTable.update({ CategoryTable.id eq categoryId }) {
|
||||||
|
it[CategoryTable.order] = position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Category.normalizeCategories()
|
||||||
|
|
||||||
|
val categories =
|
||||||
|
transaction {
|
||||||
|
CategoryTable.selectAll().orderBy(CategoryTable.order).map { CategoryType(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return UpdateCategoryOrderPayload(
|
||||||
|
clientMutationId = clientMutationId,
|
||||||
|
categories = categories,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
data class CreateCategoryInput(
|
data class CreateCategoryInput(
|
||||||
val clientMutationId: String? = null,
|
val clientMutationId: String? = null,
|
||||||
val name: String,
|
val name: String,
|
||||||
@@ -397,53 +391,52 @@ class CategoryMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun createCategory(input: CreateCategoryInput): DataFetcherResult<CreateCategoryPayload?> =
|
fun createCategory(input: CreateCategoryInput): CreateCategoryPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, name, order, default, includeInUpdate, includeInDownload) = input
|
||||||
val (clientMutationId, name, order, default, includeInUpdate, includeInDownload) = input
|
transaction {
|
||||||
transaction {
|
require(CategoryTable.selectAll().where { CategoryTable.name eq input.name }.isEmpty()) {
|
||||||
require(CategoryTable.selectAll().where { CategoryTable.name eq input.name }.isEmpty()) {
|
"'name' must be unique"
|
||||||
"'name' must be unique"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
require(!name.equals(Category.DEFAULT_CATEGORY_NAME, ignoreCase = true)) {
|
}
|
||||||
"'name' must not be ${Category.DEFAULT_CATEGORY_NAME}"
|
require(!name.equals(Category.DEFAULT_CATEGORY_NAME, ignoreCase = true)) {
|
||||||
}
|
"'name' must not be ${Category.DEFAULT_CATEGORY_NAME}"
|
||||||
if (order != null) {
|
}
|
||||||
require(order > 0) {
|
if (order != null) {
|
||||||
"'order' must not be <= 0"
|
require(order > 0) {
|
||||||
}
|
"'order' must not be <= 0"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val category =
|
val category =
|
||||||
transaction {
|
transaction {
|
||||||
if (order != null) {
|
if (order != null) {
|
||||||
CategoryTable.update({ CategoryTable.order greaterEq order }) {
|
CategoryTable.update({ CategoryTable.order greaterEq order }) {
|
||||||
it[CategoryTable.order] = CategoryTable.order + 1
|
it[CategoryTable.order] = CategoryTable.order + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val id =
|
||||||
|
CategoryTable.insertAndGetId {
|
||||||
|
it[CategoryTable.name] = input.name
|
||||||
|
it[CategoryTable.order] = order ?: Int.MAX_VALUE
|
||||||
|
if (default != null) {
|
||||||
|
it[CategoryTable.isDefault] = default
|
||||||
|
}
|
||||||
|
if (includeInUpdate != null) {
|
||||||
|
it[CategoryTable.includeInUpdate] = includeInUpdate.value
|
||||||
|
}
|
||||||
|
if (includeInDownload != null) {
|
||||||
|
it[CategoryTable.includeInDownload] = includeInDownload.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val id =
|
Category.normalizeCategories()
|
||||||
CategoryTable.insertAndGetId {
|
|
||||||
it[CategoryTable.name] = input.name
|
|
||||||
it[CategoryTable.order] = order ?: Int.MAX_VALUE
|
|
||||||
if (default != null) {
|
|
||||||
it[CategoryTable.isDefault] = default
|
|
||||||
}
|
|
||||||
if (includeInUpdate != null) {
|
|
||||||
it[CategoryTable.includeInUpdate] = includeInUpdate.value
|
|
||||||
}
|
|
||||||
if (includeInDownload != null) {
|
|
||||||
it[CategoryTable.includeInDownload] = includeInDownload.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Category.normalizeCategories()
|
CategoryType(CategoryTable.selectAll().where { CategoryTable.id eq id }.first())
|
||||||
|
}
|
||||||
|
|
||||||
CategoryType(CategoryTable.selectAll().where { CategoryTable.id eq id }.first())
|
return CreateCategoryPayload(clientMutationId, category)
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateCategoryPayload(clientMutationId, category)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class DeleteCategoryInput(
|
data class DeleteCategoryInput(
|
||||||
val clientMutationId: String? = null,
|
val clientMutationId: String? = null,
|
||||||
@@ -457,47 +450,45 @@ class CategoryMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteCategory(input: DeleteCategoryInput): DataFetcherResult<DeleteCategoryPayload?> {
|
fun deleteCategory(input: DeleteCategoryInput): DeleteCategoryPayload? {
|
||||||
return asDataFetcherResult {
|
val (clientMutationId, categoryId) = input
|
||||||
val (clientMutationId, categoryId) = input
|
if (categoryId == 0) { // Don't delete default category
|
||||||
if (categoryId == 0) { // Don't delete default category
|
return DeleteCategoryPayload(
|
||||||
return@asDataFetcherResult DeleteCategoryPayload(
|
clientMutationId,
|
||||||
clientMutationId,
|
null,
|
||||||
null,
|
emptyList(),
|
||||||
emptyList(),
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
val (category, mangas) =
|
||||||
|
transaction {
|
||||||
|
val category =
|
||||||
|
CategoryTable
|
||||||
|
.selectAll()
|
||||||
|
.where { CategoryTable.id eq categoryId }
|
||||||
|
.firstOrNull()
|
||||||
|
|
||||||
|
val mangas =
|
||||||
|
transaction {
|
||||||
|
MangaTable
|
||||||
|
.innerJoin(CategoryMangaTable)
|
||||||
|
.selectAll()
|
||||||
|
.where { CategoryMangaTable.category eq categoryId }
|
||||||
|
.map { MangaType(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
CategoryTable.deleteWhere { CategoryTable.id eq categoryId }
|
||||||
|
|
||||||
|
Category.normalizeCategories()
|
||||||
|
|
||||||
|
if (category != null) {
|
||||||
|
CategoryType(category)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
} to mangas
|
||||||
}
|
}
|
||||||
|
|
||||||
val (category, mangas) =
|
return DeleteCategoryPayload(clientMutationId, category, mangas)
|
||||||
transaction {
|
|
||||||
val category =
|
|
||||||
CategoryTable
|
|
||||||
.selectAll()
|
|
||||||
.where { CategoryTable.id eq categoryId }
|
|
||||||
.firstOrNull()
|
|
||||||
|
|
||||||
val mangas =
|
|
||||||
transaction {
|
|
||||||
MangaTable
|
|
||||||
.innerJoin(CategoryMangaTable)
|
|
||||||
.selectAll()
|
|
||||||
.where { CategoryMangaTable.category eq categoryId }
|
|
||||||
.map { MangaType(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
CategoryTable.deleteWhere { CategoryTable.id eq categoryId }
|
|
||||||
|
|
||||||
Category.normalizeCategories()
|
|
||||||
|
|
||||||
if (category != null) {
|
|
||||||
CategoryType(category)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
} to mangas
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteCategoryPayload(clientMutationId, category, mangas)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class UpdateMangaCategoriesPatch(
|
data class UpdateMangaCategoriesPatch(
|
||||||
@@ -547,38 +538,36 @@ class CategoryMutation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateMangaCategories(input: UpdateMangaCategoriesInput): DataFetcherResult<UpdateMangaCategoriesPayload?> =
|
fun updateMangaCategories(input: UpdateMangaCategoriesInput): UpdateMangaCategoriesPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, id, patch) = input
|
||||||
val (clientMutationId, id, patch) = input
|
|
||||||
|
|
||||||
updateMangas(listOf(id), patch)
|
updateMangas(listOf(id), patch)
|
||||||
|
|
||||||
val manga =
|
val manga =
|
||||||
transaction {
|
transaction {
|
||||||
MangaType(MangaTable.selectAll().where { MangaTable.id eq id }.first())
|
MangaType(MangaTable.selectAll().where { MangaTable.id eq id }.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateMangaCategoriesPayload(
|
return UpdateMangaCategoriesPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
manga = manga,
|
manga = manga,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateMangasCategories(input: UpdateMangasCategoriesInput): DataFetcherResult<UpdateMangasCategoriesPayload?> =
|
fun updateMangasCategories(input: UpdateMangasCategoriesInput): UpdateMangasCategoriesPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, ids, patch) = input
|
||||||
val (clientMutationId, ids, patch) = input
|
|
||||||
|
|
||||||
updateMangas(ids, patch)
|
updateMangas(ids, patch)
|
||||||
|
|
||||||
val mangas =
|
val mangas =
|
||||||
transaction {
|
transaction {
|
||||||
MangaTable.selectAll().where { MangaTable.id inList ids }.map { MangaType(it) }
|
MangaTable.selectAll().where { MangaTable.id inList ids }.map { MangaType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateMangasCategoriesPayload(
|
return UpdateMangasCategoriesPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
mangas = mangas,
|
mangas = mangas,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jetbrains.exposed.dao.id.EntityID
|
import org.jetbrains.exposed.dao.id.EntityID
|
||||||
@@ -16,7 +17,6 @@ import org.jetbrains.exposed.sql.selectAll
|
|||||||
import org.jetbrains.exposed.sql.statements.BatchUpdateStatement
|
import org.jetbrains.exposed.sql.statements.BatchUpdateStatement
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.ChapterMetaType
|
import suwayomi.tachidesk.graphql.types.ChapterMetaType
|
||||||
import suwayomi.tachidesk.graphql.types.ChapterType
|
import suwayomi.tachidesk.graphql.types.ChapterType
|
||||||
@@ -120,40 +120,38 @@ class ChapterMutation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateChapter(input: UpdateChapterInput): DataFetcherResult<UpdateChapterPayload?> =
|
fun updateChapter(input: UpdateChapterInput): UpdateChapterPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, id, patch) = input
|
||||||
val (clientMutationId, id, patch) = input
|
|
||||||
|
|
||||||
updateChapters(listOf(id), patch)
|
updateChapters(listOf(id), patch)
|
||||||
|
|
||||||
val chapter =
|
val chapter =
|
||||||
transaction {
|
transaction {
|
||||||
ChapterType(ChapterTable.selectAll().where { ChapterTable.id eq id }.first())
|
ChapterType(ChapterTable.selectAll().where { ChapterTable.id eq id }.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateChapterPayload(
|
return UpdateChapterPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
chapter = chapter,
|
chapter = chapter,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateChapters(input: UpdateChaptersInput): DataFetcherResult<UpdateChaptersPayload?> =
|
fun updateChapters(input: UpdateChaptersInput): UpdateChaptersPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, ids, patch) = input
|
||||||
val (clientMutationId, ids, patch) = input
|
|
||||||
|
|
||||||
updateChapters(ids, patch)
|
updateChapters(ids, patch)
|
||||||
|
|
||||||
val chapters =
|
val chapters =
|
||||||
transaction {
|
transaction {
|
||||||
ChapterTable.selectAll().where { ChapterTable.id inList ids }.map { ChapterType(it) }
|
ChapterTable.selectAll().where { ChapterTable.id inList ids }.map { ChapterType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateChaptersPayload(
|
return UpdateChaptersPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
chapters = chapters,
|
chapters = chapters,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class FetchChaptersInput(
|
data class FetchChaptersInput(
|
||||||
val clientMutationId: String? = null,
|
val clientMutationId: String? = null,
|
||||||
@@ -166,27 +164,25 @@ class ChapterMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun fetchChapters(input: FetchChaptersInput): CompletableFuture<DataFetcherResult<FetchChaptersPayload?>> {
|
fun fetchChapters(input: FetchChaptersInput): CompletableFuture<FetchChaptersPayload?> {
|
||||||
val (clientMutationId, mangaId) = input
|
val (clientMutationId, mangaId) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
Chapter.fetchChapterList(mangaId)
|
||||||
Chapter.fetchChapterList(mangaId)
|
|
||||||
|
|
||||||
val chapters =
|
val chapters =
|
||||||
transaction {
|
transaction {
|
||||||
ChapterTable
|
ChapterTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { ChapterTable.manga eq mangaId }
|
.where { ChapterTable.manga eq mangaId }
|
||||||
.orderBy(ChapterTable.sourceOrder)
|
.orderBy(ChapterTable.sourceOrder)
|
||||||
.map { ChapterType(it) }
|
.map { ChapterType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
FetchChaptersPayload(
|
FetchChaptersPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
chapters = chapters,
|
chapters = chapters,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,14 +197,13 @@ class ChapterMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setChapterMeta(input: SetChapterMetaInput): DataFetcherResult<SetChapterMetaPayload?> =
|
fun setChapterMeta(input: SetChapterMetaInput): SetChapterMetaPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, meta) = input
|
||||||
val (clientMutationId, meta) = input
|
|
||||||
|
|
||||||
Chapter.modifyChapterMeta(meta.chapterId, meta.key, meta.value)
|
Chapter.modifyChapterMeta(meta.chapterId, meta.key, meta.value)
|
||||||
|
|
||||||
SetChapterMetaPayload(clientMutationId, meta)
|
return SetChapterMetaPayload(clientMutationId, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteChapterMetaInput(
|
data class DeleteChapterMetaInput(
|
||||||
val clientMutationId: String? = null,
|
val clientMutationId: String? = null,
|
||||||
@@ -223,34 +218,33 @@ class ChapterMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteChapterMeta(input: DeleteChapterMetaInput): DataFetcherResult<DeleteChapterMetaPayload?> =
|
fun deleteChapterMeta(input: DeleteChapterMetaInput): DeleteChapterMetaPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, chapterId, key) = input
|
||||||
val (clientMutationId, chapterId, key) = input
|
|
||||||
|
|
||||||
val (meta, chapter) =
|
val (meta, chapter) =
|
||||||
transaction {
|
transaction {
|
||||||
val meta =
|
val meta =
|
||||||
ChapterMetaTable
|
ChapterMetaTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
.where { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
|
|
||||||
ChapterMetaTable.deleteWhere { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
ChapterMetaTable.deleteWhere { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
||||||
|
|
||||||
val chapter =
|
val chapter =
|
||||||
transaction {
|
transaction {
|
||||||
ChapterType(ChapterTable.selectAll().where { ChapterTable.id eq chapterId }.first())
|
ChapterType(ChapterTable.selectAll().where { ChapterTable.id eq chapterId }.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meta != null) {
|
if (meta != null) {
|
||||||
ChapterMetaType(meta)
|
ChapterMetaType(meta)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
} to chapter
|
} to chapter
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteChapterMetaPayload(clientMutationId, meta, chapter)
|
return DeleteChapterMetaPayload(clientMutationId, meta, chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SetChapterMetasItem(
|
data class SetChapterMetasItem(
|
||||||
val chapterIds: List<Int>,
|
val chapterIds: List<Int>,
|
||||||
@@ -269,43 +263,42 @@ class ChapterMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setChapterMetas(input: SetChapterMetasInput): DataFetcherResult<SetChapterMetasPayload?> =
|
fun setChapterMetas(input: SetChapterMetasInput): SetChapterMetasPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, items) = input
|
||||||
val (clientMutationId, items) = input
|
|
||||||
|
|
||||||
val metaByChapterId =
|
val metaByChapterId =
|
||||||
items
|
items
|
||||||
.flatMap { item ->
|
.flatMap { item ->
|
||||||
val metaMap = item.metas.associate { it.key to it.value }
|
val metaMap = item.metas.associate { it.key to it.value }
|
||||||
item.chapterIds.map { chapterId -> chapterId to metaMap }
|
item.chapterIds.map { chapterId -> chapterId to metaMap }
|
||||||
}.groupBy({ it.first }, { it.second })
|
}.groupBy({ it.first }, { it.second })
|
||||||
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
||||||
|
|
||||||
Chapter.modifyChaptersMetas(metaByChapterId)
|
Chapter.modifyChaptersMetas(metaByChapterId)
|
||||||
|
|
||||||
val allChapterIds = metaByChapterId.keys
|
val allChapterIds = metaByChapterId.keys
|
||||||
val allMetaKeys = metaByChapterId.values.flatMap { it.keys }.distinct()
|
val allMetaKeys = metaByChapterId.values.flatMap { it.keys }.distinct()
|
||||||
|
|
||||||
val (updatedMetas, chapters) =
|
val (updatedMetas, chapters) =
|
||||||
transaction {
|
transaction {
|
||||||
val updatedMetas =
|
val updatedMetas =
|
||||||
ChapterMetaTable
|
ChapterMetaTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { (ChapterMetaTable.ref inList allChapterIds) and (ChapterMetaTable.key inList allMetaKeys) }
|
.where { (ChapterMetaTable.ref inList allChapterIds) and (ChapterMetaTable.key inList allMetaKeys) }
|
||||||
.map { ChapterMetaType(it) }
|
.map { ChapterMetaType(it) }
|
||||||
|
|
||||||
val chapters =
|
val chapters =
|
||||||
ChapterTable
|
ChapterTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { ChapterTable.id inList allChapterIds }
|
.where { ChapterTable.id inList allChapterIds }
|
||||||
.map { ChapterType(it) }
|
.map { ChapterType(it) }
|
||||||
.distinctBy { it.id }
|
.distinctBy { it.id }
|
||||||
|
|
||||||
updatedMetas to chapters
|
updatedMetas to chapters
|
||||||
}
|
}
|
||||||
|
|
||||||
SetChapterMetasPayload(clientMutationId, updatedMetas, chapters)
|
return SetChapterMetasPayload(clientMutationId, updatedMetas, chapters)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteChapterMetasItem(
|
data class DeleteChapterMetasItem(
|
||||||
val chapterIds: List<Int>,
|
val chapterIds: List<Int>,
|
||||||
@@ -325,64 +318,63 @@ class ChapterMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteChapterMetas(input: DeleteChapterMetasInput): DataFetcherResult<DeleteChapterMetasPayload?> =
|
fun deleteChapterMetas(input: DeleteChapterMetasInput): DeleteChapterMetasPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, items) = input
|
||||||
val (clientMutationId, items) = input
|
|
||||||
|
|
||||||
items.forEach { item ->
|
items.forEach { item ->
|
||||||
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
||||||
"Either 'keys' or 'prefixes' must be provided for each item"
|
"Either 'keys' or 'prefixes' must be provided for each item"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val (allDeletedMetas, allChapterIds) =
|
||||||
|
transaction {
|
||||||
|
val deletedMetas = mutableListOf<ChapterMetaType>()
|
||||||
|
val chapterIds = mutableSetOf<Int>()
|
||||||
|
|
||||||
|
items.forEach { item ->
|
||||||
|
val keyCondition: Op<Boolean>? =
|
||||||
|
item.keys?.takeIf { it.isNotEmpty() }?.let { ChapterMetaTable.key inList it }
|
||||||
|
|
||||||
|
val prefixCondition: Op<Boolean>? =
|
||||||
|
item.prefixes
|
||||||
|
?.filter { it.isNotEmpty() }
|
||||||
|
?.map { (ChapterMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
||||||
|
?.reduceOrNull { acc, op -> acc or op }
|
||||||
|
|
||||||
|
val metaKeyCondition =
|
||||||
|
if (keyCondition != null && prefixCondition != null) {
|
||||||
|
keyCondition or prefixCondition
|
||||||
|
} else {
|
||||||
|
keyCondition ?: prefixCondition!!
|
||||||
|
}
|
||||||
|
|
||||||
|
val condition = (ChapterMetaTable.ref inList item.chapterIds) and metaKeyCondition
|
||||||
|
|
||||||
|
deletedMetas +=
|
||||||
|
ChapterMetaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { condition }
|
||||||
|
.map { ChapterMetaType(it) }
|
||||||
|
|
||||||
|
ChapterMetaTable.deleteWhere { condition }
|
||||||
|
chapterIds += item.chapterIds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deletedMetas to chapterIds
|
||||||
}
|
}
|
||||||
|
|
||||||
val (allDeletedMetas, allChapterIds) =
|
val chapters =
|
||||||
transaction {
|
transaction {
|
||||||
val deletedMetas = mutableListOf<ChapterMetaType>()
|
ChapterTable
|
||||||
val chapterIds = mutableSetOf<Int>()
|
.selectAll()
|
||||||
|
.where { ChapterTable.id inList allChapterIds }
|
||||||
|
.map { ChapterType(it) }
|
||||||
|
.distinctBy { it.id }
|
||||||
|
}
|
||||||
|
|
||||||
items.forEach { item ->
|
return DeleteChapterMetasPayload(clientMutationId, allDeletedMetas, chapters)
|
||||||
val keyCondition: Op<Boolean>? =
|
}
|
||||||
item.keys?.takeIf { it.isNotEmpty() }?.let { ChapterMetaTable.key inList it }
|
|
||||||
|
|
||||||
val prefixCondition: Op<Boolean>? =
|
|
||||||
item.prefixes
|
|
||||||
?.filter { it.isNotEmpty() }
|
|
||||||
?.map { (ChapterMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
|
||||||
?.reduceOrNull { acc, op -> acc or op }
|
|
||||||
|
|
||||||
val metaKeyCondition =
|
|
||||||
if (keyCondition != null && prefixCondition != null) {
|
|
||||||
keyCondition or prefixCondition
|
|
||||||
} else {
|
|
||||||
keyCondition ?: prefixCondition!!
|
|
||||||
}
|
|
||||||
|
|
||||||
val condition = (ChapterMetaTable.ref inList item.chapterIds) and metaKeyCondition
|
|
||||||
|
|
||||||
deletedMetas +=
|
|
||||||
ChapterMetaTable
|
|
||||||
.selectAll()
|
|
||||||
.where { condition }
|
|
||||||
.map { ChapterMetaType(it) }
|
|
||||||
|
|
||||||
ChapterMetaTable.deleteWhere { condition }
|
|
||||||
chapterIds += item.chapterIds
|
|
||||||
}
|
|
||||||
|
|
||||||
deletedMetas to chapterIds
|
|
||||||
}
|
|
||||||
|
|
||||||
val chapters =
|
|
||||||
transaction {
|
|
||||||
ChapterTable
|
|
||||||
.selectAll()
|
|
||||||
.where { ChapterTable.id inList allChapterIds }
|
|
||||||
.map { ChapterType(it) }
|
|
||||||
.distinctBy { it.id }
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteChapterMetasPayload(clientMutationId, allDeletedMetas, chapters)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class FetchChapterPagesInput(
|
data class FetchChapterPagesInput(
|
||||||
val clientMutationId: String? = null,
|
val clientMutationId: String? = null,
|
||||||
@@ -405,67 +397,65 @@ class ChapterMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun fetchChapterPages(input: FetchChapterPagesInput): CompletableFuture<DataFetcherResult<FetchChapterPagesPayload?>> {
|
fun fetchChapterPages(input: FetchChapterPagesInput): CompletableFuture<FetchChapterPagesPayload?> {
|
||||||
val (clientMutationId, chapterId) = input
|
val (clientMutationId, chapterId) = input
|
||||||
val paramsMap = input.toParams()
|
val paramsMap = input.toParams()
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
var chapter = getChapterDownloadReadyById(chapterId)
|
||||||
var chapter = getChapterDownloadReadyById(chapterId)
|
val syncResult = KoreaderSyncService.checkAndPullProgress(chapter.id)
|
||||||
val syncResult = KoreaderSyncService.checkAndPullProgress(chapter.id)
|
var syncConflictInfo: SyncConflictInfoType? = null
|
||||||
var syncConflictInfo: SyncConflictInfoType? = null
|
|
||||||
|
|
||||||
if (syncResult != null) {
|
if (syncResult != null) {
|
||||||
if (syncResult.isConflict) {
|
if (syncResult.isConflict) {
|
||||||
syncConflictInfo =
|
syncConflictInfo =
|
||||||
SyncConflictInfoType(
|
SyncConflictInfoType(
|
||||||
deviceName = syncResult.device,
|
deviceName = syncResult.device,
|
||||||
remotePage = syncResult.pageRead,
|
remotePage = syncResult.pageRead,
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (syncResult.shouldUpdate) {
|
|
||||||
// Update DB for SILENT and RECEIVE
|
|
||||||
transaction {
|
|
||||||
ChapterTable.update({ ChapterTable.id eq chapter.id }) {
|
|
||||||
it[lastPageRead] = syncResult.pageRead
|
|
||||||
it[lastReadAt] = syncResult.timestamp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// For PROMPT, SILENT, and RECEIVE, return the remote progress
|
|
||||||
chapter =
|
|
||||||
chapter.copy(
|
|
||||||
lastPageRead = if (syncResult.shouldUpdate) syncResult.pageRead else chapter.lastPageRead,
|
|
||||||
lastReadAt = if (syncResult.shouldUpdate) syncResult.timestamp else chapter.lastReadAt,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val params =
|
if (syncResult.shouldUpdate) {
|
||||||
buildString {
|
// Update DB for SILENT and RECEIVE
|
||||||
if (paramsMap.isNotEmpty()) {
|
transaction {
|
||||||
append("?")
|
ChapterTable.update({ ChapterTable.id eq chapter.id }) {
|
||||||
paramsMap.entries.forEach { entry ->
|
it[lastPageRead] = syncResult.pageRead
|
||||||
if (length > 1) {
|
it[lastReadAt] = syncResult.timestamp
|
||||||
append("&")
|
|
||||||
}
|
|
||||||
append(entry.key)
|
|
||||||
append("=")
|
|
||||||
append(URLEncoder.encode(entry.value, Charsets.UTF_8))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
FetchChapterPagesPayload(
|
// For PROMPT, SILENT, and RECEIVE, return the remote progress
|
||||||
clientMutationId = clientMutationId,
|
chapter =
|
||||||
pages =
|
chapter.copy(
|
||||||
List(chapter.pageCount) { index ->
|
lastPageRead = if (syncResult.shouldUpdate) syncResult.pageRead else chapter.lastPageRead,
|
||||||
"/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}/page/${index}$params"
|
lastReadAt = if (syncResult.shouldUpdate) syncResult.timestamp else chapter.lastReadAt,
|
||||||
},
|
)
|
||||||
chapter = ChapterType(chapter),
|
|
||||||
syncConflict = syncConflictInfo,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val params =
|
||||||
|
buildString {
|
||||||
|
if (paramsMap.isNotEmpty()) {
|
||||||
|
append("?")
|
||||||
|
paramsMap.entries.forEach { entry ->
|
||||||
|
if (length > 1) {
|
||||||
|
append("&")
|
||||||
|
}
|
||||||
|
append(entry.key)
|
||||||
|
append("=")
|
||||||
|
append(URLEncoder.encode(entry.value, Charsets.UTF_8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FetchChapterPagesPayload(
|
||||||
|
clientMutationId = clientMutationId,
|
||||||
|
pages =
|
||||||
|
List(chapter.pageCount) { index ->
|
||||||
|
"/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}/page/${index}$params"
|
||||||
|
},
|
||||||
|
chapter = ChapterType(chapter),
|
||||||
|
syncConflict = syncConflictInfo,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
import graphql.execution.DataFetcherResult
|
||||||
@@ -5,7 +7,6 @@ import kotlinx.coroutines.flow.first
|
|||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.ChapterType
|
import suwayomi.tachidesk.graphql.types.ChapterType
|
||||||
import suwayomi.tachidesk.graphql.types.DownloadStatus
|
import suwayomi.tachidesk.graphql.types.DownloadStatus
|
||||||
@@ -30,23 +31,21 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteDownloadedChapters(input: DeleteDownloadedChaptersInput): DataFetcherResult<DeleteDownloadedChaptersPayload?> {
|
fun deleteDownloadedChapters(input: DeleteDownloadedChaptersInput): DeleteDownloadedChaptersPayload? {
|
||||||
val (clientMutationId, chapters) = input
|
val (clientMutationId, chapters) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
Chapter.deleteChapters(chapters)
|
||||||
Chapter.deleteChapters(chapters)
|
|
||||||
|
|
||||||
DeleteDownloadedChaptersPayload(
|
return DeleteDownloadedChaptersPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
chapters =
|
chapters =
|
||||||
transaction {
|
transaction {
|
||||||
ChapterTable
|
ChapterTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { ChapterTable.id inList chapters }
|
.where { ChapterTable.id inList chapters }
|
||||||
.map { ChapterType(it) }
|
.map { ChapterType(it) }
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteDownloadedChapterInput(
|
data class DeleteDownloadedChapterInput(
|
||||||
@@ -60,20 +59,18 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteDownloadedChapter(input: DeleteDownloadedChapterInput): DataFetcherResult<DeleteDownloadedChapterPayload?> {
|
fun deleteDownloadedChapter(input: DeleteDownloadedChapterInput): DeleteDownloadedChapterPayload? {
|
||||||
val (clientMutationId, chapter) = input
|
val (clientMutationId, chapter) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
Chapter.deleteChapters(listOf(chapter))
|
||||||
Chapter.deleteChapters(listOf(chapter))
|
|
||||||
|
|
||||||
DeleteDownloadedChapterPayload(
|
return DeleteDownloadedChapterPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
chapters =
|
chapters =
|
||||||
transaction {
|
transaction {
|
||||||
ChapterType(ChapterTable.selectAll().where { ChapterTable.id eq chapter }.first())
|
ChapterType(ChapterTable.selectAll().where { ChapterTable.id eq chapter }.first())
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class EnqueueChapterDownloadsInput(
|
data class EnqueueChapterDownloadsInput(
|
||||||
@@ -87,28 +84,24 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun enqueueChapterDownloads(
|
fun enqueueChapterDownloads(input: EnqueueChapterDownloadsInput): CompletableFuture<EnqueueChapterDownloadsPayload?> {
|
||||||
input: EnqueueChapterDownloadsInput,
|
|
||||||
): CompletableFuture<DataFetcherResult<EnqueueChapterDownloadsPayload?>> {
|
|
||||||
val (clientMutationId, chapters) = input
|
val (clientMutationId, chapters) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
DownloadManager.enqueue(DownloadManager.EnqueueInput(chapters))
|
||||||
DownloadManager.enqueue(DownloadManager.EnqueueInput(chapters))
|
|
||||||
|
|
||||||
EnqueueChapterDownloadsPayload(
|
EnqueueChapterDownloadsPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
downloadStatus =
|
downloadStatus =
|
||||||
withTimeout(30.seconds) {
|
withTimeout(30.seconds) {
|
||||||
DownloadStatus(
|
DownloadStatus(
|
||||||
DownloadManager.updates
|
DownloadManager.updates
|
||||||
.first {
|
.first {
|
||||||
DownloadManager.getStatus().queue.any { it.chapterId in chapters }
|
DownloadManager.getStatus().queue.any { it.chapterId in chapters }
|
||||||
}.let { DownloadManager.getStatus() },
|
}.let { DownloadManager.getStatus() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,25 +116,23 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun enqueueChapterDownload(input: EnqueueChapterDownloadInput): CompletableFuture<DataFetcherResult<EnqueueChapterDownloadPayload?>> {
|
fun enqueueChapterDownload(input: EnqueueChapterDownloadInput): CompletableFuture<EnqueueChapterDownloadPayload?> {
|
||||||
val (clientMutationId, chapter) = input
|
val (clientMutationId, chapter) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
DownloadManager.enqueue(DownloadManager.EnqueueInput(listOf(chapter)))
|
||||||
DownloadManager.enqueue(DownloadManager.EnqueueInput(listOf(chapter)))
|
|
||||||
|
|
||||||
EnqueueChapterDownloadPayload(
|
EnqueueChapterDownloadPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
downloadStatus =
|
downloadStatus =
|
||||||
withTimeout(30.seconds) {
|
withTimeout(30.seconds) {
|
||||||
DownloadStatus(
|
DownloadStatus(
|
||||||
DownloadManager.updates
|
DownloadManager.updates
|
||||||
.first { it.updates.any { it.downloadQueueItem.chapterId == chapter } }
|
.first { it.updates.any { it.downloadQueueItem.chapterId == chapter } }
|
||||||
.let { DownloadManager.getStatus() },
|
.let { DownloadManager.getStatus() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,30 +147,26 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun dequeueChapterDownloads(
|
fun dequeueChapterDownloads(input: DequeueChapterDownloadsInput): CompletableFuture<DequeueChapterDownloadsPayload?> {
|
||||||
input: DequeueChapterDownloadsInput,
|
|
||||||
): CompletableFuture<DataFetcherResult<DequeueChapterDownloadsPayload?>> {
|
|
||||||
val (clientMutationId, chapters) = input
|
val (clientMutationId, chapters) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
DownloadManager.dequeue(DownloadManager.EnqueueInput(chapters))
|
||||||
DownloadManager.dequeue(DownloadManager.EnqueueInput(chapters))
|
|
||||||
|
|
||||||
DequeueChapterDownloadsPayload(
|
DequeueChapterDownloadsPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
downloadStatus =
|
downloadStatus =
|
||||||
withTimeout(30.seconds) {
|
withTimeout(30.seconds) {
|
||||||
DownloadStatus(
|
DownloadStatus(
|
||||||
DownloadManager.updates
|
DownloadManager.updates
|
||||||
.first {
|
.first {
|
||||||
it.updates.any {
|
it.updates.any {
|
||||||
it.downloadQueueItem.chapterId in chapters && it.type == DEQUEUED
|
it.downloadQueueItem.chapterId in chapters && it.type == DEQUEUED
|
||||||
}
|
}
|
||||||
}.let { DownloadManager.getStatus() },
|
}.let { DownloadManager.getStatus() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,28 +181,26 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun dequeueChapterDownload(input: DequeueChapterDownloadInput): CompletableFuture<DataFetcherResult<DequeueChapterDownloadPayload?>> {
|
fun dequeueChapterDownload(input: DequeueChapterDownloadInput): CompletableFuture<DequeueChapterDownloadPayload?> {
|
||||||
val (clientMutationId, chapter) = input
|
val (clientMutationId, chapter) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
DownloadManager.dequeue(DownloadManager.EnqueueInput(listOf(chapter)))
|
||||||
DownloadManager.dequeue(DownloadManager.EnqueueInput(listOf(chapter)))
|
|
||||||
|
|
||||||
DequeueChapterDownloadPayload(
|
DequeueChapterDownloadPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
downloadStatus =
|
downloadStatus =
|
||||||
withTimeout(30.seconds) {
|
withTimeout(30.seconds) {
|
||||||
DownloadStatus(
|
DownloadStatus(
|
||||||
DownloadManager.updates
|
DownloadManager.updates
|
||||||
.first {
|
.first {
|
||||||
it.updates.any {
|
it.updates.any {
|
||||||
it.downloadQueueItem.chapterId == chapter && it.type == DEQUEUED
|
it.downloadQueueItem.chapterId == chapter && it.type == DEQUEUED
|
||||||
}
|
}
|
||||||
}.let { DownloadManager.getStatus() },
|
}.let { DownloadManager.getStatus() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,23 +214,21 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun startDownloader(input: StartDownloaderInput): CompletableFuture<DataFetcherResult<StartDownloaderPayload?>> =
|
fun startDownloader(input: StartDownloaderInput): CompletableFuture<StartDownloaderPayload?> =
|
||||||
future {
|
future {
|
||||||
asDataFetcherResult {
|
DownloadManager.start()
|
||||||
DownloadManager.start()
|
|
||||||
|
|
||||||
StartDownloaderPayload(
|
StartDownloaderPayload(
|
||||||
input.clientMutationId,
|
input.clientMutationId,
|
||||||
downloadStatus =
|
downloadStatus =
|
||||||
withTimeout(30.seconds) {
|
withTimeout(30.seconds) {
|
||||||
DownloadStatus(
|
DownloadStatus(
|
||||||
DownloadManager.updates
|
DownloadManager.updates
|
||||||
.first { it.status == Status.Started }
|
.first { it.status == Status.Started }
|
||||||
.let { DownloadManager.getStatus() },
|
.let { DownloadManager.getStatus() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class StopDownloaderInput(
|
data class StopDownloaderInput(
|
||||||
@@ -258,23 +241,21 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun stopDownloader(input: StopDownloaderInput): CompletableFuture<DataFetcherResult<StopDownloaderPayload?>> =
|
fun stopDownloader(input: StopDownloaderInput): CompletableFuture<StopDownloaderPayload?> =
|
||||||
future {
|
future {
|
||||||
asDataFetcherResult {
|
DownloadManager.stop()
|
||||||
DownloadManager.stop()
|
|
||||||
|
|
||||||
StopDownloaderPayload(
|
StopDownloaderPayload(
|
||||||
input.clientMutationId,
|
input.clientMutationId,
|
||||||
downloadStatus =
|
downloadStatus =
|
||||||
withTimeout(30.seconds) {
|
withTimeout(30.seconds) {
|
||||||
DownloadStatus(
|
DownloadStatus(
|
||||||
DownloadManager.updates
|
DownloadManager.updates
|
||||||
.first { it.status == Status.Stopped }
|
.first { it.status == Status.Stopped }
|
||||||
.let { DownloadManager.getStatus() },
|
.let { DownloadManager.getStatus() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ClearDownloaderInput(
|
data class ClearDownloaderInput(
|
||||||
@@ -287,23 +268,21 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun clearDownloader(input: ClearDownloaderInput): CompletableFuture<DataFetcherResult<ClearDownloaderPayload?>> =
|
fun clearDownloader(input: ClearDownloaderInput): CompletableFuture<ClearDownloaderPayload?> =
|
||||||
future {
|
future {
|
||||||
asDataFetcherResult {
|
DownloadManager.clear()
|
||||||
DownloadManager.clear()
|
|
||||||
|
|
||||||
ClearDownloaderPayload(
|
ClearDownloaderPayload(
|
||||||
input.clientMutationId,
|
input.clientMutationId,
|
||||||
downloadStatus =
|
downloadStatus =
|
||||||
withTimeout(30.seconds) {
|
withTimeout(30.seconds) {
|
||||||
DownloadStatus(
|
DownloadStatus(
|
||||||
DownloadManager.updates
|
DownloadManager.updates
|
||||||
.first { it.status == Status.Stopped }
|
.first { it.status == Status.Stopped }
|
||||||
.let { DownloadManager.getStatus() },
|
.let { DownloadManager.getStatus() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ReorderChapterDownloadInput(
|
data class ReorderChapterDownloadInput(
|
||||||
@@ -318,25 +297,23 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun reorderChapterDownload(input: ReorderChapterDownloadInput): CompletableFuture<DataFetcherResult<ReorderChapterDownloadPayload?>> {
|
fun reorderChapterDownload(input: ReorderChapterDownloadInput): CompletableFuture<ReorderChapterDownloadPayload?> {
|
||||||
val (clientMutationId, chapter, to) = input
|
val (clientMutationId, chapter, to) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
DownloadManager.reorder(chapter, to)
|
||||||
DownloadManager.reorder(chapter, to)
|
|
||||||
|
|
||||||
ReorderChapterDownloadPayload(
|
ReorderChapterDownloadPayload(
|
||||||
clientMutationId,
|
clientMutationId,
|
||||||
downloadStatus =
|
downloadStatus =
|
||||||
withTimeout(30.seconds) {
|
withTimeout(30.seconds) {
|
||||||
DownloadStatus(
|
DownloadStatus(
|
||||||
DownloadManager.updates
|
DownloadManager.updates
|
||||||
.first { it.updates.indexOfFirst { it.downloadQueueItem.chapterId == chapter } <= to }
|
.first { it.updates.indexOfFirst { it.downloadQueueItem.chapterId == chapter } <= to }
|
||||||
.let { DownloadManager.getStatus() },
|
.let { DownloadManager.getStatus() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.local.LocalSource
|
import eu.kanade.tachiyomi.source.local.LocalSource
|
||||||
@@ -5,7 +7,6 @@ import graphql.execution.DataFetcherResult
|
|||||||
import io.javalin.http.UploadedFile
|
import io.javalin.http.UploadedFile
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.ExtensionType
|
import suwayomi.tachidesk.graphql.types.ExtensionType
|
||||||
import suwayomi.tachidesk.manga.impl.extension.Extension
|
import suwayomi.tachidesk.manga.impl.extension.Extension
|
||||||
@@ -75,51 +76,47 @@ class ExtensionMutation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateExtension(input: UpdateExtensionInput): CompletableFuture<DataFetcherResult<UpdateExtensionPayload?>> {
|
fun updateExtension(input: UpdateExtensionInput): CompletableFuture<UpdateExtensionPayload?> {
|
||||||
val (clientMutationId, id, patch) = input
|
val (clientMutationId, id, patch) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
updateExtensions(listOf(id), patch)
|
||||||
updateExtensions(listOf(id), patch)
|
|
||||||
|
|
||||||
val extension =
|
val extension =
|
||||||
transaction {
|
transaction {
|
||||||
ExtensionTable
|
ExtensionTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { ExtensionTable.pkgName eq id }
|
.where { ExtensionTable.pkgName eq id }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
?.let { ExtensionType(it) }
|
?.let { ExtensionType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateExtensionPayload(
|
UpdateExtensionPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
extension = extension,
|
extension = extension,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateExtensions(input: UpdateExtensionsInput): CompletableFuture<DataFetcherResult<UpdateExtensionsPayload?>> {
|
fun updateExtensions(input: UpdateExtensionsInput): CompletableFuture<UpdateExtensionsPayload?> {
|
||||||
val (clientMutationId, ids, patch) = input
|
val (clientMutationId, ids, patch) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
updateExtensions(ids, patch)
|
||||||
updateExtensions(ids, patch)
|
|
||||||
|
|
||||||
val extensions =
|
val extensions =
|
||||||
transaction {
|
transaction {
|
||||||
ExtensionTable
|
ExtensionTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { ExtensionTable.pkgName inList ids }
|
.where { ExtensionTable.pkgName inList ids }
|
||||||
.map { ExtensionType(it) }
|
.map { ExtensionType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateExtensionsPayload(
|
UpdateExtensionsPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
extensions = extensions,
|
extensions = extensions,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,26 +130,24 @@ class ExtensionMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun fetchExtensions(input: FetchExtensionsInput): CompletableFuture<DataFetcherResult<FetchExtensionsPayload?>> {
|
fun fetchExtensions(input: FetchExtensionsInput): CompletableFuture<FetchExtensionsPayload?> {
|
||||||
val (clientMutationId) = input
|
val (clientMutationId) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
ExtensionsList.fetchExtensions()
|
||||||
ExtensionsList.fetchExtensions()
|
|
||||||
|
|
||||||
val extensions =
|
val extensions =
|
||||||
transaction {
|
transaction {
|
||||||
ExtensionTable
|
ExtensionTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { ExtensionTable.name neq LocalSource.EXTENSION_NAME }
|
.where { ExtensionTable.name neq LocalSource.EXTENSION_NAME }
|
||||||
.map { ExtensionType(it) }
|
.map { ExtensionType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
FetchExtensionsPayload(
|
FetchExtensionsPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
extensions = extensions,
|
extensions = extensions,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,23 +162,19 @@ class ExtensionMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun installExternalExtension(
|
fun installExternalExtension(input: InstallExternalExtensionInput): CompletableFuture<InstallExternalExtensionPayload?> {
|
||||||
input: InstallExternalExtensionInput,
|
|
||||||
): CompletableFuture<DataFetcherResult<InstallExternalExtensionPayload?>> {
|
|
||||||
val (clientMutationId, extensionFile) = input
|
val (clientMutationId, extensionFile) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
Extension.installExternalExtension(extensionFile.content(), extensionFile.filename())
|
||||||
Extension.installExternalExtension(extensionFile.content(), extensionFile.filename())
|
|
||||||
|
|
||||||
val dbExtension =
|
val dbExtension =
|
||||||
transaction { ExtensionTable.selectAll().where { ExtensionTable.apkName eq extensionFile.filename() }.first() }
|
transaction { ExtensionTable.selectAll().where { ExtensionTable.apkName eq extensionFile.filename() }.first() }
|
||||||
|
|
||||||
InstallExternalExtensionPayload(
|
InstallExternalExtensionPayload(
|
||||||
clientMutationId,
|
clientMutationId,
|
||||||
extension = ExtensionType(dbExtension),
|
extension = ExtensionType(dbExtension),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
import graphql.execution.DataFetcherResult
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.UpdateState.DOWNLOADING
|
import suwayomi.tachidesk.graphql.types.UpdateState.DOWNLOADING
|
||||||
import suwayomi.tachidesk.graphql.types.UpdateState.ERROR
|
import suwayomi.tachidesk.graphql.types.UpdateState.ERROR
|
||||||
@@ -26,55 +27,51 @@ class InfoMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateWebUI(input: WebUIUpdateInput): CompletableFuture<DataFetcherResult<WebUIUpdatePayload?>> {
|
fun updateWebUI(input: WebUIUpdateInput): CompletableFuture<WebUIUpdatePayload?> {
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
withTimeout(30.seconds) {
|
||||||
withTimeout(30.seconds) {
|
if (WebInterfaceManager.status.value.state === DOWNLOADING) {
|
||||||
if (WebInterfaceManager.status.value.state === DOWNLOADING) {
|
return@withTimeout WebUIUpdatePayload(input.clientMutationId, WebInterfaceManager.status.value)
|
||||||
return@withTimeout WebUIUpdatePayload(input.clientMutationId, WebInterfaceManager.status.value)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val flavor = WebUIFlavor.current
|
val flavor = WebUIFlavor.current
|
||||||
|
|
||||||
val (version, updateAvailable) = WebInterfaceManager.isUpdateAvailable(flavor)
|
val (version, updateAvailable) = WebInterfaceManager.isUpdateAvailable(flavor)
|
||||||
|
|
||||||
if (!updateAvailable) {
|
if (!updateAvailable) {
|
||||||
val didUpdateCheckFail = version.isEmpty()
|
val didUpdateCheckFail = version.isEmpty()
|
||||||
|
|
||||||
return@withTimeout WebUIUpdatePayload(
|
return@withTimeout WebUIUpdatePayload(
|
||||||
input.clientMutationId,
|
|
||||||
WebInterfaceManager.getStatus(version, if (didUpdateCheckFail) ERROR else IDLE),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
WebInterfaceManager.startDownloadInScope(flavor, version)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// ignore since we use the status anyway
|
|
||||||
}
|
|
||||||
|
|
||||||
WebUIUpdatePayload(
|
|
||||||
input.clientMutationId,
|
input.clientMutationId,
|
||||||
updateStatus = WebInterfaceManager.status.first { it.state == DOWNLOADING },
|
WebInterfaceManager.getStatus(version, if (didUpdateCheckFail) ERROR else IDLE),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
WebInterfaceManager.startDownloadInScope(flavor, version)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// ignore since we use the status anyway
|
||||||
|
}
|
||||||
|
|
||||||
|
WebUIUpdatePayload(
|
||||||
|
input.clientMutationId,
|
||||||
|
updateStatus = WebInterfaceManager.status.first { it.state == DOWNLOADING },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun resetWebUIUpdateStatus(): CompletableFuture<DataFetcherResult<WebUIUpdateStatus?>> =
|
fun resetWebUIUpdateStatus(): CompletableFuture<WebUIUpdateStatus?> =
|
||||||
future {
|
future {
|
||||||
asDataFetcherResult {
|
withTimeout(30.seconds) {
|
||||||
withTimeout(30.seconds) {
|
val isUpdateFinished = WebInterfaceManager.status.value.state != DOWNLOADING
|
||||||
val isUpdateFinished = WebInterfaceManager.status.value.state != DOWNLOADING
|
if (!isUpdateFinished) {
|
||||||
if (!isUpdateFinished) {
|
throw Exception("Status reset is not allowed during status \"$DOWNLOADING\"")
|
||||||
throw Exception("Status reset is not allowed during status \"$DOWNLOADING\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
WebInterfaceManager.resetStatus()
|
|
||||||
|
|
||||||
WebInterfaceManager.status.first { it.state == IDLE }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WebInterfaceManager.resetStatus()
|
||||||
|
|
||||||
|
WebInterfaceManager.status.first { it.state == IDLE }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
import graphql.execution.DataFetcherResult
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.ChapterType
|
import suwayomi.tachidesk.graphql.types.ChapterType
|
||||||
import suwayomi.tachidesk.graphql.types.KoSyncConnectPayload
|
import suwayomi.tachidesk.graphql.types.KoSyncConnectPayload
|
||||||
@@ -62,26 +63,24 @@ class KoreaderSyncMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun pushKoSyncProgress(input: PushKoSyncProgressInput): CompletableFuture<DataFetcherResult<PushKoSyncProgressPayload?>> =
|
fun pushKoSyncProgress(input: PushKoSyncProgressInput): CompletableFuture<PushKoSyncProgressPayload?> =
|
||||||
future {
|
future {
|
||||||
asDataFetcherResult {
|
KoreaderSyncService.pushProgress(input.chapterId)
|
||||||
KoreaderSyncService.pushProgress(input.chapterId)
|
|
||||||
|
|
||||||
val chapter =
|
val chapter =
|
||||||
transaction {
|
transaction {
|
||||||
ChapterTable
|
ChapterTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { ChapterTable.id eq input.chapterId }
|
.where { ChapterTable.id eq input.chapterId }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
?.let { ChapterType(it) }
|
?.let { ChapterType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
PushKoSyncProgressPayload(
|
PushKoSyncProgressPayload(
|
||||||
clientMutationId = input.clientMutationId,
|
clientMutationId = input.clientMutationId,
|
||||||
success = true,
|
success = true,
|
||||||
chapter = chapter,
|
chapter = chapter,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class PullKoSyncProgressInput(
|
data class PullKoSyncProgressInput(
|
||||||
@@ -96,45 +95,43 @@ class KoreaderSyncMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun pullKoSyncProgress(input: PullKoSyncProgressInput): CompletableFuture<DataFetcherResult<PullKoSyncProgressPayload?>> =
|
fun pullKoSyncProgress(input: PullKoSyncProgressInput): CompletableFuture<PullKoSyncProgressPayload?> =
|
||||||
future {
|
future {
|
||||||
asDataFetcherResult {
|
val syncResult = KoreaderSyncService.checkAndPullProgress(input.chapterId)
|
||||||
val syncResult = KoreaderSyncService.checkAndPullProgress(input.chapterId)
|
var syncConflictInfo: SyncConflictInfoType? = null
|
||||||
var syncConflictInfo: SyncConflictInfoType? = null
|
|
||||||
|
|
||||||
if (syncResult != null) {
|
if (syncResult != null) {
|
||||||
if (syncResult.isConflict) {
|
if (syncResult.isConflict) {
|
||||||
syncConflictInfo =
|
syncConflictInfo =
|
||||||
SyncConflictInfoType(
|
SyncConflictInfoType(
|
||||||
deviceName = syncResult.device,
|
deviceName = syncResult.device,
|
||||||
remotePage = syncResult.pageRead,
|
remotePage = syncResult.pageRead,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (syncResult.shouldUpdate) {
|
if (syncResult.shouldUpdate) {
|
||||||
transaction {
|
transaction {
|
||||||
ChapterTable.update({ ChapterTable.id eq input.chapterId }) {
|
ChapterTable.update({ ChapterTable.id eq input.chapterId }) {
|
||||||
it[lastPageRead] = syncResult.pageRead
|
it[lastPageRead] = syncResult.pageRead
|
||||||
it[lastReadAt] = syncResult.timestamp
|
it[lastReadAt] = syncResult.timestamp
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val chapter =
|
|
||||||
transaction {
|
|
||||||
ChapterTable
|
|
||||||
.selectAll()
|
|
||||||
.where { ChapterTable.id eq input.chapterId }
|
|
||||||
.firstOrNull()
|
|
||||||
?.let { ChapterType(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
PullKoSyncProgressPayload(
|
|
||||||
clientMutationId = input.clientMutationId,
|
|
||||||
chapter = chapter,
|
|
||||||
syncConflict = syncConflictInfo,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val chapter =
|
||||||
|
transaction {
|
||||||
|
ChapterTable
|
||||||
|
.selectAll()
|
||||||
|
.where { ChapterTable.id eq input.chapterId }
|
||||||
|
.firstOrNull()
|
||||||
|
?.let { ChapterType(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
PullKoSyncProgressPayload(
|
||||||
|
clientMutationId = input.clientMutationId,
|
||||||
|
chapter = chapter,
|
||||||
|
syncConflict = syncConflictInfo,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
|
||||||
import org.jetbrains.exposed.sql.LikePattern
|
import org.jetbrains.exposed.sql.LikePattern
|
||||||
import org.jetbrains.exposed.sql.Op
|
import org.jetbrains.exposed.sql.Op
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
@@ -12,7 +13,6 @@ import org.jetbrains.exposed.sql.or
|
|||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.MangaMetaType
|
import suwayomi.tachidesk.graphql.types.MangaMetaType
|
||||||
import suwayomi.tachidesk.graphql.types.MangaType
|
import suwayomi.tachidesk.graphql.types.MangaType
|
||||||
@@ -98,44 +98,40 @@ class MangaMutation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateManga(input: UpdateMangaInput): CompletableFuture<DataFetcherResult<UpdateMangaPayload?>> {
|
fun updateManga(input: UpdateMangaInput): CompletableFuture<UpdateMangaPayload?> {
|
||||||
val (clientMutationId, id, patch) = input
|
val (clientMutationId, id, patch) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
updateMangas(listOf(id), patch)
|
||||||
updateMangas(listOf(id), patch)
|
|
||||||
|
|
||||||
val manga =
|
val manga =
|
||||||
transaction {
|
transaction {
|
||||||
MangaType(MangaTable.selectAll().where { MangaTable.id eq id }.first())
|
MangaType(MangaTable.selectAll().where { MangaTable.id eq id }.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateMangaPayload(
|
UpdateMangaPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
manga = manga,
|
manga = manga,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateMangas(input: UpdateMangasInput): CompletableFuture<DataFetcherResult<UpdateMangasPayload?>> {
|
fun updateMangas(input: UpdateMangasInput): CompletableFuture<UpdateMangasPayload?> {
|
||||||
val (clientMutationId, ids, patch) = input
|
val (clientMutationId, ids, patch) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
updateMangas(ids, patch)
|
||||||
updateMangas(ids, patch)
|
|
||||||
|
|
||||||
val mangas =
|
val mangas =
|
||||||
transaction {
|
transaction {
|
||||||
MangaTable.selectAll().where { MangaTable.id inList ids }.map { MangaType(it) }
|
MangaTable.selectAll().where { MangaTable.id inList ids }.map { MangaType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateMangasPayload(
|
UpdateMangasPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
mangas = mangas,
|
mangas = mangas,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,22 +146,20 @@ class MangaMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun fetchManga(input: FetchMangaInput): CompletableFuture<DataFetcherResult<FetchMangaPayload?>> {
|
fun fetchManga(input: FetchMangaInput): CompletableFuture<FetchMangaPayload?> {
|
||||||
val (clientMutationId, id) = input
|
val (clientMutationId, id) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
Manga.fetchManga(id)
|
||||||
Manga.fetchManga(id)
|
|
||||||
|
|
||||||
val manga =
|
val manga =
|
||||||
transaction {
|
transaction {
|
||||||
MangaTable.selectAll().where { MangaTable.id eq id }.first()
|
MangaTable.selectAll().where { MangaTable.id eq id }.first()
|
||||||
}
|
}
|
||||||
FetchMangaPayload(
|
FetchMangaPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
manga = MangaType(manga),
|
manga = MangaType(manga),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,14 +174,12 @@ class MangaMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setMangaMeta(input: SetMangaMetaInput): DataFetcherResult<SetMangaMetaPayload?> {
|
fun setMangaMeta(input: SetMangaMetaInput): SetMangaMetaPayload? {
|
||||||
val (clientMutationId, meta) = input
|
val (clientMutationId, meta) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
Manga.modifyMangaMeta(meta.mangaId, meta.key, meta.value)
|
||||||
Manga.modifyMangaMeta(meta.mangaId, meta.key, meta.value)
|
|
||||||
|
|
||||||
SetMangaMetaPayload(clientMutationId, meta)
|
return SetMangaMetaPayload(clientMutationId, meta)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteMangaMetaInput(
|
data class DeleteMangaMetaInput(
|
||||||
@@ -203,34 +195,32 @@ class MangaMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteMangaMeta(input: DeleteMangaMetaInput): DataFetcherResult<DeleteMangaMetaPayload?> {
|
fun deleteMangaMeta(input: DeleteMangaMetaInput): DeleteMangaMetaPayload? {
|
||||||
val (clientMutationId, mangaId, key) = input
|
val (clientMutationId, mangaId, key) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
val (meta, manga) =
|
||||||
val (meta, manga) =
|
transaction {
|
||||||
transaction {
|
val meta =
|
||||||
val meta =
|
MangaMetaTable
|
||||||
MangaMetaTable
|
.selectAll()
|
||||||
.selectAll()
|
.where { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
||||||
.where { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
.firstOrNull()
|
||||||
.firstOrNull()
|
|
||||||
|
|
||||||
MangaMetaTable.deleteWhere { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
MangaMetaTable.deleteWhere { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
||||||
|
|
||||||
val manga =
|
val manga =
|
||||||
transaction {
|
transaction {
|
||||||
MangaType(MangaTable.selectAll().where { MangaTable.id eq mangaId }.first())
|
MangaType(MangaTable.selectAll().where { MangaTable.id eq mangaId }.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meta != null) {
|
if (meta != null) {
|
||||||
MangaMetaType(meta)
|
MangaMetaType(meta)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
} to manga
|
} to manga
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteMangaMetaPayload(clientMutationId, meta, manga)
|
return DeleteMangaMetaPayload(clientMutationId, meta, manga)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SetMangaMetasItem(
|
data class SetMangaMetasItem(
|
||||||
@@ -250,43 +240,41 @@ class MangaMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setMangaMetas(input: SetMangaMetasInput): DataFetcherResult<SetMangaMetasPayload?> {
|
fun setMangaMetas(input: SetMangaMetasInput): SetMangaMetasPayload? {
|
||||||
val (clientMutationId, items) = input
|
val (clientMutationId, items) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
val metaByMangaId =
|
||||||
val metaByMangaId =
|
items
|
||||||
items
|
.flatMap { item ->
|
||||||
.flatMap { item ->
|
val metaMap = item.metas.associate { it.key to it.value }
|
||||||
val metaMap = item.metas.associate { it.key to it.value }
|
item.mangaIds.map { mangaId -> mangaId to metaMap }
|
||||||
item.mangaIds.map { mangaId -> mangaId to metaMap }
|
}.groupBy({ it.first }, { it.second })
|
||||||
}.groupBy({ it.first }, { it.second })
|
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
||||||
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
|
||||||
|
|
||||||
Manga.modifyMangasMetas(metaByMangaId)
|
Manga.modifyMangasMetas(metaByMangaId)
|
||||||
|
|
||||||
val allMangaIds = metaByMangaId.keys
|
val allMangaIds = metaByMangaId.keys
|
||||||
val allMetaKeys = metaByMangaId.values.flatMap { it.keys }.distinct()
|
val allMetaKeys = metaByMangaId.values.flatMap { it.keys }.distinct()
|
||||||
|
|
||||||
val (updatedMetas, mangas) =
|
val (updatedMetas, mangas) =
|
||||||
transaction {
|
transaction {
|
||||||
val updatedMetas =
|
val updatedMetas =
|
||||||
MangaMetaTable
|
MangaMetaTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { (MangaMetaTable.ref inList allMangaIds) and (MangaMetaTable.key inList allMetaKeys) }
|
.where { (MangaMetaTable.ref inList allMangaIds) and (MangaMetaTable.key inList allMetaKeys) }
|
||||||
.map { MangaMetaType(it) }
|
.map { MangaMetaType(it) }
|
||||||
|
|
||||||
val mangas =
|
val mangas =
|
||||||
MangaTable
|
MangaTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { MangaTable.id inList allMangaIds }
|
.where { MangaTable.id inList allMangaIds }
|
||||||
.map { MangaType(it) }
|
.map { MangaType(it) }
|
||||||
.distinctBy { it.id }
|
.distinctBy { it.id }
|
||||||
|
|
||||||
updatedMetas to mangas
|
updatedMetas to mangas
|
||||||
}
|
}
|
||||||
|
|
||||||
SetMangaMetasPayload(clientMutationId, updatedMetas, mangas)
|
return SetMangaMetasPayload(clientMutationId, updatedMetas, mangas)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteMangaMetasItem(
|
data class DeleteMangaMetasItem(
|
||||||
@@ -307,63 +295,61 @@ class MangaMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteMangaMetas(input: DeleteMangaMetasInput): DataFetcherResult<DeleteMangaMetasPayload?> {
|
fun deleteMangaMetas(input: DeleteMangaMetasInput): DeleteMangaMetasPayload? {
|
||||||
val (clientMutationId, items) = input
|
val (clientMutationId, items) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
items.forEach { item ->
|
||||||
items.forEach { item ->
|
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
||||||
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
"Either 'keys' or 'prefixes' must be provided for each item"
|
||||||
"Either 'keys' or 'prefixes' must be provided for each item"
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val (allDeletedMetas, allMangaIds) =
|
||||||
|
transaction {
|
||||||
|
val deletedMetas = mutableListOf<MangaMetaType>()
|
||||||
|
val mangaIds = mutableSetOf<Int>()
|
||||||
|
|
||||||
|
items.forEach { item ->
|
||||||
|
val keyCondition: Op<Boolean>? =
|
||||||
|
item.keys?.takeIf { it.isNotEmpty() }?.let { MangaMetaTable.key inList it }
|
||||||
|
|
||||||
|
val prefixCondition: Op<Boolean>? =
|
||||||
|
item.prefixes
|
||||||
|
?.filter { it.isNotEmpty() }
|
||||||
|
?.map { (MangaMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
||||||
|
?.reduceOrNull { acc, op -> acc or op }
|
||||||
|
|
||||||
|
val metaKeyCondition =
|
||||||
|
if (keyCondition != null && prefixCondition != null) {
|
||||||
|
keyCondition or prefixCondition
|
||||||
|
} else {
|
||||||
|
keyCondition ?: prefixCondition!!
|
||||||
|
}
|
||||||
|
|
||||||
|
val condition = (MangaMetaTable.ref inList item.mangaIds) and metaKeyCondition
|
||||||
|
|
||||||
|
deletedMetas +=
|
||||||
|
MangaMetaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { condition }
|
||||||
|
.map { MangaMetaType(it) }
|
||||||
|
|
||||||
|
MangaMetaTable.deleteWhere { condition }
|
||||||
|
mangaIds += item.mangaIds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deletedMetas to mangaIds
|
||||||
}
|
}
|
||||||
|
|
||||||
val (allDeletedMetas, allMangaIds) =
|
val mangas =
|
||||||
transaction {
|
transaction {
|
||||||
val deletedMetas = mutableListOf<MangaMetaType>()
|
MangaTable
|
||||||
val mangaIds = mutableSetOf<Int>()
|
.selectAll()
|
||||||
|
.where { MangaTable.id inList allMangaIds }
|
||||||
|
.map { MangaType(it) }
|
||||||
|
.distinctBy { it.id }
|
||||||
|
}
|
||||||
|
|
||||||
items.forEach { item ->
|
return DeleteMangaMetasPayload(clientMutationId, allDeletedMetas, mangas)
|
||||||
val keyCondition: Op<Boolean>? =
|
|
||||||
item.keys?.takeIf { it.isNotEmpty() }?.let { MangaMetaTable.key inList it }
|
|
||||||
|
|
||||||
val prefixCondition: Op<Boolean>? =
|
|
||||||
item.prefixes
|
|
||||||
?.filter { it.isNotEmpty() }
|
|
||||||
?.map { (MangaMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
|
||||||
?.reduceOrNull { acc, op -> acc or op }
|
|
||||||
|
|
||||||
val metaKeyCondition =
|
|
||||||
if (keyCondition != null && prefixCondition != null) {
|
|
||||||
keyCondition or prefixCondition
|
|
||||||
} else {
|
|
||||||
keyCondition ?: prefixCondition!!
|
|
||||||
}
|
|
||||||
|
|
||||||
val condition = (MangaMetaTable.ref inList item.mangaIds) and metaKeyCondition
|
|
||||||
|
|
||||||
deletedMetas +=
|
|
||||||
MangaMetaTable
|
|
||||||
.selectAll()
|
|
||||||
.where { condition }
|
|
||||||
.map { MangaMetaType(it) }
|
|
||||||
|
|
||||||
MangaMetaTable.deleteWhere { condition }
|
|
||||||
mangaIds += item.mangaIds
|
|
||||||
}
|
|
||||||
|
|
||||||
deletedMetas to mangaIds
|
|
||||||
}
|
|
||||||
|
|
||||||
val mangas =
|
|
||||||
transaction {
|
|
||||||
MangaTable
|
|
||||||
.selectAll()
|
|
||||||
.where { MangaTable.id inList allMangaIds }
|
|
||||||
.map { MangaType(it) }
|
|
||||||
.distinctBy { it.id }
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteMangaMetasPayload(clientMutationId, allDeletedMetas, mangas)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
import graphql.execution.DataFetcherResult
|
||||||
@@ -12,7 +14,6 @@ import org.jetbrains.exposed.sql.selectAll
|
|||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import suwayomi.tachidesk.global.impl.GlobalMeta
|
import suwayomi.tachidesk.global.impl.GlobalMeta
|
||||||
import suwayomi.tachidesk.global.model.table.GlobalMetaTable
|
import suwayomi.tachidesk.global.model.table.GlobalMetaTable
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.GlobalMetaType
|
import suwayomi.tachidesk.graphql.types.GlobalMetaType
|
||||||
import suwayomi.tachidesk.graphql.types.MetaInput
|
import suwayomi.tachidesk.graphql.types.MetaInput
|
||||||
@@ -29,14 +30,12 @@ class MetaMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setGlobalMeta(input: SetGlobalMetaInput): DataFetcherResult<SetGlobalMetaPayload?> {
|
fun setGlobalMeta(input: SetGlobalMetaInput): SetGlobalMetaPayload? {
|
||||||
val (clientMutationId, meta) = input
|
val (clientMutationId, meta) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
GlobalMeta.modifyMeta(meta.key, meta.value)
|
||||||
GlobalMeta.modifyMeta(meta.key, meta.value)
|
|
||||||
|
|
||||||
SetGlobalMetaPayload(clientMutationId, meta)
|
return SetGlobalMetaPayload(clientMutationId, meta)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteGlobalMetaInput(
|
data class DeleteGlobalMetaInput(
|
||||||
@@ -50,29 +49,27 @@ class MetaMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteGlobalMeta(input: DeleteGlobalMetaInput): DataFetcherResult<DeleteGlobalMetaPayload?> {
|
fun deleteGlobalMeta(input: DeleteGlobalMetaInput): DeleteGlobalMetaPayload? {
|
||||||
val (clientMutationId, key) = input
|
val (clientMutationId, key) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
val meta =
|
||||||
val meta =
|
transaction {
|
||||||
transaction {
|
val meta =
|
||||||
val meta =
|
GlobalMetaTable
|
||||||
GlobalMetaTable
|
.selectAll()
|
||||||
.selectAll()
|
.where { GlobalMetaTable.key eq key }
|
||||||
.where { GlobalMetaTable.key eq key }
|
.firstOrNull()
|
||||||
.firstOrNull()
|
|
||||||
|
|
||||||
GlobalMetaTable.deleteWhere { GlobalMetaTable.key eq key }
|
GlobalMetaTable.deleteWhere { GlobalMetaTable.key eq key }
|
||||||
|
|
||||||
if (meta != null) {
|
if (meta != null) {
|
||||||
GlobalMetaType(meta)
|
GlobalMetaType(meta)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DeleteGlobalMetaPayload(clientMutationId, meta)
|
return DeleteGlobalMetaPayload(clientMutationId, meta)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SetGlobalMetasInput(
|
data class SetGlobalMetasInput(
|
||||||
@@ -86,23 +83,21 @@ class MetaMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setGlobalMetas(input: SetGlobalMetasInput): DataFetcherResult<SetGlobalMetasPayload?> {
|
fun setGlobalMetas(input: SetGlobalMetasInput): SetGlobalMetasPayload? {
|
||||||
val (clientMutationId, metas) = input
|
val (clientMutationId, metas) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
val metaMap = metas.associate { it.key to it.value }
|
||||||
val metaMap = metas.associate { it.key to it.value }
|
GlobalMeta.modifyMetas(metaMap)
|
||||||
GlobalMeta.modifyMetas(metaMap)
|
|
||||||
|
|
||||||
val updatedMetas =
|
val updatedMetas =
|
||||||
transaction {
|
transaction {
|
||||||
GlobalMetaTable
|
GlobalMetaTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { GlobalMetaTable.key inList metaMap.keys }
|
.where { GlobalMetaTable.key inList metaMap.keys }
|
||||||
.map { GlobalMetaType(it) }
|
.map { GlobalMetaType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
SetGlobalMetasPayload(clientMutationId, updatedMetas)
|
return SetGlobalMetasPayload(clientMutationId, updatedMetas)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteGlobalMetasInput(
|
data class DeleteGlobalMetasInput(
|
||||||
@@ -117,43 +112,41 @@ class MetaMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteGlobalMetas(input: DeleteGlobalMetasInput): DataFetcherResult<DeleteGlobalMetasPayload?> {
|
fun deleteGlobalMetas(input: DeleteGlobalMetasInput): DeleteGlobalMetasPayload? {
|
||||||
val (clientMutationId, keys, prefixes) = input
|
val (clientMutationId, keys, prefixes) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
require(!keys.isNullOrEmpty() || !prefixes.isNullOrEmpty()) {
|
||||||
require(!keys.isNullOrEmpty() || !prefixes.isNullOrEmpty()) {
|
"Either 'keys' or 'prefixes' must be provided"
|
||||||
"Either 'keys' or 'prefixes' must be provided"
|
}
|
||||||
|
|
||||||
|
val metas =
|
||||||
|
transaction {
|
||||||
|
val keyCondition: Op<Boolean>? = keys?.takeIf { it.isNotEmpty() }?.let { GlobalMetaTable.key inList it }
|
||||||
|
|
||||||
|
val prefixCondition: Op<Boolean>? =
|
||||||
|
prefixes
|
||||||
|
?.filter { it.isNotEmpty() }
|
||||||
|
?.map { (GlobalMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
||||||
|
?.reduceOrNull { acc, op -> acc or op }
|
||||||
|
|
||||||
|
val finalCondition =
|
||||||
|
if (keyCondition != null && prefixCondition != null) {
|
||||||
|
keyCondition or prefixCondition
|
||||||
|
} else {
|
||||||
|
keyCondition ?: prefixCondition!!
|
||||||
|
}
|
||||||
|
|
||||||
|
val metas =
|
||||||
|
GlobalMetaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { finalCondition }
|
||||||
|
.map { GlobalMetaType(it) }
|
||||||
|
|
||||||
|
GlobalMetaTable.deleteWhere { finalCondition }
|
||||||
|
|
||||||
|
metas
|
||||||
}
|
}
|
||||||
|
|
||||||
val metas =
|
return DeleteGlobalMetasPayload(clientMutationId, metas)
|
||||||
transaction {
|
|
||||||
val keyCondition: Op<Boolean>? = keys?.takeIf { it.isNotEmpty() }?.let { GlobalMetaTable.key inList it }
|
|
||||||
|
|
||||||
val prefixCondition: Op<Boolean>? =
|
|
||||||
prefixes
|
|
||||||
?.filter { it.isNotEmpty() }
|
|
||||||
?.map { (GlobalMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
|
||||||
?.reduceOrNull { acc, op -> acc or op }
|
|
||||||
|
|
||||||
val finalCondition =
|
|
||||||
if (keyCondition != null && prefixCondition != null) {
|
|
||||||
keyCondition or prefixCondition
|
|
||||||
} else {
|
|
||||||
keyCondition ?: prefixCondition!!
|
|
||||||
}
|
|
||||||
|
|
||||||
val metas =
|
|
||||||
GlobalMetaTable
|
|
||||||
.selectAll()
|
|
||||||
.where { finalCondition }
|
|
||||||
.map { GlobalMetaType(it) }
|
|
||||||
|
|
||||||
GlobalMetaTable.deleteWhere { finalCondition }
|
|
||||||
|
|
||||||
metas
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteGlobalMetasPayload(clientMutationId, metas)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore
|
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import androidx.preference.CheckBoxPreference
|
import androidx.preference.CheckBoxPreference
|
||||||
@@ -5,7 +7,6 @@ import androidx.preference.EditTextPreference
|
|||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.MultiSelectListPreference
|
import androidx.preference.MultiSelectListPreference
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import graphql.execution.DataFetcherResult
|
|
||||||
import org.jetbrains.exposed.sql.LikePattern
|
import org.jetbrains.exposed.sql.LikePattern
|
||||||
import org.jetbrains.exposed.sql.Op
|
import org.jetbrains.exposed.sql.Op
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
@@ -16,7 +17,6 @@ import org.jetbrains.exposed.sql.deleteWhere
|
|||||||
import org.jetbrains.exposed.sql.or
|
import org.jetbrains.exposed.sql.or
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.FilterChange
|
import suwayomi.tachidesk.graphql.types.FilterChange
|
||||||
import suwayomi.tachidesk.graphql.types.MangaType
|
import suwayomi.tachidesk.graphql.types.MangaType
|
||||||
@@ -47,14 +47,12 @@ class SourceMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setSourceMeta(input: SetSourceMetaInput): DataFetcherResult<SetSourceMetaPayload?> {
|
fun setSourceMeta(input: SetSourceMetaInput): SetSourceMetaPayload? {
|
||||||
val (clientMutationId, meta) = input
|
val (clientMutationId, meta) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
Source.modifyMeta(meta.sourceId, meta.key, meta.value)
|
||||||
Source.modifyMeta(meta.sourceId, meta.key, meta.value)
|
|
||||||
|
|
||||||
SetSourceMetaPayload(clientMutationId, meta)
|
return SetSourceMetaPayload(clientMutationId, meta)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteSourceMetaInput(
|
data class DeleteSourceMetaInput(
|
||||||
@@ -70,38 +68,36 @@ class SourceMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteSourceMeta(input: DeleteSourceMetaInput): DataFetcherResult<DeleteSourceMetaPayload?> {
|
fun deleteSourceMeta(input: DeleteSourceMetaInput): DeleteSourceMetaPayload? {
|
||||||
val (clientMutationId, sourceId, key) = input
|
val (clientMutationId, sourceId, key) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
val (meta, source) =
|
||||||
val (meta, source) =
|
transaction {
|
||||||
transaction {
|
val meta =
|
||||||
val meta =
|
SourceMetaTable
|
||||||
SourceMetaTable
|
.selectAll()
|
||||||
|
.where { (SourceMetaTable.ref eq sourceId) and (SourceMetaTable.key eq key) }
|
||||||
|
.firstOrNull()
|
||||||
|
|
||||||
|
SourceMetaTable.deleteWhere { (SourceMetaTable.ref eq sourceId) and (SourceMetaTable.key eq key) }
|
||||||
|
|
||||||
|
val source =
|
||||||
|
transaction {
|
||||||
|
SourceTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { (SourceMetaTable.ref eq sourceId) and (SourceMetaTable.key eq key) }
|
.where { SourceTable.id eq sourceId }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
|
?.let { SourceType(it) }
|
||||||
|
}
|
||||||
|
|
||||||
SourceMetaTable.deleteWhere { (SourceMetaTable.ref eq sourceId) and (SourceMetaTable.key eq key) }
|
if (meta != null) {
|
||||||
|
SourceMetaType(meta)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
} to source
|
||||||
|
}
|
||||||
|
|
||||||
val source =
|
return DeleteSourceMetaPayload(clientMutationId, meta, source)
|
||||||
transaction {
|
|
||||||
SourceTable
|
|
||||||
.selectAll()
|
|
||||||
.where { SourceTable.id eq sourceId }
|
|
||||||
.firstOrNull()
|
|
||||||
?.let { SourceType(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (meta != null) {
|
|
||||||
SourceMetaType(meta)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
} to source
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteSourceMetaPayload(clientMutationId, meta, source)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SetSourceMetasItem(
|
data class SetSourceMetasItem(
|
||||||
@@ -121,43 +117,41 @@ class SourceMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setSourceMetas(input: SetSourceMetasInput): DataFetcherResult<SetSourceMetasPayload?> {
|
fun setSourceMetas(input: SetSourceMetasInput): SetSourceMetasPayload? {
|
||||||
val (clientMutationId, items) = input
|
val (clientMutationId, items) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
val metaBySourceId =
|
||||||
val metaBySourceId =
|
items
|
||||||
items
|
.flatMap { item ->
|
||||||
.flatMap { item ->
|
val metaMap = item.metas.associate { it.key to it.value }
|
||||||
val metaMap = item.metas.associate { it.key to it.value }
|
item.sourceIds.map { sourceId -> sourceId to metaMap }
|
||||||
item.sourceIds.map { sourceId -> sourceId to metaMap }
|
}.groupBy({ it.first }, { it.second })
|
||||||
}.groupBy({ it.first }, { it.second })
|
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
||||||
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
|
||||||
|
|
||||||
Source.modifySourceMetas(metaBySourceId)
|
Source.modifySourceMetas(metaBySourceId)
|
||||||
|
|
||||||
val allSourceIds = metaBySourceId.keys
|
val allSourceIds = metaBySourceId.keys
|
||||||
val allMetaKeys = metaBySourceId.values.flatMap { it.keys }.distinct()
|
val allMetaKeys = metaBySourceId.values.flatMap { it.keys }.distinct()
|
||||||
|
|
||||||
val (updatedMetas, sources) =
|
val (updatedMetas, sources) =
|
||||||
transaction {
|
transaction {
|
||||||
val updatedMetas =
|
val updatedMetas =
|
||||||
SourceMetaTable
|
SourceMetaTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { (SourceMetaTable.ref inList allSourceIds) and (SourceMetaTable.key inList allMetaKeys) }
|
.where { (SourceMetaTable.ref inList allSourceIds) and (SourceMetaTable.key inList allMetaKeys) }
|
||||||
.map { SourceMetaType(it) }
|
.map { SourceMetaType(it) }
|
||||||
|
|
||||||
val sources =
|
val sources =
|
||||||
SourceTable
|
SourceTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { SourceTable.id inList allSourceIds }
|
.where { SourceTable.id inList allSourceIds }
|
||||||
.mapNotNull { SourceType(it) }
|
.mapNotNull { SourceType(it) }
|
||||||
.distinctBy { it.id }
|
.distinctBy { it.id }
|
||||||
|
|
||||||
updatedMetas to sources
|
updatedMetas to sources
|
||||||
}
|
}
|
||||||
|
|
||||||
SetSourceMetasPayload(clientMutationId, updatedMetas, sources)
|
return SetSourceMetasPayload(clientMutationId, updatedMetas, sources)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteSourceMetasItem(
|
data class DeleteSourceMetasItem(
|
||||||
@@ -178,64 +172,62 @@ class SourceMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteSourceMetas(input: DeleteSourceMetasInput): DataFetcherResult<DeleteSourceMetasPayload?> {
|
fun deleteSourceMetas(input: DeleteSourceMetasInput): DeleteSourceMetasPayload? {
|
||||||
val (clientMutationId, items) = input
|
val (clientMutationId, items) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
items.forEach { item ->
|
||||||
items.forEach { item ->
|
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
||||||
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
"Either 'keys' or 'prefixes' must be provided for each item"
|
||||||
"Either 'keys' or 'prefixes' must be provided for each item"
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val (allDeletedMetas, allSourceIds) =
|
||||||
|
transaction {
|
||||||
|
val deletedMetas = mutableListOf<SourceMetaType>()
|
||||||
|
val sourceIds = mutableSetOf<Long>()
|
||||||
|
|
||||||
|
items.forEach { item ->
|
||||||
|
val keyCondition: Op<Boolean>? =
|
||||||
|
item.keys?.takeIf { it.isNotEmpty() }?.let { SourceMetaTable.key inList it }
|
||||||
|
|
||||||
|
val prefixCondition: Op<Boolean>? =
|
||||||
|
item.prefixes
|
||||||
|
?.filter { it.isNotEmpty() }
|
||||||
|
?.map { (SourceMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
||||||
|
?.reduceOrNull { acc, op -> acc or op }
|
||||||
|
|
||||||
|
val metaKeyCondition =
|
||||||
|
if (keyCondition != null && prefixCondition != null) {
|
||||||
|
keyCondition or prefixCondition
|
||||||
|
} else {
|
||||||
|
keyCondition ?: prefixCondition!!
|
||||||
|
}
|
||||||
|
|
||||||
|
val condition = (SourceMetaTable.ref inList item.sourceIds) and metaKeyCondition
|
||||||
|
|
||||||
|
deletedMetas +=
|
||||||
|
SourceMetaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { condition }
|
||||||
|
.map { SourceMetaType(it) }
|
||||||
|
|
||||||
|
SourceMetaTable.deleteWhere { condition }
|
||||||
|
sourceIds += item.sourceIds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deletedMetas to sourceIds
|
||||||
}
|
}
|
||||||
|
|
||||||
val (allDeletedMetas, allSourceIds) =
|
val sources =
|
||||||
transaction {
|
transaction {
|
||||||
val deletedMetas = mutableListOf<SourceMetaType>()
|
SourceTable
|
||||||
val sourceIds = mutableSetOf<Long>()
|
.selectAll()
|
||||||
|
.where { SourceTable.id inList allSourceIds }
|
||||||
|
.mapNotNull { SourceType(it) }
|
||||||
|
.distinctBy { it.id }
|
||||||
|
}
|
||||||
|
|
||||||
items.forEach { item ->
|
return DeleteSourceMetasPayload(clientMutationId, allDeletedMetas, sources)
|
||||||
val keyCondition: Op<Boolean>? =
|
|
||||||
item.keys?.takeIf { it.isNotEmpty() }?.let { SourceMetaTable.key inList it }
|
|
||||||
|
|
||||||
val prefixCondition: Op<Boolean>? =
|
|
||||||
item.prefixes
|
|
||||||
?.filter { it.isNotEmpty() }
|
|
||||||
?.map { (SourceMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
|
||||||
?.reduceOrNull { acc, op -> acc or op }
|
|
||||||
|
|
||||||
val metaKeyCondition =
|
|
||||||
if (keyCondition != null && prefixCondition != null) {
|
|
||||||
keyCondition or prefixCondition
|
|
||||||
} else {
|
|
||||||
keyCondition ?: prefixCondition!!
|
|
||||||
}
|
|
||||||
|
|
||||||
val condition = (SourceMetaTable.ref inList item.sourceIds) and metaKeyCondition
|
|
||||||
|
|
||||||
deletedMetas +=
|
|
||||||
SourceMetaTable
|
|
||||||
.selectAll()
|
|
||||||
.where { condition }
|
|
||||||
.map { SourceMetaType(it) }
|
|
||||||
|
|
||||||
SourceMetaTable.deleteWhere { condition }
|
|
||||||
sourceIds += item.sourceIds
|
|
||||||
}
|
|
||||||
|
|
||||||
deletedMetas to sourceIds
|
|
||||||
}
|
|
||||||
|
|
||||||
val sources =
|
|
||||||
transaction {
|
|
||||||
SourceTable
|
|
||||||
.selectAll()
|
|
||||||
.where { SourceTable.id inList allSourceIds }
|
|
||||||
.mapNotNull { SourceType(it) }
|
|
||||||
.distinctBy { it.id }
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteSourceMetasPayload(clientMutationId, allDeletedMetas, sources)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class FetchSourceMangaType {
|
enum class FetchSourceMangaType {
|
||||||
@@ -260,50 +252,48 @@ class SourceMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun fetchSourceManga(input: FetchSourceMangaInput): CompletableFuture<DataFetcherResult<FetchSourceMangaPayload?>> {
|
fun fetchSourceManga(input: FetchSourceMangaInput): CompletableFuture<FetchSourceMangaPayload?> {
|
||||||
val (clientMutationId, sourceId, type, page, query, filters) = input
|
val (clientMutationId, sourceId, type, page, query, filters) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
val source = GetCatalogueSource.getCatalogueSourceOrNull(sourceId)!!
|
||||||
val source = GetCatalogueSource.getCatalogueSourceOrNull(sourceId)!!
|
val mangasPage =
|
||||||
val mangasPage =
|
when (type) {
|
||||||
when (type) {
|
FetchSourceMangaType.SEARCH -> {
|
||||||
FetchSourceMangaType.SEARCH -> {
|
source.getSearchManga(
|
||||||
source.getSearchManga(
|
page = page,
|
||||||
page = page,
|
query = query.orEmpty(),
|
||||||
query = query.orEmpty(),
|
filters = updateFilterList(source, filters),
|
||||||
filters = updateFilterList(source, filters),
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
FetchSourceMangaType.POPULAR -> {
|
|
||||||
source.getPopularManga(page)
|
|
||||||
}
|
|
||||||
|
|
||||||
FetchSourceMangaType.LATEST -> {
|
|
||||||
if (!source.supportsLatest) throw Exception("Source does not support latest")
|
|
||||||
source.getLatestUpdates(page)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val mangaIds = mangasPage.insertOrUpdate(sourceId)
|
FetchSourceMangaType.POPULAR -> {
|
||||||
|
source.getPopularManga(page)
|
||||||
val mangas =
|
|
||||||
transaction {
|
|
||||||
MangaTable
|
|
||||||
.selectAll()
|
|
||||||
.where { MangaTable.id inList mangaIds }
|
|
||||||
.map { MangaType(it) }
|
|
||||||
}.sortedBy {
|
|
||||||
mangaIds.indexOf(it.id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FetchSourceMangaPayload(
|
FetchSourceMangaType.LATEST -> {
|
||||||
clientMutationId = clientMutationId,
|
if (!source.supportsLatest) throw Exception("Source does not support latest")
|
||||||
mangas = mangas,
|
source.getLatestUpdates(page)
|
||||||
hasNextPage = mangasPage.hasNextPage,
|
}
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
val mangaIds = mangasPage.insertOrUpdate(sourceId)
|
||||||
|
|
||||||
|
val mangas =
|
||||||
|
transaction {
|
||||||
|
MangaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { MangaTable.id inList mangaIds }
|
||||||
|
.map { MangaType(it) }
|
||||||
|
}.sortedBy {
|
||||||
|
mangaIds.indexOf(it.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
FetchSourceMangaPayload(
|
||||||
|
clientMutationId = clientMutationId,
|
||||||
|
mangas = mangas,
|
||||||
|
hasNextPage = mangasPage.hasNextPage,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,29 +319,27 @@ class SourceMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateSourcePreference(input: UpdateSourcePreferenceInput): DataFetcherResult<UpdateSourcePreferencePayload?> {
|
fun updateSourcePreference(input: UpdateSourcePreferenceInput): UpdateSourcePreferencePayload? {
|
||||||
val (clientMutationId, sourceId, change) = input
|
val (clientMutationId, sourceId, change) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
Source.setSourcePreference(sourceId, change.position, "") { preference ->
|
||||||
Source.setSourcePreference(sourceId, change.position, "") { preference ->
|
when (preference) {
|
||||||
when (preference) {
|
is SwitchPreferenceCompat -> change.switchState
|
||||||
is SwitchPreferenceCompat -> change.switchState
|
is CheckBoxPreference -> change.checkBoxState
|
||||||
is CheckBoxPreference -> change.checkBoxState
|
is EditTextPreference -> change.editTextState
|
||||||
is EditTextPreference -> change.editTextState
|
is ListPreference -> change.listState
|
||||||
is ListPreference -> change.listState
|
is MultiSelectListPreference -> change.multiSelectState?.toSet()
|
||||||
is MultiSelectListPreference -> change.multiSelectState?.toSet()
|
else -> throw RuntimeException("sealed class cannot have more subtypes!")
|
||||||
else -> throw RuntimeException("sealed class cannot have more subtypes!")
|
} ?: throw Exception("Expected change to ${preference::class.simpleName}")
|
||||||
} ?: throw Exception("Expected change to ${preference::class.simpleName}")
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateSourcePreferencePayload(
|
|
||||||
clientMutationId = clientMutationId,
|
|
||||||
preferences = Source.getSourcePreferencesRaw(sourceId).map { preferenceOf(it) },
|
|
||||||
source =
|
|
||||||
transaction {
|
|
||||||
SourceType(SourceTable.selectAll().where { SourceTable.id eq sourceId }.first())!!
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return UpdateSourcePreferencePayload(
|
||||||
|
clientMutationId = clientMutationId,
|
||||||
|
preferences = Source.getSourcePreferencesRaw(sourceId).map { preferenceOf(it) },
|
||||||
|
source =
|
||||||
|
transaction {
|
||||||
|
SourceType(SourceTable.selectAll().where { SourceTable.id eq sourceId }.first())!!
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated
|
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated
|
||||||
@@ -6,7 +8,6 @@ import graphql.execution.DataFetcherResult
|
|||||||
import org.jetbrains.exposed.sql.and
|
import org.jetbrains.exposed.sql.and
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.TrackRecordType
|
import suwayomi.tachidesk.graphql.types.TrackRecordType
|
||||||
import suwayomi.tachidesk.graphql.types.TrackerType
|
import suwayomi.tachidesk.graphql.types.TrackerType
|
||||||
@@ -222,24 +223,22 @@ class TrackMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun trackProgress(input: TrackProgressInput): CompletableFuture<DataFetcherResult<TrackProgressPayload?>> {
|
fun trackProgress(input: TrackProgressInput): CompletableFuture<TrackProgressPayload?> {
|
||||||
val (clientMutationId, mangaId) = input
|
val (clientMutationId, mangaId) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
Track.trackChapter(mangaId)
|
||||||
Track.trackChapter(mangaId)
|
val trackRecords =
|
||||||
val trackRecords =
|
transaction {
|
||||||
transaction {
|
TrackRecordTable
|
||||||
TrackRecordTable
|
.selectAll()
|
||||||
.selectAll()
|
.where { TrackRecordTable.mangaId eq mangaId }
|
||||||
.where { TrackRecordTable.mangaId eq mangaId }
|
.toList()
|
||||||
.toList()
|
}
|
||||||
}
|
TrackProgressPayload(
|
||||||
TrackProgressPayload(
|
clientMutationId,
|
||||||
clientMutationId,
|
trackRecords.map { TrackRecordType(it) },
|
||||||
trackRecords.map { TrackRecordType(it) },
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
import graphql.execution.DataFetcherResult
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.LibraryUpdateStatus
|
import suwayomi.tachidesk.graphql.types.LibraryUpdateStatus
|
||||||
import suwayomi.tachidesk.graphql.types.UpdateStatus
|
import suwayomi.tachidesk.graphql.types.UpdateStatus
|
||||||
@@ -28,7 +29,7 @@ class UpdateMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateLibrary(input: UpdateLibraryInput): CompletableFuture<DataFetcherResult<UpdateLibraryPayload?>> {
|
fun updateLibrary(input: UpdateLibraryInput): CompletableFuture<UpdateLibraryPayload?> {
|
||||||
updater.addCategoriesToUpdateQueue(
|
updater.addCategoriesToUpdateQueue(
|
||||||
Category.getCategoryList().filter { input.categories?.contains(it.id) ?: true },
|
Category.getCategoryList().filter { input.categories?.contains(it.id) ?: true },
|
||||||
clear = true,
|
clear = true,
|
||||||
@@ -36,17 +37,15 @@ class UpdateMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
UpdateLibraryPayload(
|
||||||
UpdateLibraryPayload(
|
input.clientMutationId,
|
||||||
input.clientMutationId,
|
updateStatus =
|
||||||
updateStatus =
|
withTimeout(30.seconds) {
|
||||||
withTimeout(30.seconds) {
|
LibraryUpdateStatus(
|
||||||
LibraryUpdateStatus(
|
updater.updates.first(),
|
||||||
updater.updates.first(),
|
)
|
||||||
)
|
},
|
||||||
},
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ class UpdateMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateLibraryManga(input: UpdateLibraryMangaInput): CompletableFuture<DataFetcherResult<UpdateLibraryMangaPayload?>> {
|
fun updateLibraryManga(input: UpdateLibraryMangaInput): CompletableFuture<UpdateLibraryMangaPayload?> {
|
||||||
updateLibrary(
|
updateLibrary(
|
||||||
UpdateLibraryInput(
|
UpdateLibraryInput(
|
||||||
clientMutationId = input.clientMutationId,
|
clientMutationId = input.clientMutationId,
|
||||||
@@ -69,15 +68,13 @@ class UpdateMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
UpdateLibraryMangaPayload(
|
||||||
UpdateLibraryMangaPayload(
|
input.clientMutationId,
|
||||||
input.clientMutationId,
|
updateStatus =
|
||||||
updateStatus =
|
withTimeout(30.seconds) {
|
||||||
withTimeout(30.seconds) {
|
UpdateStatus(updater.status.first())
|
||||||
UpdateStatus(updater.status.first())
|
},
|
||||||
},
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +89,7 @@ class UpdateMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateCategoryManga(input: UpdateCategoryMangaInput): CompletableFuture<DataFetcherResult<UpdateCategoryMangaPayload?>> {
|
fun updateCategoryManga(input: UpdateCategoryMangaInput): CompletableFuture<UpdateCategoryMangaPayload?> {
|
||||||
updateLibrary(
|
updateLibrary(
|
||||||
UpdateLibraryInput(
|
UpdateLibraryInput(
|
||||||
clientMutationId = input.clientMutationId,
|
clientMutationId = input.clientMutationId,
|
||||||
@@ -101,15 +98,13 @@ class UpdateMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
UpdateCategoryMangaPayload(
|
||||||
UpdateCategoryMangaPayload(
|
input.clientMutationId,
|
||||||
input.clientMutationId,
|
updateStatus =
|
||||||
updateStatus =
|
withTimeout(30.seconds) {
|
||||||
withTimeout(30.seconds) {
|
UpdateStatus(updater.status.first())
|
||||||
UpdateStatus(updater.status.first())
|
},
|
||||||
},
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.schema.DataFetchingEnvironment
|
import graphql.schema.DataFetchingEnvironment
|
||||||
|
|||||||
@@ -11,15 +11,21 @@ import com.expediagroup.graphql.server.execution.GraphQLRequestParser
|
|||||||
import com.expediagroup.graphql.server.types.GraphQLBatchRequest
|
import com.expediagroup.graphql.server.types.GraphQLBatchRequest
|
||||||
import com.expediagroup.graphql.server.types.GraphQLRequest
|
import com.expediagroup.graphql.server.types.GraphQLRequest
|
||||||
import com.expediagroup.graphql.server.types.GraphQLServerRequest
|
import com.expediagroup.graphql.server.types.GraphQLServerRequest
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import io.javalin.http.Context
|
import io.javalin.http.Context
|
||||||
import io.javalin.http.UploadedFile
|
import io.javalin.http.UploadedFile
|
||||||
|
import io.javalin.json.JavalinJackson
|
||||||
|
import io.javalin.json.fromJsonStream
|
||||||
import io.javalin.json.fromJsonString
|
import io.javalin.json.fromJsonString
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
|
class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
|
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
|
||||||
override suspend fun parseRequest(context: Context): GraphQLServerRequest? {
|
override suspend fun parseRequest(context: Context): GraphQLServerRequest? {
|
||||||
return try {
|
return try {
|
||||||
|
val jsonMapper = context.jsonMapper()
|
||||||
val contentType = context.contentType()
|
val contentType = context.contentType()
|
||||||
val formParam =
|
val formParam =
|
||||||
if (
|
if (
|
||||||
@@ -29,17 +35,17 @@ class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
|
|||||||
context.formParam("operations")
|
context.formParam("operations")
|
||||||
?: throw IllegalArgumentException("Cannot find 'operations' body")
|
?: throw IllegalArgumentException("Cannot find 'operations' body")
|
||||||
} else {
|
} else {
|
||||||
return context.bodyAsClass(GraphQLServerRequest::class.java)
|
return context.bodyInputStream().use { jsonMapper.fromJsonStream<GraphQLServerRequest>(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val request =
|
val request =
|
||||||
context.jsonMapper().fromJsonString<GraphQLServerRequest>(formParam)
|
jsonMapper.fromJsonString<GraphQLServerRequest>(formParam)
|
||||||
|
|
||||||
val map =
|
val map =
|
||||||
context
|
context
|
||||||
.formParam("map")
|
.formParam("map")
|
||||||
?.let {
|
?.let {
|
||||||
context.jsonMapper().fromJsonString<Map<String, List<String>>>(it)
|
jsonMapper.fromJsonString<Map<String, List<String>>>(it)
|
||||||
}.orEmpty()
|
}.orEmpty()
|
||||||
|
|
||||||
val mapItems =
|
val mapItems =
|
||||||
@@ -73,7 +79,8 @@ class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (_: IOException) {
|
} catch (e: IOException) {
|
||||||
|
logger.error(e) { "Error when parsing request" }
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ package suwayomi.tachidesk.graphql.server
|
|||||||
import com.expediagroup.graphql.generator.execution.FlowSubscriptionExecutionStrategy
|
import com.expediagroup.graphql.generator.execution.FlowSubscriptionExecutionStrategy
|
||||||
import com.expediagroup.graphql.server.execution.GraphQLRequestHandler
|
import com.expediagroup.graphql.server.execution.GraphQLRequestHandler
|
||||||
import com.expediagroup.graphql.server.execution.GraphQLServer
|
import com.expediagroup.graphql.server.execution.GraphQLServer
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
|
||||||
import graphql.ExceptionWhileDataFetching
|
import graphql.ExceptionWhileDataFetching
|
||||||
import graphql.GraphQL
|
import graphql.GraphQL
|
||||||
import graphql.execution.AsyncExecutionStrategy
|
import graphql.execution.AsyncExecutionStrategy
|
||||||
@@ -27,6 +26,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import suwayomi.tachidesk.graphql.server.subscriptions.ApolloSubscriptionProtocolHandler
|
import suwayomi.tachidesk.graphql.server.subscriptions.ApolloSubscriptionProtocolHandler
|
||||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||||
|
import tools.jackson.module.kotlin.jacksonObjectMapper
|
||||||
|
|
||||||
class TachideskGraphQLServer(
|
class TachideskGraphQLServer(
|
||||||
requestParser: JavalinGraphQLRequestParser,
|
requestParser: JavalinGraphQLRequestParser,
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ private class GraphqlCursorCoercing : Coercing<Cursor, String> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return Cursor(input.value)
|
return Cursor(input.value!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun valueToLiteralImpl(input: Any): StringValue = StringValue.newStringValue(input.toString()).build()
|
private fun valueToLiteralImpl(input: Any): StringValue = StringValue.newStringValue(input.toString()).build()
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ private class GraphqlDurationAsStringCoercing : Coercing<Duration, String> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
return try {
|
return try {
|
||||||
Duration.parse(input.value)
|
Duration.parse(input.value!!)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
throw CoercingParseLiteralException(
|
throw CoercingParseLiteralException(
|
||||||
"Invalid duration format: ${input.value}. Expected ISO-8601 duration string (e.g., 'PT30M', 'P1D')",
|
"Invalid duration format: ${input.value}. Expected ISO-8601 duration string (e.g., 'PT30M', 'P1D')",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ private class GraphqlLongAsStringCoercing : Coercing<Long, String> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return input.value.toLong()
|
return input.value!!.toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun valueToLiteralImpl(input: Any): StringValue = StringValue.newStringValue(input.toString()).build()
|
private fun valueToLiteralImpl(input: Any): StringValue = StringValue.newStringValue(input.toString()).build()
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ package suwayomi.tachidesk.graphql.server.subscriptions
|
|||||||
|
|
||||||
import com.expediagroup.graphql.server.execution.GraphQLRequestHandler
|
import com.expediagroup.graphql.server.execution.GraphQLRequestHandler
|
||||||
import com.expediagroup.graphql.server.types.GraphQLRequest
|
import com.expediagroup.graphql.server.types.GraphQLRequest
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import com.fasterxml.jackson.module.kotlin.convertValue
|
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import io.javalin.http.Header
|
import io.javalin.http.Header
|
||||||
import io.javalin.websocket.WsContext
|
import io.javalin.websocket.WsContext
|
||||||
@@ -26,7 +23,7 @@ import kotlinx.coroutines.flow.onCompletion
|
|||||||
import kotlinx.coroutines.flow.onStart
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.job
|
import kotlinx.coroutines.job
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.eclipse.jetty.websocket.api.CloseStatus
|
import org.eclipse.jetty.websocket.core.CloseStatus
|
||||||
import suwayomi.tachidesk.graphql.server.TachideskGraphQLContextFactory
|
import suwayomi.tachidesk.graphql.server.TachideskGraphQLContextFactory
|
||||||
import suwayomi.tachidesk.graphql.server.subscriptions.SubscriptionOperationMessage.ClientMessages.GQL_CONNECTION_INIT
|
import suwayomi.tachidesk.graphql.server.subscriptions.SubscriptionOperationMessage.ClientMessages.GQL_CONNECTION_INIT
|
||||||
import suwayomi.tachidesk.graphql.server.subscriptions.SubscriptionOperationMessage.ClientMessages.GQL_SUBSCRIBE
|
import suwayomi.tachidesk.graphql.server.subscriptions.SubscriptionOperationMessage.ClientMessages.GQL_SUBSCRIBE
|
||||||
@@ -41,6 +38,9 @@ import suwayomi.tachidesk.server.JavalinSetup.Attribute
|
|||||||
import suwayomi.tachidesk.server.JavalinSetup.getAttributeOrSet
|
import suwayomi.tachidesk.server.JavalinSetup.getAttributeOrSet
|
||||||
import suwayomi.tachidesk.server.user.UserType
|
import suwayomi.tachidesk.server.user.UserType
|
||||||
import suwayomi.tachidesk.server.user.getUserFromToken
|
import suwayomi.tachidesk.server.user.getUserFromToken
|
||||||
|
import tools.jackson.databind.ObjectMapper
|
||||||
|
import tools.jackson.module.kotlin.convertValue
|
||||||
|
import tools.jackson.module.kotlin.readValue
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the `graphql-transport-ws` protocol defined by Denis Badurina
|
* Implementation of the `graphql-transport-ws` protocol defined by Denis Badurina
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import kotlinx.coroutines.Job
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import kotlinx.coroutines.flow.onCompletion
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
import org.eclipse.jetty.websocket.api.CloseStatus
|
import org.eclipse.jetty.websocket.core.CloseStatus
|
||||||
import suwayomi.tachidesk.graphql.server.toGraphQLContext
|
import suwayomi.tachidesk.graphql.server.toGraphQLContext
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
|||||||
@@ -13,12 +13,14 @@ import io.github.oshai.kotlinlogging.KotlinLogging
|
|||||||
import io.javalin.Javalin
|
import io.javalin.Javalin
|
||||||
import io.javalin.apibuilder.ApiBuilder.after
|
import io.javalin.apibuilder.ApiBuilder.after
|
||||||
import io.javalin.apibuilder.ApiBuilder.path
|
import io.javalin.apibuilder.ApiBuilder.path
|
||||||
|
import io.javalin.config.RoutesConfig
|
||||||
import io.javalin.http.Context
|
import io.javalin.http.Context
|
||||||
import io.javalin.http.HandlerType
|
import io.javalin.http.HandlerType
|
||||||
import io.javalin.http.HttpStatus
|
import io.javalin.http.HttpStatus
|
||||||
import io.javalin.http.NotFoundResponse
|
import io.javalin.http.NotFoundResponse
|
||||||
import io.javalin.http.RedirectResponse
|
import io.javalin.http.RedirectResponse
|
||||||
import io.javalin.http.UnauthorizedResponse
|
import io.javalin.http.UnauthorizedResponse
|
||||||
|
import io.javalin.json.JavalinJackson3
|
||||||
import io.javalin.rendering.template.JavalinJte
|
import io.javalin.rendering.template.JavalinJte
|
||||||
import io.javalin.websocket.WsContext
|
import io.javalin.websocket.WsContext
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -47,6 +49,7 @@ import java.net.URLEncoder
|
|||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
import kotlin.text.get
|
||||||
import kotlin.time.Duration.Companion.days
|
import kotlin.time.Duration.Companion.days
|
||||||
|
|
||||||
object JavalinSetup {
|
object JavalinSetup {
|
||||||
@@ -58,10 +61,12 @@ object JavalinSetup {
|
|||||||
|
|
||||||
fun javalinSetup() {
|
fun javalinSetup() {
|
||||||
val app =
|
val app =
|
||||||
Javalin.create { config ->
|
Javalin.start { config ->
|
||||||
val templateEngine = TemplateEngine.createPrecompiled(ContentType.Html)
|
val templateEngine = TemplateEngine.createPrecompiled(ContentType.Html)
|
||||||
config.fileRenderer(JavalinJte(templateEngine))
|
config.fileRenderer(JavalinJte(templateEngine))
|
||||||
|
|
||||||
|
config.jsonMapper(JavalinJackson3())
|
||||||
|
|
||||||
WebInterfaceManager.setup(config)
|
WebInterfaceManager.setup(config)
|
||||||
|
|
||||||
// config.registerPlugin(OpenApiPlugin(getOpenApiOptions()))
|
// config.registerPlugin(OpenApiPlugin(getOpenApiOptions()))
|
||||||
@@ -104,7 +109,8 @@ object JavalinSetup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.router.apiBuilder {
|
config.routes.defineCore()
|
||||||
|
config.routes.apiBuilder {
|
||||||
path(ServerSubpath.maybeAddAsPrefix("api/")) {
|
path(ServerSubpath.maybeAddAsPrefix("api/")) {
|
||||||
path("v1/") {
|
path("v1/") {
|
||||||
GlobalAPI.defineEndpoints()
|
GlobalAPI.defineEndpoints()
|
||||||
@@ -117,17 +123,32 @@ object JavalinSetup {
|
|||||||
after { ctx ->
|
after { ctx ->
|
||||||
// If not matched, the request was for an invalid endpoint
|
// If not matched, the request was for an invalid endpoint
|
||||||
// Return a 404 instead of redirecting to the UI for usability
|
// Return a 404 instead of redirecting to the UI for usability
|
||||||
if (ctx.endpointHandlerPath() == "*") {
|
if (ctx.endpoints().lastHttpEndpoint()?.path == "*") {
|
||||||
throw NotFoundResponse()
|
throw NotFoundResponse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.events.serverStarted {
|
||||||
|
if (serverConfig.initialOpenInBrowserEnabled.value) {
|
||||||
|
Browser.openInBrowser()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when JVM is prompted to shutdown, stop javalin gracefully
|
||||||
|
Runtime.getRuntime().addShutdownHook(
|
||||||
|
thread(start = false) {
|
||||||
|
app.stop()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RoutesConfig.defineCore() {
|
||||||
val loginPath = ServerSubpath.maybeAddAsPrefix("/login.html")
|
val loginPath = ServerSubpath.maybeAddAsPrefix("/login.html")
|
||||||
|
|
||||||
app.get(loginPath) { ctx ->
|
get(loginPath) { ctx ->
|
||||||
val locale: Locale = LocalizationHelper.ctxToLocale(ctx)
|
val locale: Locale = LocalizationHelper.ctxToLocale(ctx)
|
||||||
ctx.header("content-type", "text/html")
|
ctx.header("content-type", "text/html")
|
||||||
val httpCacheSeconds = 1.days.inWholeSeconds
|
val httpCacheSeconds = 1.days.inWholeSeconds
|
||||||
@@ -141,7 +162,7 @@ object JavalinSetup {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.post(loginPath) { ctx ->
|
post(loginPath) { ctx ->
|
||||||
val username = ctx.formParam("user")
|
val username = ctx.formParam("user")
|
||||||
val password = ctx.formParam("pass")
|
val password = ctx.formParam("pass")
|
||||||
val isValid =
|
val isValid =
|
||||||
@@ -174,7 +195,7 @@ object JavalinSetup {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.beforeMatched { ctx ->
|
beforeMatched { ctx ->
|
||||||
val isWebManifest =
|
val isWebManifest =
|
||||||
listOf("site.webmanifest", "manifest.json", "login.html").any { ctx.path().endsWith(it) }
|
listOf("site.webmanifest", "manifest.json", "login.html").any { ctx.path().endsWith(it) }
|
||||||
val isPageIcon =
|
val isPageIcon =
|
||||||
@@ -219,60 +240,43 @@ object JavalinSetup {
|
|||||||
ctx.setAttribute(Attribute.TachideskBasic, credentialsValid())
|
ctx.setAttribute(Attribute.TachideskBasic, credentialsValid())
|
||||||
}
|
}
|
||||||
|
|
||||||
app.events { event ->
|
wsBefore {
|
||||||
event.serverStarted {
|
|
||||||
if (serverConfig.initialOpenInBrowserEnabled.value) {
|
|
||||||
Browser.openInBrowser()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app.wsBefore {
|
|
||||||
it.onConnect { ctx ->
|
it.onConnect { ctx ->
|
||||||
ctx.setAttribute(Attribute.TachideskUser, getUserFromWsContext(ctx))
|
ctx.setAttribute(Attribute.TachideskUser, getUserFromWsContext(ctx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// when JVM is prompted to shutdown, stop javalin gracefully
|
exception(NullPointerException::class.java) { e, ctx ->
|
||||||
Runtime.getRuntime().addShutdownHook(
|
|
||||||
thread(start = false) {
|
|
||||||
app.stop()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
app.exception(NullPointerException::class.java) { e, ctx ->
|
|
||||||
logger.error(e) { "NullPointerException while handling the request" }
|
logger.error(e) { "NullPointerException while handling the request" }
|
||||||
ctx.status(404)
|
ctx.status(404)
|
||||||
}
|
}
|
||||||
app.exception(NoSuchElementException::class.java) { e, ctx ->
|
exception(NoSuchElementException::class.java) { e, ctx ->
|
||||||
logger.error(e) { "NoSuchElementException while handling the request" }
|
logger.error(e) { "NoSuchElementException while handling the request" }
|
||||||
ctx.status(404)
|
ctx.status(404)
|
||||||
}
|
}
|
||||||
app.exception(IOException::class.java) { e, ctx ->
|
exception(IOException::class.java) { e, ctx ->
|
||||||
logger.error(e) { "IOException while handling the request" }
|
logger.error(e) { "IOException while handling the request" }
|
||||||
ctx.status(500)
|
ctx.status(500)
|
||||||
ctx.result(e.message ?: "Internal Server Error")
|
ctx.result(e.message ?: "Internal Server Error")
|
||||||
}
|
}
|
||||||
|
|
||||||
app.exception(IllegalArgumentException::class.java) { e, ctx ->
|
exception(IllegalArgumentException::class.java) { e, ctx ->
|
||||||
logger.error(e) { "IllegalArgumentException while handling the request" }
|
logger.error(e) { "IllegalArgumentException while handling the request" }
|
||||||
ctx.status(400)
|
ctx.status(400)
|
||||||
ctx.result(e.message ?: "Bad Request")
|
ctx.result(e.message ?: "Bad Request")
|
||||||
}
|
}
|
||||||
|
|
||||||
app.exception(UnauthorizedException::class.java) { e, ctx ->
|
exception(UnauthorizedException::class.java) { e, ctx ->
|
||||||
logger.error(e) { "UnauthorizedException while handling the request" }
|
logger.error(e) { "UnauthorizedException while handling the request" }
|
||||||
ctx.status(HttpStatus.UNAUTHORIZED)
|
ctx.status(HttpStatus.UNAUTHORIZED)
|
||||||
ctx.result(e.message ?: "Unauthorized")
|
ctx.result(e.message ?: "Unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
app.exception(ForbiddenException::class.java) { e, ctx ->
|
exception(ForbiddenException::class.java) { e, ctx ->
|
||||||
logger.error(e) { "ForbiddenException while handling the request" }
|
logger.error(e) { "ForbiddenException while handling the request" }
|
||||||
ctx.status(HttpStatus.FORBIDDEN)
|
ctx.status(HttpStatus.FORBIDDEN)
|
||||||
ctx.result(e.message ?: "Forbidden")
|
ctx.result(e.message ?: "Forbidden")
|
||||||
}
|
}
|
||||||
|
|
||||||
app.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// private fun getOpenApiOptions(): OpenApiOptions {
|
// private fun getOpenApiOptions(): OpenApiOptions {
|
||||||
|
|||||||
@@ -71,15 +71,14 @@ fun <T> getParam(
|
|||||||
is Param.FormParam -> ctx.formParamAsClass(param.key, clazz)
|
is Param.FormParam -> ctx.formParamAsClass(param.key, clazz)
|
||||||
is Param.PathParam -> ctx.pathParamAsClass(param.key, clazz)
|
is Param.PathParam -> ctx.pathParamAsClass(param.key, clazz)
|
||||||
is Param.QueryParam -> ctx.queryParamAsClass(param.key, clazz)
|
is Param.QueryParam -> ctx.queryParamAsClass(param.key, clazz)
|
||||||
else -> throw IllegalStateException("Invalid param")
|
|
||||||
}.let {
|
}.let {
|
||||||
if (param.nullable) {
|
if (param.nullable) {
|
||||||
it.allowNullable().get() ?: param.defaultValue
|
it.getOrNull() ?: param.defaultValue
|
||||||
} else {
|
} else {
|
||||||
if (param.defaultValue != null) {
|
if (param.defaultValue != null) {
|
||||||
it.getOrDefault(param.defaultValue!!)
|
it.getOrDefault(param.defaultValue!!)
|
||||||
} else {
|
} else {
|
||||||
it.get()
|
it.required().get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import io.github.oshai.kotlinlogging.KLogger
|
|||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import io.github.reactivecircus.cache4k.Cache
|
import io.github.reactivecircus.cache4k.Cache
|
||||||
import io.javalin.config.JavalinConfig
|
import io.javalin.config.JavalinConfig
|
||||||
|
import io.javalin.http.staticfiles.AliasCheck
|
||||||
import io.javalin.http.staticfiles.Location
|
import io.javalin.http.staticfiles.Location
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
@@ -39,7 +40,6 @@ import kotlinx.serialization.json.jsonArray
|
|||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import net.lingala.zip4j.ZipFile
|
import net.lingala.zip4j.ZipFile
|
||||||
import org.eclipse.jetty.server.handler.ContextHandler
|
|
||||||
import suwayomi.tachidesk.graphql.types.AboutWebUI
|
import suwayomi.tachidesk.graphql.types.AboutWebUI
|
||||||
import suwayomi.tachidesk.graphql.types.UpdateState
|
import suwayomi.tachidesk.graphql.types.UpdateState
|
||||||
import suwayomi.tachidesk.graphql.types.UpdateState.DOWNLOADING
|
import suwayomi.tachidesk.graphql.types.UpdateState.DOWNLOADING
|
||||||
@@ -180,7 +180,7 @@ object WebInterfaceManager {
|
|||||||
// Use canonical path to avoid Jetty alias issues
|
// Use canonical path to avoid Jetty alias issues
|
||||||
staticFiles.directory = File(applicationDirs.webUIServe).canonicalPath
|
staticFiles.directory = File(applicationDirs.webUIServe).canonicalPath
|
||||||
staticFiles.location = Location.EXTERNAL
|
staticFiles.location = Location.EXTERNAL
|
||||||
staticFiles.aliasCheck = ContextHandler.ApproveAliases()
|
staticFiles.aliasCheck = AliasCheck { _, _ -> true }
|
||||||
}
|
}
|
||||||
|
|
||||||
serveWebUI = {
|
serveWebUI = {
|
||||||
@@ -304,11 +304,25 @@ object WebInterfaceManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val flavor = WebUIFlavor.current
|
|
||||||
val servedFlavor = getServedWebUIFlavor()
|
val servedFlavor = getServedWebUIFlavor()
|
||||||
|
|
||||||
val log =
|
val log =
|
||||||
KotlinLogging.logger("${logger.name} setupWebUI(flavor= ${flavor.uiName}, servedFlavor= ${servedFlavor.uiName})")
|
KotlinLogging.logger(
|
||||||
|
"${logger.name} setupWebUI(flavor= ${WebUIFlavor.current.uiName}, servedFlavor= ${servedFlavor.uiName}, channel= ${serverConfig.webUIChannel})",
|
||||||
|
)
|
||||||
|
|
||||||
|
val flavor =
|
||||||
|
if (serverConfig.webUIChannel.value == WebUIChannel.BUNDLED) {
|
||||||
|
if (serverConfig.webUIFlavor.value != WebUIFlavor.default) {
|
||||||
|
log.warn {
|
||||||
|
"Changed flavor to ${WebUIFlavor.default.uiName}. Channel \"${WebUIChannel.BUNDLED}\" only works with the default flavor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebUIFlavor.default
|
||||||
|
} else {
|
||||||
|
WebUIFlavor.current
|
||||||
|
}
|
||||||
|
|
||||||
if (doesLocalWebUIExist(applicationDirs.webUIRoot)) {
|
if (doesLocalWebUIExist(applicationDirs.webUIRoot)) {
|
||||||
val currentVersion = getLocalVersion()
|
val currentVersion = getLocalVersion()
|
||||||
|
|||||||
Reference in New Issue
Block a user