refactor: migrate from MongoDB to Prisma for data management, removing mongoose models and updating services to use Prisma client

This commit is contained in:
Julien Froidefond
2025-10-16 22:22:20 +02:00
parent 677e2ae884
commit 3cd58f63e6
21 changed files with 636 additions and 576 deletions

View File

@@ -1,16 +1,66 @@
.git # dependencies
.gitignore
node_modules node_modules
.pnp
.pnp.js
.pnpm-store
# testing
coverage
# next.js
.next .next
.env.local out
.env.development.local
.env.test.local # production
.env.production.local build
README.md dist
.vscode
.idea # misc
*.log .DS_Store
*.pem
# debug
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
.DS_Store .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

3
.gitignore vendored
View File

@@ -39,6 +39,9 @@ next-env.d.ts
.cache .cache
debug-logs debug-logs
# MongoDB
mongo-keyfile
# Environment variables # Environment variables
.env .env
.env.local .env.local

View File

@@ -20,6 +20,9 @@ RUN corepack enable && corepack prepare pnpm@9.0.0 --activate
# Copy package files first to leverage Docker cache # Copy package files first to leverage Docker cache
COPY package.json pnpm-lock.yaml ./ COPY package.json pnpm-lock.yaml ./
# Copy Prisma schema
COPY prisma ./prisma
# Copy configuration files # Copy configuration files
COPY tsconfig.json .eslintrc.json ./ COPY tsconfig.json .eslintrc.json ./
COPY tailwind.config.ts postcss.config.js ./ 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 && \ RUN pnpm config set store-dir /app/.pnpm-store && \
pnpm install --frozen-lockfile pnpm install --frozen-lockfile
# Generate Prisma Client
RUN pnpm prisma generate
# Copy source files # Copy source files
COPY src ./src COPY src ./src
COPY public ./public COPY public ./public
@@ -40,13 +46,18 @@ FROM node:20-alpine AS runner
WORKDIR /app 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 ./ COPY package.json pnpm-lock.yaml ./
RUN corepack enable && \ COPY prisma ./prisma
corepack prepare pnpm@9.0.0 --activate && \
pnpm config set store-dir /app/.pnpm-store && \ # Enable pnpm
pnpm install --prod --frozen-lockfile && \ RUN corepack enable && corepack prepare pnpm@9.0.0 --activate
pnpm store prune
# 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 built application from builder stage
COPY --from=builder /app/.next ./.next COPY --from=builder /app/.next ./.next

10
ENV.md
View File

@@ -5,7 +5,7 @@
# MongoDB Configuration # MongoDB Configuration
MONGO_USER=admin MONGO_USER=admin
MONGO_PASSWORD=your-secure-password 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 Configuration
NEXTAUTH_SECRET=your-secret-key-here-generate-with-openssl-rand-base64-32 NEXTAUTH_SECRET=your-secret-key-here-generate-with-openssl-rand-base64-32
@@ -20,5 +20,13 @@ NODE_ENV=production
openssl rand -base64 32 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 ## Développement
Pour le développement, les variables sont définies directement dans `docker-compose.dev.yml`. Pour le développement, les variables sont définies directement dans `docker-compose.dev.yml`.

View File

@@ -12,6 +12,7 @@ services:
- "3020:3000" - "3020:3000"
volumes: volumes:
- ./src:/app/src - ./src:/app/src
- ./prisma:/app/prisma
- ./public:/app/public - ./public:/app/public
- ./package.json:/app/package.json - ./package.json:/app/package.json
- ./pnpm-lock.yaml:/app/pnpm-lock.yaml - ./pnpm-lock.yaml:/app/pnpm-lock.yaml
@@ -31,7 +32,7 @@ services:
- WATCHPACK_POLLING=true - WATCHPACK_POLLING=true
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET} - NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
- NEXTAUTH_URL=${NEXTAUTH_URL} - 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: mongodb:
image: mongo:latest image: mongo:latest
@@ -46,6 +47,14 @@ services:
volumes: volumes:
- mongodb_data:/data/db - mongodb_data:/data/db
- ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro - ./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: volumes:
mongodb_data: mongodb_data:

View File

@@ -45,6 +45,7 @@ services:
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD} MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
volumes: volumes:
- stripstream_mongodb_data:/data/db - stripstream_mongodb_data:/data/db
- ./mongo-keyfile:/data/keyfile:ro
networks: networks:
- stripstream-network - stripstream-network
deploy: deploy:
@@ -54,7 +55,13 @@ services:
memory: 512M memory: 512M
ports: ports:
- "27017:27017" - "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: networks:
stripstream-network: stripstream-network:

View File

