Delete v4 backend + docker fixes (#1116)

This commit is contained in:
2025-10-26 22:55:56 +01:00
committed by GitHub
227 changed files with 263 additions and 41509 deletions

View File

@@ -1,3 +0,0 @@
7e6e56a366babe17e7891a5897180efbf93c00c5
a5638203a6ecb9f372a5a61e1c8fd443bf3a17fe
18e301f26acd7f2e97eac26c5f48377fa13956f5

1
.gitattributes vendored
View File

@@ -1 +0,0 @@
*.Designer.cs linguist-generated=true

View File

@@ -2,20 +2,6 @@ name: Coding Style
on: [pull_request, workflow_dispatch]
jobs:
back:
name: "Lint Back"
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./back
steps:
- uses: actions/checkout@v5
- name: Check coding style
run: |
dotnet tool restore
dotnet csharpier . --check
api:
name: "Lint api"
runs-on: ubuntu-latest

View File

@@ -19,16 +19,6 @@ jobs:
fail-fast: false
matrix:
include:
- context: ./back
dockerfile: Dockerfile
label: back
image: ${{ github.repository_owner }}/kyoo_back
- context: ./back
dockerfile: Dockerfile.migrations
label: migrations
image: ${{ github.repository_owner }}/kyoo_migrations
- context: ./api
dockerfile: Dockerfile
label: api

View File

@@ -1,4 +0,0 @@
# Authors
Ordered by the date of the first commit.
* Zoe Roux ([@zoriya](http://github.com/zoriya))

View File

@@ -39,10 +39,7 @@ Kyoo does not have a plugin system and aim to have every features built-in (see
## 📺 Clients
Kyoo currently supports Web and Android clients, with additional platforms being thought about. Rough estimates:
* Today: Web & Android
* Spring 2025: Chromecast
* Summer 2025: Android-TV
Kyoo currently supports Web and Android clients, with additional platforms being thought about.
Don't see your client? Kyoo is focused on adding features, but welcomes contributors! The frontend is built with React-Native and Expo. Come hang and develop with us on Discord.

View File

@@ -1,4 +1,4 @@
FROM oven/bun AS builder
FROM oven/bun:debian AS builder
WORKDIR /app
COPY package.json bun.lock .
@@ -18,11 +18,13 @@ RUN bun build \
--outfile server \
./src/index.ts
FROM gcr.io/distroless/base
FROM debian
WORKDIR /app
COPY --from=builder /app/server server
COPY --from=builder /app/node_modules/@img /app/node_modules/@img
COPY ./drizzle ./drizzle
ENV NODE_ENV=production
EXPOSE 3567
CMD ["./server"]
CMD ["/app/server"]

View File

@@ -5,146 +5,152 @@
"name": "api",
"dependencies": {
"@elysiajs/swagger": "zoriya/elysia-swagger#build",
"@types/bun": "^1.3.1",
"blurhash": "^2.0.5",
"drizzle-kit": "^0.31.1",
"drizzle-orm": "0.43.1",
"elysia": "^1.3.1",
"jose": "^6.0.11",
"drizzle-kit": "^0.31.5",
"drizzle-orm": "0.44.7",
"elysia": "^1.4.13",
"jose": "^6.1.0",
"node-addon-api": "^8.5.0",
"parjs": "^1.3.9",
"pg": "^8.16.0",
"sharp": "^0.34.2",
"pg": "^8.16.3",
"sharp": "^0.34.4",
},
"devDependencies": {
"@biomejs/biome": "2.1.1",
"@types/pg": "^8.15.2",
"bun-types": "^1.2.14",
"node-addon-api": "^8.3.1",
"@biomejs/biome": "2.3.0",
"@types/pg": "^8.15.5",
},
},
},
"patchedDependencies": {
"drizzle-orm@0.43.1": "patches/drizzle-orm@0.43.1.patch",
"drizzle-orm@0.44.7": "patches/drizzle-orm@0.44.7.patch",
},
"packages": {
"@biomejs/biome": ["@biomejs/biome@2.1.1", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.1.1", "@biomejs/cli-darwin-x64": "2.1.1", "@biomejs/cli-linux-arm64": "2.1.1", "@biomejs/cli-linux-arm64-musl": "2.1.1", "@biomejs/cli-linux-x64": "2.1.1", "@biomejs/cli-linux-x64-musl": "2.1.1", "@biomejs/cli-win32-arm64": "2.1.1", "@biomejs/cli-win32-x64": "2.1.1" }, "bin": { "biome": "bin/biome" } }, "sha512-HFGYkxG714KzG+8tvtXCJ1t1qXQMzgWzfvQaUjxN6UeKv+KvMEuliInnbZLJm6DXFXwqVi6446EGI0sGBLIYng=="],
"@biomejs/biome": ["@biomejs/biome@2.3.0", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.0", "@biomejs/cli-darwin-x64": "2.3.0", "@biomejs/cli-linux-arm64": "2.3.0", "@biomejs/cli-linux-arm64-musl": "2.3.0", "@biomejs/cli-linux-x64": "2.3.0", "@biomejs/cli-linux-x64-musl": "2.3.0", "@biomejs/cli-win32-arm64": "2.3.0", "@biomejs/cli-win32-x64": "2.3.0" }, "bin": { "biome": "bin/biome" } }, "sha512-shdUY5H3S3tJVUWoVWo5ua+GdPW5lRHf+b0IwZ4OC1o2zOKQECZ6l2KbU6t89FNhtd3Qx5eg5N7/UsQWGQbAFw=="],
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.1.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-2Muinu5ok4tWxq4nu5l19el48cwCY/vzvI7Vjbkf3CYIQkjxZLyj0Ad37Jv2OtlXYaLvv+Sfu1hFeXt/JwRRXQ=="],
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-3cJVT0Z5pbTkoBmbjmDZTDFYxIkRcrs9sYVJbIBHU8E6qQxgXAaBfSVjjCreG56rfDuQBr43GzwzmaHPcu4vlw=="],
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.1.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cC8HM5lrgKQXLAK+6Iz2FrYW5A62pAAX6KAnRlEyLb+Q3+Kr6ur/sSuoIacqlp1yvmjHJqjYfZjPvHWnqxoEIA=="],
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-6LIkhglh3UGjuDqJXsK42qCA0XkD1Ke4K/raFOii7QQPbM8Pia7Qj2Hji4XuF2/R78hRmEx7uKJH3t/Y9UahtQ=="],
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.1.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-tw4BEbhAUkWPe4WBr6IX04DJo+2jz5qpPzpW/SWvqMjb9QuHY8+J0M23V8EPY/zWU4IG8Ui0XESapR1CB49Q7g=="],
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-uhAsbXySX7xsXahegDg5h3CDgfMcRsJvWLFPG0pjkylgBb9lErbK2C0UINW52zhwg0cPISB09lxHPxCau4e2xA=="],
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.1.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-/7FBLnTswu4jgV9ttI3AMIdDGqVEPIZd8I5u2D4tfCoj8rl9dnjrEQbAIDlWhUXdyWlFSz8JypH3swU9h9P+2A=="],
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-nDksoFdwZ2YrE7NiYDhtMhL2UgFn8Kb7Y0bYvnTAakHnqEdb4lKindtBc1f+xg2Snz0JQhJUYO7r9CDBosRU5w=="],
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.1.1", "", { "os": "linux", "cpu": "x64" }, "sha512-3WJ1GKjU7NzZb6RTbwLB59v9cTIlzjbiFLDB0z4376TkDqoNYilJaC37IomCr/aXwuU8QKkrYoHrgpSq5ffJ4Q=="],
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.0", "", { "os": "linux", "cpu": "x64" }, "sha512-uxa8reA2s1VgoH8MhbGlCmMOt3JuSE1vJBifkh1ulaPiuk0SPx8cCdpnm9NWnTe2x/LfWInWx4sZ7muaXTPGGw=="],
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.1.1", "", { "os": "linux", "cpu": "x64" }, "sha512-kUu+loNI3OCD2c12cUt7M5yaaSjDnGIksZwKnueubX6c/HWUyi/0mPbTBHR49Me3F0KKjWiKM+ZOjsmC+lUt9g=="],
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.0", "", { "os": "linux", "cpu": "x64" }, "sha512-+i9UcJwl99uAhtRQDz9jUAh+Xkb097eekxs/D9j4deWDg5/yB/jPWzISe1nBHvlzTXsdUSj0VvB4Go2DSpKIMw=="],
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.1.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-vEHK0v0oW+E6RUWLoxb2isI3rZo57OX9ZNyyGH701fZPj6Il0Rn1f5DMNyCmyflMwTnIQstEbs7n2BxYSqQx4Q=="],
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-ynjmsJLIKrAjC3CCnKMMhzcnNy8dbQWjKfSU5YA0mIruTxBNMbkAJp+Pr2iV7/hFou+66ZSD/WV8hmLEmhUaXA=="],
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.1.1", "", { "os": "win32", "cpu": "x64" }, "sha512-i2PKdn70kY++KEF/zkQFvQfX1e8SkA8hq4BgC+yE9dZqyLzB/XStY2MvwI3qswlRgnGpgncgqe0QYKVS1blksg=="],
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.0", "", { "os": "win32", "cpu": "x64" }, "sha512-zOCYmCRVkWXc9v8P7OLbLlGGMxQTKMvi+5IC4v7O8DkjLCOHRzRVK/Lno2pGZNo0lzKM60pcQOhH8HVkXMQdFg=="],
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
"@elysiajs/swagger": ["@elysiajs/swagger@github:zoriya/elysia-swagger#f88fbc7", { "dependencies": { "@scalar/themes": "^0.9.81", "@scalar/types": "^0.1.3", "openapi-types": "^12.1.3", "pathe": "^1.1.2" }, "peerDependencies": { "elysia": ">= 1.3.0" } }, "zoriya-elysia-swagger-f88fbc7"],
"@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
"@emnapi/runtime": ["@emnapi/runtime@1.6.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA=="],
"@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="],
"@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.11", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.3", "", { "os": "android", "cpu": "arm" }, "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.11", "", { "os": "android", "cpu": "arm" }, "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.3", "", { "os": "android", "cpu": "arm64" }, "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.11", "", { "os": "android", "cpu": "arm64" }, "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.3", "", { "os": "android", "cpu": "x64" }, "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.11", "", { "os": "android", "cpu": "x64" }, "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.11", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.3", "", { "os": "linux", "cpu": "arm" }, "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.11", "", { "os": "linux", "cpu": "arm" }, "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.11", "", { "os": "linux", "cpu": "ia32" }, "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.3", "", { "os": "linux", "cpu": "none" }, "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.3", "", { "os": "linux", "cpu": "none" }, "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.11", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.3", "", { "os": "linux", "cpu": "none" }, "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.11", "", { "os": "linux", "cpu": "s390x" }, "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.3", "", { "os": "linux", "cpu": "x64" }, "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.11", "", { "os": "linux", "cpu": "x64" }, "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.3", "", { "os": "none", "cpu": "arm64" }, "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.3", "", { "os": "none", "cpu": "x64" }, "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.11", "", { "os": "none", "cpu": "x64" }, "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.11", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.11", "", { "os": "openbsd", "cpu": "x64" }, "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.11", "", { "os": "sunos", "cpu": "x64" }, "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.3", "", { "os": "win32", "cpu": "x64" }, "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.11", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.2", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.1.0" }, "os": "darwin", "cpu": "arm64" }, "sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.11", "", { "os": "win32", "cpu": "x64" }, "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.2", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.1.0" }, "os": "darwin", "cpu": "x64" }, "sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g=="],
"@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.1.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.3" }, "os": "darwin", "cpu": "arm64" }, "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.1.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.3" }, "os": "darwin", "cpu": "x64" }, "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.1.0", "", { "os": "linux", "cpu": "arm" }, "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA=="],
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.1.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.3", "", { "os": "linux", "cpu": "arm" }, "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA=="],
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.1.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q=="],
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w=="],
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.2", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.1.0" }, "os": "linux", "cpu": "arm" }, "sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.2", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.1.0" }, "os": "linux", "cpu": "arm64" }, "sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g=="],
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.2", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.1.0" }, "os": "linux", "cpu": "s390x" }, "sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.3" }, "os": "linux", "cpu": "arm" }, "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.2", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.1.0" }, "os": "linux", "cpu": "x64" }, "sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.3" }, "os": "linux", "cpu": "arm64" }, "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.2", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.1.0" }, "os": "linux", "cpu": "arm64" }, "sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA=="],
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.3" }, "os": "linux", "cpu": "ppc64" }, "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.2", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.1.0" }, "os": "linux", "cpu": "x64" }, "sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA=="],
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.3" }, "os": "linux", "cpu": "s390x" }, "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw=="],
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.2", "", { "dependencies": { "@emnapi/runtime": "^1.4.3" }, "cpu": "none" }, "sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.3" }, "os": "linux", "cpu": "x64" }, "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A=="],
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.3" }, "os": "linux", "cpu": "arm64" }, "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA=="],
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.3" }, "os": "linux", "cpu": "x64" }, "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.2", "", { "os": "win32", "cpu": "x64" }, "sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw=="],
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.4", "", { "dependencies": { "@emnapi/runtime": "^1.5.0" }, "cpu": "none" }, "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA=="],
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA=="],
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.4", "", { "os": "win32", "cpu": "x64" }, "sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig=="],
"@scalar/openapi-types": ["@scalar/openapi-types@0.1.9", "", {}, "sha512-HQQudOSQBU7ewzfnBW9LhDmBE2XOJgSfwrh5PlUB7zJup/kaRkBGNgV2wMjNz9Af/uztiU/xNrO179FysmUT+g=="],
@@ -158,9 +164,13 @@
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
"@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="],
"@types/node": ["@types/node@22.13.13", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ=="],
"@types/pg": ["@types/pg@8.15.2", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^4.0.1" } }, "sha512-+BKxo5mM6+/A1soSHBI7ufUglqYXntChLDyTbvcAn1Lawi9J7J9Ok3jt6w7I0+T/UDJ4CyhHk66+GZbwmkYxSg=="],
"@types/pg": ["@types/pg@8.15.5", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ=="],
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
"@unhead/schema": ["@unhead/schema@1.11.20", "", { "dependencies": { "hookable": "^5.5.3", "zhead": "^2.2.4" } }, "sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA=="],
@@ -168,35 +178,29 @@
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
"bun-types": ["bun-types@1.2.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-Kuh4Ub28ucMRWeiUUWMHsT9Wcbr4H3kLIO72RZZElSDxSu7vpetRvxIUDUaW6QtaIeixIpm7OXtNnZPf82EzwA=="],
"bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
"char-info": ["char-info@0.3.5", "", { "dependencies": { "node-interval-tree": "^1.3.3" } }, "sha512-gRslEBFEcuLMGLNO1EFIrdN1MMUfO+aqa7y8iWzNyAzB3mYKnTIvP+ioW3jpyeEvqA5WapVLIPINGtFjEIH4cQ=="],
"color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
"cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"drizzle-kit": ["drizzle-kit@0.31.1", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.2", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-PUjYKWtzOzPtdtQlTHQG3qfv4Y0XT8+Eas6UbxCmxTj7qgMf+39dDujf1BP1I+qqZtw9uzwTh8jYtkMuCq+B0Q=="],
"drizzle-kit": ["drizzle-kit@0.31.5", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-+CHgPFzuoTQTt7cOYCV6MOw2w8vqEn/ap1yv4bpZOWL03u7rlVRQhUY0WYT3rHsgVTXwYQDZaSUJSQrMBUKuWg=="],
"drizzle-orm": ["drizzle-orm@0.43.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-dUcDaZtE/zN4RV/xqGrVSMpnEczxd5cIaoDeor7Zst9wOe/HzC/7eAaulywWGYXdDEc9oBPMjayVEDg0ziTLJA=="],
"drizzle-orm": ["drizzle-orm@0.44.7", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ=="],
"elysia": ["elysia@1.3.1", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.1.2", "fast-decode-uri-component": "^1.0.1" }, "optionalDependencies": { "@sinclair/typebox": "^0.34.33", "openapi-types": "^12.1.3" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" } }, "sha512-En41P6cDHcHtQ0nvfsn9ayB+8ahQJqG1nzvPX8FVZjOriFK/RtZPQBtXMfZDq/AsVIk7JFZGFEtAVEmztNJVhQ=="],
"elysia": ["elysia@1.4.13", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.2.2", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-6QaWQEm7QN1UCo1TPpEjaRJPHUmnM7R29y6LY224frDGk5PrpAnWmdHkoZxkcv+JRWp1j2ROr2IHbxHbG/jRjw=="],
"esbuild": ["esbuild@0.25.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.3", "@esbuild/android-arm": "0.25.3", "@esbuild/android-arm64": "0.25.3", "@esbuild/android-x64": "0.25.3", "@esbuild/darwin-arm64": "0.25.3", "@esbuild/darwin-x64": "0.25.3", "@esbuild/freebsd-arm64": "0.25.3", "@esbuild/freebsd-x64": "0.25.3", "@esbuild/linux-arm": "0.25.3", "@esbuild/linux-arm64": "0.25.3", "@esbuild/linux-ia32": "0.25.3", "@esbuild/linux-loong64": "0.25.3", "@esbuild/linux-mips64el": "0.25.3", "@esbuild/linux-ppc64": "0.25.3", "@esbuild/linux-riscv64": "0.25.3", "@esbuild/linux-s390x": "0.25.3", "@esbuild/linux-x64": "0.25.3", "@esbuild/netbsd-arm64": "0.25.3", "@esbuild/netbsd-x64": "0.25.3", "@esbuild/openbsd-arm64": "0.25.3", "@esbuild/openbsd-x64": "0.25.3", "@esbuild/sunos-x64": "0.25.3", "@esbuild/win32-arm64": "0.25.3", "@esbuild/win32-ia32": "0.25.3", "@esbuild/win32-x64": "0.25.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q=="],
"esbuild": ["esbuild@0.25.11", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.11", "@esbuild/android-arm": "0.25.11", "@esbuild/android-arm64": "0.25.11", "@esbuild/android-x64": "0.25.11", "@esbuild/darwin-arm64": "0.25.11", "@esbuild/darwin-x64": "0.25.11", "@esbuild/freebsd-arm64": "0.25.11", "@esbuild/freebsd-x64": "0.25.11", "@esbuild/linux-arm": "0.25.11", "@esbuild/linux-arm64": "0.25.11", "@esbuild/linux-ia32": "0.25.11", "@esbuild/linux-loong64": "0.25.11", "@esbuild/linux-mips64el": "0.25.11", "@esbuild/linux-ppc64": "0.25.11", "@esbuild/linux-riscv64": "0.25.11", "@esbuild/linux-s390x": "0.25.11", "@esbuild/linux-x64": "0.25.11", "@esbuild/netbsd-arm64": "0.25.11", "@esbuild/netbsd-x64": "0.25.11", "@esbuild/openbsd-arm64": "0.25.11", "@esbuild/openbsd-x64": "0.25.11", "@esbuild/openharmony-arm64": "0.25.11", "@esbuild/sunos-x64": "0.25.11", "@esbuild/win32-arm64": "0.25.11", "@esbuild/win32-ia32": "0.25.11", "@esbuild/win32-x64": "0.25.11" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q=="],
"esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
"exact-mirror": ["exact-mirror@0.1.2", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-wFCPCDLmHbKGUb8TOi/IS7jLsgR8WVDGtDK3CzcB4Guf/weq7G+I+DkXiRSZfbemBFOxOINKpraM6ml78vo8Zw=="],
"exact-mirror": ["exact-mirror@0.2.2", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-CrGe+4QzHZlnrXZVlo/WbUZ4qQZq8C0uATQVGVgXIrNXgHDBBNFD1VRfssRA2C9t3RYvh3MadZSdg2Wy7HBoQA=="],
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
@@ -210,18 +214,16 @@
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
"jose": ["jose@6.1.0", "", {}, "sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA=="],
"jose": ["jose@6.0.11", "", {}, "sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg=="],
"memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"node-addon-api": ["node-addon-api@8.3.1", "", {}, "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA=="],
"node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="],
"node-interval-tree": ["node-interval-tree@1.3.3", "", { "dependencies": { "shallowequal": "^1.0.2" } }, "sha512-K9vk96HdTK5fEipJwxSvIIqwTqr4e3HRJeJrNxBSeVMNSC/JWARRaX7etOLOuTmrRMeOI/K5TCJu3aWIwZiNTw=="],
"obuf": ["obuf@1.1.2", "", {}, "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="],
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
"parjs": ["parjs@1.3.9", "", { "dependencies": { "char-info": "0.3.*" } }, "sha512-zmQhbzWM3M391tjwTGvNvvtoT8rRE/bBTjw6+54g8ANaPpnyekDF1d8q5tzN4kxmVud82cNj8zSd+uxSL4LE0A=="],
@@ -230,33 +232,29 @@
"peek-readable": ["peek-readable@7.0.0", "", {}, "sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ=="],
"pg": ["pg@8.16.0", "", { "dependencies": { "pg-connection-string": "^2.9.0", "pg-pool": "^3.10.0", "pg-protocol": "^1.10.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.2.5" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-7SKfdvP8CTNXjMUzfcVTaI+TDzBEeaUnVwiVGZQD1Hh33Kpev7liQba9uLd4CfN8r9mCVsD0JIpq03+Unpz+kg=="],
"pg": ["pg@8.16.3", "", { "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", "pg-protocol": "^1.10.3", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.2.7" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw=="],
"pg-cloudflare": ["pg-cloudflare@1.2.5", "", {}, "sha512-OOX22Vt0vOSRrdoUPKJ8Wi2OpE/o/h9T8X1s4qSkCedbNah9ei2W2765be8iMVxQUsvgT7zIAT2eIa9fs5+vtg=="],
"pg-cloudflare": ["pg-cloudflare@1.2.7", "", {}, "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg=="],
"pg-connection-string": ["pg-connection-string@2.9.0", "", {}, "sha512-P2DEBKuvh5RClafLngkAuGe9OUlFV7ebu8w1kmaaOgPcpJd1RIFh7otETfI6hAR8YupOLFTY7nuvvIn7PLciUQ=="],
"pg-connection-string": ["pg-connection-string@2.9.1", "", {}, "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w=="],
"pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="],
"pg-numeric": ["pg-numeric@1.0.2", "", {}, "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw=="],
"pg-pool": ["pg-pool@3.10.0", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-DzZ26On4sQ0KmqnO34muPcmKbhrjmyiO4lCCR0VwEd7MjmiKf5NTg/6+apUEu0NF7ESa37CGzFxH513CoUmWnA=="],
"pg-pool": ["pg-pool@3.10.1", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg=="],
"pg-protocol": ["pg-protocol@1.10.0", "", {}, "sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q=="],
"pg-types": ["pg-types@4.0.2", "", { "dependencies": { "pg-int8": "1.0.1", "pg-numeric": "1.0.2", "postgres-array": "~3.0.1", "postgres-bytea": "~3.0.0", "postgres-date": "~2.1.0", "postgres-interval": "^3.0.0", "postgres-range": "^1.1.1" } }, "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng=="],
"pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
"pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="],
"postgres-array": ["postgres-array@3.0.4", "", {}, "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ=="],
"postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
"postgres-bytea": ["postgres-bytea@3.0.0", "", { "dependencies": { "obuf": "~1.1.2" } }, "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw=="],
"postgres-bytea": ["postgres-bytea@1.0.0", "", {}, "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="],
"postgres-date": ["postgres-date@2.1.0", "", {}, "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA=="],
"postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="],
"postgres-interval": ["postgres-interval@3.0.0", "", {}, "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw=="],
"postgres-range": ["postgres-range@1.1.4", "", {}, "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w=="],
"postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
@@ -264,9 +262,7 @@
"shallowequal": ["shallowequal@1.1.0", "", {}, "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="],
"sharp": ["sharp@0.34.2", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.4", "semver": "^7.7.2" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.2", "@img/sharp-darwin-x64": "0.34.2", "@img/sharp-libvips-darwin-arm64": "1.1.0", "@img/sharp-libvips-darwin-x64": "1.1.0", "@img/sharp-libvips-linux-arm": "1.1.0", "@img/sharp-libvips-linux-arm64": "1.1.0", "@img/sharp-libvips-linux-ppc64": "1.1.0", "@img/sharp-libvips-linux-s390x": "1.1.0", "@img/sharp-libvips-linux-x64": "1.1.0", "@img/sharp-libvips-linuxmusl-arm64": "1.1.0", "@img/sharp-libvips-linuxmusl-x64": "1.1.0", "@img/sharp-linux-arm": "0.34.2", "@img/sharp-linux-arm64": "0.34.2", "@img/sharp-linux-s390x": "0.34.2", "@img/sharp-linux-x64": "0.34.2", "@img/sharp-linuxmusl-arm64": "0.34.2", "@img/sharp-linuxmusl-x64": "0.34.2", "@img/sharp-wasm32": "0.34.2", "@img/sharp-win32-arm64": "0.34.2", "@img/sharp-win32-ia32": "0.34.2", "@img/sharp-win32-x64": "0.34.2" } }, "sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg=="],
"simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="],
"sharp": ["sharp@0.34.4", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.0", "semver": "^7.7.2" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.4", "@img/sharp-darwin-x64": "0.34.4", "@img/sharp-libvips-darwin-arm64": "1.2.3", "@img/sharp-libvips-darwin-x64": "1.2.3", "@img/sharp-libvips-linux-arm": "1.2.3", "@img/sharp-libvips-linux-arm64": "1.2.3", "@img/sharp-libvips-linux-ppc64": "1.2.3", "@img/sharp-libvips-linux-s390x": "1.2.3", "@img/sharp-libvips-linux-x64": "1.2.3", "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", "@img/sharp-libvips-linuxmusl-x64": "1.2.3", "@img/sharp-linux-arm": "0.34.4", "@img/sharp-linux-arm64": "0.34.4", "@img/sharp-linux-ppc64": "0.34.4", "@img/sharp-linux-s390x": "0.34.4", "@img/sharp-linux-x64": "0.34.4", "@img/sharp-linuxmusl-arm64": "0.34.4", "@img/sharp-linuxmusl-x64": "0.34.4", "@img/sharp-wasm32": "0.34.4", "@img/sharp-win32-arm64": "0.34.4", "@img/sharp-win32-ia32": "0.34.4", "@img/sharp-win32-x64": "0.34.4" } }, "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA=="],
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
@@ -280,8 +276,6 @@
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"uint8array-extras": ["uint8array-extras@1.4.0", "", {}, "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ=="],
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
@@ -294,7 +288,7 @@
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
"pg/pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
"pg/pg-protocol": ["pg-protocol@1.10.3", "", {}, "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
@@ -339,13 +333,5 @@
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
"pg/pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
"pg/pg-types/postgres-bytea": ["postgres-bytea@1.0.0", "", {}, "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="],
"pg/pg-types/postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="],
"pg/pg-types/postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
}
}

