refactor: migrate from MongoDB to SQLite, updating database schema and configuration for improved performance and simplicity
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -48,3 +48,9 @@ mongo-keyfile
|
|||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
|
|
||||||
|
# Database
|
||||||
|
prisma/data/
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
|
*.sqlite3
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
# Build stage
|
# Build stage
|
||||||
FROM node:20-alpine AS builder
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
# Declare MONGODB_URI as an argument for the builder stage
|
|
||||||
ARG MONGODB_URI
|
|
||||||
ENV MONGODB_URI=$MONGODB_URI
|
|
||||||
|
|
||||||
# Set working directory
|
# Set working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -74,8 +70,8 @@ RUN chmod +x docker-entrypoint.sh
|
|||||||
# Add non-root user for security
|
# Add non-root user for security
|
||||||
RUN addgroup --system --gid 1001 nodejs && \
|
RUN addgroup --system --gid 1001 nodejs && \
|
||||||
adduser --system --uid 1001 nextjs && \
|
adduser --system --uid 1001 nextjs && \
|
||||||
mkdir -p /app/.cache && \
|
mkdir -p /app/.cache /app/data && \
|
||||||
chown -R nextjs:nodejs /app /app/.cache && \
|
chown -R nextjs:nodejs /app /app/.cache /app/data && \
|
||||||
chown nextjs:nodejs docker-entrypoint.sh
|
chown nextjs:nodejs docker-entrypoint.sh
|
||||||
|
|
||||||
USER nextjs
|
USER nextjs
|
||||||
|
|||||||
14
ENV.md
14
ENV.md
@@ -2,10 +2,8 @@
|
|||||||
|
|
||||||
## Production (.env)
|
## Production (.env)
|
||||||
```env
|
```env
|
||||||
# MongoDB Configuration
|
# Database Configuration (SQLite)
|
||||||
MONGO_USER=admin
|
DATABASE_URL=file:./data/stripstream.db
|
||||||
MONGO_PASSWORD=your-secure-password
|
|
||||||
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
|
||||||
@@ -32,13 +30,5 @@ 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`.
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
args:
|
|
||||||
- MONGODB_URI=${MONGODB_URI}
|
|
||||||
container_name: stripstream-app
|
container_name: stripstream-app
|
||||||
ports:
|
ports:
|
||||||
- "3020:3000"
|
- "3020:3000"
|
||||||
@@ -24,11 +22,10 @@ services:
|
|||||||
- /app/.next
|
- /app/.next
|
||||||
- ~/.pnpm-store:/app/.pnpm-store
|
- ~/.pnpm-store:/app/.pnpm-store
|
||||||
- cache_data:/app/.cache
|
- cache_data:/app/.cache
|
||||||
|
- sqlite_data:/app/data
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
- NODE_ENV=development
|
||||||
- MONGO_USER=admin
|
- DATABASE_URL=file:/app/data/stripstream.db
|
||||||
- MONGO_PASSWORD=password123
|
|
||||||
- MONGODB_URI=${MONGODB_URI}
|
|
||||||
- PNPM_HOME=/app/.pnpm-store
|
- PNPM_HOME=/app/.pnpm-store
|
||||||
- WATCHPACK_POLLING=true
|
- WATCHPACK_POLLING=true
|
||||||
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
|
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
|
||||||
@@ -37,28 +34,6 @@ services:
|
|||||||
- KOMGA_MAX_CONCURRENT_REQUESTS=5
|
- KOMGA_MAX_CONCURRENT_REQUESTS=5
|
||||||
command: sh -c "pnpm config set store-dir /app/.pnpm-store && pnpm install --frozen-lockfile && pnpm prisma generate && 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
|
|
||||||
container_name: stripstream_mongodb
|
|
||||||
restart: always
|
|
||||||
environment:
|
|
||||||
MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER}
|
|
||||||
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
|
|
||||||
MONGO_INITDB_DATABASE: stripstream
|
|
||||||
ports:
|
|
||||||
- "27017:27017"
|
|
||||||
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:
|
volumes:
|
||||||
mongodb_data:
|
sqlite_data:
|
||||||
cache_data:
|
cache_data:
|
||||||
|
|||||||
@@ -7,25 +7,20 @@ services:
|
|||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
args:
|
args:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- MONGODB_URI=${MONGODB_URI}
|
|
||||||
container_name: stripstream-app
|
container_name: stripstream-app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "3020:3000"
|
- "3020:3000"
|
||||||
volumes:
|
volumes:
|
||||||
- stripstream_cache:/app/.cache
|
- stripstream_cache:/app/.cache
|
||||||
|
- ./prisma/data:/app/data
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- MONGODB_URI=${MONGODB_URI}
|
- DATABASE_URL=file:/app/data/stripstream.db
|
||||||
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
|
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
|
||||||
- NEXTAUTH_URL=${NEXTAUTH_URL}
|
- NEXTAUTH_URL=http://localhost:3020
|
||||||
- AUTH_TRUST_HOST=true
|
- AUTH_TRUST_HOST=true
|
||||||
- KOMGA_MAX_CONCURRENT_REQUESTS=5
|
- KOMGA_MAX_CONCURRENT_REQUESTS=5
|
||||||
depends_on:
|
|
||||||
mongodb:
|
|
||||||
condition: service_healthy
|
|
||||||
mongodb-init:
|
|
||||||
condition: service_completed_successfully
|
|
||||||
networks:
|
networks:
|
||||||
- stripstream-network
|
- stripstream-network
|
||||||
deploy:
|
deploy:
|
||||||
@@ -42,53 +37,9 @@ services:
|
|||||||
timeout: 3s
|
timeout: 3s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
mongodb:
|
|
||||||
image: mongo:latest
|
|
||||||
container_name: stripstream-mongodb
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER}
|
|
||||||
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
|
|
||||||
volumes:
|
|
||||||
- stripstream_mongodb_data:/data/db
|
|
||||||
- ./mongo-keyfile:/data/keyfile:ro
|
|
||||||
networks:
|
|
||||||
- stripstream-network
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
cpus: "0.5"
|
|
||||||
memory: 512M
|
|
||||||
ports:
|
|
||||||
- "27017:27017"
|
|
||||||
command: ["mongod", "--replSet", "rs0", "--bind_ip_all", "--keyFile", "/data/keyfile"]
|
|
||||||
healthcheck:
|
|
||||||
test: mongosh --host localhost:27017 -u ${MONGO_USER} -p ${MONGO_PASSWORD} --authenticationDatabase admin --eval "db.adminCommand('ping')" --quiet || exit 1
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
start_period: 30s
|
|
||||||
|
|
||||||
mongodb-init:
|
|
||||||
image: mongo:latest
|
|
||||||
container_name: stripstream-mongodb-init
|
|
||||||
depends_on:
|
|
||||||
mongodb:
|
|
||||||
condition: service_healthy
|
|
||||||
environment:
|
|
||||||
MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER}
|
|
||||||
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
|
|
||||||
volumes:
|
|
||||||
- ./mongo-init-rs.sh:/mongo-init-rs.sh:ro
|
|
||||||
networks:
|
|
||||||
- stripstream-network
|
|
||||||
entrypoint: ["/bin/bash", "/mongo-init-rs.sh"]
|
|
||||||
restart: "no"
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
stripstream-network:
|
stripstream-network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
stripstream_mongodb_data:
|
|
||||||
stripstream_cache:
|
stripstream_cache:
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
echo "📁 Ensuring data directory exists..."
|
||||||
|
mkdir -p /app/data
|
||||||
|
|
||||||
echo "🔄 Pushing Prisma schema to database..."
|
echo "🔄 Pushing Prisma schema to database..."
|
||||||
npx prisma db push --skip-generate --accept-data-loss
|
npx prisma db push --skip-generate --accept-data-loss
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Script d'initialisation du replica set MongoDB
|
|
||||||
|
|
||||||
echo "Attente du démarrage de MongoDB..."
|
|
||||||
sleep 10
|
|
||||||
|
|
||||||
echo "Initialisation du replica set..."
|
|
||||||
mongosh --host mongodb:27017 -u "$MONGO_INITDB_ROOT_USERNAME" -p "$MONGO_INITDB_ROOT_PASSWORD" --authenticationDatabase admin --eval "
|
|
||||||
try {
|
|
||||||
var status = rs.status();
|
|
||||||
print('Replica set déjà initialisé');
|
|
||||||
} catch (err) {
|
|
||||||
if (err.codeName === 'NotYetInitialized') {
|
|
||||||
rs.initiate({
|
|
||||||
_id: 'rs0',
|
|
||||||
members: [{ _id: 0, host: 'mongodb:27017' }]
|
|
||||||
});
|
|
||||||
print('Replica set initialisé avec succès');
|
|
||||||
} else {
|
|
||||||
print('Erreur: ' + err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"
|
|
||||||
|
|
||||||
echo "Vérification du statut du replica set..."
|
|
||||||
mongosh --host mongodb:27017 -u "$MONGO_INITDB_ROOT_USERNAME" -p "$MONGO_INITDB_ROOT_PASSWORD" --authenticationDatabase admin --eval "rs.status()"
|
|
||||||
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// MongoDB initialization script
|
|
||||||
db = db.getSiblingDB('stripstream');
|
|
||||||
|
|
||||||
// Create a user for the stripstream database
|
|
||||||
db.createUser({
|
|
||||||
user: 'admin',
|
|
||||||
pwd: 'password123',
|
|
||||||
roles: [
|
|
||||||
{
|
|
||||||
role: 'readWrite',
|
|
||||||
db: 'stripstream'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create initial collections
|
|
||||||
db.createCollection('users');
|
|
||||||
db.createCollection('configs');
|
|
||||||
db.createCollection('preferences');
|
|
||||||
db.createCollection('favorites');
|
|
||||||
db.createCollection('bookProgress');
|
|
||||||
|
|
||||||
print('MongoDB initialization completed successfully');
|
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
"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",
|
||||||
|
"mongodb": "^6.20.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",
|
||||||
|
|||||||
104
pnpm-lock.yaml
generated
104
pnpm-lock.yaml
generated
@@ -65,6 +65,9 @@ importers:
|
|||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.487.0
|
specifier: ^0.487.0
|
||||||
version: 0.487.0(react@19.2.0)
|
version: 0.487.0(react@19.2.0)
|
||||||
|
mongodb:
|
||||||
|
specifier: ^6.20.0
|
||||||
|
version: 6.20.0
|
||||||
next:
|
next:
|
||||||
specifier: 15.2.0
|
specifier: 15.2.0
|
||||||
version: 15.2.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
version: 15.2.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
@@ -471,6 +474,9 @@ 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.2':
|
||||||
|
resolution: {integrity: sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==}
|
||||||
|
|
||||||
'@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==}
|
||||||
|
|
||||||
@@ -1018,6 +1024,12 @@ 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}
|
||||||
@@ -1374,6 +1386,10 @@ 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'}
|
||||||
@@ -2207,6 +2223,9 @@ 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'}
|
||||||
@@ -2233,6 +2252,36 @@ 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.20.0:
|
||||||
|
resolution: {integrity: sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==}
|
||||||
|
engines: {node: '>=16.20.1'}
|
||||||
|
peerDependencies:
|
||||||
|
'@aws-sdk/credential-providers': ^3.188.0
|
||||||
|
'@mongodb-js/zstd': ^1.1.0 || ^2.0.0
|
||||||
|
gcp-metadata: ^5.2.0
|
||||||
|
kerberos: ^2.0.1
|
||||||
|
mongodb-client-encryption: '>=6.0.0 <7'
|
||||||
|
snappy: ^7.3.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
|
||||||
|
|
||||||
motion-dom@12.23.23:
|
motion-dom@12.23.23:
|
||||||
resolution: {integrity: sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==}
|
resolution: {integrity: sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==}
|
||||||
|
|
||||||
@@ -2722,6 +2771,9 @@ 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==}
|
||||||
|
|
||||||
@@ -2840,6 +2892,10 @@ 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'}
|
||||||
@@ -2949,6 +3005,14 @@ 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'}
|
||||||
@@ -3265,6 +3329,10 @@ 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.2':
|
||||||
|
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
|
||||||
@@ -3800,6 +3868,12 @@ 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
|
||||||
@@ -4191,6 +4265,8 @@ 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
|
||||||
@@ -5178,6 +5254,8 @@ 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:
|
||||||
@@ -5201,6 +5279,17 @@ 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.20.0:
|
||||||
|
dependencies:
|
||||||
|
'@mongodb-js/saslprep': 1.3.2
|
||||||
|
bson: 6.10.4
|
||||||
|
mongodb-connection-string-url: 3.0.2
|
||||||
|
|
||||||
motion-dom@12.23.23:
|
motion-dom@12.23.23:
|
||||||
dependencies:
|
dependencies:
|
||||||
motion-utils: 12.23.6
|
motion-utils: 12.23.6
|
||||||
@@ -5719,6 +5808,10 @@ 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:
|
||||||
@@ -5877,6 +5970,10 @@ 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
|
||||||
@@ -6008,6 +6105,13 @@ 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
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ generator client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "mongodb"
|
provider = "sqlite"
|
||||||
url = env("MONGODB_URI")
|
url = env("DATABASE_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
id Int @id @default(autoincrement())
|
||||||
email String @unique
|
email String @unique
|
||||||
password String
|
password String
|
||||||
roles String[] @default(["ROLE_USER"])
|
roles Json @default("[\"ROLE_USER\"]")
|
||||||
authenticated Boolean @default(true)
|
authenticated Boolean @default(true)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
@@ -29,8 +29,8 @@ model User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model KomgaConfig {
|
model KomgaConfig {
|
||||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
id Int @id @default(autoincrement())
|
||||||
userId String @unique
|
userId Int @unique
|
||||||
url String
|
url String
|
||||||
username String
|
username String
|
||||||
authHeader String
|
authHeader String
|
||||||
@@ -43,8 +43,8 @@ model KomgaConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model TTLConfig {
|
model TTLConfig {
|
||||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
id Int @id @default(autoincrement())
|
||||||
userId String @unique
|
userId Int @unique
|
||||||
defaultTTL Int @default(5)
|
defaultTTL Int @default(5)
|
||||||
homeTTL Int @default(5)
|
homeTTL Int @default(5)
|
||||||
librariesTTL Int @default(1440)
|
librariesTTL Int @default(1440)
|
||||||
@@ -61,15 +61,18 @@ model TTLConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Preferences {
|
model Preferences {
|
||||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
id Int @id @default(autoincrement())
|
||||||
userId String @unique
|
userId Int @unique
|
||||||
showThumbnails Boolean @default(true)
|
showThumbnails Boolean @default(true)
|
||||||
cacheMode String @default("memory") // "memory" | "file"
|
cacheMode String @default("memory") // "memory" | "file"
|
||||||
showOnlyUnread Boolean @default(false)
|
showOnlyUnread Boolean @default(false)
|
||||||
displayMode Json @default("{\"compact\": false, \"itemsPerPage\": 20}")
|
displayMode Json
|
||||||
background Json @default("{\"type\": \"default\", \"opacity\": 100, \"blur\": 0}")
|
background Json
|
||||||
createdAt DateTime @default(now())
|
komgaMaxConcurrentRequests Int @default(2)
|
||||||
updatedAt DateTime @updatedAt
|
circuitBreakerConfig Json
|
||||||
|
readerPrefetchCount Int @default(5)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
@@ -77,8 +80,8 @@ model Preferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Favorite {
|
model Favorite {
|
||||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
id Int @id @default(autoincrement())
|
||||||
userId String
|
userId Int
|
||||||
seriesId String
|
seriesId String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|||||||
47
scripts/check-db.mjs
Normal file
47
scripts/check-db.mjs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Script pour vérifier le contenu de la base de données
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
async function checkDatabase() {
|
||||||
|
try {
|
||||||
|
console.log("🔍 Checking database content...");
|
||||||
|
|
||||||
|
// Vérifier les utilisateurs
|
||||||
|
const users = await prisma.user.findMany({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
roles: true,
|
||||||
|
createdAt: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`📊 Found ${users.length} users:`);
|
||||||
|
users.forEach(user => {
|
||||||
|
console.log(` - ID: ${user.id}, Email: ${user.email}, Roles: ${JSON.stringify(user.roles)}, Created: ${user.createdAt}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Vérifier les configurations
|
||||||
|
const komgaConfigs = await prisma.komgaConfig.count();
|
||||||
|
const preferences = await prisma.preferences.count();
|
||||||
|
const favorites = await prisma.favorite.count();
|
||||||
|
|
||||||
|
console.log(`📊 Database stats:`);
|
||||||
|
console.log(` - KomgaConfigs: ${komgaConfigs}`);
|
||||||
|
console.log(` - Preferences: ${preferences}`);
|
||||||
|
console.log(` - Favorites: ${favorites}`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Error checking database:", error);
|
||||||
|
} finally {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDatabase();
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* Script d'initialisation de la base de données
|
* Script d'initialisation de la base de données SQLite
|
||||||
* Exécuté au démarrage de l'application
|
* Exécuté au démarrage de l'application
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ async function initializeAdminUser() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
console.log("🔧 Initializing database...");
|
console.log("🔧 Initializing SQLite database...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await initializeAdminUser();
|
await initializeAdminUser();
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const ERROR_MESSAGES: Record<string, string> = {
|
|||||||
|
|
||||||
// MongoDB
|
// MongoDB
|
||||||
[ERROR_CODES.MONGODB.MISSING_URI]:
|
[ERROR_CODES.MONGODB.MISSING_URI]:
|
||||||
"🔧 Please set MONGODB_URI environment variable in your .env file",
|
"🔧 Please set DATABASE_URL environment variable in your .env file",
|
||||||
[ERROR_CODES.MONGODB.CONNECTION_FAILED]: "🔌 MongoDB connection failed",
|
[ERROR_CODES.MONGODB.CONNECTION_FAILED]: "🔌 MongoDB connection failed",
|
||||||
|
|
||||||
// Auth
|
// Auth
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import { getToken } from "next-auth/jwt";
|
|||||||
|
|
||||||
export async function getAuthSession(request: NextRequest) {
|
export async function getAuthSession(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
|
const cookieName = process.env.NODE_ENV === "production"
|
||||||
|
? "__Secure-authjs.session-token"
|
||||||
|
: "authjs.session-token";
|
||||||
|
|
||||||
const token = await getToken({
|
const token = await getToken({
|
||||||
req: request,
|
req: request,
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
secret: process.env.NEXTAUTH_SECRET,
|
||||||
cookieName: process.env.NODE_ENV === "production"
|
cookieName
|
||||||
? "__Secure-authjs.session-token"
|
|
||||||
: "authjs.session-token"
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ export class AdminService {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...user,
|
...user,
|
||||||
|
id: user.id.toString(),
|
||||||
|
roles: user.roles as string[],
|
||||||
hasKomgaConfig: !!komgaConfig,
|
hasKomgaConfig: !!komgaConfig,
|
||||||
hasPreferences: !!preferences,
|
hasPreferences: !!preferences,
|
||||||
};
|
};
|
||||||
@@ -76,10 +78,11 @@ export class AdminService {
|
|||||||
static async updateUserRoles(userId: string, roles: string[]): Promise<void> {
|
static async updateUserRoles(userId: string, roles: string[]): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await requireAdmin();
|
await requireAdmin();
|
||||||
|
const userIdInt = parseInt(userId, 10);
|
||||||
|
|
||||||
// Vérifier que l'utilisateur existe
|
// Vérifier que l'utilisateur existe
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: { id: userId },
|
where: { id: userIdInt },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@@ -88,7 +91,7 @@ export class AdminService {
|
|||||||
|
|
||||||
// Mettre à jour les rôles
|
// Mettre à jour les rôles
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: { id: userId },
|
where: { id: userIdInt },
|
||||||
data: { roles },
|
data: { roles },
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -105,6 +108,7 @@ export class AdminService {
|
|||||||
static async deleteUser(userId: string): Promise<void> {
|
static async deleteUser(userId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const admin = await requireAdmin();
|
const admin = await requireAdmin();
|
||||||
|
const userIdInt = parseInt(userId, 10);
|
||||||
|
|
||||||
// Empêcher la suppression de son propre compte
|
// Empêcher la suppression de son propre compte
|
||||||
if (admin.id === userId) {
|
if (admin.id === userId) {
|
||||||
@@ -113,7 +117,7 @@ export class AdminService {
|
|||||||
|
|
||||||
// Vérifier que l'utilisateur existe
|
// Vérifier que l'utilisateur existe
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: { id: userId },
|
where: { id: userIdInt },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@@ -122,7 +126,7 @@ export class AdminService {
|
|||||||
|
|
||||||
// Supprimer l'utilisateur (cascade supprimera les relations)
|
// Supprimer l'utilisateur (cascade supprimera les relations)
|
||||||
await prisma.user.delete({
|
await prisma.user.delete({
|
||||||
where: { id: userId },
|
where: { id: userIdInt },
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error && error.message.includes("Forbidden")) {
|
if (error instanceof Error && error.message.includes("Forbidden")) {
|
||||||
@@ -138,6 +142,7 @@ export class AdminService {
|
|||||||
static async resetUserPassword(userId: string, newPassword: string): Promise<void> {
|
static async resetUserPassword(userId: string, newPassword: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const admin = await requireAdmin();
|
const admin = await requireAdmin();
|
||||||
|
const userIdInt = parseInt(userId, 10);
|
||||||
|
|
||||||
// Empêcher la modification de son propre mot de passe via cette méthode
|
// Empêcher la modification de son propre mot de passe via cette méthode
|
||||||
if (admin.id === userId) {
|
if (admin.id === userId) {
|
||||||
@@ -146,7 +151,7 @@ export class AdminService {
|
|||||||
|
|
||||||
// Vérifier que l'utilisateur existe
|
// Vérifier que l'utilisateur existe
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: { id: userId },
|
where: { id: userIdInt },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@@ -159,7 +164,7 @@ export class AdminService {
|
|||||||
|
|
||||||
// Mettre à jour le mot de passe
|
// Mettre à jour le mot de passe
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: { id: userId },
|
where: { id: userIdInt },
|
||||||
data: { password: hashedPassword },
|
data: { password: hashedPassword },
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -177,20 +182,21 @@ export class AdminService {
|
|||||||
try {
|
try {
|
||||||
await requireAdmin();
|
await requireAdmin();
|
||||||
|
|
||||||
const [totalUsers, totalAdmins, usersWithKomga, usersWithPreferences] =
|
const [totalUsers, usersWithKomga, usersWithPreferences] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
prisma.user.count(),
|
prisma.user.count(),
|
||||||
prisma.user.count({
|
|
||||||
where: {
|
|
||||||
roles: {
|
|
||||||
has: "ROLE_ADMIN",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.komgaConfig.count(),
|
prisma.komgaConfig.count(),
|
||||||
prisma.preferences.count(),
|
prisma.preferences.count(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Count admin users by fetching all users and filtering
|
||||||
|
const allUsers = await prisma.user.findMany({
|
||||||
|
select: { roles: true },
|
||||||
|
});
|
||||||
|
const totalAdmins = allUsers.filter(user =>
|
||||||
|
Array.isArray(user.roles) && user.roles.includes("ROLE_ADMIN")
|
||||||
|
).length;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalUsers,
|
totalUsers,
|
||||||
totalAdmins,
|
totalAdmins,
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ export class AuthServerService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userData: UserData = {
|
const userData: UserData = {
|
||||||
id: user.id,
|
id: user.id.toString(),
|
||||||
email: user.email,
|
email: user.email,
|
||||||
roles: user.roles,
|
roles: user.roles as string[],
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,9 +84,9 @@ export class AuthServerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const userData: UserData = {
|
const userData: UserData = {
|
||||||
id: user.id,
|
id: user.id.toString(),
|
||||||
email: user.email,
|
email: user.email,
|
||||||
roles: user.roles,
|
roles: user.roles as string[],
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,20 +16,21 @@ 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();
|
||||||
|
const userId = parseInt(user.id, 10);
|
||||||
|
|
||||||
const authHeader: string = Buffer.from(`${data.username}:${data.password}`).toString(
|
const authHeader: string = Buffer.from(`${data.username}:${data.password}`).toString(
|
||||||
"base64"
|
"base64"
|
||||||
);
|
);
|
||||||
|
|
||||||
const config = await prisma.komgaConfig.upsert({
|
const config = await prisma.komgaConfig.upsert({
|
||||||
where: { userId: user.id },
|
where: { userId },
|
||||||
update: {
|
update: {
|
||||||
url: data.url,
|
url: data.url,
|
||||||
username: data.username,
|
username: data.username,
|
||||||
authHeader,
|
authHeader,
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
userId: user.id,
|
userId,
|
||||||
url: data.url,
|
url: data.url,
|
||||||
username: data.username,
|
username: data.username,
|
||||||
authHeader,
|
authHeader,
|
||||||
@@ -48,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();
|
||||||
|
const userId = parseInt(user.id, 10);
|
||||||
|
|
||||||
const config = await prisma.komgaConfig.findUnique({
|
const config = await prisma.komgaConfig.findUnique({
|
||||||
where: { userId: user.id },
|
where: { userId },
|
||||||
});
|
});
|
||||||
return config as KomgaConfig | null;
|
return config;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof AppError) {
|
if (error instanceof AppError) {
|
||||||
throw error;
|
throw error;
|
||||||
@@ -64,9 +66,10 @@ 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();
|
||||||
|
const userId = parseInt(user.id, 10);
|
||||||
|
|
||||||
const config = await prisma.tTLConfig.findUnique({
|
const config = await prisma.tTLConfig.findUnique({
|
||||||
where: { userId: user.id },
|
where: { userId },
|
||||||
});
|
});
|
||||||
return config as TTLConfig | null;
|
return config as TTLConfig | null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -80,9 +83,10 @@ 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();
|
||||||
|
const userId = parseInt(user.id, 10);
|
||||||
|
|
||||||
const config = await prisma.tTLConfig.upsert({
|
const config = await prisma.tTLConfig.upsert({
|
||||||
where: { userId: user.id },
|
where: { userId },
|
||||||
update: {
|
update: {
|
||||||
defaultTTL: data.defaultTTL,
|
defaultTTL: data.defaultTTL,
|
||||||
homeTTL: data.homeTTL,
|
homeTTL: data.homeTTL,
|
||||||
@@ -93,7 +97,7 @@ export class ConfigDBService {
|
|||||||
imageCacheMaxAge: data.imageCacheMaxAge,
|
imageCacheMaxAge: data.imageCacheMaxAge,
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
userId: user.id,
|
userId,
|
||||||
defaultTTL: data.defaultTTL,
|
defaultTTL: data.defaultTTL,
|
||||||
homeTTL: data.homeTTL,
|
homeTTL: data.homeTTL,
|
||||||
librariesTTL: data.librariesTTL,
|
librariesTTL: data.librariesTTL,
|
||||||
|
|||||||
@@ -28,10 +28,11 @@ 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();
|
||||||
|
const userId = parseInt(user.id, 10);
|
||||||
|
|
||||||
const favorite = await prisma.favorite.findFirst({
|
const favorite = await prisma.favorite.findFirst({
|
||||||
where: {
|
where: {
|
||||||
userId: user.id,
|
userId,
|
||||||
seriesId: seriesId,
|
seriesId: seriesId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -48,17 +49,18 @@ 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();
|
||||||
|
const userId = parseInt(user.id, 10);
|
||||||
|
|
||||||
await prisma.favorite.upsert({
|
await prisma.favorite.upsert({
|
||||||
where: {
|
where: {
|
||||||
userId_seriesId: {
|
userId_seriesId: {
|
||||||
userId: user.id,
|
userId,
|
||||||
seriesId,
|
seriesId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: {},
|
update: {},
|
||||||
create: {
|
create: {
|
||||||
userId: user.id,
|
userId,
|
||||||
seriesId,
|
seriesId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -75,10 +77,11 @@ 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();
|
||||||
|
const userId = parseInt(user.id, 10);
|
||||||
|
|
||||||
await prisma.favorite.deleteMany({
|
await prisma.favorite.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
userId: user.id,
|
userId,
|
||||||
seriesId,
|
seriesId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -94,9 +97,10 @@ export class FavoriteService {
|
|||||||
*/
|
*/
|
||||||
static async getAllFavoriteIds(): Promise<string[]> {
|
static async getAllFavoriteIds(): Promise<string[]> {
|
||||||
const user = await this.getCurrentUser();
|
const user = await this.getCurrentUser();
|
||||||
|
const userId = parseInt(user.id, 10);
|
||||||
|
|
||||||
const favorites = await prisma.favorite.findMany({
|
const favorites = await prisma.favorite.findMany({
|
||||||
where: { userId: user.id },
|
where: { userId },
|
||||||
select: { seriesId: true },
|
select: { seriesId: true },
|
||||||
});
|
});
|
||||||
return favorites.map((favorite) => favorite.seriesId);
|
return favorites.map((favorite) => favorite.seriesId);
|
||||||
@@ -104,17 +108,18 @@ export class FavoriteService {
|
|||||||
|
|
||||||
static async addFavorite(seriesId: string) {
|
static async addFavorite(seriesId: string) {
|
||||||
const user = await this.getCurrentUser();
|
const user = await this.getCurrentUser();
|
||||||
|
const userId = parseInt(user.id, 10);
|
||||||
|
|
||||||
const favorite = await prisma.favorite.upsert({
|
const favorite = await prisma.favorite.upsert({
|
||||||
where: {
|
where: {
|
||||||
userId_seriesId: {
|
userId_seriesId: {
|
||||||
userId: user.id,
|
userId,
|
||||||
seriesId,
|
seriesId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: {},
|
update: {},
|
||||||
create: {
|
create: {
|
||||||
userId: user.id,
|
userId,
|
||||||
seriesId,
|
seriesId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -123,10 +128,11 @@ export class FavoriteService {
|
|||||||
|
|
||||||
static async removeFavorite(seriesId: string): Promise<boolean> {
|
static async removeFavorite(seriesId: string): Promise<boolean> {
|
||||||
const user = await this.getCurrentUser();
|
const user = await this.getCurrentUser();
|
||||||
|
const userId = parseInt(user.id, 10);
|
||||||
|
|
||||||
const result = await prisma.favorite.deleteMany({
|
const result = await prisma.favorite.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
userId: user.id,
|
userId,
|
||||||
seriesId,
|
seriesId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,8 +19,10 @@ 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();
|
||||||
|
const userId = parseInt(user.id, 10);
|
||||||
|
|
||||||
const preferences = await prisma.preferences.findUnique({
|
const preferences = await prisma.preferences.findUnique({
|
||||||
where: { userId: user.id },
|
where: { userId },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!preferences) {
|
if (!preferences) {
|
||||||
@@ -45,6 +47,7 @@ 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();
|
||||||
|
const userId = parseInt(user.id, 10);
|
||||||
|
|
||||||
const updateData: Record<string, any> = {};
|
const updateData: Record<string, any> = {};
|
||||||
if (preferences.showThumbnails !== undefined) updateData.showThumbnails = preferences.showThumbnails;
|
if (preferences.showThumbnails !== undefined) updateData.showThumbnails = preferences.showThumbnails;
|
||||||
@@ -54,15 +57,18 @@ export class PreferencesService {
|
|||||||
if (preferences.background !== undefined) updateData.background = preferences.background;
|
if (preferences.background !== undefined) updateData.background = preferences.background;
|
||||||
|
|
||||||
const updatedPreferences = await prisma.preferences.upsert({
|
const updatedPreferences = await prisma.preferences.upsert({
|
||||||
where: { userId: user.id },
|
where: { userId },
|
||||||
update: updateData,
|
update: updateData,
|
||||||
create: {
|
create: {
|
||||||
userId: user.id,
|
userId,
|
||||||
showThumbnails: preferences.showThumbnails ?? defaultPreferences.showThumbnails,
|
showThumbnails: preferences.showThumbnails ?? defaultPreferences.showThumbnails,
|
||||||
cacheMode: preferences.cacheMode ?? defaultPreferences.cacheMode,
|
cacheMode: preferences.cacheMode ?? defaultPreferences.cacheMode,
|
||||||
showOnlyUnread: preferences.showOnlyUnread ?? defaultPreferences.showOnlyUnread,
|
showOnlyUnread: preferences.showOnlyUnread ?? defaultPreferences.showOnlyUnread,
|
||||||
displayMode: preferences.displayMode ?? defaultPreferences.displayMode,
|
displayMode: preferences.displayMode ?? defaultPreferences.displayMode,
|
||||||
background: (preferences.background ?? defaultPreferences.background) as unknown as Prisma.InputJsonValue,
|
background: (preferences.background ?? defaultPreferences.background) as unknown as Prisma.InputJsonValue,
|
||||||
|
circuitBreakerConfig: {},
|
||||||
|
komgaMaxConcurrentRequests: 2,
|
||||||
|
readerPrefetchCount: 5,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,10 @@ export class UserService {
|
|||||||
if (!currentUser) {
|
if (!currentUser) {
|
||||||
throw new AppError(ERROR_CODES.AUTH.UNAUTHENTICATED);
|
throw new AppError(ERROR_CODES.AUTH.UNAUTHENTICATED);
|
||||||
}
|
}
|
||||||
|
const userId = parseInt(currentUser.id, 10);
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: { id: currentUser.id },
|
where: { id: userId },
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
email: true,
|
email: true,
|
||||||
@@ -37,7 +38,13 @@ export class UserService {
|
|||||||
throw new AppError(ERROR_CODES.AUTH.USER_NOT_FOUND);
|
throw new AppError(ERROR_CODES.AUTH.USER_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return {
|
||||||
|
id: user.id.toString(),
|
||||||
|
email: user.email,
|
||||||
|
roles: user.roles as string[],
|
||||||
|
createdAt: user.createdAt,
|
||||||
|
updatedAt: user.updatedAt,
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof AppError) {
|
if (error instanceof AppError) {
|
||||||
throw error;
|
throw error;
|
||||||
@@ -55,10 +62,11 @@ export class UserService {
|
|||||||
if (!currentUser) {
|
if (!currentUser) {
|
||||||
throw new AppError(ERROR_CODES.AUTH.UNAUTHENTICATED);
|
throw new AppError(ERROR_CODES.AUTH.UNAUTHENTICATED);
|
||||||
}
|
}
|
||||||
|
const userId = parseInt(currentUser.id, 10);
|
||||||
|
|
||||||
// Récupérer l'utilisateur avec son mot de passe
|
// Récupérer l'utilisateur avec son mot de passe
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: { id: currentUser.id },
|
where: { id: userId },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@@ -76,7 +84,7 @@ export class UserService {
|
|||||||
|
|
||||||
// Mettre à jour le mot de passe
|
// Mettre à jour le mot de passe
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: { id: currentUser.id },
|
where: { id: userId },
|
||||||
data: { password: hashedPassword },
|
data: { password: hashedPassword },
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -93,16 +101,17 @@ export class UserService {
|
|||||||
if (!currentUser) {
|
if (!currentUser) {
|
||||||
throw new AppError(ERROR_CODES.AUTH.UNAUTHENTICATED);
|
throw new AppError(ERROR_CODES.AUTH.UNAUTHENTICATED);
|
||||||
}
|
}
|
||||||
|
const userId = parseInt(currentUser.id, 10);
|
||||||
|
|
||||||
const [favoritesCount, preferences, komgaConfig] = await Promise.all([
|
const [favoritesCount, preferences, komgaConfig] = await Promise.all([
|
||||||
prisma.favorite.count({
|
prisma.favorite.count({
|
||||||
where: { userId: currentUser.id },
|
where: { userId },
|
||||||
}),
|
}),
|
||||||
prisma.preferences.findUnique({
|
prisma.preferences.findUnique({
|
||||||
where: { userId: currentUser.id },
|
where: { userId },
|
||||||
}),
|
}),
|
||||||
prisma.komgaConfig.findUnique({
|
prisma.komgaConfig.findUnique({
|
||||||
where: { userId: currentUser.id },
|
where: { userId },
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export interface KomgaConfigData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface KomgaConfig extends KomgaConfigData {
|
export interface KomgaConfig extends KomgaConfigData {
|
||||||
userId: string;
|
userId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TTLConfigData {
|
export interface TTLConfigData {
|
||||||
@@ -26,7 +26,7 @@ export interface TTLConfigData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TTLConfig extends TTLConfigData {
|
export interface TTLConfig extends TTLConfigData {
|
||||||
userId: string;
|
userId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Types liés à l'API Komga
|
// Types liés à l'API Komga
|
||||||
|
|||||||
Reference in New Issue
Block a user