@@ -11,6 +11,7 @@
"icons": "node scripts/generate-icons.js" "icons": "node scripts/generate-icons.js"
}, },
"dependencies": { "dependencies": {
"@prisma/client": "^6.17.1",
"@radix-ui/react-dialog": "1.0.5", "@radix-ui/react-dialog": "1.0.5",
"@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-progress": "^1.1.2", "@radix-ui/react-progress": "^1.1.2",
@@ -18,7 +19,6 @@
"@radix-ui/react-slot": "1.0.2", "@radix-ui/react-slot": "1.0.2",
"@radix-ui/react-toast": "1.1.5", "@radix-ui/react-toast": "1.1.5",
"@types/bcryptjs": "^3.0.0", "@types/bcryptjs": "^3.0.0",
"@types/mongoose": "5.11.97",
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
@@ -26,7 +26,6 @@
"i18next": "^24.2.2", "i18next": "^24.2.2",
"i18next-browser-languagedetector": "^8.0.4", "i18next-browser-languagedetector": "^8.0.4",
"lucide-react": "^0.487.0", "lucide-react": "^0.487.0",
"mongoose": "8.1.0",
"next": "15.2.0", "next": "15.2.0",
"next-auth": "5.0.0-beta.29", "next-auth": "5.0.0-beta.29",
"next-themes": "0.2.1", "next-themes": "0.2.1",
@@ -53,6 +52,7 @@
"eslint-plugin-typescript-sort-keys": "^3.3.0", "eslint-plugin-typescript-sort-keys": "^3.3.0",
"eslint-plugin-unused-imports": "^4.1.4", "eslint-plugin-unused-imports": "^4.1.4",
"postcss": "8.4.33", "postcss": "8.4.33",
"prisma": "^6.17.1",
"tailwindcss": "3.4.1", "tailwindcss": "3.4.1",
"typescript": "5.3.3" "typescript": "5.3.3"
}, },

452
pnpm-lock.yaml generated
View File

