From 3cd58f63e6f7825aa4735b98725c347234f207fc Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Thu, 16 Oct 2025 22:22:20 +0200 Subject: [PATCH] refactor: migrate from MongoDB to Prisma for data management, removing mongoose models and updating services to use Prisma client --- .dockerignore | 72 +++- .gitignore | 3 + Dockerfile | 23 +- ENV.md | 10 +- docker-compose.dev.yml | 11 +- docker-compose.yml | 9 +- package.json | 4 +- pnpm-lock.yaml | 452 +++++++++++++++--------- prisma/schema.prisma | 91 +++++ src/lib/models/config.model.ts | 35 -- src/lib/models/favorite.model.ts | 23 -- src/lib/models/preferences.model.ts | 94 ----- src/lib/models/ttl-config.model.ts | 46 --- src/lib/models/user.model.ts | 36 -- src/lib/mongodb.ts | 52 --- src/lib/prisma.ts | 14 + src/lib/services/auth-server.service.ts | 32 +- src/lib/services/config-db.service.ts | 72 ++-- src/lib/services/favorite.service.ts | 75 ++-- src/lib/services/preferences.service.ts | 56 ++- src/types/preferences.ts | 2 +- 21 files changed, 636 insertions(+), 576 deletions(-) create mode 100644 prisma/schema.prisma delete mode 100644 src/lib/models/config.model.ts delete mode 100644 src/lib/models/favorite.model.ts delete mode 100644 src/lib/models/preferences.model.ts delete mode 100644 src/lib/models/ttl-config.model.ts delete mode 100644 src/lib/models/user.model.ts delete mode 100644 src/lib/mongodb.ts create mode 100644 src/lib/prisma.ts diff --git a/.dockerignore b/.dockerignore index d28a3ed..847764c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,16 +1,66 @@ -.git -.gitignore +# dependencies node_modules +.pnp +.pnp.js +.pnpm-store + +# testing +coverage + +# next.js .next -.env.local -.env.development.local -.env.test.local -.env.production.local -README.md -.vscode -.idea -*.log +out + +# production +build +dist + +# misc +.DS_Store +*.pem + +# debug npm-debug.log* yarn-debug.log* yarn-error.log* -.DS_Store \ No newline at end of file +.pnpm-debug.log* + +# local env files +.env*.local +.env +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# IDE +.idea +.vscode +.vscode-test + +# git +.git +.gitignore + +# cache +.cache +debug-logs + +# docker +Dockerfile +docker-compose.yml +docker-compose.dev.yml +.dockerignore + +# documentation +README.md +docs + +# scripts +scripts diff --git a/.gitignore b/.gitignore index a538ba3..dfb7763 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,9 @@ next-env.d.ts .cache debug-logs +# MongoDB +mongo-keyfile + # Environment variables .env .env.local diff --git a/Dockerfile b/Dockerfile index 859f677..ce14e20 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,6 +20,9 @@ RUN corepack enable && corepack prepare pnpm@9.0.0 --activate # Copy package files first to leverage Docker cache COPY package.json pnpm-lock.yaml ./ +# Copy Prisma schema +COPY prisma ./prisma + # Copy configuration files COPY tsconfig.json .eslintrc.json ./ COPY tailwind.config.ts postcss.config.js ./ @@ -28,6 +31,9 @@ COPY tailwind.config.ts postcss.config.js ./ RUN pnpm config set store-dir /app/.pnpm-store && \ pnpm install --frozen-lockfile +# Generate Prisma Client +RUN pnpm prisma generate + # Copy source files COPY src ./src COPY public ./public @@ -40,13 +46,18 @@ FROM node:20-alpine AS runner WORKDIR /app -# Install production dependencies only +# Install OpenSSL (required by Prisma) +RUN apk add --no-cache openssl libc6-compat + +# Copy package files and prisma schema COPY package.json pnpm-lock.yaml ./ -RUN corepack enable && \ - corepack prepare pnpm@9.0.0 --activate && \ - pnpm config set store-dir /app/.pnpm-store && \ - pnpm install --prod --frozen-lockfile && \ - pnpm store prune +COPY prisma ./prisma + +# Enable pnpm +RUN corepack enable && corepack prepare pnpm@9.0.0 --activate + +# Copy the entire node_modules from builder (includes Prisma Client) +COPY --from=builder /app/node_modules ./node_modules # Copy built application from builder stage COPY --from=builder /app/.next ./.next diff --git a/ENV.md b/ENV.md index 58f1e2d..5b268e9 100644 --- a/ENV.md +++ b/ENV.md @@ -5,7 +5,7 @@ # MongoDB Configuration MONGO_USER=admin MONGO_PASSWORD=your-secure-password -MONGODB_URI=mongodb://admin:your-secure-password@mongodb:27017/stripstream?authSource=admin +MONGODB_URI=mongodb://admin:your-secure-password@mongodb:27017/stripstream?authSource=admin&replicaSet=rs0 # NextAuth Configuration NEXTAUTH_SECRET=your-secret-key-here-generate-with-openssl-rand-base64-32 @@ -20,5 +20,13 @@ NODE_ENV=production openssl rand -base64 32 ``` +## Génération du keyFile MongoDB (requis pour Prisma) +```bash +openssl rand -base64 756 > mongo-keyfile +chmod 400 mongo-keyfile +``` + +Ce fichier est nécessaire pour MongoDB en mode replica set (requis par Prisma pour les relations et transactions). + ## Développement Pour le développement, les variables sont définies directement dans `docker-compose.dev.yml`. diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 6369bb1..e697394 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -12,6 +12,7 @@ services: - "3020:3000" volumes: - ./src:/app/src + - ./prisma:/app/prisma - ./public:/app/public - ./package.json:/app/package.json - ./pnpm-lock.yaml:/app/pnpm-lock.yaml @@ -31,7 +32,7 @@ services: - WATCHPACK_POLLING=true - NEXTAUTH_SECRET=${NEXTAUTH_SECRET} - NEXTAUTH_URL=${NEXTAUTH_URL} - command: sh -c "pnpm config set store-dir /app/.pnpm-store && pnpm install --frozen-lockfile && pnpm dev" + command: sh -c "pnpm config set store-dir /app/.pnpm-store && pnpm install --frozen-lockfile && pnpm prisma generate && pnpm dev" mongodb: image: mongo:latest @@ -46,6 +47,14 @@ services: volumes: - mongodb_data:/data/db - ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro + - ./mongo-keyfile:/data/keyfile:ro + command: ["mongod", "--replSet", "rs0", "--bind_ip_all", "--keyFile", "/data/keyfile"] + healthcheck: + test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'mongodb:27017'}]}) }" | mongosh -u ${MONGO_USER} -p ${MONGO_PASSWORD} --authenticationDatabase admin --quiet + interval: 10s + timeout: 10s + retries: 5 + start_period: 40s volumes: mongodb_data: diff --git a/docker-compose.yml b/docker-compose.yml index 1c55e41..1679355 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -45,6 +45,7 @@ services: MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD} volumes: - stripstream_mongodb_data:/data/db + - ./mongo-keyfile:/data/keyfile:ro networks: - stripstream-network deploy: @@ -54,7 +55,13 @@ services: memory: 512M ports: - "27017:27017" - command: ["mongod", "--auth", "--bind_ip_all"] + command: ["mongod", "--auth", "--bind_ip_all", "--replSet", "rs0", "--keyFile", "/data/keyfile"] + healthcheck: + test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'mongodb:27017'}]}) }" | mongosh -u ${MONGO_USER} -p ${MONGO_PASSWORD} --authenticationDatabase admin --quiet + interval: 10s + timeout: 10s + retries: 5 + start_period: 40s networks: stripstream-network: diff --git a/package.json b/package.json index cae38da..d07eee4 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "icons": "node scripts/generate-icons.js" }, "dependencies": { + "@prisma/client": "^6.17.1", "@radix-ui/react-dialog": "1.0.5", "@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-progress": "^1.1.2", @@ -18,7 +19,6 @@ "@radix-ui/react-slot": "1.0.2", "@radix-ui/react-toast": "1.1.5", "@types/bcryptjs": "^3.0.0", - "@types/mongoose": "5.11.97", "bcryptjs": "^3.0.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", @@ -26,7 +26,6 @@ "i18next": "^24.2.2", "i18next-browser-languagedetector": "^8.0.4", "lucide-react": "^0.487.0", - "mongoose": "8.1.0", "next": "15.2.0", "next-auth": "5.0.0-beta.29", "next-themes": "0.2.1", @@ -53,6 +52,7 @@ "eslint-plugin-typescript-sort-keys": "^3.3.0", "eslint-plugin-unused-imports": "^4.1.4", "postcss": "8.4.33", + "prisma": "^6.17.1", "tailwindcss": "3.4.1", "typescript": "5.3.3" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1f12c6c..e15e99d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@prisma/client': + specifier: ^6.17.1 + version: 6.17.1(prisma@6.17.1(typescript@5.3.3))(typescript@5.3.3) '@radix-ui/react-dialog': specifier: 1.0.5 version: 1.0.5(@types/react-dom@18.2.21)(@types/react@18.2.64)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -29,9 +32,6 @@ importers: '@types/bcryptjs': specifier: ^3.0.0 version: 3.0.0 - '@types/mongoose': - specifier: 5.11.97 - version: 5.11.97 bcryptjs: specifier: ^3.0.2 version: 3.0.2 @@ -53,9 +53,6 @@ importers: lucide-react: specifier: ^0.487.0 version: 0.487.0(react@18.2.0) - mongoose: - specifier: 8.1.0 - version: 8.1.0 next: specifier: 15.2.0 version: 15.2.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -129,6 +126,9 @@ importers: postcss: specifier: 8.4.33 version: 8.4.33 + prisma: + specifier: ^6.17.1 + version: 6.17.1(typescript@5.3.3) tailwindcss: specifier: 3.4.1 version: 3.4.1 @@ -459,9 +459,6 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@mongodb-js/saslprep@1.3.1': - resolution: {integrity: sha512-6nZrq5kfAz0POWyhljnbWQQJQ5uT8oE2ddX303q1uY0tWsivWKgBDXBBvuFPwOqRRalXJuVO9EjOdVtuhLX0zg==} - '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -542,6 +539,36 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@prisma/client@6.17.1': + resolution: {integrity: sha512-zL58jbLzYamjnNnmNA51IOZdbk5ci03KviXCuB0Tydc9btH2kDWsi1pQm2VecviRTM7jGia0OPPkgpGnT3nKvw==} + engines: {node: '>=18.18'} + peerDependencies: + prisma: '*' + typescript: '>=5.1.0' + peerDependenciesMeta: + prisma: + optional: true + typescript: + optional: true + + '@prisma/config@6.17.1': + resolution: {integrity: sha512-fs8wY6DsvOCzuiyWVckrVs1LOcbY4LZNz8ki4uUIQ28jCCzojTGqdLhN2Jl5lDnC1yI8/gNIKpsWDM8pLhOdwA==} + + '@prisma/debug@6.17.1': + resolution: {integrity: sha512-Vf7Tt5Wh9XcndpbmeotuqOMLWPTjEKCsgojxXP2oxE1/xYe7PtnP76hsouG9vis6fctX+TxgmwxTuYi/+xc7dQ==} + + '@prisma/engines-version@6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac': + resolution: {integrity: sha512-17140E3huOuD9lMdJ9+SF/juOf3WR3sTJMVyyenzqUPbuH+89nPhSWcrY+Mf7tmSs6HvaO+7S+HkELinn6bhdg==} + + '@prisma/engines@6.17.1': + resolution: {integrity: sha512-D95Ik3GYZkqZ8lSR4EyFOJ/tR33FcYRP8kK61o+WMsyD10UfJwd7+YielflHfKwiGodcqKqoraWw8ElAgMDbPw==} + + '@prisma/fetch-engine@6.17.1': + resolution: {integrity: sha512-AYZiHOs184qkDMiTeshyJCtyL4yERkjfTkJiSJdYuSfc24m94lTNL5+GFinZ6vVz+ktX4NJzHKn1zIFzGTWrWg==} + + '@prisma/get-platform@6.17.1': + resolution: {integrity: sha512-AKEn6fsfz0r482S5KRDFlIGEaq9wLNcgalD1adL+fPcFFblIKs1sD81kY/utrHdqKuVC6E1XSRpegDK3ZLL4Qg==} + '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -1066,6 +1093,9 @@ packages: '@rushstack/eslint-patch@1.14.0': resolution: {integrity: sha512-WJFej426qe4RWOm9MMtP4V3CV4AucXolQty+GRgAWLgQXmpCuwzs7hEpxxhSc/znXUSxum9d/P/32MW0FlAAlA==} + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} @@ -1085,10 +1115,6 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/mongoose@5.11.97': - resolution: {integrity: sha512-cqwOVYT3qXyLiGw7ueU2kX9noE8DPGRY6z8eUxudhXY8NZ7DMKYAxyZkLSevGfhCX3dO/AoX5/SO9lAzfjon0Q==} - deprecated: Mongoose publishes its own types, so you do not need to install this package. - '@types/node@24.7.2': resolution: {integrity: sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==} @@ -1107,12 +1133,6 @@ packages: '@types/semver@7.7.1': resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} - '@types/webidl-conversions@7.0.3': - resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} - - '@types/whatwg-url@11.0.5': - resolution: {integrity: sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==} - '@typescript-eslint/eslint-plugin@8.46.1': resolution: {integrity: sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1469,14 +1489,18 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - bson@6.10.4: - resolution: {integrity: sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==} - engines: {node: '>=16.20.1'} - busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} + c12@3.1.0: + resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==} + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1508,6 +1532,13 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -1539,6 +1570,13 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1586,6 +1624,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge-ts@7.1.5: + resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} + engines: {node: '>=16.0.0'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -1594,6 +1636,12 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1619,6 +1667,10 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1626,6 +1678,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + effect@3.16.12: + resolution: {integrity: sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==} + electron-to-chromium@1.5.235: resolution: {integrity: sha512-i/7ntLFwOdoHY7sgjlTIDo4Sl8EdoTjWIaKinYOVfC6bOp71bmwenyZthWHcasxgHDNWbWxvG9M3Ia116zIaYQ==} @@ -1635,6 +1690,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + engines: {node: '>=14'} + es-abstract@1.24.0: resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} engines: {node: '>= 0.4'} @@ -1818,6 +1877,13 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + exsolve@1.0.7: + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1929,6 +1995,10 @@ packages: get-tsconfig@4.12.0: resolution: {integrity: sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==} + giget@2.0.0: + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} + hasBin: true + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2167,6 +2237,10 @@ packages: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + jose@6.1.0: resolution: {integrity: sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==} @@ -2197,10 +2271,6 @@ packages: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} - kareem@2.5.1: - resolution: {integrity: sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==} - engines: {node: '>=12.0.0'} - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -2249,9 +2319,6 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - memory-pager@1.5.0: - resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} - merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -2278,48 +2345,6 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - mongodb-connection-string-url@3.0.2: - resolution: {integrity: sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==} - - mongodb@6.3.0: - resolution: {integrity: sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==} - engines: {node: '>=16.20.1'} - peerDependencies: - '@aws-sdk/credential-providers': ^3.188.0 - '@mongodb-js/zstd': ^1.1.0 - gcp-metadata: ^5.2.0 - kerberos: ^2.0.1 - mongodb-client-encryption: '>=6.0.0 <7' - snappy: ^7.2.2 - socks: ^2.7.1 - peerDependenciesMeta: - '@aws-sdk/credential-providers': - optional: true - '@mongodb-js/zstd': - optional: true - gcp-metadata: - optional: true - kerberos: - optional: true - mongodb-client-encryption: - optional: true - snappy: - optional: true - socks: - optional: true - - mongoose@8.1.0: - resolution: {integrity: sha512-kOA4Xnq2goqNpN9EmYElGNWfxA9H80fxcr7UdJKWi3UMflza0R7wpTihCpM67dE/0MNFljoa0sjQtlXVkkySAQ==} - engines: {node: '>=16.20.1'} - - mpath@0.9.0: - resolution: {integrity: sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==} - engines: {node: '>=4.0.0'} - - mquery@5.0.0: - resolution: {integrity: sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==} - engines: {node: '>=14.0.0'} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2386,6 +2411,9 @@ packages: sass: optional: true + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + node-releases@2.0.23: resolution: {integrity: sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==} @@ -2397,6 +2425,11 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} + nypm@0.6.2: + resolution: {integrity: sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + oauth4webapi@3.8.2: resolution: {integrity: sha512-FzZZ+bht5X0FKe7Mwz3DAVAmlH1BV5blSak/lHMBKz0/EBMhX6B10GlQYI51+oRp8ObJaX0g6pXrAxZh5s8rjw==} @@ -2436,6 +2469,9 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -2485,6 +2521,12 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2504,6 +2546,9 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -2565,6 +2610,16 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prisma@6.17.1: + resolution: {integrity: sha512-ac6h0sM1Tg3zu8NInY+qhP/S9KhENVaw9n1BrGKQVFu05JT5yT5Qqqmb8tMRIE3ZXvVj4xcRA5yfrsy4X7Yy5g==} + engines: {node: '>=18.18'} + hasBin: true + peerDependencies: + typescript: '>=5.1.0' + peerDependenciesMeta: + typescript: + optional: true + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -2572,9 +2627,15 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + react-dom@18.2.0: resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: @@ -2657,6 +2718,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -2761,9 +2826,6 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} - sift@16.0.1: - resolution: {integrity: sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==} - signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -2779,9 +2841,6 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - sparse-bitfield@3.0.3: - resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} - stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} @@ -2889,6 +2948,9 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + tinyexec@1.0.1: + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -2897,10 +2959,6 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - tr46@5.1.1: - resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} - engines: {node: '>=18'} - ts-api-utils@1.4.3: resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} engines: {node: '>=16'} @@ -3010,14 +3068,6 @@ packages: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} - webidl-conversions@7.0.0: - resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} - engines: {node: '>=12'} - - whatwg-url@14.2.0: - resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} - engines: {node: '>=18'} - which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -3334,10 +3384,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@mongodb-js/saslprep@1.3.1': - dependencies: - sparse-bitfield: 3.0.3 - '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.5.0 @@ -3394,6 +3440,41 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@prisma/client@6.17.1(prisma@6.17.1(typescript@5.3.3))(typescript@5.3.3)': + optionalDependencies: + prisma: 6.17.1(typescript@5.3.3) + typescript: 5.3.3 + + '@prisma/config@6.17.1': + dependencies: + c12: 3.1.0 + deepmerge-ts: 7.1.5 + effect: 3.16.12 + empathic: 2.0.0 + transitivePeerDependencies: + - magicast + + '@prisma/debug@6.17.1': {} + + '@prisma/engines-version@6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac': {} + + '@prisma/engines@6.17.1': + dependencies: + '@prisma/debug': 6.17.1 + '@prisma/engines-version': 6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac + '@prisma/fetch-engine': 6.17.1 + '@prisma/get-platform': 6.17.1 + + '@prisma/fetch-engine@6.17.1': + dependencies: + '@prisma/debug': 6.17.1 + '@prisma/engines-version': 6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac + '@prisma/get-platform': 6.17.1 + + '@prisma/get-platform@6.17.1': + dependencies: + '@prisma/debug': 6.17.1 + '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.0.1': @@ -3889,6 +3970,8 @@ snapshots: '@rushstack/eslint-patch@1.14.0': {} + '@standard-schema/spec@1.0.0': {} + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.15': @@ -3908,19 +3991,6 @@ snapshots: '@types/json5@0.0.29': {} - '@types/mongoose@5.11.97': - dependencies: - mongoose: 8.1.0 - transitivePeerDependencies: - - '@aws-sdk/credential-providers' - - '@mongodb-js/zstd' - - gcp-metadata - - kerberos - - mongodb-client-encryption - - snappy - - socks - - supports-color - '@types/node@24.7.2': dependencies: undici-types: 7.14.0 @@ -3941,12 +4011,6 @@ snapshots: '@types/semver@7.7.1': {} - '@types/webidl-conversions@7.0.3': {} - - '@types/whatwg-url@11.0.5': - dependencies: - '@types/webidl-conversions': 7.0.3 - '@typescript-eslint/eslint-plugin@8.46.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0)(typescript@5.3.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -4338,12 +4402,25 @@ snapshots: node-releases: 2.0.23 update-browserslist-db: 1.1.3(browserslist@4.26.3) - bson@6.10.4: {} - busboy@1.6.0: dependencies: streamsearch: 1.1.0 + c12@3.1.0: + dependencies: + chokidar: 4.0.3 + confbox: 0.2.2 + defu: 6.1.4 + dotenv: 16.6.1 + exsolve: 1.0.7 + giget: 2.0.0 + jiti: 2.6.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.3.0 + rc9: 2.1.2 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -4384,6 +4461,14 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + citty@0.1.6: + dependencies: + consola: 3.4.2 + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -4412,6 +4497,10 @@ snapshots: concat-map@0.0.1: {} + confbox@0.2.2: {} + + consola@3.4.2: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -4452,6 +4541,8 @@ snapshots: deep-is@0.1.4: {} + deepmerge-ts@7.1.5: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -4464,6 +4555,10 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + defu@6.1.4: {} + + destr@2.0.5: {} + detect-libc@2.1.2: {} detect-node-es@1.1.0: {} @@ -4484,6 +4579,8 @@ snapshots: dependencies: esutils: 2.0.3 + dotenv@16.6.1: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4492,12 +4589,19 @@ snapshots: eastasianwidth@0.2.0: {} + effect@3.16.12: + dependencies: + '@standard-schema/spec': 1.0.0 + fast-check: 3.23.2 + electron-to-chromium@1.5.235: {} emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} + empathic@2.0.0: {} + es-abstract@1.24.0: dependencies: array-buffer-byte-length: 1.0.2 @@ -4829,6 +4933,12 @@ snapshots: esutils@2.0.3: {} + exsolve@1.0.7: {} + + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + fast-deep-equal@3.1.3: {} fast-glob@3.3.1: @@ -4949,6 +5059,15 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + node-fetch-native: 1.6.7 + nypm: 0.6.2 + pathe: 2.0.3 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -5200,6 +5319,8 @@ snapshots: jiti@1.21.7: {} + jiti@2.6.1: {} + jose@6.1.0: {} js-tokens@4.0.0: {} @@ -5227,8 +5348,6 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 - kareem@2.5.1: {} - keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -5268,8 +5387,6 @@ snapshots: math-intrinsics@1.1.0: {} - memory-pager@1.5.0: {} - merge2@1.4.1: {} micromatch@4.0.8: @@ -5293,44 +5410,6 @@ snapshots: minipass@7.1.2: {} - mongodb-connection-string-url@3.0.2: - dependencies: - '@types/whatwg-url': 11.0.5 - whatwg-url: 14.2.0 - - mongodb@6.3.0: - dependencies: - '@mongodb-js/saslprep': 1.3.1 - bson: 6.10.4 - mongodb-connection-string-url: 3.0.2 - - mongoose@8.1.0: - dependencies: - bson: 6.10.4 - kareem: 2.5.1 - mongodb: 6.3.0 - mpath: 0.9.0 - mquery: 5.0.0 - ms: 2.1.3 - sift: 16.0.1 - transitivePeerDependencies: - - '@aws-sdk/credential-providers' - - '@mongodb-js/zstd' - - gcp-metadata - - kerberos - - mongodb-client-encryption - - snappy - - socks - - supports-color - - mpath@0.9.0: {} - - mquery@5.0.0: - dependencies: - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - ms@2.1.3: {} mz@2.7.0: @@ -5384,12 +5463,22 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-fetch-native@1.6.7: {} + node-releases@2.0.23: {} normalize-path@3.0.0: {} normalize-range@0.1.2: {} + nypm@0.6.2: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 2.3.0 + tinyexec: 1.0.1 + oauth4webapi@3.8.2: {} object-assign@4.1.1: {} @@ -5436,6 +5525,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + ohash@2.0.11: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -5484,6 +5575,10 @@ snapshots: path-type@4.0.0: {} + pathe@2.0.3: {} + + perfect-debounce@1.0.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -5494,6 +5589,12 @@ snapshots: pirates@4.0.7: {} + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + possible-typed-array-names@1.1.0: {} postcss-import@15.1.0(postcss@8.4.33): @@ -5547,6 +5648,15 @@ snapshots: prelude-ls@1.2.1: {} + prisma@6.17.1(typescript@5.3.3): + dependencies: + '@prisma/config': 6.17.1 + '@prisma/engines': 6.17.1 + optionalDependencies: + typescript: 5.3.3 + transitivePeerDependencies: + - magicast + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -5555,8 +5665,15 @@ snapshots: punycode@2.3.1: {} + pure-rand@6.1.0: {} + queue-microtask@1.2.3: {} + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.5 + react-dom@18.2.0(react@18.2.0): dependencies: loose-envify: 1.4.0 @@ -5630,6 +5747,8 @@ snapshots: dependencies: picomatch: 2.3.1 + readdirp@4.1.2: {} + reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -5812,8 +5931,6 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 - sift@16.0.1: {} - signal-exit@4.1.0: {} simple-swizzle@0.2.4: @@ -5824,10 +5941,6 @@ snapshots: source-map-js@1.2.1: {} - sparse-bitfield@3.0.3: - dependencies: - memory-pager: 1.5.0 - stable-hash@0.0.5: {} stop-iteration-iterator@1.1.0: @@ -5975,6 +6088,8 @@ snapshots: dependencies: any-promise: 1.3.0 + tinyexec@1.0.1: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -5984,10 +6099,6 @@ snapshots: dependencies: is-number: 7.0.0 - tr46@5.1.1: - dependencies: - punycode: 2.3.1 - ts-api-utils@1.4.3(typescript@5.3.3): dependencies: typescript: 5.3.3 @@ -6119,13 +6230,6 @@ snapshots: void-elements@3.1.0: {} - webidl-conversions@7.0.0: {} - - whatwg-url@14.2.0: - dependencies: - tr46: 5.1.1 - webidl-conversions: 7.0.0 - which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..146f433 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,91 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "mongodb" + url = env("MONGODB_URI") +} + +model User { + id String @id @default(auto()) @map("_id") @db.ObjectId + email String @unique + password String + roles String[] @default(["ROLE_USER"]) + authenticated Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + config KomgaConfig? + ttlConfig TTLConfig? + preferences Preferences? + favorites Favorite[] + + @@map("users") +} + +model KomgaConfig { + id String @id @default(auto()) @map("_id") @db.ObjectId + userId String @unique + url String + username String + authHeader String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@map("komgaconfigs") +} + +model TTLConfig { + id String @id @default(auto()) @map("_id") @db.ObjectId + userId String @unique + defaultTTL Int @default(5) + homeTTL Int @default(5) + librariesTTL Int @default(1440) + seriesTTL Int @default(5) + booksTTL Int @default(5) + imagesTTL Int @default(1440) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@map("ttlconfigs") +} + +model Preferences { + id String @id @default(auto()) @map("_id") @db.ObjectId + userId String @unique + showThumbnails Boolean @default(true) + cacheMode String @default("memory") // "memory" | "file" + showOnlyUnread Boolean @default(false) + debug Boolean @default(false) + displayMode Json @default("{\"compact\": false, \"itemsPerPage\": 20}") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@map("preferences") +} + +model Favorite { + id String @id @default(auto()) @map("_id") @db.ObjectId + userId String + seriesId String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([userId, seriesId]) + @@index([userId]) + @@map("favorites") +} + diff --git a/src/lib/models/config.model.ts b/src/lib/models/config.model.ts deleted file mode 100644 index 704713b..0000000 --- a/src/lib/models/config.model.ts +++ /dev/null @@ -1,35 +0,0 @@ -import mongoose from "mongoose"; - -const configSchema = new mongoose.Schema( - { - userId: { - type: String, - required: true, - unique: true, - }, - url: { - type: String, - required: true, - }, - username: { - type: String, - required: true, - }, - authHeader: { - type: String, - required: true, - }, - }, - { - timestamps: true, - } -); - -// Middleware pour mettre à jour le champ updatedAt avant la sauvegarde -configSchema.pre("save", function (next) { - this.updatedAt = new Date(); - next(); -}); - -export const KomgaConfig = - mongoose.models.KomgaConfig || mongoose.model("KomgaConfig", configSchema); diff --git a/src/lib/models/favorite.model.ts b/src/lib/models/favorite.model.ts deleted file mode 100644 index 9e345e2..0000000 --- a/src/lib/models/favorite.model.ts +++ /dev/null @@ -1,23 +0,0 @@ -import mongoose from "mongoose"; - -const favoriteSchema = new mongoose.Schema( - { - userId: { - type: String, - required: true, - index: true, - }, - seriesId: { - type: String, - required: true, - }, - }, - { - timestamps: true, - } -); - -// Index composé pour s'assurer qu'un utilisateur ne peut pas avoir deux fois le même favori -favoriteSchema.index({ userId: 1, seriesId: 1 }, { unique: true }); - -export const FavoriteModel = mongoose.models.Favorite || mongoose.model("Favorite", favoriteSchema); diff --git a/src/lib/models/preferences.model.ts b/src/lib/models/preferences.model.ts deleted file mode 100644 index b854d33..0000000 --- a/src/lib/models/preferences.model.ts +++ /dev/null @@ -1,94 +0,0 @@ -import mongoose from "mongoose"; - -const preferencesSchema = new mongoose.Schema( - { - userId: { - type: String, - required: true, - unique: true, - }, - showThumbnails: { - type: Boolean, - default: true, - }, - cacheMode: { - type: String, - enum: ["memory", "file"], - default: "memory", - }, - showOnlyUnread: { - type: Boolean, - default: false, - required: false, - }, - debug: { - type: Boolean, - default: false, - required: false, - }, - displayMode: { - compact: { - type: Boolean, - default: false, - }, - itemsPerPage: { - type: Number, - default: 20, - enum: [20, 50, 100], - }, - }, - }, - { - timestamps: true, - strict: true, - toObject: { - transform: function (doc, ret) { - // Force la conversion en booléen - ret.showOnlyUnread = Boolean(ret.showOnlyUnread); - ret.debug = Boolean(ret.debug); - ret.displayMode = ret.displayMode || { compact: false, itemsPerPage: 20 }; - ret.displayMode.compact = Boolean(ret.displayMode.compact); - return ret; - }, - }, - } -); - -// Middleware pour s'assurer que les booléens sont toujours des booléens -preferencesSchema.pre("save", function (next) { - if (this.showOnlyUnread === undefined) { - this.showOnlyUnread = false; - } - if (this.debug === undefined) { - this.debug = false; - } - if (!this.displayMode) { - this.displayMode = { compact: false, itemsPerPage: 20 }; - } - this.showOnlyUnread = Boolean(this.showOnlyUnread); - this.debug = Boolean(this.debug); - this.displayMode.compact = Boolean(this.displayMode.compact); - next(); -}); - -preferencesSchema.pre("findOneAndUpdate", function (next) { - const update = this.getUpdate() as mongoose.UpdateQuery; - if (update && "$set" in update && update.$set && typeof update.$set === "object") { - if ("showOnlyUnread" in update.$set) { - update.$set.showOnlyUnread = Boolean(update.$set.showOnlyUnread); - } - if ("debug" in update.$set) { - update.$set.debug = Boolean(update.$set.debug); - } - if ("displayMode" in update.$set) { - update.$set.displayMode = { - compact: Boolean(update.$set.displayMode?.compact), - itemsPerPage: update.$set.displayMode?.itemsPerPage || 20, - }; - } - } - next(); -}); - -export const PreferencesModel = - mongoose.models.Preferences || mongoose.model("Preferences", preferencesSchema); diff --git a/src/lib/models/ttl-config.model.ts b/src/lib/models/ttl-config.model.ts deleted file mode 100644 index a95d947..0000000 --- a/src/lib/models/ttl-config.model.ts +++ /dev/null @@ -1,46 +0,0 @@ -import mongoose from "mongoose"; - -const ttlConfigSchema = new mongoose.Schema( - { - userId: { - type: String, - required: true, - unique: true, - }, - defaultTTL: { - type: Number, - default: 5, - }, - homeTTL: { - type: Number, - default: 5, - }, - librariesTTL: { - type: Number, - default: 1440, - }, - seriesTTL: { - type: Number, - default: 5, - }, - booksTTL: { - type: Number, - default: 5, - }, - imagesTTL: { - type: Number, - default: 1440, - }, - }, - { - timestamps: true, - } -); - -// Middleware pour mettre à jour le champ updatedAt avant la sauvegarde -ttlConfigSchema.pre("save", function (next) { - this.updatedAt = new Date(); - next(); -}); - -export const TTLConfig = mongoose.models.TTLConfig || mongoose.model("TTLConfig", ttlConfigSchema); diff --git a/src/lib/models/user.model.ts b/src/lib/models/user.model.ts deleted file mode 100644 index 2c3fa35..0000000 --- a/src/lib/models/user.model.ts +++ /dev/null @@ -1,36 +0,0 @@ -import mongoose from "mongoose"; - -const userSchema = new mongoose.Schema( - { - email: { - type: String, - required: true, - unique: true, - lowercase: true, - trim: true, - }, - password: { - type: String, - required: true, - }, - roles: { - type: [String], - default: ["ROLE_USER"], - }, - authenticated: { - type: Boolean, - default: true, - }, - }, - { - timestamps: true, - } -); - -// Middleware pour mettre à jour le champ updatedAt avant la sauvegarde -userSchema.pre("save", function (next) { - this.updatedAt = new Date(); - next(); -}); - -export const UserModel = mongoose.models.User || mongoose.model("User", userSchema); diff --git a/src/lib/mongodb.ts b/src/lib/mongodb.ts deleted file mode 100644 index 4a41f4a..0000000 --- a/src/lib/mongodb.ts +++ /dev/null @@ -1,52 +0,0 @@ -import mongoose from "mongoose"; -import { ERROR_CODES } from "../constants/errorCodes"; -import { AppError } from "../utils/errors"; - -const MONGODB_URI = process.env.MONGODB_URI; - -if (!MONGODB_URI) { - throw new AppError(ERROR_CODES.MONGODB.MISSING_URI); -} - -interface MongooseCache { - conn: typeof mongoose | null; - promise: Promise | null; -} - -declare global { - var mongoose: MongooseCache | undefined; -} - -let cached: MongooseCache = global.mongoose || { conn: null, promise: null }; - -if (!global.mongoose) { - global.mongoose = { conn: null, promise: null }; -} - -async function connectDB(): Promise { - if (cached.conn) { - return cached.conn; - } - - if (!cached.promise) { - const opts = { - bufferCommands: false, - }; - - cached.promise = mongoose.connect(MONGODB_URI!, opts).then((mongoose) => { - return mongoose; - }); - } - - try { - cached.conn = await cached.promise; - } catch (e) { - console.error("Error connecting to MongoDB:", e); - cached.promise = null; - throw new AppError(ERROR_CODES.MONGODB.CONNECTION_FAILED, {}, e); - } - - return cached.conn; -} - -export default connectDB; diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts new file mode 100644 index 0000000..5cd3a24 --- /dev/null +++ b/src/lib/prisma.ts @@ -0,0 +1,14 @@ +import { PrismaClient } from "@prisma/client"; + +declare global { + var prisma: PrismaClient | undefined; +} + +const prisma = global.prisma || new PrismaClient(); + +if (process.env.NODE_ENV !== "production") { + global.prisma = prisma; +} + +export default prisma; + diff --git a/src/lib/services/auth-server.service.ts b/src/lib/services/auth-server.service.ts index e182b54..78c33e4 100644 --- a/src/lib/services/auth-server.service.ts +++ b/src/lib/services/auth-server.service.ts @@ -1,5 +1,4 @@ -import connectDB from "@/lib/mongodb"; -import { UserModel } from "@/lib/models/user.model"; +import prisma from "@/lib/prisma"; import bcrypt from "bcryptjs"; import { ERROR_CODES } from "../../constants/errorCodes"; import { AppError } from "../../utils/errors"; @@ -15,15 +14,16 @@ export class AuthServerService { private static readonly SALT_ROUNDS = 10; static async registerUser(email: string, password: string): Promise { - await connectDB(); - //check if password is strong if (!AuthServerService.isPasswordStrong(password)) { throw new AppError(ERROR_CODES.AUTH.PASSWORD_NOT_STRONG); } // Check if user already exists - const existingUser = await UserModel.findOne({ email: email.toLowerCase() }); + const existingUser = await prisma.user.findUnique({ + where: { email: email.toLowerCase() }, + }); + if (existingUser) { throw new AppError(ERROR_CODES.AUTH.EMAIL_EXISTS); } @@ -32,15 +32,17 @@ export class AuthServerService { const hashedPassword = await bcrypt.hash(password, this.SALT_ROUNDS); // Create new user - const user = await UserModel.create({ - email: email.toLowerCase(), - password: hashedPassword, - roles: ["ROLE_USER"], - authenticated: true, + const user = await prisma.user.create({ + data: { + email: email.toLowerCase(), + password: hashedPassword, + roles: ["ROLE_USER"], + authenticated: true, + }, }); const userData: UserData = { - id: user._id.toString(), + id: user.id, email: user.email, roles: user.roles, authenticated: true, @@ -67,9 +69,9 @@ export class AuthServerService { } static async loginUser(email: string, password: string): Promise { - await connectDB(); - - const user = await UserModel.findOne({ email: email.toLowerCase() }); + const user = await prisma.user.findUnique({ + where: { email: email.toLowerCase() }, + }); if (!user) { throw new AppError(ERROR_CODES.AUTH.INVALID_CREDENTIALS); @@ -82,7 +84,7 @@ export class AuthServerService { } const userData: UserData = { - id: user._id.toString(), + id: user.id, email: user.email, roles: user.roles, authenticated: true, diff --git a/src/lib/services/config-db.service.ts b/src/lib/services/config-db.service.ts index 852d179..087093c 100644 --- a/src/lib/services/config-db.service.ts +++ b/src/lib/services/config-db.service.ts @@ -1,6 +1,4 @@ -import connectDB from "@/lib/mongodb"; -import { KomgaConfig as KomgaConfigModel } from "@/lib/models/config.model"; -import { TTLConfig as TTLConfigModel } from "@/lib/models/ttl-config.model"; +import prisma from "@/lib/prisma"; import { DebugService } from "./debug.service"; import { getCurrentUser } from "../auth-utils"; import { ERROR_CODES } from "../../constants/errorCodes"; @@ -19,28 +17,27 @@ export class ConfigDBService { static async saveConfig(data: KomgaConfigData): Promise { try { const user: User | null = await this.getCurrentUser(); - await connectDB(); const authHeader: string = Buffer.from(`${data.username}:${data.password}`).toString( "base64" ); - const config: KomgaConfig | null = await KomgaConfigModel.findOneAndUpdate( - { userId: user.id }, - { + const config = await prisma.komgaConfig.upsert({ + where: { userId: user.id }, + update: { + url: data.url, + username: data.username, + authHeader, + }, + create: { userId: user.id, url: data.url, username: data.username, - // password: data.password, authHeader, }, - { upsert: true, new: true } - ); - if (!config) { - throw new AppError(ERROR_CODES.CONFIG.SAVE_ERROR); - } + }); - return config; + return config as KomgaConfig; } catch (error) { if (error instanceof AppError) { throw error; @@ -52,11 +49,12 @@ export class ConfigDBService { static async getConfig(): Promise { try { const user: User | null = await this.getCurrentUser(); - await connectDB(); return DebugService.measureMongoOperation("getConfig", async () => { - const config: KomgaConfig | null = await KomgaConfigModel.findOne({ userId: user.id }); - return config; + const config = await prisma.komgaConfig.findUnique({ + where: { userId: user.id }, + }); + return config as KomgaConfig | null; }); } catch (error) { if (error instanceof AppError) { @@ -69,11 +67,12 @@ export class ConfigDBService { static async getTTLConfig(): Promise { try { const user: User | null = await this.getCurrentUser(); - await connectDB(); return DebugService.measureMongoOperation("getTTLConfig", async () => { - const config: TTLConfig | null = await TTLConfigModel.findOne({ userId: user.id }); - return config; + const config = await prisma.tTLConfig.findUnique({ + where: { userId: user.id }, + }); + return config as TTLConfig | null; }); } catch (error) { if (error instanceof AppError) { @@ -86,23 +85,30 @@ export class ConfigDBService { static async saveTTLConfig(data: TTLConfigData): Promise { try { const user: User | null = await this.getCurrentUser(); - await connectDB(); return DebugService.measureMongoOperation("saveTTLConfig", async () => { - const config: TTLConfig | null = await TTLConfigModel.findOneAndUpdate( - { userId: user.id }, - { - userId: user.id, - ...data, + const config = await prisma.tTLConfig.upsert({ + where: { userId: user.id }, + update: { + defaultTTL: data.defaultTTL, + homeTTL: data.homeTTL, + librariesTTL: data.librariesTTL, + seriesTTL: data.seriesTTL, + booksTTL: data.booksTTL, + imagesTTL: data.imagesTTL, }, - { upsert: true, new: true } - ); + create: { + userId: user.id, + defaultTTL: data.defaultTTL, + homeTTL: data.homeTTL, + librariesTTL: data.librariesTTL, + seriesTTL: data.seriesTTL, + booksTTL: data.booksTTL, + imagesTTL: data.imagesTTL, + }, + }); - if (!config) { - throw new AppError(ERROR_CODES.CONFIG.TTL_SAVE_ERROR); - } - - return config; + return config as TTLConfig; }); } catch (error) { if (error instanceof AppError) { diff --git a/src/lib/services/favorite.service.ts b/src/lib/services/favorite.service.ts index f7a0841..21d35f5 100644 --- a/src/lib/services/favorite.service.ts +++ b/src/lib/services/favorite.service.ts @@ -1,5 +1,4 @@ -import connectDB from "@/lib/mongodb"; -import { FavoriteModel } from "@/lib/models/favorite.model"; +import prisma from "@/lib/prisma"; import { DebugService } from "./debug.service"; import { getCurrentUser } from "../auth-utils"; import { ERROR_CODES } from "../../constants/errorCodes"; @@ -30,12 +29,13 @@ export class FavoriteService { static async isFavorite(seriesId: string): Promise { try { const user = await this.getCurrentUser(); - await connectDB(); return DebugService.measureMongoOperation("isFavorite", async () => { - const favorite = await FavoriteModel.findOne({ - userId: user.id, - seriesId: seriesId, + const favorite = await prisma.favorite.findFirst({ + where: { + userId: user.id, + seriesId: seriesId, + }, }); return !!favorite; }); @@ -51,14 +51,21 @@ export class FavoriteService { static async addToFavorites(seriesId: string): Promise { try { const user = await this.getCurrentUser(); - await connectDB(); await DebugService.measureMongoOperation("addToFavorites", async () => { - await FavoriteModel.findOneAndUpdate( - { userId: user.id, seriesId }, - { userId: user.id, seriesId }, - { upsert: true } - ); + await prisma.favorite.upsert({ + where: { + userId_seriesId: { + userId: user.id, + seriesId, + }, + }, + update: {}, + create: { + userId: user.id, + seriesId, + }, + }); }); this.dispatchFavoritesChanged(); @@ -73,12 +80,13 @@ export class FavoriteService { static async removeFromFavorites(seriesId: string): Promise { try { const user = await this.getCurrentUser(); - await connectDB(); await DebugService.measureMongoOperation("removeFromFavorites", async () => { - await FavoriteModel.findOneAndDelete({ - userId: user.id, - seriesId, + await prisma.favorite.deleteMany({ + where: { + userId: user.id, + seriesId, + }, }); }); @@ -93,35 +101,48 @@ export class FavoriteService { */ static async getAllFavoriteIds(): Promise { const user = await this.getCurrentUser(); - await connectDB(); return DebugService.measureMongoOperation("getAllFavoriteIds", async () => { - const favorites = await FavoriteModel.find({ userId: user.id }); + const favorites = await prisma.favorite.findMany({ + where: { userId: user.id }, + select: { seriesId: true }, + }); return favorites.map((favorite) => favorite.seriesId); }); } static async addFavorite(seriesId: string) { const user = await this.getCurrentUser(); - await connectDB(); return DebugService.measureMongoOperation("addFavorite", async () => { - const favorite = await FavoriteModel.findOneAndUpdate( - { userId: user.id, seriesId }, - { userId: user.id, seriesId }, - { upsert: true, new: true } - ); + const favorite = await prisma.favorite.upsert({ + where: { + userId_seriesId: { + userId: user.id, + seriesId, + }, + }, + update: {}, + create: { + userId: user.id, + seriesId, + }, + }); return favorite; }); } static async removeFavorite(seriesId: string): Promise { const user = await this.getCurrentUser(); - await connectDB(); return DebugService.measureMongoOperation("removeFavorite", async () => { - const result = await FavoriteModel.deleteOne({ userId: user.id, seriesId }); - return result.deletedCount > 0; + const result = await prisma.favorite.deleteMany({ + where: { + userId: user.id, + seriesId, + }, + }); + return result.count > 0; }); } } diff --git a/src/lib/services/preferences.service.ts b/src/lib/services/preferences.service.ts index c0c90f7..5e5ce42 100644 --- a/src/lib/services/preferences.service.ts +++ b/src/lib/services/preferences.service.ts @@ -1,11 +1,10 @@ -import { PreferencesModel } from "@/lib/models/preferences.model"; +import prisma from "@/lib/prisma"; import { getCurrentUser } from "../auth-utils"; import { ERROR_CODES } from "../../constants/errorCodes"; import { AppError } from "../../utils/errors"; import type { UserPreferences } from "@/types/preferences"; import { defaultPreferences } from "@/types/preferences"; import type { User } from "@/types/komga"; -import connectDB from "@/lib/mongodb"; export class PreferencesService { static async getCurrentUser(): Promise { @@ -19,15 +18,20 @@ export class PreferencesService { static async getPreferences(): Promise { try { const user = await this.getCurrentUser(); - await connectDB(); - const preferences = await PreferencesModel.findOne({ userId: user.id }); + const preferences = await prisma.preferences.findUnique({ + where: { userId: user.id }, + }); + if (!preferences) { - return defaultPreferences; + return { ...defaultPreferences }; } + return { - ...defaultPreferences, - ...preferences.toObject(), - _id: undefined, //plain object KO on server components hydration + showThumbnails: preferences.showThumbnails, + cacheMode: preferences.cacheMode as "memory" | "file", + showOnlyUnread: preferences.showOnlyUnread, + debug: preferences.debug, + displayMode: preferences.displayMode as UserPreferences["displayMode"], }; } catch (error) { if (error instanceof AppError) { @@ -40,18 +44,34 @@ export class PreferencesService { static async updatePreferences(preferences: Partial): Promise { try { const user = await this.getCurrentUser(); - await connectDB(); - const updatedPreferences = await PreferencesModel.findOneAndUpdate( - { userId: user.id }, - { $set: preferences }, - { new: true, upsert: true } - ); + + const updateData: Record = {}; + if (preferences.showThumbnails !== undefined) updateData.showThumbnails = preferences.showThumbnails; + if (preferences.cacheMode !== undefined) updateData.cacheMode = preferences.cacheMode; + if (preferences.showOnlyUnread !== undefined) updateData.showOnlyUnread = preferences.showOnlyUnread; + if (preferences.debug !== undefined) updateData.debug = preferences.debug; + if (preferences.displayMode !== undefined) updateData.displayMode = preferences.displayMode; - const result = { - ...defaultPreferences, - ...updatedPreferences.toObject(), + const updatedPreferences = await prisma.preferences.upsert({ + where: { userId: user.id }, + update: updateData, + create: { + userId: user.id, + showThumbnails: preferences.showThumbnails ?? defaultPreferences.showThumbnails, + cacheMode: preferences.cacheMode ?? defaultPreferences.cacheMode, + showOnlyUnread: preferences.showOnlyUnread ?? defaultPreferences.showOnlyUnread, + debug: preferences.debug ?? defaultPreferences.debug, + displayMode: preferences.displayMode ?? defaultPreferences.displayMode, + }, + }); + + return { + showThumbnails: updatedPreferences.showThumbnails, + cacheMode: updatedPreferences.cacheMode as "memory" | "file", + showOnlyUnread: updatedPreferences.showOnlyUnread, + debug: updatedPreferences.debug, + displayMode: updatedPreferences.displayMode as UserPreferences["displayMode"], }; - return result; } catch (error) { if (error instanceof AppError) { throw error; diff --git a/src/types/preferences.ts b/src/types/preferences.ts index 16727fb..bf7a500 100644 --- a/src/types/preferences.ts +++ b/src/types/preferences.ts @@ -18,4 +18,4 @@ export const defaultPreferences: UserPreferences = { compact: false, itemsPerPage: 20, }, -} as const; +};