View File

@@ -10,23 +10,23 @@
},
"dependencies": {
"@elysiajs/swagger": "zoriya/elysia-swagger#build",
"@types/bun": "^1.3.1",
"blurhash": "^2.0.5",
"drizzle-kit": "^0.31.1",
"drizzle-orm": "0.43.1",
"elysia": "^1.3.1",
"jose": "^6.0.11",
"drizzle-kit": "^0.31.5",
"drizzle-orm": "0.44.7",
"elysia": "^1.4.13",
"jose": "^6.1.0",
"node-addon-api": "^8.5.0",
"parjs": "^1.3.9",
"pg": "^8.16.0",
"sharp": "^0.34.2"
"pg": "^8.16.3",
"sharp": "^0.34.4"
},
"devDependencies": {
"@biomejs/biome": "2.1.1",
"@types/pg": "^8.15.2",
"bun-types": "^1.2.14",
"node-addon-api": "^8.3.1"
"@biomejs/biome": "2.3.0",
"@types/pg": "^8.15.5"
},
"module": "src/index.js",
"patchedDependencies": {
"drizzle-orm@0.43.1": "patches/drizzle-orm@0.43.1.patch"
"drizzle-orm@0.44.7": "patches/drizzle-orm@0.44.7.patch"
}
}

View File