@@ -8,6 +8,9 @@ importers:
.: .:
dependencies: 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': '@radix-ui/react-dialog':
specifier: 1.0.5 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) 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': '@types/bcryptjs':
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.0.0 version: 3.0.0
'@types/mongoose':
specifier: 5.11.97
version: 5.11.97
bcryptjs: bcryptjs:
specifier: ^3.0.2 specifier: ^3.0.2
version: 3.0.2 version: 3.0.2
@@ -53,9 +53,6 @@ importers:
lucide-react: lucide-react:
specifier: ^0.487.0 specifier: ^0.487.0
version: 0.487.0(react@18.2.0) version: 0.487.0(react@18.2.0)
mongoose:
specifier: 8.1.0
version: 8.1.0
next: next:
specifier: 15.2.0 specifier: 15.2.0
version: 15.2.0(react-dom@18.2.0(react@18.2.0))(react@18.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: postcss:
specifier: 8.4.33 specifier: 8.4.33
version: 8.4.33 version: 8.4.33
prisma:
specifier: ^6.17.1
version: 6.17.1(typescript@5.3.3)
tailwindcss: tailwindcss:
specifier: 3.4.1 specifier: 3.4.1
version: 3.4.1 version: 3.4.1
@@ -459,9 +459,6 @@ packages:
'@jridgewell/trace-mapping@0.3.31': '@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@mongodb-js/saslprep@1.3.1':
resolution: {integrity: sha512-6nZrq5kfAz0POWyhljnbWQQJQ5uT8oE2ddX303q1uY0tWsivWKgBDXBBvuFPwOqRRalXJuVO9EjOdVtuhLX0zg==}
'@napi-rs/wasm-runtime@0.2.12': '@napi-rs/wasm-runtime@0.2.12':
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
@@ -542,6 +539,36 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'} 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': '@radix-ui/number@1.1.1':
resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
@@ -1066,6 +1093,9 @@ packages:
'@rushstack/eslint-patch@1.14.0': '@rushstack/eslint-patch@1.14.0':
resolution: {integrity: sha512-WJFej426qe4RWOm9MMtP4V3CV4AucXolQty+GRgAWLgQXmpCuwzs7hEpxxhSc/znXUSxum9d/P/32MW0FlAAlA==} resolution: {integrity: sha512-WJFej426qe4RWOm9MMtP4V3CV4AucXolQty+GRgAWLgQXmpCuwzs7hEpxxhSc/znXUSxum9d/P/32MW0FlAAlA==}
'@standard-schema/spec@1.0.0':
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
'@swc/counter@0.1.3': '@swc/counter@0.1.3':
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
@@ -1085,10 +1115,6 @@ packages:
'@types/json5@0.0.29': '@types/json5@0.0.29':
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} 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': '@types/node@24.7.2':
resolution: {integrity: sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==} resolution: {integrity: sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==}
@@ -1107,12 +1133,6 @@ packages:
'@types/semver@7.7.1': '@types/semver@7.7.1':
resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} 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': '@typescript-eslint/eslint-plugin@8.46.1':
resolution: {integrity: sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==} resolution: {integrity: sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 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} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true hasBin: true
bson@6.10.4:
resolution: {integrity: sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==}
engines: {node: '>=16.20.1'}
busboy@1.6.0: busboy@1.6.0:
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
engines: {node: '>=10.16.0'} 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: call-bind-apply-helpers@1.0.2:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -1508,6 +1532,13 @@ packages:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'} 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: class-variance-authority@0.7.1:
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
@@ -1539,6 +1570,13 @@ packages:
concat-map@0.0.1: concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 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: cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@@ -1586,6 +1624,10 @@ packages:
deep-is@0.1.4: deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 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: define-data-property@1.1.4:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -1594,6 +1636,12 @@ packages:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'} 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: detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -1619,6 +1667,10 @@ packages:
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dotenv@16.6.1:
resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
engines: {node: '>=12'}
dunder-proto@1.0.1: dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -1626,6 +1678,9 @@ packages:
eastasianwidth@0.2.0: eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
effect@3.16.12:
resolution: {integrity: sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==}
electron-to-chromium@1.5.235: electron-to-chromium@1.5.235:
resolution: {integrity: sha512-i/7ntLFwOdoHY7sgjlTIDo4Sl8EdoTjWIaKinYOVfC6bOp71bmwenyZthWHcasxgHDNWbWxvG9M3Ia116zIaYQ==} resolution: {integrity: sha512-i/7ntLFwOdoHY7sgjlTIDo4Sl8EdoTjWIaKinYOVfC6bOp71bmwenyZthWHcasxgHDNWbWxvG9M3Ia116zIaYQ==}
@@ -1635,6 +1690,10 @@ packages:
emoji-regex@9.2.2: emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
empathic@2.0.0:
resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==}
engines: {node: '>=14'}
es-abstract@1.24.0: es-abstract@1.24.0:
resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -1818,6 +1877,13 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'} 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: fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@@ -1929,6 +1995,10 @@ packages:
get-tsconfig@4.12.0: get-tsconfig@4.12.0:
resolution: {integrity: sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==} resolution: {integrity: sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==}
giget@2.0.0:
resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==}
hasBin: true
glob-parent@5.1.2: glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@@ -2167,6 +2237,10 @@ packages:
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
hasBin: true hasBin: true
jiti@2.6.1:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
jose@6.1.0: jose@6.1.0:
resolution: {integrity: sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==} resolution: {integrity: sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==}
@@ -2197,10 +2271,6 @@ packages:
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
engines: {node: '>=4.0'} 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: keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@@ -2249,9 +2319,6 @@ packages:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
memory-pager@1.5.0:
resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==}
merge2@1.4.1: merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@@ -2278,48 +2345,6 @@ packages:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'} 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: ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -2386,6 +2411,9 @@ packages:
sass: sass:
optional: true optional: true
node-fetch-native@1.6.7:
resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==}
node-releases@2.0.23: node-releases@2.0.23:
resolution: {integrity: sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==} resolution: {integrity: sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==}
@@ -2397,6 +2425,11 @@ packages:
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
engines: {node: '>=0.10.0'} 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: oauth4webapi@3.8.2:
resolution: {integrity: sha512-FzZZ+bht5X0FKe7Mwz3DAVAmlH1BV5blSak/lHMBKz0/EBMhX6B10GlQYI51+oRp8ObJaX0g6pXrAxZh5s8rjw==} resolution: {integrity: sha512-FzZZ+bht5X0FKe7Mwz3DAVAmlH1BV5blSak/lHMBKz0/EBMhX6B10GlQYI51+oRp8ObJaX0g6pXrAxZh5s8rjw==}
@@ -2436,6 +2469,9 @@ packages:
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
ohash@2.0.11:
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
once@1.4.0: once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@@ -2485,6 +2521,12 @@ packages:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'} 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: picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -2504,6 +2546,9 @@ packages:
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
pkg-types@2.3.0:
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
possible-typed-array-names@1.1.0: possible-typed-array-names@1.1.0:
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -2565,6 +2610,16 @@ packages:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'} 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: prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
@@ -2572,9 +2627,15 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'} engines: {node: '>=6'}
pure-rand@6.1.0:
resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==}
queue-microtask@1.2.3: queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
rc9@2.1.2:
resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==}
react-dom@18.2.0: react-dom@18.2.0:
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
peerDependencies: peerDependencies:
@@ -2657,6 +2718,10 @@ packages:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'} 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: reflect.getprototypeof@1.0.10:
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -2761,9 +2826,6 @@ packages:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
sift@16.0.1:
resolution: {integrity: sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==}
signal-exit@4.1.0: signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'} engines: {node: '>=14'}
@@ -2779,9 +2841,6 @@ packages:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
sparse-bitfield@3.0.3:
resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==}
stable-hash@0.0.5: stable-hash@0.0.5:
resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==}
@@ -2889,6 +2948,9 @@ packages:
thenify@3.3.1: thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
tinyexec@1.0.1:
resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==}
tinyglobby@0.2.15: tinyglobby@0.2.15:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
@@ -2897,10 +2959,6 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'} engines: {node: '>=8.0'}
tr46@5.1.1:
resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
engines: {node: '>=18'}
ts-api-utils@1.4.3: ts-api-utils@1.4.3:
resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==}
engines: {node: '>=16'} engines: {node: '>=16'}
@@ -3010,14 +3068,6 @@ packages:
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
engines: {node: '>=0.10.0'} 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: which-boxed-primitive@1.1.1:
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -3334,10 +3384,6 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2 '@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5 '@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': '@napi-rs/wasm-runtime@0.2.12':
dependencies: dependencies:
'@emnapi/core': 1.5.0 '@emnapi/core': 1.5.0
@@ -3394,6 +3440,41 @@ snapshots:
'@pkgjs/parseargs@0.11.0': '@pkgjs/parseargs@0.11.0':
optional: true 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/number@1.1.1': {}
'@radix-ui/primitive@1.0.1': '@radix-ui/primitive@1.0.1':
@@ -3889,6 +3970,8 @@ snapshots:
'@rushstack/eslint-patch@1.14.0': {} '@rushstack/eslint-patch@1.14.0': {}
'@standard-schema/spec@1.0.0': {}
'@swc/counter@0.1.3': {} '@swc/counter@0.1.3': {}
'@swc/helpers@0.5.15': '@swc/helpers@0.5.15':
@@ -3908,19 +3991,6 @@ snapshots:
'@types/json5@0.0.29': {} '@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': '@types/node@24.7.2':
dependencies: dependencies:
undici-types: 7.14.0 undici-types: 7.14.0
@@ -3941,12 +4011,6 @@ snapshots:
'@types/semver@7.7.1': {} '@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)': '@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: dependencies:
'@eslint-community/regexpp': 4.12.1 '@eslint-community/regexpp': 4.12.1
@@ -4338,12 +4402,25 @@ snapshots:
node-releases: 2.0.23 node-releases: 2.0.23
update-browserslist-db: 1.1.3(browserslist@4.26.3) update-browserslist-db: 1.1.3(browserslist@4.26.3)
bson@6.10.4: {}
busboy@1.6.0: busboy@1.6.0:
dependencies: dependencies:
streamsearch: 1.1.0 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: call-bind-apply-helpers@1.0.2:
dependencies: dependencies:
es-errors: 1.3.0 es-errors: 1.3.0
@@ -4384,6 +4461,14 @@ snapshots:
optionalDependencies: optionalDependencies:
fsevents: 2.3.3 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: class-variance-authority@0.7.1:
dependencies: dependencies:
clsx: 2.1.1 clsx: 2.1.1
@@ -4412,6 +4497,10 @@ snapshots:
concat-map@0.0.1: {} concat-map@0.0.1: {}
confbox@0.2.2: {}
consola@3.4.2: {}
cross-spawn@7.0.6: cross-spawn@7.0.6:
dependencies: dependencies:
path-key: 3.1.1 path-key: 3.1.1
@@ -4452,6 +4541,8 @@ snapshots:
deep-is@0.1.4: {} deep-is@0.1.4: {}
deepmerge-ts@7.1.5: {}
define-data-property@1.1.4: define-data-property@1.1.4:
dependencies: dependencies:
es-define-property: 1.0.1 es-define-property: 1.0.1
@@ -4464,6 +4555,10 @@ snapshots:
has-property-descriptors: 1.0.2 has-property-descriptors: 1.0.2
object-keys: 1.1.1 object-keys: 1.1.1
defu@6.1.4: {}
destr@2.0.5: {}
detect-libc@2.1.2: {} detect-libc@2.1.2: {}
detect-node-es@1.1.0: {} detect-node-es@1.1.0: {}
@@ -4484,6 +4579,8 @@ snapshots:
dependencies: dependencies:
esutils: 2.0.3 esutils: 2.0.3
dotenv@16.6.1: {}
dunder-proto@1.0.1: dunder-proto@1.0.1:
dependencies: dependencies:
call-bind-apply-helpers: 1.0.2 call-bind-apply-helpers: 1.0.2
@@ -4492,12 +4589,19 @@ snapshots:
eastasianwidth@0.2.0: {} 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: {} electron-to-chromium@1.5.235: {}
emoji-regex@8.0.0: {} emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {} emoji-regex@9.2.2: {}
empathic@2.0.0: {}
es-abstract@1.24.0: es-abstract@1.24.0:
dependencies: dependencies:
array-buffer-byte-length: 1.0.2 array-buffer-byte-length: 1.0.2
@@ -4829,6 +4933,12 @@ snapshots:
esutils@2.0.3: {} 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-deep-equal@3.1.3: {}
fast-glob@3.3.1: fast-glob@3.3.1:
@@ -4949,6 +5059,15 @@ snapshots:
dependencies: dependencies:
resolve-pkg-maps: 1.0.0 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: glob-parent@5.1.2:
dependencies: dependencies:
is-glob: 4.0.3 is-glob: 4.0.3
@@ -5200,6 +5319,8 @@ snapshots:
jiti@1.21.7: {} jiti@1.21.7: {}
jiti@2.6.1: {}
jose@6.1.0: {} jose@6.1.0: {}
js-tokens@4.0.0: {} js-tokens@4.0.0: {}
@@ -5227,8 +5348,6 @@ snapshots:
object.assign: 4.1.7 object.assign: 4.1.7
object.values: 1.2.1 object.values: 1.2.1
kareem@2.5.1: {}
keyv@4.5.4: keyv@4.5.4:
dependencies: dependencies:
json-buffer: 3.0.1 json-buffer: 3.0.1
@@ -5268,8 +5387,6 @@ snapshots:
math-intrinsics@1.1.0: {} math-intrinsics@1.1.0: {}
memory-pager@1.5.0: {}
merge2@1.4.1: {} merge2@1.4.1: {}
micromatch@4.0.8: micromatch@4.0.8:
@@ -5293,44 +5410,6 @@ snapshots:
minipass@7.1.2: {} 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: {} ms@2.1.3: {}
mz@2.7.0: mz@2.7.0:
@@ -5384,12 +5463,22 @@ snapshots:
- '@babel/core' - '@babel/core'
- babel-plugin-macros - babel-plugin-macros
node-fetch-native@1.6.7: {}
node-releases@2.0.23: {} node-releases@2.0.23: {}
normalize-path@3.0.0: {} normalize-path@3.0.0: {}
normalize-range@0.1.2: {} 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: {} oauth4webapi@3.8.2: {}
object-assign@4.1.1: {} object-assign@4.1.1: {}
@@ -5436,6 +5525,8 @@ snapshots:
define-properties: 1.2.1 define-properties: 1.2.1
es-object-atoms: 1.1.1 es-object-atoms: 1.1.1
ohash@2.0.11: {}
once@1.4.0: once@1.4.0:
dependencies: dependencies:
wrappy: 1.0.2 wrappy: 1.0.2
@@ -5484,6 +5575,10 @@ snapshots:
path-type@4.0.0: {} path-type@4.0.0: {}
pathe@2.0.3: {}
perfect-debounce@1.0.0: {}
picocolors@1.1.1: {} picocolors@1.1.1: {}
picomatch@2.3.1: {} picomatch@2.3.1: {}
@@ -5494,6 +5589,12 @@ snapshots:
pirates@4.0.7: {} 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: {} possible-typed-array-names@1.1.0: {}
postcss-import@15.1.0(postcss@8.4.33): postcss-import@15.1.0(postcss@8.4.33):
@@ -5547,6 +5648,15 @@ snapshots:
prelude-ls@1.2.1: {} 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: prop-types@15.8.1:
dependencies: dependencies:
loose-envify: 1.4.0 loose-envify: 1.4.0
@@ -5555,8 +5665,15 @@ snapshots:
punycode@2.3.1: {} punycode@2.3.1: {}
pure-rand@6.1.0: {}
queue-microtask@1.2.3: {} 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): react-dom@18.2.0(react@18.2.0):
dependencies: dependencies:
loose-envify: 1.4.0 loose-envify: 1.4.0
@@ -5630,6 +5747,8 @@ snapshots:
dependencies: dependencies:
picomatch: 2.3.1 picomatch: 2.3.1
readdirp@4.1.2: {}
reflect.getprototypeof@1.0.10: reflect.getprototypeof@1.0.10:
dependencies: dependencies:
call-bind: 1.0.8 call-bind: 1.0.8
@@ -5812,8 +5931,6 @@ snapshots:
side-channel-map: 1.0.1 side-channel-map: 1.0.1
side-channel-weakmap: 1.0.2 side-channel-weakmap: 1.0.2
sift@16.0.1: {}
signal-exit@4.1.0: {} signal-exit@4.1.0: {}
simple-swizzle@0.2.4: simple-swizzle@0.2.4:
@@ -5824,10 +5941,6 @@ snapshots:
source-map-js@1.2.1: {} source-map-js@1.2.1: {}
sparse-bitfield@3.0.3:
dependencies:
memory-pager: 1.5.0
stable-hash@0.0.5: {} stable-hash@0.0.5: {}
stop-iteration-iterator@1.1.0: stop-iteration-iterator@1.1.0:
@@ -5975,6 +6088,8 @@ snapshots:
dependencies: dependencies:
any-promise: 1.3.0 any-promise: 1.3.0
tinyexec@1.0.1: {}
tinyglobby@0.2.15: tinyglobby@0.2.15:
dependencies: dependencies:
fdir: 6.5.0(picomatch@4.0.3) fdir: 6.5.0(picomatch@4.0.3)
@@ -5984,10 +6099,6 @@ snapshots:
dependencies: dependencies:
is-number: 7.0.0 is-number: 7.0.0
tr46@5.1.1:
dependencies:
punycode: 2.3.1
ts-api-utils@1.4.3(typescript@5.3.3): ts-api-utils@1.4.3(typescript@5.3.3):
dependencies: dependencies:
typescript: 5.3.3 typescript: 5.3.3
@@ -6119,13 +6230,6 @@ snapshots:
void-elements@3.1.0: {} 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: which-boxed-primitive@1.1.1:
dependencies: dependencies:
is-bigint: 1.1.0 is-bigint: 1.1.0

