mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Add image downloading + blurhash (#846)
This commit is contained in:
commit
5da13f8f41
@ -7,6 +7,8 @@ JWT_SECRET=
|
||||
# keibi's server to retrieve the public jwt secret
|
||||
AUHT_SERVER=http://auth:4568
|
||||
|
||||
IMAGES_PATH=./images
|
||||
|
||||
POSTGRES_USER=kyoo
|
||||
POSTGRES_PASSWORD=password
|
||||
POSTGRES_DB=kyooDB
|
||||
|
1
api/.gitignore
vendored
1
api/.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
node_modules
|
||||
**/*.bun
|
||||
images
|
||||
|
102
api/bun.lock
102
api/bun.lock
@ -2,18 +2,22 @@
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "api",
|
||||
"dependencies": {
|
||||
"@elysiajs/jwt": "^1.2.0",
|
||||
"@elysiajs/swagger": "^1.2.2",
|
||||
"blurhash": "^2.0.5",
|
||||
"drizzle-kit": "^0.30.4",
|
||||
"drizzle-orm": "0.39.0",
|
||||
"elysia": "^1.2.23",
|
||||
"parjs": "^1.3.9",
|
||||
"pg": "^8.13.3",
|
||||
"sharp": "^0.33.5",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/pg": "^8.11.11",
|
||||
"bun-types": "^1.2.4",
|
||||
"node-addon-api": "^8.3.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -27,6 +31,8 @@
|
||||
|
||||
"@elysiajs/swagger": ["@elysiajs/swagger@1.2.2", "", { "dependencies": { "@scalar/themes": "^0.9.52", "@scalar/types": "^0.0.12", "openapi-types": "^12.1.3", "pathe": "^1.1.2" }, "peerDependencies": { "elysia": ">= 1.2.0" } }, "sha512-DG0PbX/wzQNQ6kIpFFPCvmkkWTIbNWDS7lVLv3Puy6ONklF14B4NnbDfpYjX1hdSYKeCqKBBOuenh6jKm8tbYA=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.3.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw=="],
|
||||
|
||||
"@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=="],
|
||||
@ -77,39 +83,89 @@
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="],
|
||||
|
||||
"@petamoriken/float16": ["@petamoriken/float16@3.9.1", "", {}, "sha512-j+ejhYwY6PeB+v1kn7lZFACUIG97u90WxMuGosILFsl9d4Ovi0sjk0GlPfoEcx+FzvXZDAfioD+NGnnPamXgMA=="],
|
||||
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="],
|
||||
|
||||
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="],
|
||||
|
||||
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="],
|
||||
|
||||
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="],
|
||||
|
||||
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="],
|
||||
|
||||
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="],
|
||||
|
||||
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.0.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="],
|
||||
|
||||
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="],
|
||||
|
||||
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="],
|
||||
|
||||
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="],
|
||||
|
||||
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="],
|
||||
|
||||
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="],
|
||||
|
||||
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.0.4" }, "os": "linux", "cpu": "s390x" }, "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q=="],
|
||||
|
||||
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="],
|
||||
|
||||
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="],
|
||||
|
||||
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="],
|
||||
|
||||
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.33.5", "", { "dependencies": { "@emnapi/runtime": "^1.2.0" }, "cpu": "none" }, "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg=="],
|
||||
|
||||
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.33.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="],
|
||||
|
||||
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="],
|
||||
|
||||
"@petamoriken/float16": ["@petamoriken/float16@3.9.2", "", {}, "sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog=="],
|
||||
|
||||
"@scalar/openapi-types": ["@scalar/openapi-types@0.1.1", "", {}, "sha512-NMy3QNk6ytcCoPUGJH0t4NNr36OWXgZhA3ormr3TvhX1NDgoF95wFyodGVH8xiHeUyn2/FxtETm8UBLbB5xEmg=="],
|
||||
|
||||
"@scalar/themes": ["@scalar/themes@0.9.58", "", { "dependencies": { "@scalar/types": "0.0.25" } }, "sha512-voMgCIq0N19N8Ehjs8rSS0j5P1mpgWbpN5dXIToGUbVj7KcxMnOfkH3P1/cy2CoUd1gRYe0newUBEcI1+tQi1g=="],
|
||||
"@scalar/themes": ["@scalar/themes@0.9.79", "", { "dependencies": { "@scalar/types": "0.1.1" } }, "sha512-zWiHCZAIjPGa8X9o/NORBPRMTMblLEz2+2RcfW9yIKNO/8H4Gz0rltiGGlJ6vX0o+qHwx7AdgfY+7njmWQR4ng=="],
|
||||
|
||||
"@scalar/types": ["@scalar/types@0.0.12", "", { "dependencies": { "@scalar/openapi-types": "0.1.1", "@unhead/schema": "^1.9.5" } }, "sha512-XYZ36lSEx87i4gDqopQlGCOkdIITHHEvgkuJFrXFATQs9zHARop0PN0g4RZYWj+ZpCUclOcaOjbCt8JGe22mnQ=="],
|
||||
|
||||
"@sinclair/typebox": ["@sinclair/typebox@0.34.27", "", {}, "sha512-C7mxE1VC3WC2McOufZXEU48IfRVI+BcKxk4NOyNn3+JMUNdJHEWGS5CqjuDX+ij2NCCz8/nse1mT7yn8Fv2GHg=="],
|
||||
"@sinclair/typebox": ["@sinclair/typebox@0.34.30", "", {}, "sha512-gFB3BiqjDxEoadW0zn+xyMVb7cLxPCoblVn2C/BKpI41WPYi2d6fwHAlynPNZ5O/Q4WEiujdnJzVtvG/Jc2CBQ=="],
|
||||
|
||||
"@types/node": ["@types/node@22.10.5", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ=="],
|
||||
"@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||
|
||||
"@types/pg": ["@types/pg@8.11.11", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^4.0.1" } }, "sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw=="],
|
||||
|
||||
"@types/ws": ["@types/ws@8.5.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA=="],
|
||||
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
|
||||
|
||||
"@unhead/schema": ["@unhead/schema@1.11.14", "", { "dependencies": { "hookable": "^5.5.3", "zhead": "^2.2.4" } }, "sha512-V9W9u5tF1/+TiLqxu+Qvh1ShoMDkPEwHoEo4DKdDG6ko7YlbzFfDxV6el9JwCren45U/4Vy/4Xi7j8OH02wsiA=="],
|
||||
"@unhead/schema": ["@unhead/schema@1.11.20", "", { "dependencies": { "hookable": "^5.5.3", "zhead": "^2.2.4" } }, "sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA=="],
|
||||
|
||||
"blurhash": ["blurhash@2.0.5", "", {}, "sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w=="],
|
||||
|
||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.4", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-nDPymR207ZZEoWD4AavvEaa/KZe/qlrbMSchqpQwovPZCKc7pwMoENjEtHgMKaAjJhy+x6vfqSBA1QU3bJgs0Q=="],
|
||||
"bun-types": ["bun-types@1.2.5", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-3oO6LVGGRRKI4kHINx5PIdIgnLRb7l/SprhzqXapmoYkFl5m4j6EvALvbDVuuBFaamB46Ap6HCUxIXNLCGy+tg=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="],
|
||||
|
||||
"drizzle-kit": ["drizzle-kit@0.30.5", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0", "gel": "^2.0.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-l6dMSE100u7sDaTbLczibrQZjA35jLsHNqIV+jmhNVO3O8jzM6kywMOmV9uOz9ZVSCMPQhAZEFjL/qDPVrqpUA=="],
|
||||
|
||||
"drizzle-orm": ["drizzle-orm@0.39.0", "", { "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", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/react": ">=18", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "react": ">=18", "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/react", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "knex", "kysely", "mysql2", "pg", "postgres", "react", "sql.js", "sqlite3"] }, "sha512-kkZwo3Jvht0fdJD/EWGx0vYcEK0xnGrlNVaY07QYluRZA9N21B9VFbY+54bnb/1xvyzcg97tE65xprSAP/fFGQ=="],
|
||||
|
||||
"elysia": ["elysia@1.2.23", "", { "dependencies": { "@sinclair/typebox": "^0.34.27", "cookie": "^1.0.2", "memoirist": "^0.3.0", "openapi-types": "^12.1.3" }, "peerDependencies": { "typescript": ">= 5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-hMezhUkbpzZQduu01tmODsNJXk5CJ8oAQvc5gN1+GLv8cjiOFOnMQdEpTtaMplTKU0lr7MBtwL2duVFXEBPKOg=="],
|
||||
"elysia": ["elysia@1.2.25", "", { "dependencies": { "@sinclair/typebox": "^0.34.27", "cookie": "^1.0.2", "memoirist": "^0.3.0", "openapi-types": "^12.1.3" }, "peerDependencies": { "typescript": ">= 5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-WsdQpORJvb4uszzeqYT0lg97knw1iBW1NTzJ1Jm57tiHg+DfAotlWXYbjmvQ039ssV0fYELDHinLLoUazZkEHg=="],
|
||||
|
||||
"env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="],
|
||||
|
||||
@ -117,12 +173,14 @@
|
||||
|
||||
"esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
|
||||
|
||||
"gel": ["gel@2.0.0", "", { "dependencies": { "@petamoriken/float16": "^3.8.7", "debug": "^4.3.4", "env-paths": "^3.0.0", "semver": "^7.6.2", "shell-quote": "^1.8.1", "which": "^4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-Oq3Fjay71s00xzDc0BF/mpcLmnA+uRqMEJK8p5K4PaZjUEsxaeo+kR9OHBVAf289/qPd+0OcLOLUN0UhqiUCog=="],
|
||||
"gel": ["gel@2.0.1", "", { "dependencies": { "@petamoriken/float16": "^3.8.7", "debug": "^4.3.4", "env-paths": "^3.0.0", "semver": "^7.6.2", "shell-quote": "^1.8.1", "which": "^4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-gfem3IGvqKqXwEq7XseBogyaRwGsQGuE7Cw/yQsjLGdgiyqX92G1xENPCE0ltunPGcsJIa6XBOTx/PK169mOqw=="],
|
||||
|
||||
"get-tsconfig": ["get-tsconfig@4.8.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg=="],
|
||||
"get-tsconfig": ["get-tsconfig@4.10.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A=="],
|
||||
|
||||
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
||||
|
||||
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
|
||||
|
||||
"isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
|
||||
|
||||
"jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
|
||||
@ -131,6 +189,8 @@
|
||||
|
||||
"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-interval-tree": ["node-interval-tree@1.3.3", "", { "dependencies": { "shallowequal": "^1.0.2" } }, "sha512-K9vk96HdTK5fEipJwxSvIIqwTqr4e3HRJeJrNxBSeVMNSC/JWARRaX7etOLOuTmrRMeOI/K5TCJu3aWIwZiNTw=="],
|
||||
|
||||
"obuf": ["obuf@1.1.2", "", {}, "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="],
|
||||
@ -141,7 +201,7 @@
|
||||
|
||||
"pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
|
||||
|
||||
"pg": ["pg@8.13.3", "", { "dependencies": { "pg-connection-string": "^2.7.0", "pg-pool": "^3.7.1", "pg-protocol": "^1.7.1", "pg-types": "^2.1.0", "pgpass": "1.x" }, "optionalDependencies": { "pg-cloudflare": "^1.1.1" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-P6tPt9jXbL9HVu/SSRERNYaYG++MjnscnegFh9pPHihfoBSujsrka0hyuymMzeJKFWrcG8wvCKy8rCe8e5nDUQ=="],
|
||||
"pg": ["pg@8.14.0", "", { "dependencies": { "pg-connection-string": "^2.7.0", "pg-pool": "^3.8.0", "pg-protocol": "^1.8.0", "pg-types": "^2.1.0", "pgpass": "1.x" }, "optionalDependencies": { "pg-cloudflare": "^1.1.1" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-nXbVpyoaXVmdqlKEzToFf37qzyeeh7mbiXsnoWvstSqohj88yaa/I/Rq/HEVn2QPSZEuLIJa/jSpRDyzjEx4FQ=="],
|
||||
|
||||
"pg-cloudflare": ["pg-cloudflare@1.1.1", "", {}, "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q=="],
|
||||
|
||||
@ -151,15 +211,15 @@
|
||||
|
||||
"pg-numeric": ["pg-numeric@1.0.2", "", {}, "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw=="],
|
||||
|
||||
"pg-pool": ["pg-pool@3.7.1", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-xIOsFoh7Vdhojas6q3596mXFsR8nwBQBXX5JiV7p9buEVAGqYL4yFzclON5P9vFrpu1u7Zwl2oriyDa89n0wbw=="],
|
||||
"pg-pool": ["pg-pool@3.8.0", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-VBw3jiVm6ZOdLBTIcXLNdSotb6Iy3uOCwDGFAksZCXmi10nyRvnP2v3jl4d+IsLYRyXf6o9hIm/ZtUzlByNUdw=="],
|
||||
|
||||
"pg-protocol": ["pg-protocol@1.7.0", "", {}, "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ=="],
|
||||
"pg-protocol": ["pg-protocol@1.8.0", "", {}, "sha512-jvuYlEkL03NRvOoyoRktBK7+qU5kOvlAwvmrH8sr3wbLrOdVWsRxQfz8mMy9sZFsqJ1hEWNfdWKI4SAmoL+j7g=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="],
|
||||
|
||||
"postgres-array": ["postgres-array@3.0.2", "", {}, "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog=="],
|
||||
"postgres-array": ["postgres-array@3.0.4", "", {}, "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ=="],
|
||||
|
||||
"postgres-bytea": ["postgres-bytea@3.0.0", "", { "dependencies": { "obuf": "~1.1.2" } }, "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw=="],
|
||||
|
||||
@ -175,14 +235,20 @@
|
||||
|
||||
"shallowequal": ["shallowequal@1.1.0", "", {}, "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="],
|
||||
|
||||
"sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="],
|
||||
|
||||
"shell-quote": ["shell-quote@1.8.2", "", {}, "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA=="],
|
||||
|
||||
"simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="],
|
||||
|
||||
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
|
||||
|
||||
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
|
||||
@ -191,11 +257,11 @@
|
||||
|
||||
"zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="],
|
||||
|
||||
"zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@scalar/themes/@scalar/types": ["@scalar/types@0.0.25", "", { "dependencies": { "@scalar/openapi-types": "0.1.5", "@unhead/schema": "^1.11.11" } }, "sha512-sGnOFnfiSn4o23rklU/jrg81hO+630bsFIdHg8MZ/w2Nc6IoUwARA2hbe4d4Fg+D0KBu40Tan/L+WAYDXkTJQg=="],
|
||||
|
||||
"pg/pg-protocol": ["pg-protocol@1.7.1", "", {}, "sha512-gjTHWGYWsEgy9MsY0Gp6ZJxV24IjDqdpTW7Eh0x+WfJLFsm/TJx1MzL6T0D88mBvkpxotCQ6TwW6N+Kko7lhgQ=="],
|
||||
"@scalar/themes/@scalar/types": ["@scalar/types@0.1.1", "", { "dependencies": { "@scalar/openapi-types": "0.1.9", "@unhead/schema": "^1.11.11", "zod": "^3.23.8" } }, "sha512-LlUX6AmOOGoRqOMoO835V2FezM1KiO5UlvQC3poT/s7oqD6ranqwRNFxyrPz/IxClPYR+SV1yBUSNKely4ZQhQ=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
@ -243,7 +309,7 @@
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
|
||||
|
||||
"@scalar/themes/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.1.5", "", {}, "sha512-6geH9ehvQ/sG/xUyy3e0lyOw3BaY5s6nn22wHjEJhcobdmWyFER0O6m7AU0ZN4QTjle/gYvFJOjj552l/rsNSw=="],
|
||||
"@scalar/themes/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.1.9", "", {}, "sha512-HQQudOSQBU7ewzfnBW9LhDmBE2XOJgSfwrh5PlUB7zJup/kaRkBGNgV2wMjNz9Af/uztiU/xNrO179FysmUT+g=="],
|
||||
|
||||
"pg/pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
|
||||
|
||||
|
9
api/drizzle/0016_mqueue.sql
Normal file
9
api/drizzle/0016_mqueue.sql
Normal file
@ -0,0 +1,9 @@
|
||||
CREATE TABLE "kyoo"."mqueue" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"kind" varchar(255) NOT NULL,
|
||||
"message" jsonb NOT NULL,
|
||||
"attempt" integer DEFAULT 0 NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX "mqueue_created" ON "kyoo"."mqueue" USING btree ("created_at");
|
1555
api/drizzle/meta/0016_snapshot.json
Normal file
1555
api/drizzle/meta/0016_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -113,6 +113,13 @@
|
||||
"when": 1741623934941,
|
||||
"tag": "0015_news",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 16,
|
||||
"version": "7",
|
||||
"when": 1742205790510,
|
||||
"tag": "0016_mqueue",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "api",
|
||||
"version": "1.0.50",
|
||||
"version": "5.0.0",
|
||||
"scripts": {
|
||||
"dev": "bun --watch src/index.ts",
|
||||
"build": "bun build src/index.ts --target bun --outdir ./dist",
|
||||
@ -11,14 +11,17 @@
|
||||
"dependencies": {
|
||||
"@elysiajs/jwt": "^1.2.0",
|
||||
"@elysiajs/swagger": "^1.2.2",
|
||||
"blurhash": "^2.0.5",
|
||||
"drizzle-kit": "^0.30.4",
|
||||
"drizzle-orm": "0.39.0",
|
||||
"elysia": "^1.2.23",
|
||||
"parjs": "^1.3.9",
|
||||
"pg": "^8.13.3"
|
||||
"pg": "^8.13.3",
|
||||
"sharp": "^0.33.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/pg": "^8.11.11",
|
||||
"node-addon-api": "^8.3.1",
|
||||
"bun-types": "^1.2.4"
|
||||
},
|
||||
"module": "src/index.js",
|
||||
|
@ -1,22 +1,178 @@
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { encode } from "blurhash";
|
||||
import { type SQL, eq, is, sql } from "drizzle-orm";
|
||||
import { PgColumn, type PgTable } from "drizzle-orm/pg-core";
|
||||
import { version } from "package.json";
|
||||
import type { PoolClient } from "pg";
|
||||
import sharp from "sharp";
|
||||
import { type Transaction, db } from "~/db";
|
||||
import { mqueue } from "~/db/schema/mqueue";
|
||||
import type { Image } from "~/models/utils";
|
||||
|
||||
export const imageDir = process.env.IMAGES_PATH ?? "./images";
|
||||
await mkdir(imageDir, { recursive: true });
|
||||
|
||||
export const defaultBlurhash = "000000";
|
||||
|
||||
type ImageTask = {
|
||||
id: string;
|
||||
url: string;
|
||||
table: string;
|
||||
column: string;
|
||||
};
|
||||
|
||||
// this will only push a task to the image downloader service and not download it instantly.
|
||||
// this is both done to prevent to many requests to be sent at once and to make sure POST
|
||||
// requests are not blocked by image downloading or blurhash calculation
|
||||
export const processImage = (url: string): Image => {
|
||||
const hasher = new Bun.CryptoHasher("sha256");
|
||||
hasher.update(url);
|
||||
export const enqueueOptImage = async (
|
||||
tx: Transaction,
|
||||
img:
|
||||
| { url: string | null; column: PgColumn }
|
||||
| { url: string | null; table: PgTable; column: SQL },
|
||||
): Promise<Image | null> => {
|
||||
if (!img.url) return null;
|
||||
|
||||
// TODO: download source, save it in multiples qualities & process blurhash
|
||||
const hasher = new Bun.CryptoHasher("sha256");
|
||||
hasher.update(img.url);
|
||||
const id = hasher.digest().toString("hex");
|
||||
|
||||
const cleanupColumn = (column: SQL) =>
|
||||
// @ts-expect-error dialect is private
|
||||
db.dialect.sqlToQuery(
|
||||
sql.join(
|
||||
column.queryChunks.map((x) => {
|
||||
if (is(x, PgColumn)) {
|
||||
return sql.identifier(x.name);
|
||||
}
|
||||
return x;
|
||||
}),
|
||||
),
|
||||
).sql;
|
||||
|
||||
const message: ImageTask =
|
||||
"table" in img
|
||||
? {
|
||||
id,
|
||||
url: img.url,
|
||||
// @ts-expect-error dialect is private
|
||||
table: db.dialect.sqlToQuery(sql`${img.table}`).sql,
|
||||
column: cleanupColumn(img.column),
|
||||
}
|
||||
: {
|
||||
id,
|
||||
url: img.url,
|
||||
// @ts-expect-error dialect is private
|
||||
table: db.dialect.sqlToQuery(sql`${img.column.table}`).sql,
|
||||
column: sql.identifier(img.column.name).value,
|
||||
};
|
||||
await tx.insert(mqueue).values({
|
||||
kind: "image",
|
||||
message,
|
||||
});
|
||||
await tx.execute(sql`notify image`);
|
||||
|
||||
return {
|
||||
id: hasher.digest().toString("hex"),
|
||||
source: url,
|
||||
blurhash: "",
|
||||
id,
|
||||
source: img.url,
|
||||
blurhash: defaultBlurhash,
|
||||
};
|
||||
};
|
||||
|
||||
export const processOptImage = (url: string | null): Image | null => {
|
||||
if (!url) return null;
|
||||
return processImage(url);
|
||||
export const processImages = async () => {
|
||||
async function processOne() {
|
||||
return await db.transaction(async (tx) => {
|
||||
const [item] = await tx
|
||||
.select()
|
||||
.from(mqueue)
|
||||
.for("update", { skipLocked: true })
|
||||
.where(eq(mqueue.kind, "image"))
|
||||
.orderBy(mqueue.createdAt)
|
||||
.limit(1);
|
||||
|
||||
if (!item) return false;
|
||||
|
||||
const img = item.message as ImageTask;
|
||||
const blurhash = await downloadImage(img.id, img.url);
|
||||
const ret: Image = { id: img.id, source: img.url, blurhash };
|
||||
|
||||
const table = sql.raw(img.table);
|
||||
const column = sql.raw(img.column);
|
||||
|
||||
await tx.execute(sql`
|
||||
update ${table} set ${column} = ${ret} where ${column}->'id' = ${sql.raw(`'"${img.id}"'::jsonb`)}
|
||||
`);
|
||||
|
||||
await tx.delete(mqueue).where(eq(mqueue.id, item.id));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
let running = false;
|
||||
async function processAll() {
|
||||
if (running) return;
|
||||
running = true;
|
||||
|
||||
let found = true;
|
||||
while (found) {
|
||||
found = await processOne();
|
||||
}
|
||||
running = false;
|
||||
}
|
||||
|
||||
const client = (await db.$client.connect()) as PoolClient;
|
||||
client.on("notification", (evt) => {
|
||||
if (evt.channel !== "image") return;
|
||||
processAll();
|
||||
});
|
||||
await client.query("listen image");
|
||||
|
||||
// start processing old tasks
|
||||
await processAll();
|
||||
return () => client.release(true);
|
||||
};
|
||||
|
||||
async function downloadImage(id: string, url: string): Promise<string> {
|
||||
// TODO: check if file exists before downloading
|
||||
const resp = await fetch(url, {
|
||||
headers: { "User-Agent": `Kyoo v${version}` },
|
||||
});
|
||||
if (!resp.ok) {
|
||||
throw new Error(`Failed to fetch image: ${resp.status} ${resp.statusText}`);
|
||||
}
|
||||
const buf = Buffer.from(await resp.arrayBuffer());
|
||||
|
||||
const image = sharp(buf);
|
||||
const metadata = await image.metadata();
|
||||
|
||||
if (!metadata.width || !metadata.height) {
|
||||
throw new Error("Could not determine image dimensions");
|
||||
}
|
||||
const resolutions = {
|
||||
low: { width: 320 },
|
||||
medium: { width: 640 },
|
||||
high: { width: 1280 },
|
||||
};
|
||||
await Promise.all(
|
||||
Object.entries(resolutions).map(async ([resolution, dimensions]) => {
|
||||
const buffer = await image.clone().resize(dimensions.width).toBuffer();
|
||||
await writeFile(path.join(imageDir, `${id}.${resolution}.jpg`), buffer);
|
||||
}),
|
||||
);
|
||||
|
||||
const { data, info } = await image
|
||||
.resize(32, 32, { fit: "inside" })
|
||||
.ensureAlpha()
|
||||
.raw()
|
||||
.toBuffer({ resolveWithObject: true });
|
||||
|
||||
const blurHash = encode(
|
||||
new Uint8ClampedArray(data),
|
||||
info.width,
|
||||
info.height,
|
||||
4,
|
||||
3,
|
||||
);
|
||||
|
||||
return blurHash;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { conflictUpdateAllExcept } from "~/db/utils";
|
||||
import type { SeedCollection } from "~/models/collections";
|
||||
import type { SeedMovie } from "~/models/movie";
|
||||
import type { SeedSerie } from "~/models/serie";
|
||||
import { processOptImage } from "../images";
|
||||
import { enqueueOptImage } from "../images";
|
||||
|
||||
type ShowTrans = typeof showTranslations.$inferInsert;
|
||||
|
||||
@ -48,16 +48,28 @@ export const insertCollection = async (
|
||||
})
|
||||
.returning({ pk: shows.pk, id: shows.id, slug: shows.slug });
|
||||
|
||||
const trans: ShowTrans[] = Object.entries(translations).map(
|
||||
([lang, tr]) => ({
|
||||
const trans: ShowTrans[] = await Promise.all(
|
||||
Object.entries(translations).map(async ([lang, tr]) => ({
|
||||
pk: ret.pk,
|
||||
language: lang,
|
||||
...tr,
|
||||
poster: processOptImage(tr.poster),
|
||||
thumbnail: processOptImage(tr.thumbnail),
|
||||
logo: processOptImage(tr.logo),
|
||||
banner: processOptImage(tr.banner),
|
||||
}),
|
||||
poster: await enqueueOptImage(tx, {
|
||||
url: tr.poster,
|
||||
column: showTranslations.poster,
|
||||
}),
|
||||
thumbnail: await enqueueOptImage(tx, {
|
||||
url: tr.thumbnail,
|
||||
column: showTranslations.thumbnail,
|
||||
}),
|
||||
logo: await enqueueOptImage(tx, {
|
||||
url: tr.logo,
|
||||
column: showTranslations.logo,
|
||||
}),
|
||||
banner: await enqueueOptImage(tx, {
|
||||
url: tr.banner,
|
||||
column: showTranslations.banner,
|
||||
}),
|
||||
})),
|
||||
);
|
||||
await tx
|
||||
.insert(showTranslations)
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
} from "~/db/schema";
|
||||
import { conflictUpdateAllExcept, sqlarr, values } from "~/db/utils";
|
||||
import type { SeedEntry as SEntry, SeedExtra as SExtra } from "~/models/entry";
|
||||
import { processOptImage } from "../images";
|
||||
import { enqueueOptImage } from "../images";
|
||||
import { guessNextRefresh } from "../refresh";
|
||||
import { updateAvailableCount } from "./shows";
|
||||
|
||||
@ -23,6 +23,7 @@ type SeedExtra = Omit<SExtra, "kind"> & {
|
||||
};
|
||||
|
||||
type EntryI = typeof entries.$inferInsert;
|
||||
type EntryTransI = typeof entryTranslations.$inferInsert;
|
||||
|
||||
const generateSlug = (
|
||||
showSlug: string,
|
||||
@ -49,25 +50,30 @@ export const insertEntries = async (
|
||||
if (!items) return [];
|
||||
|
||||
const retEntries = await db.transaction(async (tx) => {
|
||||
const vals: EntryI[] = items.map((seed) => {
|
||||
const { translations, videos, video, ...entry } = seed;
|
||||
return {
|
||||
...entry,
|
||||
showPk: show.pk,
|
||||
slug: generateSlug(show.slug, seed),
|
||||
thumbnail: processOptImage(seed.thumbnail),
|
||||
nextRefresh:
|
||||
entry.kind !== "extra"
|
||||
? guessNextRefresh(entry.airDate ?? new Date())
|
||||
: guessNextRefresh(new Date()),
|
||||
episodeNumber:
|
||||
entry.kind === "episode"
|
||||
? entry.episodeNumber
|
||||
: entry.kind === "special"
|
||||
? entry.number
|
||||
: undefined,
|
||||
};
|
||||
});
|
||||
const vals: EntryI[] = await Promise.all(
|
||||
items.map(async (seed) => {
|
||||
const { translations, videos, video, ...entry } = seed;
|
||||
return {
|
||||
...entry,
|
||||
showPk: show.pk,
|
||||
slug: generateSlug(show.slug, seed),
|
||||
thumbnail: await enqueueOptImage(tx, {
|
||||
url: seed.thumbnail,
|
||||
column: entries.thumbnail,
|
||||
}),
|
||||
nextRefresh:
|
||||
entry.kind !== "extra"
|
||||
? guessNextRefresh(entry.airDate ?? new Date())
|
||||
: guessNextRefresh(new Date()),
|
||||
episodeNumber:
|
||||
entry.kind === "episode"
|
||||
? entry.episodeNumber
|
||||
: entry.kind === "special"
|
||||
? entry.number
|
||||
: undefined,
|
||||
};
|
||||
}),
|
||||
);
|
||||
const ret = await tx
|
||||
.insert(entries)
|
||||
.values(vals)
|
||||
@ -83,30 +89,41 @@ export const insertEntries = async (
|
||||
})
|
||||
.returning({ pk: entries.pk, id: entries.id, slug: entries.slug });
|
||||
|
||||
const trans = items.flatMap((seed, i) => {
|
||||
if (seed.kind === "extra") {
|
||||
return {
|
||||
pk: ret[i].pk,
|
||||
// yeah we hardcode the language to extra because if we want to support
|
||||
// translations one day it won't be awkward
|
||||
language: "extra",
|
||||
name: seed.name,
|
||||
description: null,
|
||||
poster: undefined,
|
||||
};
|
||||
}
|
||||
const trans: EntryTransI[] = (
|
||||
await Promise.all(
|
||||
items.map(async (seed, i) => {
|
||||
if (seed.kind === "extra") {
|
||||
return [
|
||||
{
|
||||
pk: ret[i].pk,
|
||||
// yeah we hardcode the language to extra because if we want to support
|
||||
// translations one day it won't be awkward
|
||||
language: "extra",
|
||||
name: seed.name,
|
||||
description: null,
|
||||
poster: undefined,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return Object.entries(seed.translations).map(([lang, tr]) => ({
|
||||
// assumes ret is ordered like items.
|
||||
pk: ret[i].pk,
|
||||
language: lang,
|
||||
...tr,
|
||||
poster:
|
||||
seed.kind === "movie"
|
||||
? processOptImage((tr as any).poster)
|
||||
: undefined,
|
||||
}));
|
||||
});
|
||||
return await Promise.all(
|
||||
Object.entries(seed.translations).map(async ([lang, tr]) => ({
|
||||
// assumes ret is ordered like items.
|
||||
pk: ret[i].pk,
|
||||
language: lang,
|
||||
...tr,
|
||||
poster:
|
||||
seed.kind === "movie"
|
||||
? await enqueueOptImage(tx, {
|
||||
url: (tr as any).poster,
|
||||
column: entryTranslations.poster,
|
||||
})
|
||||
: undefined,
|
||||
})),
|
||||
);
|
||||
}),
|
||||
)
|
||||
).flat();
|
||||
await tx
|
||||
.insert(entryTranslations)
|
||||
.values(trans)
|
||||
|
@ -2,7 +2,7 @@ import { db } from "~/db";
|
||||
import { seasonTranslations, seasons } from "~/db/schema";
|
||||
import { conflictUpdateAllExcept } from "~/db/utils";
|
||||
import type { SeedSeason } from "~/models/season";
|
||||
import { processOptImage } from "../images";
|
||||
import { enqueueOptImage } from "../images";
|
||||
import { guessNextRefresh } from "../refresh";
|
||||
|
||||
type SeasonI = typeof seasons.$inferInsert;
|
||||
@ -37,17 +37,33 @@ export const insertSeasons = async (
|
||||
})
|
||||
.returning({ pk: seasons.pk, id: seasons.id, slug: seasons.slug });
|
||||
|
||||
const trans: SeasonTransI[] = items.flatMap((seed, i) =>
|
||||
Object.entries(seed.translations).map(([lang, tr]) => ({
|
||||
// assumes ret is ordered like items.
|
||||
pk: ret[i].pk,
|
||||
language: lang,
|
||||
...tr,
|
||||
poster: processOptImage(tr.poster),
|
||||
thumbnail: processOptImage(tr.thumbnail),
|
||||
banner: processOptImage(tr.banner),
|
||||
})),
|
||||
);
|
||||
const trans: SeasonTransI[] = (
|
||||
await Promise.all(
|
||||
items.map(
|
||||
async (seed, i) =>
|
||||
await Promise.all(
|
||||
Object.entries(seed.translations).map(async ([lang, tr]) => ({
|
||||
// assumes ret is ordered like items.
|
||||
pk: ret[i].pk,
|
||||
language: lang,
|
||||
...tr,
|
||||
poster: await enqueueOptImage(tx, {
|
||||
url: tr.poster,
|
||||
column: seasonTranslations.poster,
|
||||
}),
|
||||
thumbnail: await enqueueOptImage(tx, {
|
||||
url: tr.thumbnail,
|
||||
column: seasonTranslations.thumbnail,
|
||||
}),
|
||||
banner: await enqueueOptImage(tx, {
|
||||
url: tr.banner,
|
||||
column: seasonTranslations.banner,
|
||||
}),
|
||||
})),
|
||||
),
|
||||
),
|
||||
)
|
||||
).flat();
|
||||
await tx
|
||||
.insert(seasonTranslations)
|
||||
.values(trans)
|
||||
|
@ -1,37 +1,80 @@
|
||||
import { and, count, eq, exists, ne, sql } from "drizzle-orm";
|
||||
import { db } from "~/db";
|
||||
import { type Transaction, db } from "~/db";
|
||||
import { entries, entryVideoJoin, showTranslations, shows } from "~/db/schema";
|
||||
import { conflictUpdateAllExcept, sqlarr } from "~/db/utils";
|
||||
import type { SeedCollection } from "~/models/collections";
|
||||
import type { SeedMovie } from "~/models/movie";
|
||||
import type { SeedSerie } from "~/models/serie";
|
||||
import type { Original } from "~/models/utils";
|
||||
import { getYear } from "~/utils";
|
||||
import { processOptImage } from "../images";
|
||||
import { enqueueOptImage } from "../images";
|
||||
|
||||
type Show = typeof shows.$inferInsert;
|
||||
type ShowTrans = typeof showTranslations.$inferInsert;
|
||||
|
||||
export const insertShow = async (
|
||||
show: Show,
|
||||
show: Omit<Show, "original">,
|
||||
original: Original & {
|
||||
poster: string | null;
|
||||
thumbnail: string | null;
|
||||
banner: string | null;
|
||||
logo: string | null;
|
||||
},
|
||||
translations:
|
||||
| SeedMovie["translations"]
|
||||
| SeedSerie["translations"]
|
||||
| SeedCollection["translations"],
|
||||
) => {
|
||||
return await db.transaction(async (tx) => {
|
||||
const ret = await insertBaseShow(tx, show);
|
||||
const orig = {
|
||||
...original,
|
||||
poster: await enqueueOptImage(tx, {
|
||||
url: original.poster,
|
||||
table: shows,
|
||||
column: sql`${shows.original}['poster']`,
|
||||
}),
|
||||
thumbnail: await enqueueOptImage(tx, {
|
||||
url: original.thumbnail,
|
||||
table: shows,
|
||||
column: sql`${shows.original}['thumbnail']`,
|
||||
}),
|
||||
banner: await enqueueOptImage(tx, {
|
||||
url: original.banner,
|
||||
table: shows,
|
||||
column: sql`${shows.original}['banner']`,
|
||||
}),
|
||||
logo: await enqueueOptImage(tx, {
|
||||
url: original.logo,
|
||||
table: shows,
|
||||
column: sql`${shows.original}['logo']`,
|
||||
}),
|
||||
};
|
||||
const ret = await insertBaseShow(tx, { ...show, original: orig });
|
||||
if ("status" in ret) return ret;
|
||||
|
||||
const trans: ShowTrans[] = Object.entries(translations).map(
|
||||
([lang, tr]) => ({
|
||||
const trans: ShowTrans[] = await Promise.all(
|
||||
Object.entries(translations).map(async ([lang, tr]) => ({
|
||||
pk: ret.pk,
|
||||
language: lang,
|
||||
...tr,
|
||||
poster: processOptImage(tr.poster),
|
||||
thumbnail: processOptImage(tr.thumbnail),
|
||||
logo: processOptImage(tr.logo),
|
||||
banner: processOptImage(tr.banner),
|
||||
}),
|
||||
latinName: tr.latinName ?? null,
|
||||
poster: await enqueueOptImage(tx, {
|
||||
url: tr.poster,
|
||||
column: showTranslations.poster,
|
||||
}),
|
||||
thumbnail: await enqueueOptImage(tx, {
|
||||
url: tr.thumbnail,
|
||||
column: showTranslations.thumbnail,
|
||||
}),
|
||||
logo: await enqueueOptImage(tx, {
|
||||
url: tr.logo,
|
||||
column: showTranslations.logo,
|
||||
}),
|
||||
banner: await enqueueOptImage(tx, {
|
||||
url: tr.banner,
|
||||
column: showTranslations.banner,
|
||||
}),
|
||||
})),
|
||||
);
|
||||
await tx
|
||||
.insert(showTranslations)
|
||||
@ -44,10 +87,7 @@ export const insertShow = async (
|
||||
});
|
||||
};
|
||||
|
||||
async function insertBaseShow(
|
||||
tx: Parameters<Parameters<typeof db.transaction>[0]>[0],
|
||||
show: Show,
|
||||
) {
|
||||
async function insertBaseShow(tx: Transaction, show: Show) {
|
||||
function insert() {
|
||||
return tx
|
||||
.insert(shows)
|
||||
@ -97,7 +137,7 @@ async function insertBaseShow(
|
||||
}
|
||||
|
||||
export async function updateAvailableCount(
|
||||
tx: typeof db | Parameters<Parameters<typeof db.transaction>[0]>[0],
|
||||
tx: Transaction,
|
||||
showPks: number[],
|
||||
updateEntryCount = true,
|
||||
) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { db } from "~/db";
|
||||
import { roles, staff } from "~/db/schema";
|
||||
import { conflictUpdateAllExcept } from "~/db/utils";
|
||||
import type { SeedStaff } from "~/models/staff";
|
||||
import { processOptImage } from "../images";
|
||||
import { enqueueOptImage } from "../images";
|
||||
|
||||
export const insertStaff = async (
|
||||
seed: SeedStaff[] | undefined,
|
||||
@ -12,10 +12,15 @@ export const insertStaff = async (
|
||||
if (!seed?.length) return [];
|
||||
|
||||
return await db.transaction(async (tx) => {
|
||||
const people = seed.map((x) => ({
|
||||
...x.staff,
|
||||
image: processOptImage(x.staff.image),
|
||||
}));
|
||||
const people = await Promise.all(
|
||||
seed.map(async (x) => ({
|
||||
...x.staff,
|
||||
image: await enqueueOptImage(tx, {
|
||||
url: x.staff.image,
|
||||
column: staff.image,
|
||||
}),
|
||||
})),
|
||||
);
|
||||
const ret = await tx
|
||||
.insert(staff)
|
||||
.values(people)
|
||||
@ -25,16 +30,22 @@ export const insertStaff = async (
|
||||
})
|
||||
.returning({ pk: staff.pk, id: staff.id, slug: staff.slug });
|
||||
|
||||
const rval = seed.map((x, i) => ({
|
||||
showPk,
|
||||
staffPk: ret[i].pk,
|
||||
kind: x.kind,
|
||||
order: i,
|
||||
character: {
|
||||
...x.character,
|
||||
image: processOptImage(x.character.image),
|
||||
},
|
||||
}));
|
||||
const rval = await Promise.all(
|
||||
seed.map(async (x, i) => ({
|
||||
showPk,
|
||||
staffPk: ret[i].pk,
|
||||
kind: x.kind,
|
||||
order: i,
|
||||
character: {
|
||||
...x.character,
|
||||
image: await enqueueOptImage(tx, {
|
||||
url: x.character.image,
|
||||
table: roles,
|
||||
column: sql`${roles.character}['image']`,
|
||||
}),
|
||||
},
|
||||
})),
|
||||
);
|
||||
|
||||
// always replace all roles. this is because:
|
||||
// - we want `order` to stay in sync (& without duplicates)
|
||||
|
@ -2,7 +2,7 @@ import { db } from "~/db";
|
||||
import { showStudioJoin, studioTranslations, studios } from "~/db/schema";
|
||||
import { conflictUpdateAllExcept } from "~/db/utils";
|
||||
import type { SeedStudio } from "~/models/studio";
|
||||
import { processOptImage } from "../images";
|
||||
import { enqueueOptImage } from "../images";
|
||||
|
||||
type StudioI = typeof studios.$inferInsert;
|
||||
type StudioTransI = typeof studioTranslations.$inferInsert;
|
||||
@ -33,14 +33,24 @@ export const insertStudios = async (
|
||||
})
|
||||
.returning({ pk: studios.pk, id: studios.id, slug: studios.slug });
|
||||
|
||||
const trans: StudioTransI[] = seed.flatMap((x, i) =>
|
||||
Object.entries(x.translations).map(([lang, tr]) => ({
|
||||
pk: ret[i].pk,
|
||||
language: lang,
|
||||
name: tr.name,
|
||||
logo: processOptImage(tr.logo),
|
||||
})),
|
||||
);
|
||||
const trans: StudioTransI[] = (
|
||||
await Promise.all(
|
||||
seed.map(
|
||||
async (x, i) =>
|
||||
await Promise.all(
|
||||
Object.entries(x.translations).map(async ([lang, tr]) => ({
|
||||
pk: ret[i].pk,
|
||||
language: lang,
|
||||
name: tr.name,
|
||||
logo: await enqueueOptImage(tx, {
|
||||
url: tr.logo,
|
||||
column: studioTranslations.logo,
|
||||
}),
|
||||
})),
|
||||
),
|
||||
),
|
||||
)
|
||||
).flat();
|
||||
await tx
|
||||
.insert(studioTranslations)
|
||||
.values(trans)
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { t } from "elysia";
|
||||
import type { SeedMovie } from "~/models/movie";
|
||||
import { getYear } from "~/utils";
|
||||
import { processOptImage } from "./images";
|
||||
import { insertCollection } from "./insert/collection";
|
||||
import { insertEntries } from "./insert/entries";
|
||||
import { insertShow, updateAvailableCount } from "./insert/shows";
|
||||
import { insertShow } from "./insert/shows";
|
||||
import { insertStaff } from "./insert/staff";
|
||||
import { insertStudios } from "./insert/studios";
|
||||
import { guessNextRefresh } from "./refresh";
|
||||
@ -55,6 +54,7 @@ export const seedMovie = async (
|
||||
|
||||
const { translations, videos, collection, studios, staff, ...movie } = seed;
|
||||
const nextRefresh = guessNextRefresh(movie.airDate ?? new Date());
|
||||
|
||||
const original = translations[movie.originalLanguage];
|
||||
if (!original) {
|
||||
return {
|
||||
@ -76,17 +76,13 @@ export const seedMovie = async (
|
||||
nextRefresh,
|
||||
collectionPk: col?.pk,
|
||||
entriesCount: 1,
|
||||
original: {
|
||||
language: movie.originalLanguage,
|
||||
name: original.name,
|
||||
latinName: original.latinName ?? null,
|
||||
poster: processOptImage(original.poster),
|
||||
thumbnail: processOptImage(original.thumbnail),
|
||||
logo: processOptImage(original.logo),
|
||||
banner: processOptImage(original.banner),
|
||||
},
|
||||
...movie,
|
||||
},
|
||||
{
|
||||
...original,
|
||||
latinName: original.latinName ?? null,
|
||||
language: movie.originalLanguage,
|
||||
},
|
||||
translations,
|
||||
);
|
||||
if ("status" in show) return show;
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { t } from "elysia";
|
||||
import type { SeedSerie } from "~/models/serie";
|
||||
import { getYear } from "~/utils";
|
||||
import { processOptImage } from "./images";
|
||||
import { insertCollection } from "./insert/collection";
|
||||
import { insertEntries } from "./insert/entries";
|
||||
import { insertSeasons } from "./insert/seasons";
|
||||
import { insertShow, updateAvailableCount } from "./insert/shows";
|
||||
import { insertShow } from "./insert/shows";
|
||||
import { insertStaff } from "./insert/staff";
|
||||
import { insertStudios } from "./insert/studios";
|
||||
import { guessNextRefresh } from "./refresh";
|
||||
@ -91,6 +90,7 @@ export const seedSerie = async (
|
||||
...serie
|
||||
} = seed;
|
||||
const nextRefresh = guessNextRefresh(serie.startAir ?? new Date());
|
||||
|
||||
const original = translations[serie.originalLanguage];
|
||||
if (!original) {
|
||||
return {
|
||||
@ -111,17 +111,13 @@ export const seedSerie = async (
|
||||
nextRefresh,
|
||||
collectionPk: col?.pk,
|
||||
entriesCount: entries.length,
|
||||
original: {
|
||||
language: serie.originalLanguage,
|
||||
name: original.name,
|
||||
latinName: original.latinName ?? null,
|
||||
poster: processOptImage(original.poster),
|
||||
thumbnail: processOptImage(original.thumbnail),
|
||||
logo: processOptImage(original.logo),
|
||||
banner: processOptImage(original.banner),
|
||||
},
|
||||
...serie,
|
||||
},
|
||||
{
|
||||
...original,
|
||||
latinName: original.latinName ?? null,
|
||||
language: serie.originalLanguage,
|
||||
},
|
||||
translations,
|
||||
);
|
||||
if ("status" in show) return show;
|
||||
|
@ -31,3 +31,7 @@ export const migrate = async () => {
|
||||
});
|
||||
console.log(`Database ${dbConfig.database} migrated!`);
|
||||
};
|
||||
|
||||
export type Transaction =
|
||||
| typeof db
|
||||
| Parameters<Parameters<typeof db.transaction>[0]>[0];
|
||||
|
@ -4,3 +4,4 @@ export * from "./shows";
|
||||
export * from "./studios";
|
||||
export * from "./staff";
|
||||
export * from "./videos";
|
||||
export * from "./mqueue";
|
||||
|
23
api/src/db/schema/mqueue.ts
Normal file
23
api/src/db/schema/mqueue.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import {
|
||||
index,
|
||||
integer,
|
||||
jsonb,
|
||||
timestamp,
|
||||
uuid,
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { schema } from "./utils";
|
||||
|
||||
export const mqueue = schema.table(
|
||||
"mqueue",
|
||||
{
|
||||
id: uuid().notNull().primaryKey().defaultRandom(),
|
||||
kind: varchar({ length: 255 }).notNull(),
|
||||
message: jsonb().notNull(),
|
||||
attempt: integer().notNull().default(0),
|
||||
createdAt: timestamp({ withTimezone: true, mode: "string" })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
},
|
||||
(t) => [index("mqueue_created").on(t.createdAt)],
|
||||
);
|
@ -58,10 +58,10 @@ export const genres = schema.enum("genres", [
|
||||
]);
|
||||
|
||||
type OriginalWithImages = Original & {
|
||||
poster: Image | null;
|
||||
thumbnail: Image | null;
|
||||
banner: Image | null;
|
||||
logo: Image | null;
|
||||
poster?: Image | null;
|
||||
thumbnail?: Image | null;
|
||||
banner?: Image | null;
|
||||
logo?: Image | null;
|
||||
};
|
||||
|
||||
export const shows = schema.table(
|
||||
|
@ -1,5 +1,6 @@
|
||||
import jwt from "@elysiajs/jwt";
|
||||
import { swagger } from "@elysiajs/swagger";
|
||||
import { processImages } from "./controllers/seed/images";
|
||||
import { migrate } from "./db";
|
||||
import { app } from "./elysia";
|
||||
import { comment } from "./utils";
|
||||
@ -23,6 +24,9 @@ if (!secret) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// run image processor task in background
|
||||
processImages();
|
||||
|
||||
app
|
||||
.use(jwt({ secret }))
|
||||
.use(
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { processImages } from "~/controllers/seed/images";
|
||||
import { db, migrate } from "~/db";
|
||||
import { shows, videos } from "~/db/schema";
|
||||
import { mqueue, shows, videos } from "~/db/schema";
|
||||
import { madeInAbyss, madeInAbyssVideo } from "~/models/examples";
|
||||
import { createSerie, createVideo, getSerie } from "./helpers";
|
||||
|
||||
@ -8,10 +9,16 @@ import { createSerie, createVideo, getSerie } from "./helpers";
|
||||
await migrate();
|
||||
await db.delete(shows);
|
||||
await db.delete(videos);
|
||||
await db.delete(mqueue);
|
||||
|
||||
const [_, vid] = await createVideo(madeInAbyssVideo);
|
||||
console.log(vid);
|
||||
const [__, ser] = await createSerie(madeInAbyss);
|
||||
console.log(ser);
|
||||
|
||||
await processImages();
|
||||
|
||||
const [___, got] = await getSerie(madeInAbyss.slug, { with: ["translations"] });
|
||||
console.log(got);
|
||||
|
||||
process.exit(0);
|
||||
|
31
api/tests/misc/images.test.ts
Normal file
31
api/tests/misc/images.test.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { beforeAll, describe, expect, it } from "bun:test";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { defaultBlurhash, processImages } from "~/controllers/seed/images";
|
||||
import { db } from "~/db";
|
||||
import { mqueue, shows, staff, studios, videos } from "~/db/schema";
|
||||
import { madeInAbyss } from "~/models/examples";
|
||||
import { createSerie } from "../helpers";
|
||||
|
||||
beforeAll(async () => {
|
||||
await db.delete(shows);
|
||||
await db.delete(studios);
|
||||
await db.delete(staff);
|
||||
await db.delete(videos);
|
||||
await db.delete(mqueue);
|
||||
|
||||
await createSerie(madeInAbyss);
|
||||
const release = await processImages();
|
||||
// remove notifications to prevent other images to be downloaded (do not curl 20000 images for nothing)
|
||||
release();
|
||||
});
|
||||
|
||||
describe("images", () => {
|
||||
it("Create a serie download images", async () => {
|
||||
const ret = await db.query.shows.findFirst({
|
||||
where: eq(shows.slug, madeInAbyss.slug),
|
||||
});
|
||||
expect(ret!.slug).toBe(madeInAbyss.slug);
|
||||
expect(ret!.original.poster!.blurhash).toBeString();
|
||||
expect(ret!.original.poster!.blurhash).not.toBe(defaultBlurhash);
|
||||
});
|
||||
});
|
@ -9,9 +9,11 @@
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noErrorTruncation": true,
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./src/*"]
|
||||
"~/*": ["./src/*"],
|
||||
"package.json": ["package.json"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user