@@ -1,8 +1,8 @@
diff --git a/pg-core/dialect.cjs b/pg-core/dialect.cjs
index a0ef03142f21d319376bc50070ff7fdcd4d18132..45fc94e5a7c3fa4c201e636dd227122164e1bd02 100644
index d776a1fb503f35b5e53c6d3c1c086efa8230ea94..86541bf408e4955029c65be59d7b8ec98bb6e914 100644
--- a/pg-core/dialect.cjs
+++ b/pg-core/dialect.cjs
@@ -348,7 +348,14 @@ class PgDialect {
@@ -347,7 +347,14 @@ class PgDialect {
buildInsertQuery({ table, values: valuesOrSelect, onConflict, returning, withList, select, overridingSystemValue_ }) {
const valuesSqlList = [];
const columns = table[import_table2.Table.Symbol.Columns];
@@ -19,30 +19,30 @@ index a0ef03142f21d319376bc50070ff7fdcd4d18132..45fc94e5a7c3fa4c201e636dd2271221
([, column]) => import_sql2.sql.identifier(this.casing.getColumnCasing(column))
);
diff --git a/pg-core/dialect.js b/pg-core/dialect.js
index 120aaed9c3e4ae0a24653893379b98506c866f6f..48df463c0a6d5864fe2c324c8f86432860e50e00 100644
index 74a16c9e86fe3a89ced32af44af0a72f1cf43cf6..08f820d46a040c315fed40b8b36d94d094174425 100644
--- a/pg-core/dialect.js
+++ b/pg-core/dialect.js
@@ -346,7 +346,14 @@ class PgDialect {
@@ -345,7 +345,14 @@ class PgDialect {
buildInsertQuery({ table, values: valuesOrSelect, onConflict, returning, withList, select, overridingSystemValue_ }) {
const valuesSqlList = [];
const columns = table[Table.Symbol.Columns];
- const colEntries = Object.entries(columns).filter(([_, col]) => !col.shouldDisableInsert());
+ let colEntries = Object.entries(columns);
+ colEntries = select && !is(valuesOrSelect, SQL)
+ ? Object
+ .keys(valuesOrSelect._.selectedFields)
+ .map((key) => [key, columns[key]])
+ : overridingSystemValue_
+ ? colEntries
+ : colEntries.filter(([_, col]) => !col.shouldDisableInsert());
+ let colEntries = Object.entries(columns);
+ colEntries = select && !is(valuesOrSelect, SQL)
+ ? Object
+ .keys(valuesOrSelect._.selectedFields)
+ .map((key) => [key, columns[key]])
+ : overridingSystemValue_
+ ? colEntries
+ : colEntries.filter(([_, col]) => !col.shouldDisableInsert());
const insertOrder = colEntries.map(
([, column]) => sql.identifier(this.casing.getColumnCasing(column))
);
diff --git a/pg-core/query-builders/insert.cjs b/pg-core/query-builders/insert.cjs
index 08bb0d7485ebf997e3f081e2254ea8fd8bc20f65..20c8036374a1f25f7c5880c40e8d3c42c05f3eee 100644
index 22e0a4b4ad7ac64b065fc540416c6ed15ff4336b..fd590a6a3feb48c9f1894a0619b09ca6a5d22a5c 100644
--- a/pg-core/query-builders/insert.cjs
+++ b/pg-core/query-builders/insert.cjs
@@ -75,11 +75,6 @@ class PgInsertBuilder {
@@ -76,11 +76,6 @@ class PgInsertBuilder {
}
select(selectQuery) {
const select = typeof selectQuery === "function" ? selectQuery(new import_query_builder.QueryBuilder()) : selectQuery;
@@ -55,10 +55,10 @@ index 08bb0d7485ebf997e3f081e2254ea8fd8bc20f65..20c8036374a1f25f7c5880c40e8d3c42
}
}
diff --git a/pg-core/query-builders/insert.js b/pg-core/query-builders/insert.js
index 0fc8eeb80f4a5512f6c84f3d596832623a33b748..998e2ab0bfe3f322bf268a01f71ebd06c57d4d07 100644
index 60a8bb0d1c22b890bd8fbf4c85d5df41ca42444c..8754d0f2923f905816016c42f339c3e9097b4128 100644
--- a/pg-core/query-builders/insert.js
+++ b/pg-core/query-builders/insert.js
@@ -51,11 +51,6 @@ class PgInsertBuilder {
@@ -52,11 +52,6 @@ class PgInsertBuilder {
}
select(selectQuery) {
const select = typeof selectQuery === "function" ? selectQuery(new QueryBuilder()) : selectQuery;

View File

@@ -18,6 +18,16 @@ import type { KError } from "./models/error";
export const base = new Elysia({ name: "base" })
.onError(({ code, error }) => {
// sometimes elysia as an unknown code when throwing errors
if (code === "UNKNOWN") {
try {
const details = JSON.parse(error.message);
if (details?.code === "KError") {
const { code, ...ret } = details;
return ret;
}
} catch {}
}
if (code === "VALIDATION") {
const details = JSON.parse(error.message);
if (details.code === "KError") {
@@ -34,25 +44,21 @@ export const base = new Elysia({ name: "base" })
details: details,
} as KError;
}
if (code === "INTERNAL_SERVER_ERROR") {
console.error(error);
return {
status: 500,
message: error.message,
details: error,
} as KError;
}
if (code === "NOT_FOUND") {
return error;
}
console.error(code, error);
return error;
return {
status: 500,
message: "message" in error ? (error?.message ?? code) : code,
details: error,
} as KError;
})
.get("/health", () => ({ status: "healthy" }) as const, {
detail: { description: "Check if the api is healthy." },
response: { 200: t.Object({ status: t.Literal("healthy") }) },
})
.as("scoped");
.as("global");
export const prefix = "/api";
export const handlers = new Elysia({ prefix })

View File

@@ -148,8 +148,13 @@ export const jsonbBuildObject = <T>(select: JsonFields) => {
};
export const isUniqueConstraint = (e: unknown): boolean => {
if (typeof e !== "object" || !e || !("cause" in e)) return false;
const cause = e.cause;
return (
typeof e === "object" && e != null && "code" in e && e.code === "23505"
typeof cause === "object" &&
cause != null &&
"code" in cause &&
cause.code === "23505"
);
};

View File

@@ -55,6 +55,7 @@ export const Sort = (
),
)
.Decode((sort: string[]): Sort => {
if (!Array.isArray(sort)) sort = [sort];
const random = sort.find((x) => x.startsWith("random"));
if (random) {
const seed = random.includes(":")

View File

@@ -1,18 +0,0 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "8.0.21",
"commands": [
"dotnet-ef"
]
},
"csharpier": {
"version": "0.28.2",
"commands": [
"dotnet-csharpier"
]
}
}
}

View File

@@ -1,17 +0,0 @@
Dockerfile
Dockerfile.dev
Dockerfile.*
.dockerignore
.gitignore
docker-compose.yml
README.md
**/build
**/dist
**/bin
**/obj
out
docs
tests
front
video
nginx.conf.template

View File

@@ -1,99 +0,0 @@
root = false
[*]
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = tab
indent_size = tab
smart_tab = true
[*.cs]
csharp_prefer_braces = false
dotnet_diagnostic.IDE0046.severity = none
dotnet_diagnostic.IDE0055.severity = none
dotnet_diagnostic.IDE0058.severity = none
dotnet_diagnostic.IDE0130.severity = none
# Convert to file-scoped namespace
csharp_style_namespace_declarations = file_scoped:warning
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
csharp_using_directive_placement = outside_namespace:warning
# Avoid "this." if not necessary
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion
# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# Suggest more modern language features when available
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
dotnet_style_prefer_auto_properties = true
dotnet_style_prefer_conditional_expression_over_assignment = true
dotnet_style_prefer_conditional_expression_over_return = true
# Disable strange throw.
csharp_style_throw_expression = false:suggestion
# Forbid "var" everywhere
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = false:suggestion
csharp_style_var_elsewhere = false:suggestion
# Prefer method-like constructs to have a block body
csharp_style_expression_bodied_methods = false:none
csharp_style_expression_bodied_constructors = false:none
csharp_style_expression_bodied_operators = false:none
# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_properties = true:none
csharp_style_expression_bodied_indexers = true:none
csharp_style_expression_bodied_accessors = true:none
# Newline settings
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = false
csharp_new_line_before_members_in_anonymous_types = true
# Indentation settings
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
# Modifiers
dotnet_style_readonly_field = true:suggestion
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
# Naming style
dotnet_naming_symbols.privates.applicable_kinds = property,method,event,delegate
dotnet_naming_symbols.privates.applicable_accessibilities = private
dotnet_naming_style.underscore_pascal.capitalization = pascal_case
dotnet_naming_style.underscore_pascal.required_prefix = _
dotnet_naming_rule.privates_with_underscore.symbols = privates
dotnet_naming_rule.privates_with_underscore.style = underscore_pascal
dotnet_naming_rule.privates_with_underscore.severity = warning
dotnet_diagnostic.IDE1006.severity = warning
# ReSharper properties
resharper_align_multiline_binary_expressions_chain = false
resharper_csharp_empty_block_style = together_same_line
resharper_indent_nested_foreach_stmt = true
resharper_indent_nested_for_stmt = true
resharper_indent_nested_while_stmt = true
resharper_keep_existing_embedded_arrangement = false
resharper_place_accessorholder_attribute_on_same_line = true
resharper_place_simple_embedded_statement_on_same_line = false
resharper_wrap_before_arrow_with_expressions = true
resharper_xmldoc_attribute_indent = align_by_first_attribute
resharper_xmldoc_indent_child_elements = RemoveIndent
resharper_xmldoc_indent_text = RemoveIndent
# Switch on enum
dotnet_diagnostic.CS8509.severity=error # missing switch case for named enum value
dotnet_diagnostic.CS8524.severity=none # missing switch case for unnamed enum value
# Waiting for https://github.com/dotnet/roslyn/issues/44596 to get fixed.
# file_header_template = Kyoo - A portable and vast media library solution.\nCopyright (c) Kyoo.\n\nSee AUTHORS.md and LICENSE file in the project root for full license information.\n\nKyoo is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\nany later version.\n\nKyoo is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with Kyoo. If not, see <https://www.gnu.org/licenses/>.

View File

@@ -1,37 +0,0 @@
# vi: ft=sh
# shellcheck disable=SC2034
# http route prefix (will listen to $KYOO_PREFIX/movie for example)
KYOO_PREFIX=""
# Optional authentication settings
# Set to true to disable login with password (OIDC auth must be configured)
# AUTHENTICATION_DISABLE_PASSWORD_LOGIN=true
# Set to true to disable the creation of new users (OIDC auth must be configured)
# AUTHENTICATION_DISABLE_USER_REGISTRATION=true
# Postgres settings
# POSTGRES_URL=postgres://user:password@hostname:port/dbname?sslmode=verify-full&sslrootcert=/path/to/server.crt&sslcert=/path/to/client.crt&sslkey=/path/to/client.key
# The behavior of the below variables match what is documented here:
# https://www.postgresql.org/docs/current/libpq-envars.html
PGUSER=kyoo
PGPASSWORD=password
PGDB=kyooDB
PGSERVER=postgres
PGPORT=5432
# PGOPTIONS=-c search_path=kyoo,public
# PGPASSFILE=/my/password # Takes precedence over PGPASSWORD. New line characters are not trimmed.
# PGSSLMODE=verify-full
# PGSSLROOTCERT=/my/serving.crt
# PGSSLCERT=/my/client.crt
# PGSSLKEY=/my/client.key
# RabbitMQ settings
# Full list of options: https://www.rabbitmq.com/uri-spec.html, https://www.rabbitmq.com/docs/uri-query-parameters
# RABBITMQ_URL=amqps://user:password@rabbitmq-server:1234/vhost?cacertfile=/path/to/cacert.pem&certfile=/path/to/cert.pem&keyfile=/path/to/key.pem&verify=verify_peer&auth_mechanism=EXTERNAL
# These values override what is provided the the URL variable
RABBITMQ_DEFAULT_USER=guest
RABBITMQ_DEFAULT_PASS=guest
RABBITMQ_HOST=rabbitmq
RABBITMQ_PORT=5672

352
back/.gitignore vendored
View File

@@ -1,352 +0,0 @@
out
libtranscoder.so
libtranscoder.dylib
transcoder.dll
kyoo_datadir
video
.env
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- Backup*.rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
/Kyoo/TheTVDB-Credentials.json
.vscode
.netcoredbg_hist

View File

@@ -1,29 +0,0 @@
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 as builder
ARG TARGETARCH
WORKDIR /kyoo
COPY Kyoo.sln ./Kyoo.sln
COPY nuget.config ./nuget.config
COPY src/Directory.Build.props src/Directory.Build.props
COPY src/Kyoo.Authentication/Kyoo.Authentication.csproj src/Kyoo.Authentication/Kyoo.Authentication.csproj
COPY src/Kyoo.Abstractions/Kyoo.Abstractions.csproj src/Kyoo.Abstractions/Kyoo.Abstractions.csproj
COPY src/Kyoo.Core/Kyoo.Core.csproj src/Kyoo.Core/Kyoo.Core.csproj
COPY src/Kyoo.Postgresql/Kyoo.Postgresql.csproj src/Kyoo.Postgresql/Kyoo.Postgresql.csproj
COPY src/Kyoo.Meilisearch/Kyoo.Meilisearch.csproj src/Kyoo.Meilisearch/Kyoo.Meilisearch.csproj
COPY src/Kyoo.RabbitMq/Kyoo.RabbitMq.csproj src/Kyoo.RabbitMq/Kyoo.RabbitMq.csproj
COPY src/Kyoo.Swagger/Kyoo.Swagger.csproj src/Kyoo.Swagger/Kyoo.Swagger.csproj
RUN dotnet restore -a $TARGETARCH
COPY . .
ARG VERSION
RUN dotnet publish -a $TARGETARCH --no-restore -c Release -o /app "-p:Version=${VERSION:-"0.0.0-dev"}" src/Kyoo.Core
FROM mcr.microsoft.com/dotnet/aspnet:8.0
RUN apt-get update && apt-get install -y curl
COPY --from=builder /app /app
WORKDIR /app
EXPOSE 5000
# The back can take a long time to start if meilisearch is initializing
HEALTHCHECK --interval=30s --retries=15 CMD curl --fail http://localhost:5000/health || exit
ENTRYPOINT ["/app/kyoo"]

View File

@@ -1,22 +0,0 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0
RUN apt-get update && apt-get install -y curl
WORKDIR /app
COPY Kyoo.sln ./Kyoo.sln
COPY nuget.config ./nuget.config
COPY src/Directory.Build.props src/Directory.Build.props
COPY src/Kyoo.Authentication/Kyoo.Authentication.csproj src/Kyoo.Authentication/Kyoo.Authentication.csproj
COPY src/Kyoo.Abstractions/Kyoo.Abstractions.csproj src/Kyoo.Abstractions/Kyoo.Abstractions.csproj
COPY src/Kyoo.Core/Kyoo.Core.csproj src/Kyoo.Core/Kyoo.Core.csproj
COPY src/Kyoo.Postgresql/Kyoo.Postgresql.csproj src/Kyoo.Postgresql/Kyoo.Postgresql.csproj
COPY src/Kyoo.Meilisearch/Kyoo.Meilisearch.csproj src/Kyoo.Meilisearch/Kyoo.Meilisearch.csproj
COPY src/Kyoo.RabbitMq/Kyoo.RabbitMq.csproj src/Kyoo.RabbitMq/Kyoo.RabbitMq.csproj
COPY src/Kyoo.Swagger/Kyoo.Swagger.csproj src/Kyoo.Swagger/Kyoo.Swagger.csproj
RUN dotnet restore
WORKDIR /app
EXPOSE 5000
ENV DOTNET_USE_POLLING_FILE_WATCHER 1
# HEALTHCHECK --interval=30s CMD curl --fail http://localhost:5000/health || exit
HEALTHCHECK CMD true
ENTRYPOINT ["dotnet", "watch", "--non-interactive", "run", "--no-restore", "--project", "/app/src/Kyoo.Core"]

View File

@@ -1,30 +0,0 @@
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 as builder
ARG TARGETARCH
WORKDIR /kyoo
COPY .config/dotnet-tools.json .config/dotnet-tools.json
RUN dotnet tool restore
COPY Kyoo.sln ./Kyoo.sln
COPY nuget.config ./nuget.config
COPY src/Directory.Build.props src/Directory.Build.props
COPY src/Kyoo.Authentication/Kyoo.Authentication.csproj src/Kyoo.Authentication/Kyoo.Authentication.csproj
COPY src/Kyoo.Abstractions/Kyoo.Abstractions.csproj src/Kyoo.Abstractions/Kyoo.Abstractions.csproj
COPY src/Kyoo.Core/Kyoo.Core.csproj src/Kyoo.Core/Kyoo.Core.csproj
COPY src/Kyoo.Postgresql/Kyoo.Postgresql.csproj src/Kyoo.Postgresql/Kyoo.Postgresql.csproj
COPY src/Kyoo.Meilisearch/Kyoo.Meilisearch.csproj src/Kyoo.Meilisearch/Kyoo.Meilisearch.csproj
COPY src/Kyoo.RabbitMq/Kyoo.RabbitMq.csproj src/Kyoo.RabbitMq/Kyoo.RabbitMq.csproj
COPY src/Kyoo.Swagger/Kyoo.Swagger.csproj src/Kyoo.Swagger/Kyoo.Swagger.csproj
RUN dotnet restore -a $TARGETARCH
COPY . .
RUN dotnet build
RUN dotnet ef migrations bundle \
--msbuildprojectextensionspath out/obj/Kyoo.Postgresql \
--no-build --self-contained -r linux-${TARGETARCH} -f \
-o /app/migrate -p src/Kyoo.Postgresql --verbose
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0
COPY --from=builder /app/migrate /app/migrate
ENTRYPOINT ["/app/migrate"]

View File

@@ -1,47 +0,0 @@
<RuleSet Name="Kyoo" ToolsVersion="10.0">
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.MaintainabilityRules">
<Rule Id="SA1413" Action="None" /> <!-- UseTrailingCommasInMultiLineInitializers -->
<Rule Id="SA1414" Action="None" /> <!-- UseTrailingCommasInMultiLineInitializers -->
<Rule Id="SA1114" Action="None" /> <!-- UseTrailingCommasInMultiLineInitializers -->
</Rules>
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.OrderingRules">
<Rule Id="SA1201" Action="None" /> <!-- ElementsMustAppearInTheCorrectOrder -->
<Rule Id="SA1202" Action="None" /> <!-- ElementsMustBeOrderedByAccess -->
</Rules>
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.NamingRules">
<Rule Id="SA1309" Action="None" /> <!-- FieldNamesMustNotBeginWithUnderscore -->
<Rule Id="SX1309" Action="Warning" /> <!-- FieldNamesMustBeginWithUnderscore -->
<Rule Id="SA1300" Action="None" /> <!-- ElementMustBeginWithUpperCaseLetter (this conflict with the _ prefix for privates, enforced by an IDE rule) -->
<Rule Id="SA1316" Action="None" /> <!-- TupleElementNamesShouldUseCorrectCasing (should be camels when deconstructing but pascal otherwise. -->
</Rules>
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.ReadabilityRules">
<Rule Id="SA1101" Action="None" /> <!-- PrefixLocalCallsWithThis -->
<Rule Id="SX1101" Action="Warning" /> <!-- DoNotPrefixLocalMembersWithThis -->
<Rule Id="SA1134" Action="None" /> <!-- AttributesMustNotShareLine -->
<Rule Id="SA1117" Action="None" /> <!-- ParametersMustBeOnSameLineOrSeparateLines -->
<Rule Id="SA1116" Action="None" /> <!-- SplitParametersMustStartOnLineAfterDeclaration -->
<Rule Id="SA1111" Action="None" /> <!-- ClosingParenthesisMustBeOnLineOfLastParameter -->
<Rule Id="SA1009" Action="None" /> <!-- ClosingParenthesisMustBeSpacedCorrectly (bugged if the parenthesis is on a line by iteself) -->
</Rules>
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.SpacingRules">
<Rule Id="SA1502" Action="None"/> <!-- DocumentationLinesMustBeginWithSingleSpace -->
<Rule Id="SA1027" Action="None"/> <!-- UseTabsCorrectly (smarts tabs are broken). TODO find a way to enable smart tabs -->
</Rules>
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.LayoutRules">
<Rule Id="SA1402" Action="None"/> <!-- SingleClassPerFile -->
<Rule Id="SA1649" Action="None"/> <!-- Class name must be filename -->
<Rule Id="SA1503" Action="None"/> <!-- BracesMustNotBeOmitted -->
<Rule Id="SA1512" Action="None"/> <!-- Let me comment -->
<Rule Id="SA1520" Action="None"/> <!-- UseBracesConsistently -->
<Rule Id="SA1515" Action="None"/> <!-- SingleLineCommentMustBePrecededByBlankLine -->
<Rule Id="SA1513" Action="None"/> <!-- ClosingBraceMustBeFollowedByBlankLine -->
</Rules>
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.DocumentationRules">
<Rule Id="SA1600" Action="None" /> <!-- Elements Shuld be Documented -->
<Rule Id="SA1602" Action="None" /> <!-- Enums should be documented -->
<Rule Id="SA1642" Action="None" /> <!-- ConstructorSummaryDocumentationMustBeginWithStandardText -->
<Rule Id="SA1643" Action="None" /> <!-- DestructorSummaryDocumentationMustBeginWithStandardText -->
<Rule Id="SA1623" Action="None" /> <!-- PropertySummaryDocumentationMustMatchAccessors -->
<Rule Id="SA1629" Action="None" /> <!-- DocumentationTextMustEndWithAPeriod -->
</Rules>
</RuleSet>

View File

@@ -1,64 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
#
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kyoo.Core", "src\Kyoo.Core\Kyoo.Core.csproj", "{0F8275B6-C7DD-42DF-A168-755C81B1C329}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Abstractions", "src\Kyoo.Abstractions\Kyoo.Abstractions.csproj", "{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Postgresql", "src\Kyoo.Postgresql\Kyoo.Postgresql.csproj", "{3213C96D-0BF3-460B-A8B5-B9977229408A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Authentication", "src\Kyoo.Authentication\Kyoo.Authentication.csproj", "{7A841335-6523-47DB-9717-80AA7BD943FD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Swagger", "src\Kyoo.Swagger\Kyoo.Swagger.csproj", "{7D1A7596-73F6-4D35-842E-A5AD9C620596}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Meilisearch", "src\Kyoo.Meilisearch\Kyoo.Meilisearch.csproj", "{F8E6018A-FD51-40EB-99FF-A26BA59F2762}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.RabbitMq", "src\Kyoo.RabbitMq\Kyoo.RabbitMq.csproj", "{B97AD4A8-E6E6-41CD-87DF-5F1326FD7198}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Release|Any CPU.Build.0 = Release|Any CPU
{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}.Release|Any CPU.Build.0 = Release|Any CPU
{3213C96D-0BF3-460B-A8B5-B9977229408A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3213C96D-0BF3-460B-A8B5-B9977229408A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3213C96D-0BF3-460B-A8B5-B9977229408A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3213C96D-0BF3-460B-A8B5-B9977229408A}.Release|Any CPU.Build.0 = Release|Any CPU
{7A841335-6523-47DB-9717-80AA7BD943FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7A841335-6523-47DB-9717-80AA7BD943FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7A841335-6523-47DB-9717-80AA7BD943FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7A841335-6523-47DB-9717-80AA7BD943FD}.Release|Any CPU.Build.0 = Release|Any CPU
{6515380E-1E57-42DA-B6E3-E1C8A848818A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6515380E-1E57-42DA-B6E3-E1C8A848818A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6515380E-1E57-42DA-B6E3-E1C8A848818A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6515380E-1E57-42DA-B6E3-E1C8A848818A}.Release|Any CPU.Build.0 = Release|Any CPU
{2374D500-1ADB-4752-85DB-8BB0DDF5A8E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2374D500-1ADB-4752-85DB-8BB0DDF5A8E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2374D500-1ADB-4752-85DB-8BB0DDF5A8E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2374D500-1ADB-4752-85DB-8BB0DDF5A8E8}.Release|Any CPU.Build.0 = Release|Any CPU
{4FF1ECD9-6EEF-4440-B037-A661D78FB04D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4FF1ECD9-6EEF-4440-B037-A661D78FB04D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FF1ECD9-6EEF-4440-B037-A661D78FB04D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FF1ECD9-6EEF-4440-B037-A661D78FB04D}.Release|Any CPU.Build.0 = Release|Any CPU
{7D1A7596-73F6-4D35-842E-A5AD9C620596}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7D1A7596-73F6-4D35-842E-A5AD9C620596}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7D1A7596-73F6-4D35-842E-A5AD9C620596}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7D1A7596-73F6-4D35-842E-A5AD9C620596}.Release|Any CPU.Build.0 = Release|Any CPU
{F8E6018A-FD51-40EB-99FF-A26BA59F2762}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F8E6018A-FD51-40EB-99FF-A26BA59F2762}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F8E6018A-FD51-40EB-99FF-A26BA59F2762}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F8E6018A-FD51-40EB-99FF-A26BA59F2762}.Release|Any CPU.Build.0 = Release|Any CPU
{B97AD4A8-E6E6-41CD-87DF-5F1326FD7198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B97AD4A8-E6E6-41CD-87DF-5F1326FD7198}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B97AD4A8-E6E6-41CD-87DF-5F1326FD7198}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B97AD4A8-E6E6-41CD-87DF-5F1326FD7198}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -1,4 +0,0 @@
--project
src/Kyoo.Postgresql
--msbuildprojectextensionspath
out/obj/Kyoo.Postgresql

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

View File

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear/>
<add key="feedz" value="https://f.feedz.io/zoriya/entityframeworkcore-projectables/nuget/index.json"/>
<add key="nuget" value="https://api.nuget.org/v3/index.json"/>
</packageSources>
<disabledPackageSources>
<clear/>
</disabledPackageSources>
<fallbackPackageFolders>
<clear/>
</fallbackPackageFolders>
</configuration>

View File

@@ -1,15 +0,0 @@
{pkgs ? import <nixpkgs> {}}: let
dotnet = with pkgs.dotnetCorePackages;
combinePackages [
sdk_8_0
aspnetcore_8_0
];
in
pkgs.mkShell {
packages = with pkgs; [
dotnet
csharpier
];
DOTNET_ROOT = "${dotnet}";
}

View File

@@ -1,47 +0,0 @@
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>default</LangVersion>
<Nullable>enable</Nullable>
<Company>Kyoo</Company>
<Authors>Kyoo</Authors>
<Copyright>Copyright (c) Kyoo</Copyright>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageLicenseExpression>GPL-3.0-or-later</PackageLicenseExpression>
<RequireLicenseAcceptance>true</RequireLicenseAcceptance>
<RepositoryUrl>https://github.com/zoriya/Kyoo</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<PackageProjectUrl>https://github.com/zoriya/Kyoo</PackageProjectUrl>
<PackageVersion>1.0.0</PackageVersion>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<ApplicationIcon>$(MSBuildThisFileDirectory)../icon.ico</ApplicationIcon>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<CheckCodingStyle Condition="$(CheckCodingStyle) == ''">true</CheckCodingStyle>
</PropertyGroup>
<PropertyGroup>
<BaseIntermediateOutputPath>$(MsBuildThisFileDirectory)/../out/obj/$(MSBuildProjectName)</BaseIntermediateOutputPath>
<BaseOutputPath>$(MsBuildThisFileDirectory)/../out/bin/$(MSBuildProjectName)</BaseOutputPath>
</PropertyGroup>
<ItemGroup Condition="$(CheckCodingStyle) == true">
<None Include="$(MSBuildThisFileDirectory)../.editorconfig" Link=".editorconfig" Visible="false" />
</ItemGroup>
<PropertyGroup Condition="$(CheckCodingStyle) == true">
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)../Kyoo.ruleset</CodeAnalysisRuleSet>
<NoWarn>1591;1305;8618;SYSLIB1045;CS1573</NoWarn>
<!-- <AnalysisMode>All</AnalysisMode> -->
</PropertyGroup>
<!-- vim: ft=xml -->
</Project>

View File

@@ -1,232 +0,0 @@
## PROJECT CUSTOM IGNORES
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
bin/
Bin/
obj/
Obj/
# Visual Studio 2015 cache/options directory
.vs/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Microsoft Azure ApplicationInsights config file
ApplicationInsights.config
# Windows Store app package directory
AppPackages/
BundleArtifacts/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
orleans.codegen.cs
/node_modules
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
# FAKE - F# Make
.fake/

View File

@@ -1,35 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Collections.Generic;
using System.Threading.Tasks;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Utils;
namespace Kyoo.Abstractions.Controllers;
public interface IIssueRepository
{
Task<ICollection<Issue>> GetAll(Filter<Issue>? filter = default);
Task<int> GetCount(Filter<Issue>? filter = default);
Task<Issue> Upsert(Issue issue);
Task DeleteAll(Filter<Issue>? filter = default);
}

View File

@@ -1,80 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using Kyoo.Abstractions.Models;
namespace Kyoo.Abstractions.Controllers;
/// <summary>
/// An interface to interact with the database. Every repository is mapped through here.
/// </summary>
public interface ILibraryManager
{
IRepository<T> Repository<T>()
where T : IResource, IQuery;
/// <summary>
/// The repository that handle libraries items (a wrapper around shows and collections).
/// </summary>
IRepository<ILibraryItem> LibraryItems { get; }
/// <summary>
/// The repository that handle new items.
/// </summary>
IRepository<INews> News { get; }
/// <summary>
/// The repository that handle watched items.
/// </summary>
IWatchStatusRepository WatchStatus { get; }
/// <summary>
/// The repository that handle collections.
/// </summary>
IRepository<Collection> Collections { get; }
/// <summary>
/// The repository that handle shows.
/// </summary>
IRepository<Movie> Movies { get; }
/// <summary>
/// The repository that handle shows.
/// </summary>
IRepository<Show> Shows { get; }
/// <summary>
/// The repository that handle seasons.
/// </summary>
IRepository<Season> Seasons { get; }
/// <summary>
/// The repository that handle episodes.
/// </summary>
IRepository<Episode> Episodes { get; }
/// <summary>
/// The repository that handle studios.
/// </summary>
IRepository<Studio> Studios { get; }
/// <summary>
/// The repository that handle users.
/// </summary>
IRepository<User> Users { get; }
}

View File

@@ -1,46 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using Kyoo.Abstractions.Models.Permissions;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Kyoo.Abstractions.Controllers;
/// <summary>
/// A service to validate permissions.
/// </summary>
public interface IPermissionValidator
{
/// <summary>
/// Create an IAuthorizationFilter that will be used to validate permissions.
/// This can registered with any lifetime.
/// </summary>
/// <param name="attribute">The permission attribute to validate.</param>
/// <returns>An authorization filter used to validate the permission.</returns>
IFilterMetadata Create(PermissionAttribute attribute);
/// <summary>
/// Create an IAuthorizationFilter that will be used to validate permissions.
/// This can registered with any lifetime.
/// </summary>
/// <param name="attribute">
/// A partial attribute to validate. See <see cref="PartialPermissionAttribute"/>.
/// </param>
/// <returns>An authorization filter used to validate the permission.</returns>
IFilterMetadata Create(PartialPermissionAttribute attribute);
}

View File

@@ -1,267 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Abstractions.Models.Utils;
namespace Kyoo.Abstractions.Controllers;
/// <summary>
/// A common repository for every resources.
/// </summary>
/// <typeparam name="T">The resource's type that this repository manage.</typeparam>
public interface IRepository<T> : IBaseRepository
where T : IResource, IQuery
{
/// <summary>
/// The event handler type for all events of this repository.
/// </summary>
/// <param name="resource">The resource created/modified/deleted</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public delegate Task ResourceEventHandler(T resource);
/// <summary>
/// Get a resource from it's ID.
/// </summary>
/// <param name="id">The id of the resource</param>
/// <param name="include">The related fields to include.</param>
/// <exception cref="ItemNotFoundException">If the item could not be found.</exception>
/// <returns>The resource found</returns>
Task<T> Get(Guid id, Include<T>? include = default);
/// <summary>
/// Get a resource from it's slug.
/// </summary>
/// <param name="slug">The slug of the resource</param>
/// <param name="include">The related fields to include.</param>
/// <exception cref="ItemNotFoundException">If the item could not be found.</exception>
/// <returns>The resource found</returns>
Task<T> Get(string slug, Include<T>? include = default);
/// <summary>
/// Get the first resource that match the predicate.
/// </summary>
/// <param name="filter">A predicate to filter the resource.</param>
/// <param name="include">The related fields to include.</param>
/// <param name="sortBy">A custom sort method to handle cases where multiples items match the filters.</param>
/// <param name="reverse">Reverse the sort.</param>
/// <param name="afterId">Select the first element after this id if it was in a list.</param>
/// <exception cref="ItemNotFoundException">If the item could not be found.</exception>
/// <returns>The resource found</returns>
Task<T> Get(
Filter<T> filter,
Include<T>? include = default,
Sort<T>? sortBy = default,
bool reverse = false,
Guid? afterId = default
);
/// <summary>
/// Get a resource from it's ID or null if it is not found.
/// </summary>
/// <param name="id">The id of the resource</param>
/// <param name="include">The related fields to include.</param>
/// <returns>The resource found</returns>
Task<T?> GetOrDefault(Guid id, Include<T>? include = default);
/// <summary>
/// Get a resource from it's slug or null if it is not found.
/// </summary>
/// <param name="slug">The slug of the resource</param>
/// <param name="include">The related fields to include.</param>
/// <returns>The resource found</returns>
Task<T?> GetOrDefault(string slug, Include<T>? include = default);
/// <summary>
/// Get the first resource that match the predicate or null if it is not found.
/// </summary>
/// <param name="filter">A predicate to filter the resource.</param>
/// <param name="include">The related fields to include.</param>
/// <param name="sortBy">A custom sort method to handle cases where multiples items match the filters.</param>
/// <param name="reverse">Reverse the sort.</param>
/// <param name="afterId">Select the first element after this id if it was in a list.</param>
/// <returns>The resource found</returns>
Task<T?> GetOrDefault(
Filter<T>? filter,
Include<T>? include = default,
Sort<T>? sortBy = default,
bool reverse = false,
Guid? afterId = default
);
/// <summary>
/// Search for resources with the database.
/// </summary>
/// <param name="query">The query string.</param>
/// <param name="include">The related fields to include.</param>
/// <returns>A list of resources found</returns>
Task<ICollection<T>> Search(string query, Include<T>? include = default);
/// <summary>
/// Get every resources that match all filters
/// </summary>
/// <param name="filter">A filter predicate</param>
/// <param name="sort">Sort information about the query (sort by, sort order)</param>
/// <param name="include">The related fields to include.</param>
/// <param name="limit">How pagination should be done (where to start and how many to return)</param>
/// <returns>A list of resources that match every filters</returns>
Task<ICollection<T>> GetAll(
Filter<T>? filter = null,
Sort<T>? sort = default,
Include<T>? include = default,
Pagination? limit = default
);
/// <summary>
/// Get the number of resources that match the filter's predicate.
/// </summary>
/// <param name="filter">A filter predicate</param>
/// <returns>How many resources matched that filter</returns>
Task<int> GetCount(Filter<T>? filter = null);
/// <summary>
/// Map a list of ids to a list of items (keep the order).
/// </summary>
/// <param name="ids">The list of items id.</param>
/// <param name="include">The related fields to include.</param>
/// <returns>A list of resources mapped from ids.</returns>
Task<ICollection<T>> FromIds(IList<Guid> ids, Include<T>? include = default);
/// <summary>
/// Create a new resource.
/// </summary>
/// <param name="obj">The item to register</param>
/// <returns>The resource registers and completed by database's information (related items and so on)</returns>
Task<T> Create(T obj);
/// <summary>
/// Create a new resource if it does not exist already. If it does, the existing value is returned instead.
/// </summary>
/// <param name="obj">The object to create</param>
/// <returns>The newly created item or the existing value if it existed.</returns>
Task<T> CreateIfNotExists(T obj);
/// <summary>
/// Called when a resource has been created.
/// </summary>
static event ResourceEventHandler OnCreated;
/// <summary>
/// Callback that should be called after a resource has been created.
/// </summary>
/// <param name="obj">The resource newly created.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected static Task OnResourceCreated(T obj) => OnCreated?.Invoke(obj) ?? Task.CompletedTask;
/// <summary>
/// Edit a resource and replace every property
/// </summary>
/// <param name="edited">The resource to edit, it's ID can't change.</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The resource edited and completed by database's information (related items and so on)</returns>
Task<T> Edit(T edited);
/// <summary>
/// Edit only specific properties of a resource
/// </summary>
/// <param name="id">The id of the resource to edit</param>
/// <param name="patch">
/// A method that will be called when you need to update every properties that you want to
/// persist.
/// </param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The resource edited and completed by database's information (related items and so on)</returns>
Task<T> Patch(Guid id, Func<T, T> patch);
/// <summary>
/// Called when a resource has been edited.
/// </summary>
static event ResourceEventHandler OnEdited;
/// <summary>
/// Callback that should be called after a resource has been edited.
/// </summary>
/// <param name="obj">The resource newly edited.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected static Task OnResourceEdited(T obj) => OnEdited?.Invoke(obj) ?? Task.CompletedTask;
/// <summary>
/// Delete a resource by it's ID
/// </summary>
/// <param name="id">The ID of the resource</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task Delete(Guid id);
/// <summary>
/// Delete a resource by it's slug
/// </summary>
/// <param name="slug">The slug of the resource</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task Delete(string slug);
/// <summary>
/// Delete a resource
/// </summary>
/// <param name="obj">The resource to delete</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task Delete(T obj);
/// <summary>
/// Delete all resources that match the predicate.
/// </summary>
/// <param name="filter">A predicate to filter resources to delete. Every resource that match this will be deleted.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task DeleteAll(Filter<T> filter);
/// <summary>
/// Called when a resource has been edited.
/// </summary>
static event ResourceEventHandler OnDeleted;
/// <summary>
/// Callback that should be called after a resource has been deleted.
/// </summary>
/// <param name="obj">The resource newly deleted.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected static Task OnResourceDeleted(T obj) => OnDeleted?.Invoke(obj) ?? Task.CompletedTask;
}
/// <summary>
/// A base class for repositories. Every service implementing this will be handled by the <see cref="ILibraryManager"/>.
/// </summary>
public interface IBaseRepository
{
/// <summary>
/// The type for witch this repository is responsible or null if non applicable.
/// </summary>
Type RepositoryType { get; }
}
public interface IUserRepository : IRepository<User>
{
Task<User?> GetByExternalId(string provider, string id);
Task<User> AddExternalToken(Guid userId, string provider, ExternalToken token);
Task<User> DeleteExternalToken(Guid userId, string provider);
}

View File

@@ -1,28 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Threading.Tasks;
namespace Kyoo.Abstractions.Controllers;
public interface IScanner
{
Task SendRescanRequest();
Task SendRefreshRequest(string kind, Guid id);
}

View File

@@ -1,125 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Threading.Tasks;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Utils;
namespace Kyoo.Abstractions.Controllers;
/// <summary>
/// The service to search items.
/// </summary>
public interface ISearchManager
{
/// <summary>
/// Search for items.
/// </summary>
/// <param name="query">The seach query.</param>
/// <param name="sortBy">Sort information about the query (sort by, sort order)</param>
/// <param name="pagination">How pagination should be done (where to start and how many to return)</param>
/// <param name="include">The related fields to include.</param>
/// <returns>A list of resources that match every filters</returns>
public Task<SearchPage<ILibraryItem>.SearchResult> SearchItems(
string? query,
Sort<ILibraryItem> sortBy,
Filter<ILibraryItem>? filter,
SearchPagination pagination,
Include<ILibraryItem>? include = default
);
/// <summary>
/// Search for movies.
/// </summary>
/// <param name="query">The seach query.</param>
/// <param name="sortBy">Sort information about the query (sort by, sort order)</param>
/// <param name="pagination">How pagination should be done (where to start and how many to return)</param>
/// <param name="include">The related fields to include.</param>
/// <returns>A list of resources that match every filters</returns>
public Task<SearchPage<Movie>.SearchResult> SearchMovies(
string? query,
Sort<Movie> sortBy,
Filter<ILibraryItem>? filter,
SearchPagination pagination,
Include<Movie>? include = default
);
/// <summary>
/// Search for shows.
/// </summary>
/// <param name="query">The seach query.</param>
/// <param name="sortBy">Sort information about the query (sort by, sort order)</param>
/// <param name="pagination">How pagination should be done (where to start and how many to return)</param>
/// <param name="include">The related fields to include.</param>
/// <returns>A list of resources that match every filters</returns>
public Task<SearchPage<Show>.SearchResult> SearchShows(
string? query,
Sort<Show> sortBy,
Filter<ILibraryItem>? filter,
SearchPagination pagination,
Include<Show>? include = default
);
/// <summary>
/// Search for collections.
/// </summary>
/// <param name="query">The seach query.</param>
/// <param name="sortBy">Sort information about the query (sort by, sort order)</param>
/// <param name="pagination">How pagination should be done (where to start and how many to return)</param>
/// <param name="include">The related fields to include.</param>
/// <returns>A list of resources that match every filters</returns>
public Task<SearchPage<Collection>.SearchResult> SearchCollections(
string? query,
Sort<Collection> sortBy,
Filter<ILibraryItem>? filter,
SearchPagination pagination,
Include<Collection>? include = default
);
/// <summary>
/// Search for episodes.
/// </summary>
/// <param name="query">The seach query.</param>
/// <param name="sortBy">Sort information about the query (sort by, sort order)</param>
/// <param name="pagination">How pagination should be done (where to start and how many to return)</param>
/// <param name="include">The related fields to include.</param>
/// <returns>A list of resources that match every filters</returns>
public Task<SearchPage<Episode>.SearchResult> SearchEpisodes(
string? query,
Sort<Episode> sortBy,
Filter<Episode>? filter,
SearchPagination pagination,
Include<Episode>? include = default
);
/// <summary>
/// Search for studios.
/// </summary>
/// <param name="query">The seach query.</param>
/// <param name="sortBy">Sort information about the query (sort by, sort order)</param>
/// <param name="pagination">How pagination should be done (where to start and how many to return)</param>
/// <param name="include">The related fields to include.</param>
/// <returns>A list of resources that match every filters</returns>
public Task<SearchPage<Studio>.SearchResult> SearchStudios(
string? query,
Sort<Studio> sortBy,
Filter<Studio>? filter,
SearchPagination pagination,
Include<Studio>? include = default
);
}

View File

@@ -1,43 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.IO;
using System.Threading.Tasks;
using Kyoo.Abstractions.Models;
namespace Kyoo.Abstractions.Controllers;
public interface IThumbnailsManager
{
Task DownloadImages<T>(T item)
where T : IThumbnails;
Task DownloadImage(Image? image, string what);
Task<bool> IsImageSaved(Guid imageId, ImageQuality quality);
Task<Stream> GetImage(Guid imageId, ImageQuality quality);
Task DeleteImages<T>(T item)
where T : IThumbnails;
Task<Stream> GetUserImage(Guid userId);
Task SetUserImage(Guid userId, Stream? image);
}

View File

@@ -1,83 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Utils;
namespace Kyoo.Abstractions.Controllers;
/// <summary>
/// A local repository to handle watched items
/// </summary>
public interface IWatchStatusRepository
{
public delegate Task ResourceEventHandler<T>(T resource);
Task<ICollection<IWatchlist>> GetAll(
Filter<IWatchlist>? filter = default,
Include<IWatchlist>? include = default,
Pagination? limit = default
);
Task<MovieWatchStatus?> GetMovieStatus(Guid movieId, Guid userId);
Task<MovieWatchStatus?> SetMovieStatus(
Guid movieId,
Guid userId,
WatchStatus status,
int? watchedTime,
int? percent
);
static event ResourceEventHandler<WatchStatus<Movie>> OnMovieStatusChangedHandler;
protected static Task OnMovieStatusChanged(WatchStatus<Movie> obj) =>
OnMovieStatusChangedHandler?.Invoke(obj) ?? Task.CompletedTask;
Task DeleteMovieStatus(Guid movieId, Guid userId);
Task<ShowWatchStatus?> GetShowStatus(Guid showId, Guid userId);
Task<ShowWatchStatus?> SetShowStatus(Guid showId, Guid userId, WatchStatus status);
static event ResourceEventHandler<WatchStatus<Show>> OnShowStatusChangedHandler;
protected static Task OnShowStatusChanged(WatchStatus<Show> obj) =>
OnShowStatusChangedHandler?.Invoke(obj) ?? Task.CompletedTask;
Task DeleteShowStatus(Guid showId, Guid userId);
Task<EpisodeWatchStatus?> GetEpisodeStatus(Guid episodeId, Guid userId);
/// <param name="watchedTime">Where the user has stopped watching. Only usable if Status
/// is <see cref="WatchStatus.Watching"/></param>
Task<EpisodeWatchStatus?> SetEpisodeStatus(
Guid episodeId,
Guid userId,
WatchStatus status,
int? watchedTime,
int? percent
);
static event ResourceEventHandler<WatchStatus<Episode>> OnEpisodeStatusChangedHandler;
protected static Task OnEpisodeStatusChanged(WatchStatus<Episode> obj) =>
OnEpisodeStatusChangedHandler?.Invoke(obj) ?? Task.CompletedTask;
Task DeleteEpisodeStatus(Guid episodeId, Guid userId);
}

View File

@@ -1,64 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Authentication.Models;
namespace Kyoo.Authentication;
/// <summary>
/// Extension methods.
/// </summary>
public static class Extensions
{
/// <summary>
/// Get the permissions of an user.
/// </summary>
/// <param name="user">The user</param>
/// <returns>The list of permissions</returns>
public static ICollection<string> GetPermissions(this ClaimsPrincipal user)
{
return user.Claims.FirstOrDefault(x => x.Type == Claims.Permissions)?.Value.Split(',')
?? Array.Empty<string>();
}
/// <summary>
/// Get the id of the current user or null if unlogged or invalid.
/// </summary>
/// <param name="user">The user.</param>
/// <returns>The id of the user or null.</returns>
public static Guid? GetId(this ClaimsPrincipal user)
{
Claim? value = user.FindFirst(Claims.Id);
if (Guid.TryParse(value?.Value, out Guid id))
return id;
return null;
}
public static Guid GetIdOrThrow(this ClaimsPrincipal user)
{
Guid? ret = user.GetId();
if (ret == null)
throw new UnauthorizedException();
return ret.Value;
}
}

View File

@@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Title>Kyoo.Abstractions</Title>
<Description>Base package to create plugins for Kyoo.</Description>
<RootNamespace>Kyoo.Abstractions</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="EntityFrameworkCore.Projectables" Version="4.1.4-prebeta" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.3.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
<PackageReference Include="Sprache" Version="2.3.1" />
<PackageReference Include="System.ComponentModel.Composition" Version="9.0.10" />
</ItemGroup>
</Project>

View File

@@ -1,51 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
namespace Kyoo.Abstractions.Models.Attributes;
/// <summary>
/// An attribute to specify on apis to specify it's documentation's name and category.
/// If this is applied on a method, the specified method will be exploded from the controller's page and be
/// included on the specified tag page.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ApiDefinitionAttribute : Attribute
{
/// <summary>
/// The public name of this api.
/// </summary>
public string Name { get; }
/// <summary>
/// The name of the group in witch this API is. You can also specify a custom sort order using the following
/// format: <code>order:name</code>. Everything before the first <c>:</c> will be removed but kept for
/// th alphabetical ordering.
/// </summary>
public string? Group { get; set; }
/// <summary>
/// Create a new <see cref="ApiDefinitionAttribute"/>.
/// </summary>
/// <param name="name">The name of the api that will be used on the documentation page.</param>
public ApiDefinitionAttribute(string name)
{
Name = name;
}
}

View File

@@ -1,27 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
namespace Kyoo.Abstractions.Models.Attributes;
/// <summary>
/// An attribute to inform that the property is computed automatically and can't be assigned manually.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ComputedAttribute : NotMergeableAttribute { }

View File

@@ -1,53 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
namespace Kyoo.Abstractions.Models.Attributes;
/// <summary>
/// The targeted relation can be loaded.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class LoadableRelationAttribute : Attribute
{
/// <summary>
/// The name of the field containing the related resource's ID.
/// </summary>
public string? RelationID { get; }
public string? Sql { get; set; }
public string? On { get; set; }
public string? Projected { get; set; }
/// <summary>
/// Create a new <see cref="LoadableRelationAttribute"/>.
/// </summary>
public LoadableRelationAttribute() { }
/// <summary>
/// Create a new <see cref="LoadableRelationAttribute"/> with a baking relationID field.
/// </summary>
/// <param name="relationID">The name of the RelationID field.</param>
public LoadableRelationAttribute(string relationID)
{
RelationID = relationID;
}
}

View File

@@ -1,39 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
namespace Kyoo.Abstractions.Models.Attributes;
/// <summary>
/// Specify that a property can't be merged.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class NotMergeableAttribute : Attribute { }
/// <summary>
/// An interface with a method called when this object is merged.
/// </summary>
public interface IOnMerge
{
/// <summary>
/// This function is called after the object has been merged.
/// </summary>
/// <param name="merged">The object that has been merged with this.</param>
void OnMerge(object merged);
}

View File

@@ -1,33 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
namespace Kyoo.Abstractions.Models.Attributes;
/// <summary>
/// An attribute to inform that this interface is a type union
/// </summary>
[AttributeUsage(AttributeTargets.Interface)]
public class OneOfAttribute : Attribute
{
/// <summary>
/// The types this union concist of.
/// </summary>
public Type[] Types { get; set; }
}

View File

@@ -1,87 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using Kyoo.Abstractions.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
namespace Kyoo.Abstractions.Models.Permissions;
/// <summary>
/// Specify one part of a permissions needed for the API (the kind or the type).
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class PartialPermissionAttribute : Attribute, IFilterFactory
{
/// <summary>
/// The needed permission type.
/// </summary>
public string? Type { get; }
/// <summary>
/// The needed permission kind.
/// </summary>
public Kind? Kind { get; }
/// <summary>
/// The group of this permission.
/// </summary>
public Group Group { get; set; }
/// <summary>
/// Ask a permission to run an action.
/// </summary>
/// <remarks>
/// With this attribute, you can only specify a type or a kind.
/// To have a valid permission attribute, you must specify the kind and the permission using two attributes.
/// Those attributes can be dispatched at different places (one on the class, one on the method for example).
/// If you don't put exactly two of those attributes, the permission attribute will be ill-formed and will
/// lead to unspecified behaviors.
/// </remarks>
/// <param name="type">The type of the action</param>
public PartialPermissionAttribute(string type)
{
Type = type.ToLower();
}
/// <summary>
/// Ask a permission to run an action.
/// </summary>
/// <remarks>
/// With this attribute, you can only specify a type or a kind.
/// To have a valid permission attribute, you must specify the kind and the permission using two attributes.
/// Those attributes can be dispatched at different places (one on the class, one on the method for example).
/// If you don't put exactly two of those attributes, the permission attribute will be ill-formed and will
/// lead to unspecified behaviors.
/// </remarks>
/// <param name="permission">The kind of permission needed.</param>
public PartialPermissionAttribute(Kind permission)
{
Kind = permission;
}
/// <inheritdoc />
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return serviceProvider.GetRequiredService<IPermissionValidator>().Create(this);
}
/// <inheritdoc />
public bool IsReusable => true;
}