91
prisma/schema.prisma Normal file
View File

@@ -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")
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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<any>;
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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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<typeof mongoose> | 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<typeof mongoose> {
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;

14
src/lib/prisma.ts Normal file
View File

@@ -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;

View File

@@ -1,5 +1,4 @@
import connectDB from "@/lib/mongodb"; import prisma from "@/lib/prisma";
import { UserModel } from "@/lib/models/user.model";
import bcrypt from "bcryptjs"; import bcrypt from "bcryptjs";
import { ERROR_CODES } from "../../constants/errorCodes"; import { ERROR_CODES } from "../../constants/errorCodes";
import { AppError } from "../../utils/errors"; import { AppError } from "../../utils/errors";
@@ -15,15 +14,16 @@ export class AuthServerService {
private static readonly SALT_ROUNDS = 10; private static readonly SALT_ROUNDS = 10;
static async registerUser(email: string, password: string): Promise<UserData> { static async registerUser(email: string, password: string): Promise<UserData> {
await connectDB();
//check if password is strong //check if password is strong
if (!AuthServerService.isPasswordStrong(password)) { if (!AuthServerService.isPasswordStrong(password)) {
throw new AppError(ERROR_CODES.AUTH.PASSWORD_NOT_STRONG); throw new AppError(ERROR_CODES.AUTH.PASSWORD_NOT_STRONG);
} }
// Check if user already exists // 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) { if (existingUser) {
throw new AppError(ERROR_CODES.AUTH.EMAIL_EXISTS); throw new AppError(ERROR_CODES.AUTH.EMAIL_EXISTS);
} }
@@ -32,15 +32,17 @@ export class AuthServerService {
const hashedPassword = await bcrypt.hash(password, this.SALT_ROUNDS); const hashedPassword = await bcrypt.hash(password, this.SALT_ROUNDS);
// Create new user // Create new user
const user = await UserModel.create({ const user = await prisma.user.create({
email: email.toLowerCase(), data: {
password: hashedPassword, email: email.toLowerCase(),
roles: ["ROLE_USER"], password: hashedPassword,
authenticated: true, roles: ["ROLE_USER"],
authenticated: true,
},
}); });
const userData: UserData = { const userData: UserData = {
id: user._id.toString(), id: user.id,
email: user.email, email: user.email,
roles: user.roles, roles: user.roles,
authenticated: true, authenticated: true,
@@ -67,9 +69,9 @@ export class AuthServerService {
} }
static async loginUser(email: string, password: string): Promise<UserData> { static async loginUser(email: string, password: string): Promise<UserData> {
await connectDB(); const user = await prisma.user.findUnique({
where: { email: email.toLowerCase() },
const user = await UserModel.findOne({ email: email.toLowerCase() }); });
if (!user) { if (!user) {
throw new AppError(ERROR_CODES.AUTH.INVALID_CREDENTIALS); throw new AppError(ERROR_CODES.AUTH.INVALID_CREDENTIALS);
@@ -82,7 +84,7 @@ export class AuthServerService {
} }
const userData: UserData = { const userData: UserData = {
id: user._id.toString(), id: user.id,
email: user.email, email: user.email,
roles: user.roles, roles: user.roles,
authenticated: true, authenticated: true,

View File

@@ -1,6 +1,4 @@
import connectDB from "@/lib/mongodb"; import prisma from "@/lib/prisma";
import { KomgaConfig as KomgaConfigModel } from "@/lib/models/config.model";
import { TTLConfig as TTLConfigModel } from "@/lib/models/ttl-config.model";
import { DebugService } from "./debug.service"; import { DebugService } from "./debug.service";
import { getCurrentUser } from "../auth-utils"; import { getCurrentUser } from "../auth-utils";
import { ERROR_CODES } from "../../constants/errorCodes"; import { ERROR_CODES } from "../../constants/errorCodes";
@@ -19,28 +17,27 @@ export class ConfigDBService {
static async saveConfig(data: KomgaConfigData): Promise<KomgaConfig> { static async saveConfig(data: KomgaConfigData): Promise<KomgaConfig> {
try { try {
const user: User | null = await this.getCurrentUser(); const user: User | null = await this.getCurrentUser();
await connectDB();
const authHeader: string = Buffer.from(`${data.username}:${data.password}`).toString( const authHeader: string = Buffer.from(`${data.username}:${data.password}`).toString(
"base64" "base64"
); );
const config: KomgaConfig | null = await KomgaConfigModel.findOneAndUpdate( const config = await prisma.komgaConfig.upsert({
{ userId: user.id }, where: { userId: user.id },
{ update: {
url: data.url,
username: data.username,
authHeader,
},
create: {
userId: user.id, userId: user.id,
url: data.url, url: data.url,
username: data.username, username: data.username,
// password: data.password,
authHeader, authHeader,
}, },
{ upsert: true, new: true } });
);
if (!config) {
throw new AppError(ERROR_CODES.CONFIG.SAVE_ERROR);
}
return config; return config as KomgaConfig;
} catch (error) { } catch (error) {
if (error instanceof AppError) { if (error instanceof AppError) {
throw error; throw error;
@@ -52,11 +49,12 @@ export class ConfigDBService {
static async getConfig(): Promise<KomgaConfig | null> { static async getConfig(): Promise<KomgaConfig | null> {
try { try {
const user: User | null = await this.getCurrentUser(); const user: User | null = await this.getCurrentUser();
await connectDB();
return DebugService.measureMongoOperation("getConfig", async () => { return DebugService.measureMongoOperation("getConfig", async () => {
const config: KomgaConfig | null = await KomgaConfigModel.findOne({ userId: user.id }); const config = await prisma.komgaConfig.findUnique({
return config; where: { userId: user.id },
});
return config as KomgaConfig | null;
}); });
} catch (error) { } catch (error) {
if (error instanceof AppError) { if (error instanceof AppError) {
@@ -69,11 +67,12 @@ export class ConfigDBService {
static async getTTLConfig(): Promise<TTLConfig | null> { static async getTTLConfig(): Promise<TTLConfig | null> {
try { try {
const user: User | null = await this.getCurrentUser(); const user: User | null = await this.getCurrentUser();
await connectDB();
return DebugService.measureMongoOperation("getTTLConfig", async () => { return DebugService.measureMongoOperation("getTTLConfig", async () => {
const config: TTLConfig | null = await TTLConfigModel.findOne({ userId: user.id }); const config = await prisma.tTLConfig.findUnique({
return config; where: { userId: user.id },
});
return config as TTLConfig | null;
}); });
} catch (error) { } catch (error) {
if (error instanceof AppError) { if (error instanceof AppError) {
@@ -86,23 +85,30 @@ export class ConfigDBService {
static async saveTTLConfig(data: TTLConfigData): Promise<TTLConfig> { static async saveTTLConfig(data: TTLConfigData): Promise<TTLConfig> {
try { try {
const user: User | null = await this.getCurrentUser(); const user: User | null = await this.getCurrentUser();
await connectDB();
return DebugService.measureMongoOperation("saveTTLConfig", async () => { return DebugService.measureMongoOperation("saveTTLConfig", async () => {
const config: TTLConfig | null = await TTLConfigModel.findOneAndUpdate( const config = await prisma.tTLConfig.upsert({
{ userId: user.id }, where: { userId: user.id },
{ update: {
userId: user.id, defaultTTL: data.defaultTTL,
...data, 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) { return config as TTLConfig;
throw new AppError(ERROR_CODES.CONFIG.TTL_SAVE_ERROR);
}
return config;
}); });
} catch (error) { } catch (error) {
if (error instanceof AppError) { if (error instanceof AppError) {

View File

@@ -1,5 +1,4 @@
import connectDB from "@/lib/mongodb"; import prisma from "@/lib/prisma";
import { FavoriteModel } from "@/lib/models/favorite.model";
import { DebugService } from "./debug.service"; import { DebugService } from "./debug.service";
import { getCurrentUser } from "../auth-utils"; import { getCurrentUser } from "../auth-utils";
import { ERROR_CODES } from "../../constants/errorCodes"; import { ERROR_CODES } from "../../constants/errorCodes";
@@ -30,12 +29,13 @@ export class FavoriteService {
static async isFavorite(seriesId: string): Promise<boolean> { static async isFavorite(seriesId: string): Promise<boolean> {
try { try {
const user = await this.getCurrentUser(); const user = await this.getCurrentUser();
await connectDB();
return DebugService.measureMongoOperation("isFavorite", async () => { return DebugService.measureMongoOperation("isFavorite", async () => {
const favorite = await FavoriteModel.findOne({ const favorite = await prisma.favorite.findFirst({
userId: user.id, where: {
seriesId: seriesId, userId: user.id,
seriesId: seriesId,
},
}); });
return !!favorite; return !!favorite;
}); });
@@ -51,14 +51,21 @@ export class FavoriteService {
static async addToFavorites(seriesId: string): Promise<void> { static async addToFavorites(seriesId: string): Promise<void> {
try { try {
const user = await this.getCurrentUser(); const user = await this.getCurrentUser();
await connectDB();
await DebugService.measureMongoOperation("addToFavorites", async () => { await DebugService.measureMongoOperation("addToFavorites", async () => {
await FavoriteModel.findOneAndUpdate( await prisma.favorite.upsert({
{ userId: user.id, seriesId }, where: {
{ userId: user.id, seriesId }, userId_seriesId: {
{ upsert: true } userId: user.id,
); seriesId,
},
},
update: {},
create: {
userId: user.id,
seriesId,
},
});
}); });
this.dispatchFavoritesChanged(); this.dispatchFavoritesChanged();
@@ -73,12 +80,13 @@ export class FavoriteService {
static async removeFromFavorites(seriesId: string): Promise<void> { static async removeFromFavorites(seriesId: string): Promise<void> {
try { try {
const user = await this.getCurrentUser(); const user = await this.getCurrentUser();
await connectDB();
await DebugService.measureMongoOperation("removeFromFavorites", async () => { await DebugService.measureMongoOperation("removeFromFavorites", async () => {
await FavoriteModel.findOneAndDelete({ await prisma.favorite.deleteMany({
userId: user.id, where: {
seriesId, userId: user.id,
seriesId,
},
}); });
}); });
@@ -93,35 +101,48 @@ export class FavoriteService {
*/ */
static async getAllFavoriteIds(): Promise<string[]> { static async getAllFavoriteIds(): Promise<string[]> {
const user = await this.getCurrentUser(); const user = await this.getCurrentUser();
await connectDB();
return DebugService.measureMongoOperation("getAllFavoriteIds", async () => { 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); return favorites.map((favorite) => favorite.seriesId);
}); });
} }
static async addFavorite(seriesId: string) { static async addFavorite(seriesId: string) {
const user = await this.getCurrentUser(); const user = await this.getCurrentUser();
await connectDB();
return DebugService.measureMongoOperation("addFavorite", async () => { return DebugService.measureMongoOperation("addFavorite", async () => {
const favorite = await FavoriteModel.findOneAndUpdate( const favorite = await prisma.favorite.upsert({
{ userId: user.id, seriesId }, where: {
{ userId: user.id, seriesId }, userId_seriesId: {
{ upsert: true, new: true } userId: user.id,
); seriesId,
},
},
update: {},
create: {
userId: user.id,
seriesId,
},
});
return favorite; return favorite;
}); });
} }
static async removeFavorite(seriesId: string): Promise<boolean> { static async removeFavorite(seriesId: string): Promise<boolean> {
const user = await this.getCurrentUser(); const user = await this.getCurrentUser();
await connectDB();
return DebugService.measureMongoOperation("removeFavorite", async () => { return DebugService.measureMongoOperation("removeFavorite", async () => {
const result = await FavoriteModel.deleteOne({ userId: user.id, seriesId }); const result = await prisma.favorite.deleteMany({
return result.deletedCount > 0; where: {
userId: user.id,
seriesId,
},
});
return result.count > 0;
}); });
} }
} }

View File

@@ -1,11 +1,10 @@
import { PreferencesModel } from "@/lib/models/preferences.model"; import prisma from "@/lib/prisma";
import { getCurrentUser } from "../auth-utils"; import { getCurrentUser } from "../auth-utils";
import { ERROR_CODES } from "../../constants/errorCodes"; import { ERROR_CODES } from "../../constants/errorCodes";
import { AppError } from "../../utils/errors"; import { AppError } from "../../utils/errors";
import type { UserPreferences } from "@/types/preferences"; import type { UserPreferences } from "@/types/preferences";
import { defaultPreferences } from "@/types/preferences"; import { defaultPreferences } from "@/types/preferences";
import type { User } from "@/types/komga"; import type { User } from "@/types/komga";
import connectDB from "@/lib/mongodb";
export class PreferencesService { export class PreferencesService {
static async getCurrentUser(): Promise<User> { static async getCurrentUser(): Promise<User> {
@@ -19,15 +18,20 @@ export class PreferencesService {
static async getPreferences(): Promise<UserPreferences> { static async getPreferences(): Promise<UserPreferences> {
try { try {
const user = await this.getCurrentUser(); const user = await this.getCurrentUser();
await connectDB(); const preferences = await prisma.preferences.findUnique({
const preferences = await PreferencesModel.findOne({ userId: user.id }); where: { userId: user.id },
});
if (!preferences) { if (!preferences) {
return defaultPreferences; return { ...defaultPreferences };
} }
return { return {
...defaultPreferences, showThumbnails: preferences.showThumbnails,
...preferences.toObject(), cacheMode: preferences.cacheMode as "memory" | "file",
_id: undefined, //plain object KO on server components hydration showOnlyUnread: preferences.showOnlyUnread,
debug: preferences.debug,
displayMode: preferences.displayMode as UserPreferences["displayMode"],
}; };
} catch (error) { } catch (error) {
if (error instanceof AppError) { if (error instanceof AppError) {
@@ -40,18 +44,34 @@ export class PreferencesService {
static async updatePreferences(preferences: Partial<UserPreferences>): Promise<UserPreferences> { static async updatePreferences(preferences: Partial<UserPreferences>): Promise<UserPreferences> {
try { try {
const user = await this.getCurrentUser(); const user = await this.getCurrentUser();
await connectDB();
const updatedPreferences = await PreferencesModel.findOneAndUpdate(
{ userId: user.id },
{ $set: preferences },
{ new: true, upsert: true }
);
const result = { const updateData: Record<string, any> = {};
...defaultPreferences, if (preferences.showThumbnails !== undefined) updateData.showThumbnails = preferences.showThumbnails;
...updatedPreferences.toObject(), 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 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) { } catch (error) {
if (error instanceof AppError) { if (error instanceof AppError) {
throw error; throw error;

View File

@@ -18,4 +18,4 @@ export const defaultPreferences: UserPreferences = {
compact: false, compact: false,
itemsPerPage: 20, itemsPerPage: 20,
}, },
} as const; };