View File

@@ -1,136 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using Kyoo.Abstractions.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
namespace Kyoo.Abstractions.Models.Permissions;
/// <summary>
/// The kind of permission needed.
/// </summary>
public enum Kind
{
/// <summary>
/// Allow the user to read for this kind of data.
/// </summary>
Read,
/// <summary>
/// Allow the user to write for this kind of data.
/// </summary>
Write,
/// <summary>
/// Allow the user to create this kind of data.
/// </summary>
Create,
/// <summary>
/// Allow the user to delete this kind of data.
/// </summary>
Delete,
/// <summary>
/// Allow the user to play this file.
/// </summary>
Play,
}
/// <summary>
/// The group of the permission.
/// </summary>
public enum Group
{
/// <summary>
/// Default group indicating no value.
/// </summary>
None,
/// <summary>
/// Allow all operations on basic items types.
/// </summary>
Overall,
/// <summary>
/// Allow operation on sensitive items like libraries path, configurations and so on.
/// </summary>
Admin
}
/// <summary>
/// Specify permissions needed for the API.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class PermissionAttribute : Attribute, IFilterFactory
{
/// <summary>
/// The needed permission as string.
/// </summary>
public string Type { get; }
/// <summary>
/// The needed permission kind.
/// </summary>
public Kind Kind { get; }
/// <summary>
/// The group of this permission.
/// </summary>
public Group Group { get; set; }
/// <summary>
/// Ask a permission to run an action.
/// </summary>
/// <param name="type">
/// The type of the action
/// </param>
/// <param name="permission">
/// The kind of permission needed.
/// </param>
/// <param name="group">
/// The group of this permission (allow grouped permission like overall.read
/// for all read permissions of this group).
/// </param>
public PermissionAttribute(string type, Kind permission, Group group = Group.Overall)
{
Type = type.ToLower();
Kind = permission;
Group = group;
}
/// <inheritdoc />
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return serviceProvider.GetRequiredService<IPermissionValidator>().Create(this);
}
/// <inheritdoc />
public bool IsReusable => true;
/// <summary>
/// Return this permission attribute as a string.
/// </summary>
/// <returns>The string representation.</returns>
public string AsPermissionString()
{
return Type;
}
}

View File

@@ -1,30 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
namespace Kyoo.Abstractions.Models.Permissions;
/// <summary>
/// The annotated route can only be accessed by a logged in user.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class UserOnlyAttribute : Attribute
{
// TODO: Implement a Filter Attribute to make this work. For now, this attribute is only useful as documentation.
}

View File

@@ -1,37 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using Kyoo.Utils;
namespace Kyoo.Abstractions.Models.Attributes;
[AttributeUsage(AttributeTargets.Class)]
public class SqlFirstColumnAttribute : Attribute
{
/// <summary>
/// The name of the first column of the element. Used to split multiples
/// items on a single sql query. If not specified, it defaults to "Id".
/// </summary>
public string Name { get; set; }
public SqlFirstColumnAttribute(string name)
{
Name = name.ToSnakeCase();
}
}

View File

@@ -1,34 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
namespace Kyoo.Abstractions.Models.Exceptions;
/// <summary>
/// An exception raised when an item already exists in the database.
/// </summary>
[Serializable]
public class DuplicatedItemException(object? existing = null)
: Exception("Already exists in the database.")
{
/// <summary>
/// The existing object.
/// </summary>
public object? Existing { get; } = existing;
}

View File

@@ -1,41 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
namespace Kyoo.Abstractions.Models.Exceptions;
/// <summary>
/// An exception raised when an item could not be found.
/// </summary>
[Serializable]
public class ItemNotFoundException : Exception
{
/// <summary>
/// Create a default <see cref="ItemNotFoundException"/> with no message.
/// </summary>
public ItemNotFoundException()
: base("Item not found") { }
/// <summary>
/// Create a new <see cref="ItemNotFoundException"/> with a message
/// </summary>
/// <param name="message">The message of the exception</param>
public ItemNotFoundException(string message)
: base(message) { }
}

View File

@@ -1,31 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
namespace Kyoo.Abstractions.Models.Exceptions;
[Serializable]
public class UnauthorizedException : Exception
{
public UnauthorizedException()
: base("User not authenticated or token invalid.") { }
public UnauthorizedException(string message)
: base(message) { }
}

View File

@@ -1,50 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
namespace Kyoo.Abstractions.Models;
/// <summary>
/// A genre that allow one to specify categories for shows.
/// </summary>
public enum Genre
{
Action,
Adventure,
Animation,
Comedy,
Crime,
Documentary,
Drama,
Family,
Fantasy,
History,
Horror,
Music,
Mystery,
Romance,
ScienceFiction,
Thriller,
War,
Western,
Kids,
News,
Reality,
Soap,
Talk,
Politics,
}

View File

@@ -1,31 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// A show, a movie or a collection.
/// </summary>
[OneOf(Types = new[] { typeof(Show), typeof(Movie), typeof(Collection) })]
public interface ILibraryItem : IResource, IThumbnails, IMetadata, IAddedDate, IQuery
{
static Sort IQuery.DefaultSort => new Sort<ILibraryItem>.By(nameof(Movie.Name));
}

View File

@@ -1,31 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// A show, a movie or a collection.
/// </summary>
[OneOf(Types = [typeof(Episode), typeof(Movie)])]
public interface INews : IResource, IThumbnails, IAddedDate, IQuery
{
static Sort IQuery.DefaultSort => new Sort<INews>.By(nameof(AddedDate), true);
}

View File

@@ -1,27 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using Kyoo.Abstractions.Models.Attributes;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// A watch list item.
/// </summary>
[OneOf(Types = new[] { typeof(Show), typeof(Movie) })]
public interface IWatchlist : IResource, IThumbnails, IMetadata, IAddedDate { }

View File

@@ -1,52 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// An issue that occured on kyoo.
/// </summary>
public class Issue : IAddedDate
{
/// <summary>
/// The type of issue (for example, "Scanner" if this issue was created due to scanning error).
/// </summary>
public string Domain { get; set; }
/// <summary>
/// Why this issue was caused? An unique cause that can be used to identify this issue.
/// For the scanner, a cause should be a video path.
/// </summary>
public string Cause { get; set; }
/// <summary>
/// A human readable string explaining why this issue occured.
/// </summary>
public string Reason { get; set; }
/// <summary>
/// Some extra data that could store domain-specific info.
/// </summary>
public Dictionary<string, object> Extra { get; set; } = new();
/// <inheritdoc/>
public DateTime AddedDate { get; set; }
}

View File

@@ -1,61 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
namespace Kyoo.Abstractions.Models;
/// <summary>
/// ID and link of an item on an external provider.
/// </summary>
public class MetadataId
{
/// <summary>
/// The ID of the resource on the external provider.
/// </summary>
public string DataId { get; set; }
/// <summary>
/// The URL of the resource on the external provider.
/// </summary>
public string? Link { get; set; }
}
/// <summary>
/// ID informations about an episode.
/// </summary>
public class EpisodeId
{
/// <summary>
/// The Id of the show on the metadata database.
/// </summary>
public string ShowId { get; set; }
/// <summary>
/// The season number or null if absolute numbering is used in this database.
/// </summary>
public int? Season { get; set; }
/// <summary>
/// The episode number or absolute number if Season is null.
/// </summary>
public int Episode { get; set; }
/// <summary>
/// The URL of the resource on the external provider.
/// </summary>
public string? Link { get; set; }
}

View File

@@ -1,105 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Collections.Generic;
using System.Linq;
using Kyoo.Utils;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// A page of resource that contains information about the pagination of resources.
/// </summary>
/// <typeparam name="T">The type of resource contained in this page.</typeparam>
public class Page<T>
where T : IResource
{
/// <summary>
/// The link of the current page.
/// </summary>
public string This { get; }
/// <summary>
/// The link of the first page.
/// </summary>
public string First { get; }
/// <summary>
/// The link of the previous page.
/// </summary>
public string? Previous { get; }
/// <summary>
/// The link of the next page.
/// </summary>
public string? Next { get; }
/// <summary>
/// The number of items in the current page.
/// </summary>
public int Count => Items.Count;
/// <summary>
/// The list of items in the page.
/// </summary>
public ICollection<T> Items { get; }
/// <summary>
/// Create a new <see cref="Page{T}"/>.
/// </summary>
/// <param name="items">The list of items in the page.</param>
/// <param name="this">The link of the current page.</param>
/// <param name="previous">The link of the previous page.</param>
/// <param name="next">The link of the next page.</param>
/// <param name="first">The link of the first page.</param>
public Page(ICollection<T> items, string @this, string? previous, string? next, string first)
{
Items = items;
This = @this;
Previous = previous;
Next = next;
First = first;
}
/// <summary>
/// Create a new <see cref="Page{T}"/> and compute the urls.
/// </summary>
/// <param name="items">The list of items in the page.</param>
/// <param name="url">The base url of the resources available from this page.</param>
/// <param name="query">The list of query strings of the current page</param>
/// <param name="limit">The number of items requested for the current page.</param>
public Page(ICollection<T> items, string url, Dictionary<string, string> query, int limit)
{
Items = items;
This = url + query.ToQueryString();
if (items.Count > 0 && query.ContainsKey("afterID"))
{
query["afterID"] = items.First().Id.ToString();
query["reverse"] = "true";
Previous = url + query.ToQueryString();
}
query.Remove("reverse");
if (items.Count == limit && limit > 0)
{
query["afterID"] = items.Last().Id.ToString();
Next = url + query.ToQueryString();
}
query.Remove("afterID");
First = url + query.ToQueryString();
}
}

View File

@@ -1,46 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;
using Kyoo.Abstractions.Models;
namespace Kyoo.Models;
public class Patch<T> : Dictionary<string, JsonDocument>
where T : class, IResource
{
public Guid? Id => this.GetValueOrDefault(nameof(IResource.Id))?.Deserialize<Guid>();
public string? Slug => this.GetValueOrDefault(nameof(IResource.Slug))?.Deserialize<string>();
public T Apply(T current)
{
foreach ((string property, JsonDocument value) in this)
{
PropertyInfo prop = typeof(T).GetProperty(
property,
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance
)!;
prop.SetValue(current, value.Deserialize(prop.PropertyType));
}
return current;
}
}

View File

@@ -1,100 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using Kyoo.Abstractions.Controllers;
using Kyoo.Utils;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// A class representing collections of <see cref="Show"/>.
/// </summary>
public class Collection
: IQuery,
IResource,
IMetadata,
IThumbnails,
IAddedDate,
IRefreshable,
ILibraryItem
{
public static Sort DefaultSort => new Sort<Collection>.By(nameof(Collection.Name));
/// <inheritdoc />
public Guid Id { get; set; }
/// <inheritdoc />
[MaxLength(256)]
public string Slug { get; set; }
/// <summary>
/// The name of this collection.
/// </summary>
public string Name { get; set; }
/// <summary>
/// The description of this collection.
/// </summary>
public string? Overview { get; set; }
/// <inheritdoc />
public DateTime AddedDate { get; set; }
/// <inheritdoc />
public Image? Poster { get; set; }
/// <inheritdoc />
public Image? Thumbnail { get; set; }
/// <inheritdoc />
public Image? Logo { get; set; }
/// <summary>
/// The list of movies contained in this collection.
/// </summary>
[JsonIgnore]
public ICollection<Movie>? Movies { get; set; }
/// <summary>
/// The list of shows contained in this collection.
/// </summary>
[JsonIgnore]
public ICollection<Show>? Shows { get; set; }
/// <inheritdoc />
public Dictionary<string, MetadataId> ExternalId { get; set; } = new();
/// <inheritdoc />
public DateTime? NextMetadataRefresh { get; set; }
public Collection() { }
[JsonConstructor]
public Collection(string name)
{
if (name != null)
{
Slug = Utility.ToSlug(name);
Name = name;
}
}
}

View File

@@ -1,302 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using EntityFrameworkCore.Projectables;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// A class to represent a single show's episode.
/// </summary>
public class Episode : IQuery, IResource, IThumbnails, IAddedDate, IRefreshable, INews
{
// Use absolute numbers by default and fallback to season/episodes if it does not exists.
public static Sort DefaultSort =>
new Sort<Episode>.Conglomerate(
new Sort<Episode>.By(x => x.AbsoluteNumber),
new Sort<Episode>.By(x => x.SeasonNumber),
new Sort<Episode>.By(x => x.EpisodeNumber)
);
/// <inheritdoc />
public Guid Id { get; set; }
/// <inheritdoc />
[Computed]
[MaxLength(256)]
public string Slug
{
get
{
if (ShowSlug != null || Show?.Slug != null)
return GetSlug(ShowSlug ?? Show!.Slug, SeasonNumber, EpisodeNumber, AbsoluteNumber);
return GetSlug(ShowId.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber);
}
private set
{
Match match = Regex.Match(value, @"(?<show>.+)-s(?<season>\d+)e(?<episode>\d+)");
if (match.Success)
{
ShowSlug = match.Groups["show"].Value;
SeasonNumber = int.Parse(match.Groups["season"].Value);
EpisodeNumber = int.Parse(match.Groups["episode"].Value);
}
else
{
match = Regex.Match(value, @"(?<show>.+)-(?<absolute>\d+)");
if (match.Success)
{
ShowSlug = match.Groups["show"].Value;
AbsoluteNumber = int.Parse(match.Groups["absolute"].Value);
}
else
ShowSlug = value;
SeasonNumber = null;
EpisodeNumber = null;
}
}
}
/// <summary>
/// The slug of the Show that contain this episode. If this is not set, this episode is ill-formed.
/// </summary>
[JsonIgnore]
public string? ShowSlug { private get; set; }
/// <summary>
/// The ID of the Show containing this episode.
/// </summary>
public Guid ShowId { get; set; }
/// <summary>
/// The show that contains this episode.
/// </summary>
[LoadableRelation(nameof(ShowId))]
public Show? Show { get; set; }
/// <summary>
/// The ID of the Season containing this episode.
/// </summary>
public Guid? SeasonId { get; set; }
/// <summary>
/// The season that contains this episode.
/// </summary>
/// <remarks>
/// This can be null if the season is unknown and the episode is only identified
/// by it's <see cref="AbsoluteNumber"/>.
/// </remarks>
[LoadableRelation(nameof(SeasonId))]
public Season? Season { get; set; }
/// <summary>
/// The season in witch this episode is in.
/// </summary>
public int? SeasonNumber { get; set; }
/// <summary>
/// The number of this episode in it's season.
/// </summary>
public int? EpisodeNumber { get; set; }
/// <summary>
/// The absolute number of this episode. It's an episode number that is not reset to 1 after a new season.
/// </summary>
public int? AbsoluteNumber { get; set; }
/// <summary>
/// The path of the video file for this episode.
/// </summary>
public string Path { get; set; }
/// <summary>
/// The title of this episode.
/// </summary>
public string? Name { get; set; }
/// <summary>
/// The overview of this episode.
/// </summary>
public string? Overview { get; set; }
/// <summary>
/// How long is this episode? (in minutes)
/// </summary>
public int? Runtime { get; set; }
/// <summary>
/// The release date of this episode. It can be null if unknown.
/// </summary>
public DateOnly? ReleaseDate { get; set; }
/// <inheritdoc />
public DateTime AddedDate { get; set; }
/// <inheritdoc />
public Image? Poster { get; set; }
/// <inheritdoc />
public Image? Thumbnail { get; set; }
/// <inheritdoc />
public Image? Logo { get; set; }
/// <inheritdoc />
public Dictionary<string, EpisodeId> ExternalId { get; set; } = [];
/// <inheritdoc />
public DateTime? NextMetadataRefresh { get; set; }
/// <summary>
/// The previous episode that should be seen before viewing this one.
/// </summary>
[Projectable(UseMemberBody = nameof(_PreviousEpisode), OnlyOnInclude = true)]
[LoadableRelation(
// language=PostgreSQL
Sql = """
select
pe.* -- Episode as pe
from
episodes as "pe"
where
pe.show_id = "this".show_id
and (pe.absolute_number < "this".absolute_number
or pe.season_number < "this".season_number
or (pe.season_number = "this".season_number
and e.episode_number < "this".episode_number))
order by
pe.absolute_number desc nulls last,
pe.season_number desc,
pe.episode_number desc
limit 1
"""
)]
public Episode? PreviousEpisode { get; set; }
private Episode? _PreviousEpisode =>
Show!
.Episodes!.OrderBy(x => x.AbsoluteNumber == null)
.ThenByDescending(x => x.AbsoluteNumber)
.ThenByDescending(x => x.SeasonNumber)
.ThenByDescending(x => x.EpisodeNumber)
.FirstOrDefault(x =>
x.AbsoluteNumber < AbsoluteNumber
|| x.SeasonNumber < SeasonNumber
|| (x.SeasonNumber == SeasonNumber && x.EpisodeNumber < EpisodeNumber)
);
/// <summary>
/// The next episode to watch after this one.
/// </summary>
[Projectable(UseMemberBody = nameof(_NextEpisode), OnlyOnInclude = true)]
[LoadableRelation(
// language=PostgreSQL
Sql = """
select
ne.* -- Episode as ne
from
episodes as "ne"
where
ne.show_id = "this".show_id
and (ne.absolute_number > "this".absolute_number
or ne.season_number > "this".season_number
or (ne.season_number = "this".season_number
and e.episode_number > "this".episode_number))
order by
ne.absolute_number,
ne.season_number,
ne.episode_number
limit 1
"""
)]
public Episode? NextEpisode { get; set; }
private Episode? _NextEpisode =>
Show!
.Episodes!.OrderBy(x => x.AbsoluteNumber)
.ThenBy(x => x.SeasonNumber)
.ThenBy(x => x.EpisodeNumber)
.FirstOrDefault(x =>
x.AbsoluteNumber > AbsoluteNumber
|| x.SeasonNumber > SeasonNumber
|| (x.SeasonNumber == SeasonNumber && x.EpisodeNumber > EpisodeNumber)
);
[JsonIgnore]
public ICollection<EpisodeWatchStatus>? Watched { get; set; }
/// <summary>
/// Metadata of what an user as started/planned to watch.
/// </summary>
[Projectable(UseMemberBody = nameof(_WatchStatus), OnlyOnInclude = true)]
[LoadableRelation(
Sql = "episode_watch_status",
On = "episode_id = \"this\".id and \"relation\".user_id = [current_user]"
)]
public EpisodeWatchStatus? WatchStatus { get; set; }
// There is a global query filter to filter by user so we just need to do single.
private EpisodeWatchStatus? _WatchStatus => Watched!.FirstOrDefault();
/// <summary>
/// Links to watch this episode.
/// </summary>
public VideoLinks Links =>
new() { Direct = $"/episode/{Slug}/direct", Hls = $"/episode/{Slug}/master.m3u8", };
/// <summary>
/// Get the slug of an episode.
/// </summary>
/// <param name="showSlug">The slug of the show. It can't be null.</param>
/// <param name="seasonNumber">
/// The season in which the episode is.
/// If this is a movie or if the episode should be referred by it's absolute number, set this to null.
/// </param>
/// <param name="episodeNumber">
/// The number of the episode in it's season.
/// If this is a movie or if the episode should be referred by it's absolute number, set this to null.
/// </param>
/// <param name="absoluteNumber">
/// The absolute number of this show.
/// If you don't know it or this is a movie, use null
/// </param>
/// <returns>The slug corresponding to the given arguments</returns>
public static string GetSlug(
string showSlug,
int? seasonNumber,
int? episodeNumber,
int? absoluteNumber = null
)
{
return seasonNumber switch
{
null when absoluteNumber == null => showSlug,
null => $"{showSlug}-{absoluteNumber}",
_ => $"{showSlug}-s{seasonNumber}e{episodeNumber}"
};
}
}

View File

@@ -1,32 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// An interface applied to resources.
/// </summary>
public interface IAddedDate
{
/// <summary>
/// The date at which this resource was added to kyoo.
/// </summary>
public DateTime AddedDate { get; set; }
}

View File

@@ -1,32 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Collections.Generic;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// An interface applied to resources containing external metadata.
/// </summary>
public interface IMetadata
{
/// <summary>
/// The link to metadata providers that this show has. See <see cref="MetadataId"/> for more information.
/// </summary>
public Dictionary<string, MetadataId> ExternalId { get; set; }
}

View File

@@ -1,30 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using Kyoo.Abstractions.Controllers;
namespace Kyoo.Abstractions.Models;
public interface IQuery
{
/// <summary>
/// The sorting that will be used when no user defined one is present.
/// </summary>
public static virtual Sort DefaultSort => throw new NotImplementedException();
}

View File

@@ -1,40 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
namespace Kyoo.Abstractions.Models;
public interface IRefreshable
{
/// <summary>
/// The date of the next metadata refresh. Null if auto-refresh is disabled.
/// </summary>
public DateTime? NextMetadataRefresh { get; set; }
public static DateTime ComputeNextRefreshDate(DateOnly airDate)
{
int days = DateOnly.FromDateTime(DateTime.UtcNow).DayNumber - airDate.DayNumber;
return days switch
{
<= 4 => DateTime.UtcNow.AddDays(1),
<= 21 => DateTime.UtcNow.AddDays(14),
_ => DateTime.UtcNow.AddMonths(2)
};
}
}

View File

@@ -1,49 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.ComponentModel.DataAnnotations;
using Kyoo.Abstractions.Controllers;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// An interface to represent a resource that can be retrieved from the database.
/// </summary>
public interface IResource : IQuery
{
/// <summary>
/// A unique ID for this type of resource. This can't be changed and duplicates are not allowed.
/// </summary>
/// <remarks>
/// You don't need to specify an ID manually when creating a new resource,
/// this field is automatically assigned by the <see cref="IRepository{T}"/>.
/// </remarks>
public Guid Id { get; set; }
/// <summary>
/// A human-readable identifier that can be used instead of an ID.
/// A slug must be unique for a type of resource but it can be changed.
/// </summary>
/// <remarks>
/// There is no setter for a slug since it can be computed from other fields.
/// For example, a season slug is {ShowSlug}-s{SeasonNumber}.
/// </remarks>
[MaxLength(256)]
public string Slug { get; }
}

View File

@@ -1,147 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// An interface representing items that contains images (like posters, thumbnails, logo, banners...)
/// </summary>
public interface IThumbnails
{
/// <summary>
/// A poster is a 2/3 format image with the cover of the resource.
/// </summary>
public Image? Poster { get; set; }
/// <summary>
/// A thumbnail is a 16/9 format image, it could ether be used as a background or as a preview but it usually
/// is not an official image.
/// </summary>
public Image? Thumbnail { get; set; }
/// <summary>
/// A logo is a small image representing the resource.
/// </summary>
public Image? Logo { get; set; }
}
[JsonConverter(typeof(ImageConvertor))]
public class Image
{
/// <summary>
/// A unique identifier for the image. Used for proper http caches.
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// The original image from another server.
/// </summary>
public string Source { get; set; }
/// <summary>
/// A hash to display as placeholder while the image is loading.
/// </summary>
[MaxLength(32)]
public string Blurhash { get; set; }
/// <summary>
/// The url to access the image in low quality.
/// </summary>
public string Low => $"/thumbnails/{Id}?quality=low";
/// <summary>
/// The url to access the image in medium quality.
/// </summary>
public string Medium => $"/thumbnails/{Id}?quality=medium";
/// <summary>
/// The url to access the image in high quality.
/// </summary>
public string High => $"/thumbnails/{Id}?quality=high";
public Image() { }
[JsonConstructor]
public Image(string source, string? blurhash = null)
{
Source = source;
Blurhash = blurhash ?? "000000";
}
//
public class ImageConvertor : JsonConverter<Image>
{
/// <inheritdoc />
public override Image? Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options
)
{
if (reader.TokenType == JsonTokenType.String && reader.GetString() is string source)
return new Image(source);
using JsonDocument document = JsonDocument.ParseValue(ref reader);
string? src = document.RootElement.GetProperty("Source").GetString();
string? blurhash = document.RootElement.GetProperty("Blurhash").GetString();
Guid? id = document.RootElement.GetProperty("Id").GetGuid();
return new Image(src ?? string.Empty, blurhash) { Id = id ?? Guid.Empty };
}
/// <inheritdoc />
public override void Write(
Utf8JsonWriter writer,
Image value,
JsonSerializerOptions options
)
{
writer.WriteStartObject();
writer.WriteString("source", value.Source);
writer.WriteString("blurhash", value.Blurhash);
writer.WriteString("low", value.Low);
writer.WriteString("medium", value.Medium);
writer.WriteString("high", value.High);
writer.WriteEndObject();
}
}
}
/// <summary>
/// The quality of an image
/// </summary>
public enum ImageQuality
{
/// <summary>
/// Small
/// </summary>
Low,
/// <summary>
/// Medium
/// </summary>
Medium,
/// <summary>
/// Large
/// </summary>
High,
}

View File

@@ -1,65 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Text.Json.Serialization;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// A container representing the response of a login or token refresh.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="JwtToken"/> class.
/// </remarks>
/// <param name="accessToken">The access token used to authorize requests.</param>
/// <param name="refreshToken">The refresh token to retrieve a new access token.</param>
/// <param name="expireIn">When the access token will expire.</param>
public class JwtToken(string accessToken, string refreshToken, TimeSpan expireIn)
{
/// <summary>
/// The type of this token (always a Bearer).
/// </summary>
[JsonPropertyName("token_type")]
public string TokenType => "Bearer";
/// <summary>
/// The access token used to authorize requests.
/// </summary>
[JsonPropertyName("access_token")]
public string AccessToken { get; set; } = accessToken;
/// <summary>
/// The refresh token used to retrieve a new access/refresh token when the access token has expired.
/// </summary>
[JsonPropertyName("refresh_token")]
public string RefreshToken { get; set; } = refreshToken;
/// <summary>
/// When the access token will expire. After this time, the refresh token should be used to retrieve.
/// a new token.cs
/// </summary>
[JsonPropertyName("expire_in")]
public TimeSpan ExpireIn => ExpireAt.Subtract(DateTime.UtcNow);
/// <summary>
/// The exact date at which the access token will expire.
/// </summary>
[JsonPropertyName("expire_at")]
public DateTime ExpireAt { get; set; } = DateTime.UtcNow + expireIn;
}

View File

@@ -1,192 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text.Json.Serialization;
using EntityFrameworkCore.Projectables;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// A series or a movie.
/// </summary>
public class Movie
: IQuery,
IResource,
IMetadata,
IThumbnails,
IAddedDate,
IRefreshable,
ILibraryItem,
INews,
IWatchlist
{
public static Sort DefaultSort => new Sort<Movie>.By(x => x.Name);
/// <inheritdoc />
public Guid Id { get; set; }
/// <inheritdoc />
[MaxLength(256)]
public string Slug { get; set; }
/// <summary>
/// The title of this show.
/// </summary>
public string Name { get; set; }
/// <summary>
/// A catchphrase for this movie.
/// </summary>
public string? Tagline { get; set; }
/// <summary>
/// The list of alternative titles of this show.
/// </summary>
public string[] Aliases { get; set; } = Array.Empty<string>();
/// <summary>
/// The path of the movie video file.
/// </summary>
public string Path { get; set; }
/// <summary>
/// The summary of this show.
/// </summary>
public string? Overview { get; set; }
/// <summary>
/// A list of tags that match this movie.
/// </summary>
public string[] Tags { get; set; } = [];
/// <summary>
/// The list of genres (themes) this show has.
/// </summary>
public List<Genre> Genres { get; set; } = [];
/// <summary>
/// Is this show airing, not aired yet or finished?
/// </summary>
public Status Status { get; set; }
/// <summary>
/// How well this item is rated? (from 0 to 100).
/// </summary>
public int Rating { get; set; }
/// <summary>
/// How long is this movie? (in minutes)
/// </summary>
public int? Runtime { get; set; }
/// <summary>
/// The date this movie aired.
/// </summary>
public DateOnly? AirDate { get; set; }
/// <inheritdoc />
public DateTime AddedDate { get; set; }
/// <inheritdoc />
public Image? Poster { get; set; }
/// <inheritdoc />
public Image? Thumbnail { get; set; }
/// <inheritdoc />
public Image? Logo { get; set; }
[JsonIgnore]
[Column("air_date")]
public DateOnly? StartAir => AirDate;
[JsonIgnore]
[Column("air_date")]
public DateOnly? EndAir => AirDate;
/// <summary>
/// A video of a few minutes that tease the content.
/// </summary>
public string? Trailer { get; set; }
/// <inheritdoc />
public Dictionary<string, MetadataId> ExternalId { get; set; } = new();
/// <inheritdoc />
public DateTime? NextMetadataRefresh { get; set; }
/// <summary>
/// The ID of the Studio that made this show.
/// </summary>
[JsonIgnore]
public Guid? StudioId { get; set; }
/// <summary>
/// The Studio that made this show.
/// </summary>
[LoadableRelation(nameof(StudioId))]
public Studio? Studio { get; set; }
/// <summary>
/// The list of collections that contains this show.
/// </summary>
[JsonIgnore]
public ICollection<Collection>? Collections { get; set; }
/// <summary>
/// Links to watch this movie.
/// </summary>
public VideoLinks Links =>
new() { Direct = $"/movie/{Slug}/direct", Hls = $"/movie/{Slug}/master.m3u8", };
[JsonIgnore]
public ICollection<MovieWatchStatus>? Watched { get; set; }
/// <summary>
/// Metadata of what an user as started/planned to watch.
/// </summary>
[Projectable(UseMemberBody = nameof(_WatchStatus), OnlyOnInclude = true)]
[LoadableRelation(
Sql = "movie_watch_status",
On = "movie_id = \"this\".id and \"relation\".user_id = [current_user]"
)]
public MovieWatchStatus? WatchStatus { get; set; }
// There is a global query filter to filter by user so we just need to do single.
private MovieWatchStatus? _WatchStatus => Watched!.FirstOrDefault();
public Movie() { }
[JsonConstructor]
public Movie(string name)
{
if (name != null)
{
Slug = Utility.ToSlug(name);
Name = name;
}
}
}

View File

@@ -1,151 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using EntityFrameworkCore.Projectables;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// A season of a <see cref="Show"/>.
/// </summary>
public class Season : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, IRefreshable
{
public static Sort DefaultSort => new Sort<Season>.By(x => x.SeasonNumber);
/// <inheritdoc />
public Guid Id { get; set; }
/// <inheritdoc />
[Computed]
[MaxLength(256)]
public string Slug
{
get
{
if (ShowSlug == null && Show == null)
return $"{ShowId}-s{SeasonNumber}";
return $"{ShowSlug ?? Show?.Slug}-s{SeasonNumber}";
}
private set
{
Match match = Regex.Match(value, @"(?<show>.+)-s(?<season>\d+)");
if (!match.Success)
throw new ArgumentException(
"Invalid season slug. Format: {showSlug}-s{seasonNumber}"
);
ShowSlug = match.Groups["show"].Value;
SeasonNumber = int.Parse(match.Groups["season"].Value);
}
}
/// <summary>
/// The slug of the Show that contain this episode. If this is not set, this season is ill-formed.
/// </summary>
[JsonIgnore]
public string? ShowSlug { private get; set; }
/// <summary>
/// The ID of the Show containing this season.
/// </summary>
public Guid ShowId { get; set; }
/// <summary>
/// The show that contains this season.
/// </summary>
[LoadableRelation(nameof(ShowId))]
public Show? Show { get; set; }
/// <summary>
/// The number of this season. This can be set to 0 to indicate specials.
/// </summary>
public int SeasonNumber { get; set; }
/// <summary>
/// The title of this season.
/// </summary>
public string? Name { get; set; }
/// <summary>
/// A quick overview of this season.
/// </summary>
public string? Overview { get; set; }
/// <summary>
/// The starting air date of this season.
/// </summary>
public DateOnly? StartDate { get; set; }
/// <inheritdoc />
public DateTime AddedDate { get; set; }
/// <summary>
/// The ending date of this season.
/// </summary>
public DateOnly? EndDate { get; set; }
/// <inheritdoc />
public Image? Poster { get; set; }
/// <inheritdoc />
public Image? Thumbnail { get; set; }
/// <inheritdoc />
public Image? Logo { get; set; }
/// <inheritdoc />
public Dictionary<string, MetadataId> ExternalId { get; set; } = new();
/// <inheritdoc />
public DateTime? NextMetadataRefresh { get; set; }
/// <summary>
/// The list of episodes that this season contains.
/// </summary>
[JsonIgnore]
public ICollection<Episode>? Episodes { get; set; }
/// <summary>
/// The number of episodes in this season.
/// </summary>
[Projectable(UseMemberBody = nameof(_EpisodesCount), OnlyOnInclude = true)]
[NotMapped]
[LoadableRelation(
// language=PostgreSQL
Projected = """
(
select
count(*)::int
from
episodes as e
where
e.season_id = id) as episodes_count
"""
)]
public int EpisodesCount { get; set; }
private int _EpisodesCount => Episodes!.Count;
}

View File

@@ -1,283 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text.Json.Serialization;
using EntityFrameworkCore.Projectables;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// A series or a movie.
/// </summary>
public class Show
: IQuery,
IResource,
IMetadata,
IOnMerge,
IThumbnails,
IAddedDate,
IRefreshable,
ILibraryItem,
IWatchlist
{
public static Sort DefaultSort => new Sort<Show>.By(x => x.Name);
/// <inheritdoc />
public Guid Id { get; set; }
/// <inheritdoc />
[MaxLength(256)]
public string Slug { get; set; }
/// <summary>
/// The title of this show.
/// </summary>
public string Name { get; set; }
/// <summary>
/// A catchphrase for this show.
/// </summary>
public string? Tagline { get; set; }
/// <summary>
/// The list of alternative titles of this show.
/// </summary>
public List<string> Aliases { get; set; } = new();
/// <summary>
/// The summary of this show.
/// </summary>
public string? Overview { get; set; }
/// <summary>
/// A list of tags that match this movie.
/// </summary>
public List<string> Tags { get; set; } = new();
/// <summary>
/// The list of genres (themes) this show has.
/// </summary>
public List<Genre> Genres { get; set; } = new();
/// <summary>
/// Is this show airing, not aired yet or finished?
/// </summary>
public Status Status { get; set; }
/// <summary>
/// How well this item is rated? (from 0 to 100).
/// </summary>
public int Rating { get; set; }
/// <summary>
/// The date this show started airing. It can be null if this is unknown.
/// </summary>
public DateOnly? StartAir { get; set; }
/// <summary>
/// The date this show finished airing.
/// It can also be null if this is unknown.
/// </summary>
public DateOnly? EndAir { get; set; }
/// <inheritdoc />
public DateTime AddedDate { get; set; }
/// <inheritdoc />
public Image? Poster { get; set; }
/// <inheritdoc />
public Image? Thumbnail { get; set; }
/// <inheritdoc />
public Image? Logo { get; set; }
/// <summary>
/// A video of a few minutes that tease the content.
/// </summary>
public string? Trailer { get; set; }
[JsonIgnore]
[Column("start_air")]
public DateOnly? AirDate => StartAir;
/// <inheritdoc />
public Dictionary<string, MetadataId> ExternalId { get; set; } = new();
/// <inheritdoc />
public DateTime? NextMetadataRefresh { get; set; }
/// <summary>
/// The ID of the Studio that made this show.
/// </summary>
public Guid? StudioId { get; set; }
/// <summary>
/// The Studio that made this show.
/// </summary>
[LoadableRelation(nameof(StudioId))]
public Studio? Studio { get; set; }
/// <summary>
/// The different seasons in this show. If this is a movie, this list is always null or empty.
/// </summary>
[JsonIgnore]
public ICollection<Season>? Seasons { get; set; }
/// <summary>
/// The list of episodes in this show.
/// If this is a movie, there will be a unique episode (with the seasonNumber and episodeNumber set to null).
/// Having an episode is necessary to store metadata and tracks.
/// </summary>
[JsonIgnore]
public ICollection<Episode>? Episodes { get; set; }
/// <summary>
/// The list of collections that contains this show.
/// </summary>
[JsonIgnore]
public ICollection<Collection>? Collections { get; set; }
/// <summary>
/// The first episode of this show.
/// </summary>
[Projectable(UseMemberBody = nameof(_FirstEpisode), OnlyOnInclude = true)]
[LoadableRelation(
// language=PostgreSQL
Sql = """
select
fe.* -- Episode as fe
from (
select
e.*,
row_number() over (partition by e.show_id order by e.absolute_number, e.season_number, e.episode_number) as number
from
episodes as e) as "fe"
where
fe.number <= 1
""",
On = "show_id = \"this\".id"
)]
public Episode? FirstEpisode { get; set; }
private Episode? _FirstEpisode =>
Episodes!
.OrderBy(x => x.AbsoluteNumber)
.ThenBy(x => x.SeasonNumber)
.ThenBy(x => x.EpisodeNumber)
.FirstOrDefault();
/// <summary>
/// The number of episodes in this show.
/// </summary>
[Projectable(UseMemberBody = nameof(_EpisodesCount), OnlyOnInclude = true)]
[NotMapped]
[LoadableRelation(
// language=PostgreSQL
Projected = """
(
select
count(*)::int
from
episodes as e
where
e.show_id = "this".id) as episodes_count
"""
)]
public int EpisodesCount { get; set; }
private int _EpisodesCount => Episodes!.Count;
[JsonIgnore]
public ICollection<ShowWatchStatus>? Watched { get; set; }
/// <summary>
/// Metadata of what an user as started/planned to watch.
/// </summary>
[Projectable(UseMemberBody = nameof(_WatchStatus), OnlyOnInclude = true)]
[LoadableRelation(
Sql = "show_watch_status",
On = "show_id = \"this\".id and \"relation\".user_id = [current_user]"
)]
public ShowWatchStatus? WatchStatus { get; set; }
// There is a global query filter to filter by user so we just need to do single.
private ShowWatchStatus? _WatchStatus => Watched!.FirstOrDefault();
/// <inheritdoc />
public void OnMerge(object merged)
{
if (Seasons != null)
{
foreach (Season season in Seasons)
season.Show = this;
}
if (Episodes != null)
{
foreach (Episode episode in Episodes)
episode.Show = this;
}
}
public Show() { }
[JsonConstructor]
public Show(string name)
{
if (name != null)
{
Slug = Utility.ToSlug(name);
Name = name;
}
}
}
/// <summary>
/// The enum containing show's status.
/// </summary>
public enum Status
{
/// <summary>
/// The status of the show is not known.
/// </summary>
Unknown,
/// <summary>
/// The show has finished airing.
/// </summary>
Finished,
/// <summary>
/// The show is still actively airing.
/// </summary>
Airing,
/// <summary>
/// This show has not aired yet but has been announced.
/// </summary>
Planned
}

View File

@@ -1,80 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using Kyoo.Abstractions.Controllers;
using Kyoo.Utils;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// A studio that make shows.
/// </summary>
public class Studio : IQuery, IResource, IMetadata
{
public static Sort DefaultSort => new Sort<Studio>.By(x => x.Name);
/// <inheritdoc />
public Guid Id { get; set; }
/// <inheritdoc />
[MaxLength(256)]
public string Slug { get; set; }
/// <summary>
/// The name of this studio.
/// </summary>
public string Name { get; set; }
/// <summary>
/// The list of shows that are made by this studio.
/// </summary>
[JsonIgnore]
public ICollection<Show>? Shows { get; set; }
/// <summary>
/// The list of movies that are made by this studio.
/// </summary>
[JsonIgnore]
public ICollection<Movie>? Movies { get; set; }
/// <inheritdoc />
public Dictionary<string, MetadataId> ExternalId { get; set; } = new();
/// <summary>
/// Create a new, empty, <see cref="Studio"/>.
/// </summary>
public Studio() { }
/// <summary>
/// Create a new <see cref="Studio"/> with a specific name, the slug is calculated automatically.
/// </summary>
/// <param name="name">The name of the studio.</param>
[JsonConstructor]
public Studio(string name)
{
if (name != null)
{
Slug = Utility.ToSlug(name);
Name = name;
}
}
}

View File

@@ -1,116 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using Kyoo.Abstractions.Controllers;
using Kyoo.Utils;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// A single user of the app.
/// </summary>
public class User : IQuery, IResource, IAddedDate
{
public static Sort DefaultSort => new Sort<User>.By(x => x.Username);
/// <inheritdoc />
public Guid Id { get; set; }
/// <inheritdoc />
[MaxLength(256)]
public string Slug { get; set; }
/// <summary>
/// A username displayed to the user.
/// </summary>
public string Username { get; set; }
/// <summary>
/// The user email address.
/// </summary>
public string Email { get; set; }
/// <summary>
/// The user password (hashed, it can't be read like that). The hashing format is implementation defined.
/// </summary>
[JsonIgnore]
public string? Password { get; set; }
/// <summary>
/// Does the user can sign-in with a password or only via oidc?
/// </summary>
public bool HasPassword => Password != null;
/// <summary>
/// The list of permissions of the user. The format of this is implementation dependent.
/// </summary>
public string[] Permissions { get; set; } = Array.Empty<string>();
/// <inheritdoc />
public DateTime AddedDate { get; set; }
/// <summary>
/// User settings
/// </summary>
public Dictionary<string, string> Settings { get; set; } = new();
/// <summary>
/// User accounts on other services.
/// </summary>
public Dictionary<string, ExternalToken> ExternalId { get; set; } = new();
public User() { }
[JsonConstructor]
public User(string username)
{
if (username != null)
{
Slug = Utility.ToSlug(username);
Username = username;
}
}
}
public class ExternalToken
{
/// <summary>
/// The id of this user on the external service.
/// </summary>
public string Id { get; set; }
/// <summary>
/// The username on the external service.
/// </summary>
public string Username { get; set; }
/// <summary>
/// The link to the user profile on this website. Null if it does not exist.
/// </summary>
public string? ProfileUrl { get; set; }
/// <summary>
/// A jwt token used to interact with the service.
/// Do not forget to refresh it when using it if necessary.
/// </summary>
public JwtToken Token { get; set; }
}

View File

@@ -1,279 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Text.Json.Serialization;
using Kyoo.Abstractions.Models.Attributes;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// Has the user started watching, is it planned?
/// </summary>
public enum WatchStatus
{
/// <summary>
/// The user has already watched this.
/// </summary>
Completed,
/// <summary>
/// The user started watching this but has not finished.
/// </summary>
Watching,
/// <summary>
/// The user does not plan to continue watching.
/// </summary>
Droped,
/// <summary>
/// The user has not started watching this but plans to.
/// </summary>
Planned,
/// <summary>
/// The watch status was deleted and can not be retrived again.
/// </summary>
Deleted,
}
/// <summary>
/// Metadata of what an user as started/planned to watch.
/// </summary>
[SqlFirstColumn(nameof(UserId))]
public class MovieWatchStatus : IAddedDate
{
/// <summary>
/// The ID of the user that started watching this episode.
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// The user that started watching this episode.
/// </summary>
[JsonIgnore]
public User User { get; set; }
/// <summary>
/// The ID of the movie started.
/// </summary>
public Guid MovieId { get; set; }
/// <summary>
/// The <see cref="Movie"/> started.
/// </summary>
[JsonIgnore]
public Movie Movie { get; set; }
/// <inheritdoc/>
public DateTime AddedDate { get; set; }
/// <summary>
/// The date at which this item was played.
/// </summary>
public DateTime? PlayedDate { get; set; }
/// <summary>
/// Has the user started watching, is it planned?
/// </summary>
public WatchStatus Status { get; set; }
/// <summary>
/// Where the player has stopped watching the movie (in seconds).
/// </summary>
/// <remarks>
/// Null if the status is not Watching.
/// </remarks>
public int? WatchedTime { get; set; }
/// <summary>
/// Where the player has stopped watching the movie (in percentage between 0 and 100).
/// </summary>
/// <remarks>
/// Null if the status is not Watching.
/// </remarks>
public int? WatchedPercent { get; set; }
}
[SqlFirstColumn(nameof(UserId))]
public class EpisodeWatchStatus : IAddedDate
{
/// <summary>
/// The ID of the user that started watching this episode.
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// The user that started watching this episode.
/// </summary>
[JsonIgnore]
public User User { get; set; }
/// <summary>
/// The ID of the episode started.
/// </summary>
public Guid? EpisodeId { get; set; }
/// <summary>
/// The <see cref="Episode"/> started.
/// </summary>
[JsonIgnore]
public Episode Episode { get; set; }
/// <inheritdoc/>
public DateTime AddedDate { get; set; }
/// <summary>
/// The date at which this item was played.
/// </summary>
public DateTime? PlayedDate { get; set; }
/// <summary>
/// Has the user started watching, is it planned?
/// </summary>
public WatchStatus Status { get; set; }
/// <summary>
/// Where the player has stopped watching the episode (in seconds).
/// </summary>
/// <remarks>
/// Null if the status is not Watching.
/// </remarks>
public int? WatchedTime { get; set; }
/// <summary>
/// Where the player has stopped watching the episode (in percentage between 0 and 100).
/// </summary>
/// <remarks>
/// Null if the status is not Watching or if the next episode is not started.
/// </remarks>
public int? WatchedPercent { get; set; }
}
[SqlFirstColumn(nameof(UserId))]
public class ShowWatchStatus : IAddedDate
{
/// <summary>
/// The ID of the user that started watching this episode.
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// The user that started watching this episode.
/// </summary>
[JsonIgnore]
public User User { get; set; }
/// <summary>
/// The ID of the show started.
/// </summary>
public Guid ShowId { get; set; }
/// <summary>
/// The <see cref="Show"/> started.
/// </summary>
[JsonIgnore]
public Show Show { get; set; }
/// <inheritdoc/>
public DateTime AddedDate { get; set; }
/// <summary>
/// The date at which this item was played.
/// </summary>
public DateTime? PlayedDate { get; set; }
/// <summary>
/// Has the user started watching, is it planned?
/// </summary>
public WatchStatus Status { get; set; }
/// <summary>
/// The number of episodes the user has not seen.
/// </summary>
public int UnseenEpisodesCount { get; set; }
/// <summary>
/// The ID of the episode started.
/// </summary>
public Guid? NextEpisodeId { get; set; }
/// <summary>
/// The next <see cref="Episode"/> to watch.
/// </summary>
public Episode? NextEpisode { get; set; }
/// <summary>
/// Where the player has stopped watching the episode (in seconds).
/// </summary>
/// <remarks>
/// Null if the status is not Watching or if the next episode is not started.
/// </remarks>
public int? WatchedTime { get; set; }
/// <summary>
/// Where the player has stopped watching the episode (in percentage between 0 and 100).
/// </summary>
/// <remarks>
/// Null if the status is not Watching or if the next episode is not started.
/// </remarks>
public int? WatchedPercent { get; set; }
}
public class WatchStatus<T> : IAddedDate
{
/// <summary>
/// Has the user started watching, is it planned?
/// </summary>
public required WatchStatus Status { get; set; }
/// <inheritdoc/>
public DateTime AddedDate { get; set; }
/// <summary>
/// The date at which this item was played.
/// </summary>
public DateTime? PlayedDate { get; set; }
/// <summary>
/// Where the player has stopped watching the episode (in seconds).
/// </summary>
/// <remarks>
/// Null if the status is not Watching or if the next episode is not started.
/// </remarks>
public int? WatchedTime { get; set; }
/// <summary>
/// Where the player has stopped watching the episode (in percentage between 0 and 100).
/// </summary>
/// <remarks>
/// Null if the status is not Watching or if the next episode is not started.
/// </remarks>
public int? WatchedPercent { get; set; }
/// <summary>
/// The user that started watching this episode.
/// </summary>
public required User User { get; set; }
/// <summary>
/// The episode/show/movie whose status changed
/// </summary>
public required T Resource { get; set; }
}

View File

@@ -1,53 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Collections.Generic;
namespace Kyoo.Abstractions.Models;
/// <summary>
/// Results of a search request.
/// </summary>
/// <typeparam name="T">The search item's type.</typeparam>
public class SearchPage<T> : Page<T>
where T : IResource
{
public SearchPage(
SearchResult result,
string @this,
string? previous,
string? next,
string first
)
: base(result.Items, @this, previous, next, first)
{
Query = result.Query;
}
/// <summary>
/// The query of the search request.
/// </summary>
public string? Query { get; init; }
public class SearchResult
{
public string? Query { get; set; }
public ICollection<T> Items { get; set; }
}
}

View File

@@ -1,55 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
namespace Kyoo.Authentication.Models;
/// <summary>
/// List of well known claims of kyoo
/// </summary>
public static class Claims
{
/// <summary>
/// The id of the user
/// </summary>
public static string Id => "id";
/// <summary>
/// The name of the user
/// </summary>
public static string Name => "name";
/// <summary>
/// The email of the user.
/// </summary>
public static string Email => "email";
/// <summary>
/// The list of permissions that the user has.
/// </summary>
public static string Permissions => "permissions";
/// <summary>
/// The type of the token (either "access" or "refresh").
/// </summary>
public static string Type => "type";
/// <summary>
/// A guid used to identify a specific refresh token. This is only useful for the server to revokate tokens.
/// </summary>
public static string Guid => "guid";
}

View File

@@ -1,60 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using Kyoo.Abstractions.Models.Attributes;
namespace Kyoo.Abstractions.Models.Utils;
/// <summary>
/// A class containing constant numbers.
/// </summary>
public static class Constants
{
/// <summary>
/// A property to use on a Microsoft.AspNet.MVC.Route.Order property to mark it as an alternative route
/// that won't be included on the swagger.
/// </summary>
public const int AlternativeRoute = 1;
/// <summary>
/// A group name for <see cref="ApiDefinitionAttribute"/>. It should be used for endpoints used by users.
/// </summary>
public const string UsersGroup = "0:Users";
/// <summary>
/// A group name for <see cref="ApiDefinitionAttribute"/>. It should be used for main resources of kyoo.
/// </summary>
public const string ResourcesGroup = "1:Resources";
/// <summary>
/// A group name for <see cref="ApiDefinitionAttribute"/>.
/// It should be used for sub resources of kyoo that help define the main resources.
/// </summary>
public const string MetadataGroup = "2:Metadata";
/// <summary>
/// A group name for <see cref="ApiDefinitionAttribute"/>. It should be used for endpoints useful for playback.
/// </summary>
public const string WatchGroup = "3:Watch";
/// <summary>
/// A group name for <see cref="ApiDefinitionAttribute"/>. It should be used for endpoints used by admins.
/// </summary>
public const string AdminGroup = "4:Admin";
public const string OtherGroup = "5:Other";
}

View File

@@ -1,369 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Kyoo.Abstractions.Models.Attributes;
using Sprache;
namespace Kyoo.Abstractions.Models.Utils;
public static class ParseHelper
{
public static Parser<T> ErrorMessage<T>(this Parser<T> @this, string message) =>
input =>
{
IResult<T> result = @this(input);
return result.WasSuccessful
? result
: Result.Failure<T>(result.Remainder, message, result.Expectations);
};
public static Parser<T> Error<T>(string message) =>
input =>
{
return Result.Failure<T>(input, message, Array.Empty<string>());
};
}
public abstract record Filter
{
public static Filter<T>? And<T>(params Filter<T>?[] filters)
{
return filters
.Where(x => x != null)
.Aggregate(
(Filter<T>?)null,
(acc, filter) =>
{
if (acc == null)
return filter;
return new Filter<T>.And(acc, filter!);
}
);
}
public static Filter<T>? Or<T>(params Filter<T>?[] filters)
{
return filters
.Where(x => x != null)
.Aggregate(
(Filter<T>?)null,
(acc, filter) =>
{
if (acc == null)
return filter;
return new Filter<T>.Or(acc, filter!);
}
);
}
}
public abstract record Filter<T> : Filter
{
public record And(Filter<T> First, Filter<T> Second) : Filter<T>;
public record Or(Filter<T> First, Filter<T> Second) : Filter<T>;
public record Not(Filter<T> Filter) : Filter<T>;
public record Eq(string Property, object? Value) : Filter<T>;
public record Ne(string Property, object? Value) : Filter<T>;
public record Gt(string Property, object Value) : Filter<T>;
public record Ge(string Property, object Value) : Filter<T>;
public record Lt(string Property, object Value) : Filter<T>;
public record Le(string Property, object Value) : Filter<T>;
public record Has(string Property, object Value) : Filter<T>;
/// <summary>
/// Internal filter used for keyset paginations to resume random sorts.
/// The pseudo sql is md5(seed || table.id) = md5(seed || 'hardCodedId')
/// </summary>
public record CmpRandom(string cmp, string Seed, Guid ReferenceId) : Filter<T>;
/// <summary>
/// Internal filter used only in EF with hard coded lamdas (used for relations).
/// </summary>
public record Lambda(Expression<Func<T, bool>> Inner) : Filter<T>;
public static class FilterParsers
{
public static readonly Parser<Filter<T>> Filter = Parse
.Ref(() => Bracket)
.Or(Parse.Ref(() => Not))
.Or(Parse.Ref(() => Eq))
.Or(Parse.Ref(() => Ne))
.Or(Parse.Ref(() => Gt))
.Or(Parse.Ref(() => Ge))
.Or(Parse.Ref(() => Lt))
.Or(Parse.Ref(() => Le))
.Or(Parse.Ref(() => Has));
public static readonly Parser<Filter<T>> CompleteFilter = Parse
.Ref(() => Or)
.Or(Parse.Ref(() => And))
.Or(Filter);
public static readonly Parser<Filter<T>> Bracket =
from open in Parse.Char('(').Token()
from filter in CompleteFilter
from close in Parse.Char(')').Token()
select filter;
public static readonly Parser<IEnumerable<char>> AndOperator = Parse
.IgnoreCase("and")
.Or(Parse.String("&&"))
.Token();
public static readonly Parser<IEnumerable<char>> OrOperator = Parse
.IgnoreCase("or")
.Or(Parse.String("||"))
.Token();
public static readonly Parser<Filter<T>> And = Parse.ChainOperator(
AndOperator,
Filter,
(_, a, b) => new And(a, b)
);
public static readonly Parser<Filter<T>> Or = Parse.ChainOperator(
OrOperator,
And.Or(Filter),
(_, a, b) => new Or(a, b)
);
public static readonly Parser<Filter<T>> Not =
from not in Parse.IgnoreCase("not").Or(Parse.String("!")).Token()
from filter in CompleteFilter
select new Not(filter);
private static Parser<object> _GetValueParser(Type type)
{
Type? nullable = Nullable.GetUnderlyingType(type);
if (nullable != null)
{
return from value in _GetValueParser(nullable) select value;
}
if (type == typeof(int))
return Parse.Number.Select(x => int.Parse(x) as object);
if (type == typeof(float))
{
return from a in Parse.Number
from dot in Parse.Char('.')
from b in Parse.Number
select float.Parse($"{a}.{b}") as object;
}
if (type == typeof(Guid))
{
return from guid in Parse.Regex(
@"[({]?[a-fA-F0-9]{8}[-]?([a-fA-F0-9]{4}[-]?){3}[a-fA-F0-9]{12}[})]?",
"Guid"
)
select Guid.Parse(guid) as object;
}
if (type == typeof(string))
{
return (
from lq in Parse.Char('"').Or(Parse.Char('\''))
from str in Parse.AnyChar.Where(x => x != lq).Many().Text()
from rq in Parse.Char(lq)
select str
).Or(Parse.LetterOrDigit.Many().Text());
}
if (type.IsEnum)
{
return Parse
.LetterOrDigit.Many()
.Text()
.Then(x =>
{
if (Enum.TryParse(type, x, true, out object? value))
return Parse.Return(value);
return ParseHelper.Error<object>($"Invalid enum value. Unexpected {x}");
});
}
if (type == typeof(DateTime) || type == typeof(DateOnly))
{
return from year in Parse.Digit.Repeat(4).Text().Select(int.Parse)
from yd in Parse.Char('-')
from month in Parse.Digit.Repeat(2).Text().Select(int.Parse)
from md in Parse.Char('-')
from day in Parse.Digit.Repeat(2).Text().Select(int.Parse)
select type == typeof(DateTime)
? new DateTime(year, month, day) as object
: new DateOnly(year, month, day) as object;
}
if (typeof(IEnumerable).IsAssignableFrom(type))
return ParseHelper.Error<object>(
"Can't filter a list with a default comparator, use the 'has' filter."
);
return ParseHelper.Error<object>("Unfilterable field found");
}
private static Parser<Filter<T>> _GetOperationParser(
Parser<object> op,
Func<string, object, Filter<T>> apply,
Func<Type, Parser<object?>>? customTypeParser = null
)
{
Parser<string> property = Parse.LetterOrDigit.AtLeastOnce().Text();
return property.Then(prop =>
{
Type[] types =
typeof(T).GetCustomAttribute<OneOfAttribute>()?.Types ?? new[] { typeof(T) };
if (string.Equals(prop, "kind", StringComparison.OrdinalIgnoreCase))
{
return from eq in op
from val in types
.Select(x => Parse.IgnoreCase(x.Name).Text())
.Aggregate(
null as Parser<string>,
(acc, x) => acc == null ? x : Parse.Or(acc, x)
)
select apply("kind", val);
}
PropertyInfo? propInfo = types
.Select(x =>
x.GetProperty(
prop,
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance
)
)
.FirstOrDefault();
if (propInfo == null)
return ParseHelper.Error<Filter<T>>($"The given filter '{prop}' is invalid.");
Parser<object?> value =
customTypeParser != null
? customTypeParser(propInfo.PropertyType)
: _GetValueParser(propInfo.PropertyType);
return from eq in op
from val in value
select apply(propInfo.Name, val);
});
}
public static readonly Parser<Filter<T>> Eq = _GetOperationParser(
Parse.IgnoreCase("eq").Or(Parse.String("=")).Token(),
(property, value) => new Eq(property, value),
(Type type) =>
{
Type? inner = Nullable.GetUnderlyingType(type);
if (inner == null)
return _GetValueParser(type);
return Parse
.String("null")
.Token()
.Return((object?)null)
.Or(_GetValueParser(inner));
}
);
public static readonly Parser<Filter<T>> Ne = _GetOperationParser(
Parse.IgnoreCase("ne").Or(Parse.String("!=")).Token(),
(property, value) => new Ne(property, value),
(Type type) =>
{
Type? inner = Nullable.GetUnderlyingType(type);
if (inner == null)
return _GetValueParser(type);
return Parse
.String("null")
.Token()
.Return((object?)null)
.Or(_GetValueParser(inner));
}
);
public static readonly Parser<Filter<T>> Gt = _GetOperationParser(
Parse.IgnoreCase("gt").Or(Parse.String(">")).Token(),
(property, value) => new Gt(property, value)
);
public static readonly Parser<Filter<T>> Ge = _GetOperationParser(
Parse.IgnoreCase("ge").Or(Parse.IgnoreCase("gte")).Or(Parse.String(">=")).Token(),
(property, value) => new Ge(property, value)
);
public static readonly Parser<Filter<T>> Lt = _GetOperationParser(
Parse.IgnoreCase("lt").Or(Parse.String("<")).Token(),
(property, value) => new Lt(property, value)
);
public static readonly Parser<Filter<T>> Le = _GetOperationParser(
Parse.IgnoreCase("le").Or(Parse.IgnoreCase("lte")).Or(Parse.String("<=")).Token(),
(property, value) => new Le(property, value)
);
public static readonly Parser<Filter<T>> Has = _GetOperationParser(
Parse.IgnoreCase("has").Token(),
(property, value) => new Has(property, value),
(Type type) =>
{
if (typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string))
return _GetValueParser(
type.GetElementType() ?? type.GenericTypeArguments.First()
);
return ParseHelper.Error<object>("Can't use 'has' on a non-list.");
}
);
}
public static Filter<T>? From(string? filter)
{
if (filter == null)
return null;
try
{
IResult<Filter<T>> ret = FilterParsers.CompleteFilter.End().TryParse(filter);
if (ret.WasSuccessful)
return ret.Value;
throw new ValidationException(
$"Could not parse filter argument: {ret.Message}. Not parsed: {filter[ret.Remainder.Position..]}"
);
}
catch (ParseException ex)
{
throw new ValidationException($"Could not parse filter argument: {ex.Message}.");
}
}
}

View File

@@ -1,245 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace Kyoo.Abstractions.Models.Utils;
/// <summary>
/// A class that represent a resource. It is made to be used as a parameter in a query and not used somewhere else
/// on the application.
/// This class allow routes to be used via ether IDs or Slugs, this is suitable for every <see cref="IResource"/>.
/// </summary>
[TypeConverter(typeof(IdentifierConvertor))]
public class Identifier
{
/// <summary>
/// The ID of the resource or null if the slug is specified.
/// </summary>
private readonly Guid? _id;
/// <summary>
/// The slug of the resource or null if the id is specified.
/// </summary>
private readonly string? _slug;
/// <summary>
/// Create a new <see cref="Identifier"/> for the given id.
/// </summary>
/// <param name="id">The id of the resource.</param>
public Identifier(Guid id)
{
_id = id;
}
/// <summary>
/// Create a new <see cref="Identifier"/> for the given slug.
/// </summary>
/// <param name="slug">The slug of the resource.</param>
public Identifier(string slug)
{
_slug = slug;
}
/// <summary>
/// Pattern match out of the identifier to a resource.
/// </summary>
/// <param name="idFunc">The function to match the ID to a type <typeparamref name="T"/>.</param>
/// <param name="slugFunc">The function to match the slug to a type <typeparamref name="T"/>.</param>
/// <typeparam name="T">The return type that will be converted to from an ID or a slug.</typeparam>
/// <returns>
/// The result of the <paramref name="idFunc"/> or <paramref name="slugFunc"/> depending on the pattern.
/// </returns>
/// <example>
/// Example usage:
/// <code lang="csharp">
/// T ret = await identifier.Match(
/// id => _repository.GetOrDefault(id),
/// slug => _repository.GetOrDefault(slug)
/// );
/// </code>
/// </example>
public T Match<T>(Func<Guid, T> idFunc, Func<string, T> slugFunc)
{
return _id.HasValue ? idFunc(_id.Value) : slugFunc(_slug!);
}
/// <summary>
/// Match a custom type to an identifier. This can be used for wrapped resources (see example for more details).
/// </summary>
/// <param name="idGetter">An expression to retrieve an ID from the type <typeparamref name="T"/>.</param>
/// <param name="slugGetter">An expression to retrieve a slug from the type <typeparamref name="T"/>.</param>
/// <typeparam name="T">The type to match against this identifier.</typeparam>
/// <returns>An expression to match the type <typeparamref name="T"/> to this identifier.</returns>
/// <example>
/// <code lang="csharp">
/// identifier.Matcher&lt;Season&gt;(x => x.ShowID, x => x.Show.Slug)
/// </code>
/// </example>
public Filter<T> Matcher<T>(
Expression<Func<T, Guid>> idGetter,
Expression<Func<T, string>> slugGetter
)
{
ConstantExpression self = Expression.Constant(_id.HasValue ? _id.Value : _slug);
BinaryExpression equal = Expression.Equal(
_id.HasValue ? idGetter.Body : slugGetter.Body,
self
);
ICollection<ParameterExpression> parameters = _id.HasValue
? idGetter.Parameters
: slugGetter.Parameters;
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(equal, parameters);
return new Filter<T>.Lambda(lambda);
}
/// <summary>
/// A matcher overload for nullable IDs. See
/// <see cref="Matcher{T}(Expression{Func{T,Guid}},Expression{Func{T,string}})"/>
/// for more details.
/// </summary>
/// <param name="idGetter">An expression to retrieve an ID from the type <typeparamref name="T"/>.</param>
/// <param name="slugGetter">An expression to retrieve a slug from the type <typeparamref name="T"/>.</param>
/// <typeparam name="T">The type to match against this identifier.</typeparam>
/// <returns>An expression to match the type <typeparamref name="T"/> to this identifier.</returns>
public Filter<T> Matcher<T>(
Expression<Func<T, Guid?>> idGetter,
Expression<Func<T, string>> slugGetter
)
{
ConstantExpression self = Expression.Constant(_id.HasValue ? _id.Value : _slug);
BinaryExpression equal = Expression.Equal(
_id.HasValue ? idGetter.Body : slugGetter.Body,
self
);
ICollection<ParameterExpression> parameters = _id.HasValue
? idGetter.Parameters
: slugGetter.Parameters;
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(equal, parameters);
return new Filter<T>.Lambda(lambda);
}
/// <summary>
/// Return true if this <see cref="Identifier"/> match a resource.
/// </summary>
/// <param name="resource">The resource to match</param>
/// <returns>
/// <c>true</c> if the <paramref name="resource"/> match this identifier, <c>false</c> otherwise.
/// </returns>
public bool IsSame(IResource resource)
{
return Match(id => resource.Id == id, slug => resource.Slug == slug);
}
/// <summary>
/// Return a filter to get this <see cref="Identifier"/> match a given resource.
/// </summary>
/// <typeparam name="T">The type of resource to match against.</typeparam>
/// <returns>
/// <c>true</c> if the given resource match this identifier, <c>false</c> otherwise.
/// </returns>
public Filter<T> IsSame<T>()
where T : IResource
{
return _id.HasValue ? new Filter<T>.Eq("Id", _id.Value) : new Filter<T>.Eq("Slug", _slug!);
}
public bool Is(Guid uid)
{
return _id.HasValue && _id.Value == uid;
}
public bool Is(string slug)
{
return !_id.HasValue && _slug == slug;
}
private Expression<Func<T, bool>> _IsSameExpression<T>()
where T : IResource
{
return _id.HasValue ? x => x.Id == _id.Value : x => x.Slug == _slug;
}
/// <summary>
/// Return an expression that return true if this <see cref="Identifier"/> is containing in a collection.
/// </summary>
/// <param name="listGetter">An expression to retrieve the list to check.</param>
/// <typeparam name="T">The type that contain the list to check.</typeparam>
/// <typeparam name="T2">The type of resource to check this identifier against.</typeparam>
/// <returns>An expression to check if this <see cref="Identifier"/> is contained.</returns>
public Filter<T> IsContainedIn<T, T2>(Expression<Func<T, IEnumerable<T2>?>> listGetter)
where T2 : IResource
{
MethodInfo method = typeof(Enumerable)
.GetMethods()
.Where(x => x.Name == nameof(Enumerable.Any))
.FirstOrDefault(x => x.GetParameters().Length == 2)!
.MakeGenericMethod(typeof(T2));
MethodCallExpression call = Expression.Call(
null,
method,
listGetter.Body,
_IsSameExpression<T2>()
);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(
call,
listGetter.Parameters
);
return new Filter<T>.Lambda(lambda);
}
/// <inheritdoc />
public override string ToString()
{
return _id.HasValue ? _id.Value.ToString() : _slug!;
}
/// <summary>
/// A custom <see cref="TypeConverter"/> used to convert int or strings to an <see cref="Identifier"/>.
/// </summary>
public class IdentifierConvertor : TypeConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{
if (sourceType == typeof(Guid) || sourceType == typeof(string))
return true;
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override object ConvertFrom(
ITypeDescriptorContext? context,
CultureInfo? culture,
object value
)
{
if (value is Guid id)
return new Identifier(id);
if (value is not string slug)
return base.ConvertFrom(context, culture, value)!;
return Guid.TryParse(slug, out id) ? new Identifier(id) : new Identifier(slug);
}
}
}

View File

@@ -1,109 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using Kyoo.Abstractions.Models.Attributes;
namespace Kyoo.Abstractions.Models.Utils;
public class Include
{
/// <summary>
/// The aditional fields to include in the result.
/// </summary>
public ICollection<Metadata> Metadatas { get; set; } = ArraySegment<Metadata>.Empty;
public abstract record Metadata(string Name);
public record SingleRelation(string Name, Type type, string RelationIdName) : Metadata(Name);
public record CustomRelation(string Name, Type type, string Sql, string? On, Type Declaring)
: Metadata(Name);
public record ProjectedRelation(string Name, string Sql) : Metadata(Name);
}
/// <summary>
/// The aditional fields to include in the result.
/// </summary>
/// <typeparam name="T">The type related to the new fields</typeparam>
public class Include<T> : Include
{
/// <summary>
/// The aditional fields names to include in the result.
/// </summary>
public ICollection<string> Fields => Metadatas.Select(x => x.Name).ToList();
public Include() { }
public Include(params string[] fields)
{
Type[] types = typeof(T).GetCustomAttribute<OneOfAttribute>()?.Types ?? new[] { typeof(T) };
Metadatas = fields
.SelectMany(key =>
{
var relations = types
.Select(x =>
x.GetProperty(
key,
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance
)!
)
.Select(prop =>
(prop, attr: prop?.GetCustomAttribute<LoadableRelationAttribute>()!)
)
.Where(x => x.prop != null && x.attr != null)
.ToList();
if (!relations.Any())
throw new ValidationException($"No loadable relation with the name {key}.");
return relations
.Select(x =>
{
(PropertyInfo prop, LoadableRelationAttribute attr) = x;
if (attr.RelationID != null)
return new SingleRelation(prop.Name, prop.PropertyType, attr.RelationID)
as Metadata;
if (attr.Sql != null)
return new CustomRelation(
prop.Name,
prop.PropertyType,
attr.Sql,
attr.On,
prop.DeclaringType!
);
if (attr.Projected != null)
return new ProjectedRelation(prop.Name, attr.Projected);
throw new NotImplementedException();
})
.Distinct();
})
.ToArray();
}
public static Include<T> From(string? fields)
{
if (string.IsNullOrEmpty(fields))
return new Include<T>();
return new Include<T>(fields.Split(','));
}
}

View File

@@ -1,72 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
namespace Kyoo.Abstractions.Controllers;
/// <summary>
/// Information about the pagination. How many items should be displayed and where to start.
/// </summary>
public class Pagination
{
/// <summary>
/// The count of items to return.
/// </summary>
public int Limit { get; set; }
/// <summary>
/// Where to start? Using the given sort.
/// </summary>
public Guid? AfterID { get; set; }
/// <summary>
/// Should the previous page be returned instead of the next?
/// </summary>
public bool Reverse { get; set; }
/// <summary>
/// Create a new <see cref="Pagination"/> with default values.
/// </summary>
public Pagination()
{
Limit = 50;
AfterID = null;
Reverse = false;
}
/// <summary>
/// Create a new <see cref="Pagination"/> instance.
/// </summary>
/// <param name="count">Set the <see cref="Limit"/> value</param>
/// <param name="afterID">Set the <see cref="AfterID"/> value. If not specified, it will start from the start</param>
/// <param name="reverse">Should the previous page be returned instead of the next?</param>
public Pagination(int count, Guid? afterID = null, bool reverse = false)
{
Limit = count;
AfterID = afterID;
Reverse = reverse;
}
/// <summary>
/// Implicitly create a new pagination from a limit number.
/// </summary>
/// <param name="limit">Set the <see cref="Limit"/> value</param>
/// <returns>A new <see cref="Pagination"/> instance</returns>
public static implicit operator Pagination(int limit) => new(limit);
}

View File

@@ -1,56 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Linq;
namespace Kyoo.Abstractions.Models.Utils;
/// <summary>
/// The list of errors that where made in the request.
/// </summary>
public class RequestError
{
/// <summary>
/// The list of errors that where made in the request.
/// </summary>
/// <example><c>["InvalidFilter: no field 'startYear' on a collection"]</c></example>
public string[] Errors { get; set; }
/// <summary>
/// Create a new <see cref="RequestError"/> with one error.
/// </summary>
/// <param name="error">The error to specify in the response.</param>
public RequestError(string error)
{
if (error == null)
throw new ArgumentNullException(nameof(error));
Errors = new[] { error };
}
/// <summary>
/// Create a new <see cref="RequestError"/> with multiple errors.
/// </summary>
/// <param name="errors">The errors to specify in the response.</param>
public RequestError(string[] errors)
{
if (errors == null || !errors.Any())
throw new ArgumentException("Errors must be non null and not empty", nameof(errors));
Errors = errors;
}
}

View File

@@ -1,35 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
namespace Kyoo.Abstractions.Controllers;
/// <summary>
/// Information about the pagination. How many items should be displayed and where to start.
/// </summary>
public class SearchPagination
{
/// <summary>
/// The count of items to return.
/// </summary>
public int Limit { get; set; } = 50;
/// <summary>
/// Where to start? How many items to skip?
/// </summary>
public int? Skip { get; set; }
}

View File

@@ -1,137 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
namespace Kyoo.Abstractions.Controllers;
public record Sort;
/// <summary>
/// Information about how a query should be sorted. What factor should decide the sort and in which order.
/// </summary>
/// <typeparam name="T">For witch type this sort applies</typeparam>
public record Sort<T> : Sort
where T : IQuery
{
/// <summary>
/// Sort by a specific key
/// </summary>
/// <param name="Key">The sort keys. This members will be used to sort the results.</param>
/// <param name="Desendant">
/// If this is set to true, items will be sorted in descend order else, they will be sorted in ascendant order.
/// </param>
public record By(string Key, bool Desendant = false) : Sort<T>
{
/// <summary>
/// Sort by a specific key
/// </summary>
/// <param name="key">The sort keys. This members will be used to sort the results.</param>
/// <param name="desendant">
/// If this is set to true, items will be sorted in descend order else, they will be sorted in ascendant order.
/// </param>
public By(Expression<Func<T, object?>> key, bool desendant = false)
: this(Utility.GetPropertyName(key), desendant) { }
}
/// <summary>
/// Sort by multiple keys.
/// </summary>
/// <param name="List">The list of keys to sort by.</param>
public record Conglomerate(params Sort<T>[] List) : Sort<T>;
/// <summary>Sort randomly items</summary>
public record Random(uint Seed) : Sort<T>
{
public Random()
: this(0)
{
uint seed = BitConverter.ToUInt32(
BitConverter.GetBytes(new System.Random().Next(int.MinValue, int.MaxValue)),
0
);
Seed = seed;
}
}
/// <summary>The default sort method for the given type.</summary>
public record Default : Sort<T>
{
public void Deconstruct(out Sort<T> value)
{
value = (Sort<T>)T.DefaultSort;
}
}
/// <summary>
/// Create a new <see cref="Sort{T}"/> instance from a key's name (case insensitive).
/// </summary>
/// <param name="sortBy">A key name with an optional order specifier. Format: "key:asc", "key:desc" or "key".</param>
/// <param name="seed">The random seed.</param>
/// <exception cref="ArgumentException">An invalid key or sort specifier as been given.</exception>
/// <returns>A <see cref="Sort{T}"/> for the given string</returns>
public static Sort<T> From(string? sortBy, uint seed)
{
if (string.IsNullOrEmpty(sortBy) || sortBy == "default")
return new Default();
if (sortBy == "random")
return new Random(seed);
if (sortBy.Contains(','))
return new Conglomerate(sortBy.Split(',').Select(x => From(x, seed)).ToArray());
if (sortBy.StartsWith("random:"))
{
if (uint.TryParse(sortBy["random:".Length..], out uint sseed))
return new Random(sseed);
throw new ValidationException("Invalid random seed specified. Expected a number.");
}
string key = sortBy.Contains(':') ? sortBy[..sortBy.IndexOf(':')] : sortBy;
string? order = sortBy.Contains(':') ? sortBy[(sortBy.IndexOf(':') + 1)..] : null;
bool desendant = order switch
{
"desc" => true,
"asc" => false,
null => false,
_
=> throw new ValidationException(
$"The sort order, if set, should be :asc or :desc but it was :{order}."
)
};
Type[] types = typeof(T).GetCustomAttribute<OneOfAttribute>()?.Types ?? new[] { typeof(T) };
PropertyInfo? property = types
.Select(x =>
x.GetProperty(
key,
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance
)
)
.FirstOrDefault(x => x != null);
if (property == null)
throw new ValidationException("The given sort key is not valid.");
return new By(property.Name, desendant);
}
}

View File

@@ -1,35 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
namespace Kyoo.Abstractions.Models;
/// <summary>
/// The links to see a movie or an episode.
/// </summary>
public class VideoLinks
{
/// <summary>
/// The direct link to the unprocessed video (pristine quality).
/// </summary>
public string Direct { get; set; }
/// <summary>
/// The link to an HLS master playlist containing all qualities available for this video.
/// </summary>
public string Hls { get; set; }
}

View File

@@ -1,51 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace Kyoo.Utils;
public sealed class ExpressionArgumentReplacer : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, Expression> _mapping;
public ExpressionArgumentReplacer(Dictionary<ParameterExpression, Expression> dict)
{
_mapping = dict;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (_mapping.TryGetValue(node, out Expression? mappedArgument))
return Visit(mappedArgument);
return base.VisitParameter(node);
}
public static Expression ReplaceParams(
Expression expression,
IEnumerable<ParameterExpression> epxParams,
params ParameterExpression[] param
)
{
ExpressionArgumentReplacer replacer =
new(epxParams.Zip(param).ToDictionary(x => x.First, x => x.Second as Expression));
return replacer.Visit(expression);
}
}

View File

@@ -1,78 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using static System.Text.Json.JsonNamingPolicy;
namespace Kyoo.Utils;
public class JsonKindResolver : DefaultJsonTypeInfoResolver
{
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options);
if (jsonTypeInfo.Type.GetCustomAttribute<OneOfAttribute>() != null)
{
jsonTypeInfo.PolymorphismOptions = new()
{
TypeDiscriminatorPropertyName = "kind",
IgnoreUnrecognizedTypeDiscriminators = true,
DerivedTypes = { },
};
IEnumerable<Type> derived = AppDomain
.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => type.IsAssignableFrom(p) && p.IsClass);
foreach (Type der in derived)
{
jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(
new JsonDerivedType(der, CamelCase.ConvertName(der.Name))
);
}
}
else if (
jsonTypeInfo.Type.IsAssignableTo(typeof(IResource))
&& jsonTypeInfo.Properties.All(x => x.Name != "kind")
)
{
jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions
{
TypeDiscriminatorPropertyName = "kind",
IgnoreUnrecognizedTypeDiscriminators = true,
DerivedTypes =
{
new JsonDerivedType(
jsonTypeInfo.Type,
CamelCase.ConvertName(jsonTypeInfo.Type.Name)
),
},
};
}
return jsonTypeInfo;
}
}

View File

@@ -1,212 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
namespace Kyoo.Utils;
/// <summary>
/// A set of utility functions that can be used everywhere.
/// </summary>
public static class Utility
{
public static readonly JsonSerializerOptions JsonOptions =
new()
{
TypeInfoResolver = new JsonKindResolver(),
Converters = { new JsonStringEnumConverter() },
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
/// <summary>
/// Convert a string to snake case. Stollen from
/// https://github.com/efcore/EFCore.NamingConventions/blob/main/EFCore.NamingConventions/Internal/SnakeCaseNameRewriter.cs
/// </summary>
/// <param name="name">The string to convert.</param>
/// <returns>The string in snake case</returns>
public static string ToSnakeCase(this string name)
{
StringBuilder builder = new(name.Length + Math.Min(2, name.Length / 5));
UnicodeCategory? previousCategory = default;
for (int currentIndex = 0; currentIndex < name.Length; currentIndex++)
{
char currentChar = name[currentIndex];
if (currentChar == '_')
{
builder.Append('_');
previousCategory = null;
continue;
}
UnicodeCategory currentCategory = char.GetUnicodeCategory(currentChar);
switch (currentCategory)
{
case UnicodeCategory.UppercaseLetter:
case UnicodeCategory.TitlecaseLetter:
if (
previousCategory == UnicodeCategory.SpaceSeparator
|| previousCategory == UnicodeCategory.LowercaseLetter
|| (
previousCategory != UnicodeCategory.DecimalDigitNumber
&& previousCategory != null
&& currentIndex > 0
&& currentIndex + 1 < name.Length
&& char.IsLower(name[currentIndex + 1])
)
)
{
builder.Append('_');
}
currentChar = char.ToLowerInvariant(currentChar);
break;
case UnicodeCategory.LowercaseLetter:
case UnicodeCategory.DecimalDigitNumber:
if (previousCategory == UnicodeCategory.SpaceSeparator)
{
builder.Append('_');
}
break;
default:
if (previousCategory != null)
{
previousCategory = UnicodeCategory.SpaceSeparator;
}
continue;
}
builder.Append(currentChar);
previousCategory = currentCategory;
}
return builder.ToString();
}
/// <summary>
/// Is the lambda expression a member (like x => x.Body).
/// </summary>
/// <param name="ex">The expression that should be checked</param>
/// <returns>True if the expression is a member, false otherwise</returns>
public static bool IsPropertyExpression(LambdaExpression ex)
{
return ex.Body is MemberExpression
|| (
ex.Body.NodeType == ExpressionType.Convert
&& ((UnaryExpression)ex.Body).Operand is MemberExpression
);
}
/// <summary>
/// Get the name of a property. Useful for selectors as members ex: Load(x => x.Shows)
/// </summary>
/// <param name="ex">The expression</param>
/// <returns>The name of the expression</returns>
/// <exception cref="ArgumentException">If the expression is not a property, ArgumentException is thrown.</exception>
public static string GetPropertyName(LambdaExpression ex)
{
if (!IsPropertyExpression(ex))
throw new ArgumentException($"{ex} is not a property expression.");
MemberExpression? member =
ex.Body.NodeType == ExpressionType.Convert
? ((UnaryExpression)ex.Body).Operand as MemberExpression
: ex.Body as MemberExpression;
return member!.Member.Name;
}
/// <summary>
/// Slugify a string (Replace spaces by -, Uniformize accents)
/// </summary>
/// <param name="str">The string to slugify</param>
/// <returns>The slug version of the given string</returns>
public static string ToSlug(string str)
{
str = str.ToLowerInvariant();
string normalizedString = str.Normalize(NormalizationForm.FormD);
StringBuilder stringBuilder = new();
foreach (char c in normalizedString)
{
UnicodeCategory unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
if (unicodeCategory != UnicodeCategory.NonSpacingMark)
stringBuilder.Append(c);
}
str = stringBuilder.ToString().Normalize(NormalizationForm.FormC);
str = Regex.Replace(str, @"\s", "-", RegexOptions.Compiled);
str = Regex.Replace(str, @"[^\w\s\p{Pd}]", string.Empty, RegexOptions.Compiled);
str = str.Trim('-', '_');
str = Regex.Replace(str, @"([-_]){2,}", "$1", RegexOptions.Compiled);
return str;
}
/// <summary>
/// Return every <see cref="Type"/> in the inheritance tree of the parameter (interfaces are not returned)
/// </summary>
/// <param name="self">The starting type</param>
/// <returns>A list of types</returns>
public static IEnumerable<Type> GetInheritanceTree(this Type self)
{
for (Type? type = self; type != null; type = type.BaseType)
yield return type;
}
/// <summary>
/// Get the generic definition of <paramref name="genericType"/>.
/// For example, calling this function with List&lt;string&gt; and typeof(IEnumerable&lt;&gt;) will return IEnumerable&lt;string&gt;
/// </summary>
/// <param name="type">The type to check</param>
/// <param name="genericType">The generic type to check against (Only generic types are supported like typeof(IEnumerable&lt;&gt;).</param>
/// <returns>The generic definition of genericType that type inherit or null if type does not implement the generic type.</returns>
/// <exception cref="ArgumentException"><paramref name="genericType"/> must be a generic type</exception>
public static Type? GetGenericDefinition(Type type, Type genericType)
{
if (!genericType.IsGenericType)
throw new ArgumentException($"{nameof(genericType)} is not a generic type.");
IEnumerable<Type> types = genericType.IsInterface
? type.GetInterfaces()
: type.GetInheritanceTree();
return types
.Prepend(type)
.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType);
}
/// <summary>
/// Convert a dictionary to a query string.
/// </summary>
/// <param name="query">The list of query parameters.</param>
/// <returns>A valid query string with all items in the dictionary.</returns>
public static string ToQueryString(this Dictionary<string, string> query)
{
if (!query.Any())
return string.Empty;
return "?" + string.Join('&', query.Select(x => $"{x.Key}={x.Value}"));
}
}

View File

@@ -1,47 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Data;
using Dapper;
namespace Kyoo.Utils;
// Only used due to https://github.com/DapperLib/Dapper/issues/332
public class Wrapper
{
public object Value { get; set; }
public Wrapper(object value)
{
Value = value;
}
public class Handler : SqlMapper.TypeHandler<Wrapper>
{
public override Wrapper? Parse(object value)
{
throw new NotImplementedException("Wrapper should only be used to write");
}
public override void SetValue(IDbDataParameter parameter, Wrapper? value)
{
parameter.Value = value?.Value;
}
}
}

View File

@@ -1,22 +0,0 @@
using System;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Kyoo.Authentication.Attributes;
/// <summary>
/// Disables the action if the specified environment variable is set to true.
/// </summary>
public class DisableOnEnvVarAttribute(string varName) : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var config = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>();
if (config.GetValue(varName, false))
context.Result = new Microsoft.AspNetCore.Mvc.NotFoundResult();
}
public void OnResourceExecuted(ResourceExecutedContext context) { }
}

View File

@@ -1,165 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Authentication.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives;
using Microsoft.IdentityModel.Tokens;
using Serilog;
namespace Kyoo.Authentication;
public static class AuthenticationModule
{
public static void ConfigureAuthentication(this WebApplicationBuilder builder)
{
PermissionOption options =
new()
{
Default = builder
.Configuration.GetValue("UNLOGGED_PERMISSIONS", "")!
.Split(',')
.Where(x => x.Length > 0)
.ToArray(),
NewUser = builder
.Configuration.GetValue("DEFAULT_PERMISSIONS", "overall.read,overall.play")!
.Split(','),
RequireVerification = builder.Configuration.GetValue(
"REQUIRE_ACCOUNT_VERIFICATION",
true
),
PublicUrl =
builder.Configuration.GetValue<string?>("PUBLIC_URL")
?? "http://localhost:8901",
ApiKeys = builder.Configuration.GetValue("KYOO_APIKEYS", string.Empty)!.Split(','),
OIDC = builder
.Configuration.AsEnumerable()
.Where((pair) => pair.Key.StartsWith("OIDC_"))
.Aggregate(
new Dictionary<string, OidcProvider>(),
(acc, val) =>
{
if (val.Value is null)
return acc;
if (val.Key.Split("_") is not ["OIDC", string provider, string key])
{
Log.Error("Invalid oidc config value: {Key}", val.Key);
return acc;
}
provider = provider.ToLowerInvariant();
key = key.ToLowerInvariant();
if (!acc.ContainsKey(provider))
acc.Add(provider, new(provider));
switch (key)
{
case "clientid":
acc[provider].ClientId = val.Value;
break;
case "secret":
acc[provider].Secret = val.Value;
break;
case "scope":
acc[provider].Scope = val.Value;
break;
case "authorization":
acc[provider].AuthorizationUrl = val.Value;
break;
case "token":
acc[provider].TokenUrl = val.Value;
break;
case "userinfo":
case "profile":
acc[provider].ProfileUrl = val.Value;
break;
case "name":
acc[provider].DisplayName = val.Value;
break;
case "logo":
acc[provider].LogoUrl = val.Value;
break;
case "clientauthmethod":
case "authmethod":
case "auth":
case "method":
if (!Enum.TryParse(val.Value, out AuthMethod method))
{
Log.Error(
"Invalid AuthMethod value: {AuthMethod}. Ignoring.",
val.Value
);
break;
}
acc[provider].ClientAuthMethod = method;
break;
default:
Log.Error("Invalid oidc config value: {Key}", key);
return acc;
}
return acc;
}
),
};
builder.Services.AddSingleton(options);
byte[] secret = builder.Configuration.GetValue<byte[]>("AUTHENTICATION_SECRET")!;
builder.Services.AddSingleton(new AuthenticationOption() { Secret = secret });
builder
.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Events = new()
{
OnMessageReceived = (ctx) =>
{
string prefix = "Bearer ";
if (
ctx.Request.Headers.TryGetValue("Authorization", out StringValues val)
&& val.ToString() is string auth
&& auth.StartsWith(prefix)
)
{
ctx.Token ??= auth[prefix.Length..];
}
ctx.Token ??= ctx.Request.Cookies["X-Bearer"];
return Task.CompletedTask;
}
};
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(secret)
};
});
builder.Services.AddSingleton<IPermissionValidator, PermissionValidator>();
builder.Services.AddSingleton<ITokenController, TokenController>();
}
}

View File

@@ -1,53 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Threading.Tasks;
using Kyoo.Abstractions.Models;
using Microsoft.IdentityModel.Tokens;
namespace Kyoo.Authentication;
/// <summary>
/// The service that controls jwt creation and validation.
/// </summary>
public interface ITokenController
{
/// <summary>
/// Create a new access token for the given user.
/// </summary>
/// <param name="user">The user to create a token for.</param>
/// <param name="expireIn">When this token will expire.</param>
/// <returns>A new, valid access token.</returns>
string CreateAccessToken(User user, out TimeSpan expireIn);
/// <summary>
/// Create a new refresh token for the given user.
/// </summary>
/// <param name="user">The user to create a token for.</param>
/// <returns>A new, valid refresh token.</returns>
Task<string> CreateRefreshToken(User user);
/// <summary>
/// Check if the given refresh token is valid and if it is, retrieve the id of the user this token belongs to.
/// </summary>
/// <param name="refreshToken">The refresh token to validate.</param>
/// <exception cref="SecurityTokenException">The given refresh token is not valid.</exception>
/// <returns>The id of the token's user.</returns>
Guid GetRefreshTokenUserID(string refreshToken);
}

View File

@@ -1,143 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Authentication.Models;
using Kyoo.Authentication.Models.DTO;
namespace Kyoo.Authentication;
public class OidcController(
IUserRepository users,
IHttpClientFactory clientFactory,
PermissionOption options
)
{
private async Task<(User, ExternalToken)> _TranslateCode(string provider, string code)
{
OidcProvider prov = options.OIDC[provider];
HttpClient client = clientFactory.CreateClient();
Dictionary<string, string> data =
new()
{
["code"] = code,
["redirect_uri"] = $"{options.PublicUrl.TrimEnd('/')}/api/auth/logged/{provider}",
["grant_type"] = "authorization_code",
};
if (prov.ClientAuthMethod == AuthMethod.ClientSecretBasic)
{
string auth = Convert.ToBase64String(
Encoding.UTF8.GetBytes($"{prov.ClientId}:{prov.Secret}")
);
client.DefaultRequestHeaders.Add("Authorization", $"Basic {auth}");
}
else if (prov.ClientAuthMethod == AuthMethod.ClientSecretPost)
{
data["client_id"] = prov.ClientId;
data["client_secret"] = prov.Secret;
}
HttpResponseMessage resp = prov.TokenUseJsonBody
? await client.PostAsJsonAsync(prov.TokenUrl, data)
: await client.PostAsync(prov.TokenUrl, new FormUrlEncodedContent(data));
if (!resp.IsSuccessStatusCode)
throw new ValidationException(
$"Invalid code or configuration. {resp.StatusCode}: {await resp.Content.ReadAsStringAsync()}"
);
JwtToken? token = await resp.Content.ReadFromJsonAsync<JwtToken>();
if (token is null)
throw new ValidationException("Could not retrive token.");
client.DefaultRequestHeaders.Remove("Authorization");
client.DefaultRequestHeaders.Add("Authorization", $"{token.TokenType} {token.AccessToken}");
Dictionary<string, string>? extraHeaders = prov.GetExtraHeaders?.Invoke(prov);
if (extraHeaders is not null)
{
foreach ((string key, string value) in extraHeaders)
client.DefaultRequestHeaders.Add(key, value);
}
JwtProfile? profile = await client.GetFromJsonAsync<JwtProfile>(prov.ProfileUrl);
if (profile is null || profile.Sub is null)
throw new ValidationException(
$"Missing sub on user object. Got: {JsonSerializer.Serialize(profile)}"
);
ExternalToken extToken =
new()
{
Id = profile.Sub,
Token = token,
ProfileUrl = prov.GetProfileUrl?.Invoke(profile),
};
User newUser = new();
if (profile.Email is not null)
newUser.Email = profile.Email;
if (profile.Username is null)
{
throw new ValidationException(
$"Could not find a username for the user. You may need to add more scopes. Fields: {string.Join(',', profile.Extra)}"
);
}
extToken.Username = profile.Username;
newUser.Username = profile.Username;
newUser.Slug = Utils.Utility.ToSlug(newUser.Username);
newUser.ExternalId.Add(provider, extToken);
return (newUser, extToken);
}
public async Task<User> LoginViaCode(string provider, string code)
{
(User newUser, ExternalToken extToken) = await _TranslateCode(provider, code);
User? user = await users.GetByExternalId(provider, extToken.Id);
if (user == null)
{
try
{
user = await users.Create(newUser);
}
catch
{
throw new ValidationException(
"A user already exists with the same username. If this is you, login via username and then link your account."
);
}
}
return user;
}
public async Task<User> LinkAccountOrLogin(Guid userId, string provider, string code)
{
(_, ExternalToken extToken) = await _TranslateCode(provider, code);
User? user = await users.GetByExternalId(provider, extToken.Id);
if (user != null)
return user;
return await users.AddExternalToken(userId, provider, extToken);
}
}

View File

@@ -1,284 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Permissions;
using Kyoo.Abstractions.Models.Utils;
using Kyoo.Authentication.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Primitives;
namespace Kyoo.Authentication;
/// <summary>
/// A permission validator to validate permission with user Permission array
/// or the default array from the configurations if the user is not logged.
/// </summary>
public class PermissionValidator : IPermissionValidator
{
/// <summary>
/// The permissions options to retrieve default permissions.
/// </summary>
private readonly PermissionOption _options;
/// <summary>
/// Create a new factory with the given options.
/// </summary>
/// <param name="options">The option containing default values.</param>
public PermissionValidator(PermissionOption options)
{
_options = options;
}
/// <inheritdoc />
public IFilterMetadata Create(PermissionAttribute attribute)
{
return new PermissionValidatorFilter(
attribute.Type,
attribute.Kind,
attribute.Group,
_options
);
}
/// <inheritdoc />
public IFilterMetadata Create(PartialPermissionAttribute attribute)
{
return new PermissionValidatorFilter(
((object?)attribute.Type ?? attribute.Kind)!,
attribute.Group,
_options
);
}
/// <summary>
/// The authorization filter used by <see cref="PermissionValidator"/>.
/// </summary>
private class PermissionValidatorFilter : IAsyncAuthorizationFilter
{
/// <summary>
/// The permission to validate.
/// </summary>
private readonly string? _permission;
/// <summary>
/// The kind of permission needed.
/// </summary>
private readonly Kind? _kind;
/// <summary>
/// The group of he permission.
/// </summary>
private Group _group;
/// <summary>
/// The permissions options to retrieve default permissions.
/// </summary>
private readonly PermissionOption _options;
/// <summary>
/// Create a new permission validator with the given options.
/// </summary>
/// <param name="permission">The permission to validate.</param>
/// <param name="kind">The kind of permission needed.</param>
/// <param name="group">The group of the permission.</param>
/// <param name="options">The option containing default values.</param>
public PermissionValidatorFilter(
string permission,
Kind kind,
Group group,
PermissionOption options
)
{
_permission = permission;
_kind = kind;
_group = group;
_options = options;
}
/// <summary>
/// Create a new permission validator with the given options.
/// </summary>
/// <param name="partialInfo">The partial permission to validate.</param>
/// <param name="group">The group of the permission.</param>
/// <param name="options">The option containing default values.</param>
public PermissionValidatorFilter(object partialInfo, Group? group, PermissionOption options)
{
switch (partialInfo)
{
case Kind kind:
_kind = kind;
break;
case string perm:
_permission = perm;
break;
default:
throw new ArgumentException(
$"{nameof(partialInfo)} can only be a permission string or a kind."
);
}
if (group is not null and not Group.None)
_group = group.Value;
_options = options;
}
/// <inheritdoc />
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
string? permission = _permission;
Kind? kind = _kind;
if (permission == null || kind == null)
{
if (context.HttpContext.Items["PermissionGroup"] is Group group and not Group.None)
_group = group;
else if (_group == Group.None)
_group = Group.Overall;
else
context.HttpContext.Items["PermissionGroup"] = _group;
switch (context.HttpContext.Items["PermissionType"])
{
case string perm:
permission = perm;
break;
case Kind kin:
kind = kin;
break;
case null when kind != null:
context.HttpContext.Items["PermissionType"] = kind;
return;
case null when permission != null:
context.HttpContext.Items["PermissionType"] = permission;
return;
default:
throw new ArgumentException(
"Multiple non-matching partial permission attribute "
+ "are not supported."
);
}
if (permission == null || kind == null)
{
throw new ArgumentException(
"The permission type or kind is still missing after two partial "
+ "permission attributes, this is unsupported."
);
}
}
string permStr = $"{permission.ToLower()}.{kind.ToString()!.ToLower()}";
string overallStr = $"{_group.ToString().ToLower()}.{kind.ToString()!.ToLower()}";
AuthenticateResult res = _ApiKeyCheck(context);
if (res.None)
res = await _JwtCheck(context);
if (res.Succeeded)
{
ICollection<string> permissions = res.Principal.GetPermissions();
if (permissions.All(x => x != permStr && x != overallStr))
context.Result = _ErrorResult(
$"Missing permission {permStr} or {overallStr}",
StatusCodes.Status403Forbidden
);
}
else if (res.None)
{
ICollection<string> permissions = _options.Default ?? Array.Empty<string>();
if (permissions.All(x => x != permStr && x != overallStr))
{
context.Result = _ErrorResult(
$"Unlogged user does not have permission {permStr} or {overallStr}",
StatusCodes.Status401Unauthorized
);
}
}
else if (res.Failure != null)
context.Result = _ErrorResult(res.Failure.Message, StatusCodes.Status403Forbidden);
else
context.Result = _ErrorResult(
"Authentication panic",
StatusCodes.Status500InternalServerError
);
}
private AuthenticateResult _ApiKeyCheck(ActionContext context)
{
if (
!context.HttpContext.Request.Headers.TryGetValue(
"X-API-Key",
out StringValues apiKey
)
)
return AuthenticateResult.NoResult();
if (!_options.ApiKeys.Contains<string>(apiKey!))
return AuthenticateResult.Fail("Invalid API-Key.");
return AuthenticateResult.Success(
new AuthenticationTicket(
new ClaimsPrincipal(
new[]
{
new ClaimsIdentity(
new[]
{
// TODO: Make permission configurable, for now every APIKEY as all permissions.
new Claim(
Claims.Permissions,
string.Join(',', PermissionOption.Admin)
)
}
)
}
),
"apikey"
)
);
}
private async Task<AuthenticateResult> _JwtCheck(ActionContext context)
{
AuthenticateResult ret = await context.HttpContext.AuthenticateAsync(
JwtBearerDefaults.AuthenticationScheme
);
// Change the failure message to make the API nice to use.
if (ret.Failure != null)
return AuthenticateResult.Fail("Invalid JWT token. The token may have expired.");
return ret;
}
}
/// <summary>
/// Create a new action result with the given error message and error code.
/// </summary>
/// <param name="error">The error message.</param>
/// <param name="code">The status code of the error.</param>
/// <returns>The resulting error action.</returns>
private static IActionResult _ErrorResult(string error, int code)
{
return new ObjectResult(new RequestError(error)) { StatusCode = code };
}
}

View File

@@ -1,116 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Kyoo.Abstractions.Models;
using Kyoo.Authentication.Models;
using Microsoft.IdentityModel.Tokens;
namespace Kyoo.Authentication;
public class TokenController(AuthenticationOption options) : ITokenController
{
/// <inheritdoc />
public string CreateAccessToken(User user, out TimeSpan expireIn)
{
expireIn = new TimeSpan(1, 0, 0);
SymmetricSecurityKey key = new(options.Secret);
SigningCredentials credential = new(key, SecurityAlgorithms.HmacSha256Signature);
string permissions =
user.Permissions != null ? string.Join(',', user.Permissions) : string.Empty;
List<Claim> claims =
new()
{
new Claim(Claims.Id, user.Id.ToString()),
new Claim(Claims.Name, user.Username),
new Claim(Claims.Permissions, permissions),
new Claim(Claims.Type, "access")
};
if (user.Email != null)
claims.Add(new Claim(Claims.Email, user.Email));
JwtSecurityToken token =
new(
signingCredentials: credential,
claims: claims,
expires: DateTime.UtcNow.Add(expireIn)
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
/// <inheritdoc />
public Task<string> CreateRefreshToken(User user)
{
SymmetricSecurityKey key = new(options.Secret);
SigningCredentials credential = new(key, SecurityAlgorithms.HmacSha256Signature);
JwtSecurityToken token =
new(
signingCredentials: credential,
claims: new[]
{
new Claim(Claims.Id, user.Id.ToString()),
new Claim(Claims.Guid, Guid.NewGuid().ToString()),
new Claim(Claims.Type, "refresh")
},
expires: DateTime.UtcNow.AddYears(1)
);
// TODO: refresh keys are unique (thanks to the guid) but we could store them in DB to invalidate them if requested by the user.
return Task.FromResult(new JwtSecurityTokenHandler().WriteToken(token));
}
/// <inheritdoc />
public Guid GetRefreshTokenUserID(string refreshToken)
{
SymmetricSecurityKey key = new(options.Secret);
JwtSecurityTokenHandler tokenHandler = new();
ClaimsPrincipal principal;
try
{
principal = tokenHandler.ValidateToken(
refreshToken,
new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
IssuerSigningKey = key
},
out SecurityToken _
);
}
catch (Exception)
{
throw new SecurityTokenException("Invalid refresh token");
}
if (principal.Claims.First(x => x.Type == Claims.Type).Value != "refresh")
throw new SecurityTokenException(
"Invalid token type. The token should be a refresh token."
);
Claim identifier = principal.Claims.First(x => x.Type == Claims.Id);
if (Guid.TryParse(identifier.Value, out Guid id))
return id;
throw new SecurityTokenException("Token not associated to any user.");
}
}

View File

@@ -1,11 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.21" />
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="Serilog" Version="4.3.0" />
<ProjectReference Include="../Kyoo.Abstractions/Kyoo.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,77 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Collections.Generic;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace Kyoo.Authentication.Models.DTO;
public class JwtProfile
{
public string? Sub { get; set; }
public string? Uid
{
set => Sub ??= value;
}
public string? Id
{
set => Sub ??= value;
}
public string? Guid
{
set => Sub ??= value;
}
public string? Username { get; set; }
public string? Name
{
set => Username ??= value;
}
public string? Email { get; set; }
public JsonObject? Account
{
set
{
if (value is null)
return;
// simkl store their ids there.
Sub ??= value["id"]?.ToString();
}
}
public JsonObject? User
{
set
{
if (value is null)
return;
// trakt store their name there (they also store name but that's not the same).
Username ??= value["username"]?.ToString();
// simkl store their name there.
Username ??= value["name"]?.ToString();
Sub ??= value["ids"]?["uuid"]?.ToString();
}
}
[JsonExtensionData]
public Dictionary<string, object> Extra { get; set; }
}

View File

@@ -1,46 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
namespace Kyoo.Authentication.Models.DTO;
/// <summary>
/// A model only used on login requests.
/// </summary>
public class LoginRequest
{
/// <summary>
/// The user's username.
/// </summary>
public string Username { get; set; }
/// <summary>
/// The user's password.
/// </summary>
public string Password { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="LoginRequest"/> class.
/// </summary>
/// <param name="username">The user's username.</param>
/// <param name="password">The user's password.</param>
public LoginRequest(string username, string password)
{
Username = username;
Password = password;
}
}

View File

@@ -1,38 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.ComponentModel.DataAnnotations;
namespace Kyoo.Authentication.Models.DTO;
/// <summary>
/// A model only used on password resets.
/// </summary>
public class PasswordResetRequest
{
/// <summary>
/// The old password
/// </summary>
public string? OldPassword { get; set; }
/// <summary>
/// The new password
/// </summary>
[MinLength(4, ErrorMessage = "The password must have at least {1} characters")]
public string NewPassword { get; set; }
}

View File

@@ -1,76 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.ComponentModel.DataAnnotations;
using Kyoo.Abstractions.Models;
using Kyoo.Utils;
using BCryptNet = BCrypt.Net.BCrypt;
namespace Kyoo.Authentication.Models.DTO;
/// <summary>
/// A model only used on register requests.
/// </summary>
public class RegisterRequest
{
/// <summary>
/// The user email address
/// </summary>
[EmailAddress(ErrorMessage = "The email must be a valid email address")]
public string Email { get; set; }
/// <summary>
/// The user's username.
/// </summary>
[MinLength(4, ErrorMessage = "The username must have at least {1} characters")]
public string Username { get; set; }
/// <summary>
/// The user's password.
/// </summary>
[MinLength(4, ErrorMessage = "The password must have at least {1} characters")]
public string Password { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="RegisterRequest"/> class.
/// </summary>
/// <param name="email">The user email address.</param>
/// <param name="username">The user's username.</param>
/// <param name="password">The user's password.</param>
public RegisterRequest(string email, string username, string password)
{
Email = email;
Username = username;
Password = password;
}
/// <summary>
/// Convert this register request to a new <see cref="User"/> class.
/// </summary>
/// <returns>A user representing this request.</returns>
public User ToUser()
{
return new User
{
Slug = Utility.ToSlug(Username),
Username = Username,
Password = BCryptNet.HashPassword(Password),
Email = Email,
};
}
}

Some files were not shown because too many files have changed in this diff Show More