diff --git a/.gitignore b/.gitignore index dfb7763..af53e33 100644 --- a/.gitignore +++ b/.gitignore @@ -47,4 +47,10 @@ mongo-keyfile .env.local .env.development.local .env.test.local -.env.production.local \ No newline at end of file +.env.production.local + +# Database +prisma/data/ +*.db +*.sqlite +*.sqlite3 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 141188d..10f736e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,6 @@ # Build stage 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 WORKDIR /app @@ -74,8 +70,8 @@ RUN chmod +x docker-entrypoint.sh # Add non-root user for security RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 nextjs && \ - mkdir -p /app/.cache && \ - chown -R nextjs:nodejs /app /app/.cache && \ + mkdir -p /app/.cache /app/data && \ + chown -R nextjs:nodejs /app /app/.cache /app/data && \ chown nextjs:nodejs docker-entrypoint.sh USER nextjs diff --git a/ENV.md b/ENV.md index d2709e3..140e039 100644 --- a/ENV.md +++ b/ENV.md @@ -2,10 +2,8 @@ ## Production (.env) ```env -# MongoDB Configuration -MONGO_USER=admin -MONGO_PASSWORD=your-secure-password -MONGODB_URI=mongodb://admin:your-secure-password@mongodb:27017/stripstream?authSource=admin&replicaSet=rs0 +# Database Configuration (SQLite) +DATABASE_URL=file:./data/stripstream.db # NextAuth Configuration NEXTAUTH_SECRET=your-secret-key-here-generate-with-openssl-rand-base64-32 @@ -32,13 +30,5 @@ NODE_ENV=production openssl rand -base64 32 ``` -## Génération du keyFile MongoDB (requis pour Prisma) -```bash -openssl rand -base64 756 > mongo-keyfile -chmod 400 mongo-keyfile -``` - -Ce fichier est nécessaire pour MongoDB en mode replica set (requis par Prisma pour les relations et transactions). - ## Développement Pour le développement, les variables sont définies directement dans `docker-compose.dev.yml`. diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index a763c93..ec3081d 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -5,8 +5,6 @@ services: build: context: . dockerfile: Dockerfile - args: - - MONGODB_URI=${MONGODB_URI} container_name: stripstream-app ports: - "3020:3000" @@ -24,11 +22,10 @@ services: - /app/.next - ~/.pnpm-store:/app/.pnpm-store - cache_data:/app/.cache + - sqlite_data:/app/data environment: - NODE_ENV=development - - MONGO_USER=admin - - MONGO_PASSWORD=password123 - - MONGODB_URI=${MONGODB_URI} + - DATABASE_URL=file:/app/data/stripstream.db - PNPM_HOME=/app/.pnpm-store - WATCHPACK_POLLING=true - NEXTAUTH_SECRET=${NEXTAUTH_SECRET} @@ -37,28 +34,6 @@ services: - 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" - 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: - mongodb_data: + sqlite_data: cache_data: diff --git a/docker-compose.yml b/docker-compose.yml index 506d225..6a68eef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,25 +7,20 @@ services: dockerfile: Dockerfile args: - NODE_ENV=production - - MONGODB_URI=${MONGODB_URI} container_name: stripstream-app restart: unless-stopped ports: - "3020:3000" volumes: - stripstream_cache:/app/.cache + - ./prisma/data:/app/data environment: - NODE_ENV=production - - MONGODB_URI=${MONGODB_URI} + - DATABASE_URL=file:/app/data/stripstream.db - NEXTAUTH_SECRET=${NEXTAUTH_SECRET} - - NEXTAUTH_URL=${NEXTAUTH_URL} + - NEXTAUTH_URL=http://localhost:3020 - AUTH_TRUST_HOST=true - KOMGA_MAX_CONCURRENT_REQUESTS=5 - depends_on: - mongodb: - condition: service_healthy - mongodb-init: - condition: service_completed_successfully networks: - stripstream-network deploy: @@ -42,53 +37,9 @@ services: timeout: 3s 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: stripstream-network: driver: bridge volumes: - stripstream_mongodb_data: stripstream_cache: diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 51265e0..4a0ce90 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,6 +1,9 @@ #!/bin/sh set -e +echo "📁 Ensuring data directory exists..." +mkdir -p /app/data + echo "🔄 Pushing Prisma schema to database..." npx prisma db push --skip-generate --accept-data-loss diff --git a/mongo-init-rs.sh b/mongo-init-rs.sh deleted file mode 100755 index c3a87d9..0000000 --- a/mongo-init-rs.sh +++ /dev/null @@ -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()" - diff --git a/mongo-init.js b/mongo-init.js deleted file mode 100644 index 63594c9..0000000 --- a/mongo-init.js +++ /dev/null @@ -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'); diff --git a/package.json b/package.json index 6afc162..e53df67 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "i18next": "^24.2.2", "i18next-browser-languagedetector": "^8.0.4", "lucide-react": "^0.487.0", + "mongodb": "^6.20.0", "next": "15.2.0", "next-auth": "5.0.0-beta.29", "next-themes": "0.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f600a6b..ac6b034 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,9 @@ importers: lucide-react: specifier: ^0.487.0 version: 0.487.0(react@19.2.0) + mongodb: + specifier: ^6.20.0 + version: 6.20.0 next: specifier: 15.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': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@mongodb-js/saslprep@1.3.2': + resolution: {integrity: sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==} + '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -1018,6 +1024,12 @@ packages: '@types/semver@7.7.1': resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + '@types/webidl-conversions@7.0.3': + resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} + + '@types/whatwg-url@11.0.5': + resolution: {integrity: sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==} + '@typescript-eslint/eslint-plugin@8.46.1': resolution: {integrity: sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1374,6 +1386,10 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + bson@6.10.4: + resolution: {integrity: sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==} + engines: {node: '>=16.20.1'} + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -2207,6 +2223,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + memory-pager@1.5.0: + resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -2233,6 +2252,36 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + mongodb-connection-string-url@3.0.2: + resolution: {integrity: sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==} + + mongodb@6.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: resolution: {integrity: sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==} @@ -2722,6 +2771,9 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + sparse-bitfield@3.0.3: + resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} + stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} @@ -2840,6 +2892,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + ts-api-utils@1.4.3: resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} engines: {node: '>=16'} @@ -2949,6 +3005,14 @@ packages: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -3265,6 +3329,10 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@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': dependencies: '@emnapi/core': 1.5.0 @@ -3800,6 +3868,12 @@ snapshots: '@types/semver@7.7.1': {} + '@types/webidl-conversions@7.0.3': {} + + '@types/whatwg-url@11.0.5': + dependencies: + '@types/webidl-conversions': 7.0.3 + '@typescript-eslint/eslint-plugin@8.46.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0)(typescript@5.3.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -4191,6 +4265,8 @@ snapshots: node-releases: 2.0.23 update-browserslist-db: 1.1.3(browserslist@4.26.3) + bson@6.10.4: {} + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -5178,6 +5254,8 @@ snapshots: math-intrinsics@1.1.0: {} + memory-pager@1.5.0: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -5201,6 +5279,17 @@ snapshots: minipass@7.1.2: {} + mongodb-connection-string-url@3.0.2: + dependencies: + '@types/whatwg-url': 11.0.5 + whatwg-url: 14.2.0 + + mongodb@6.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: dependencies: motion-utils: 12.23.6 @@ -5719,6 +5808,10 @@ snapshots: source-map-js@1.2.1: {} + sparse-bitfield@3.0.3: + dependencies: + memory-pager: 1.5.0 + stable-hash@0.0.5: {} stop-iteration-iterator@1.1.0: @@ -5877,6 +5970,10 @@ snapshots: dependencies: is-number: 7.0.0 + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + ts-api-utils@1.4.3(typescript@5.3.3): dependencies: typescript: 5.3.3 @@ -6008,6 +6105,13 @@ snapshots: void-elements@3.1.0: {} + webidl-conversions@7.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8145bed..484ff87 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -6,15 +6,15 @@ generator client { } datasource db { - provider = "mongodb" - url = env("MONGODB_URI") + provider = "sqlite" + url = env("DATABASE_URL") } model User { - id String @id @default(auto()) @map("_id") @db.ObjectId + id Int @id @default(autoincrement()) email String @unique password String - roles String[] @default(["ROLE_USER"]) + roles Json @default("[\"ROLE_USER\"]") authenticated Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -29,8 +29,8 @@ model User { } model KomgaConfig { - id String @id @default(auto()) @map("_id") @db.ObjectId - userId String @unique + id Int @id @default(autoincrement()) + userId Int @unique url String username String authHeader String @@ -43,8 +43,8 @@ model KomgaConfig { } model TTLConfig { - id String @id @default(auto()) @map("_id") @db.ObjectId - userId String @unique + id Int @id @default(autoincrement()) + userId Int @unique defaultTTL Int @default(5) homeTTL Int @default(5) librariesTTL Int @default(1440) @@ -61,15 +61,18 @@ model TTLConfig { } 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) - displayMode Json @default("{\"compact\": false, \"itemsPerPage\": 20}") - background Json @default("{\"type\": \"default\", \"opacity\": 100, \"blur\": 0}") - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id Int @id @default(autoincrement()) + userId Int @unique + showThumbnails Boolean @default(true) + cacheMode String @default("memory") // "memory" | "file" + showOnlyUnread Boolean @default(false) + displayMode Json + background Json + komgaMaxConcurrentRequests Int @default(2) + circuitBreakerConfig Json + readerPrefetchCount Int @default(5) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@ -77,8 +80,8 @@ model Preferences { } model Favorite { - id String @id @default(auto()) @map("_id") @db.ObjectId - userId String + id Int @id @default(autoincrement()) + userId Int seriesId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/scripts/check-db.mjs b/scripts/check-db.mjs new file mode 100644 index 0000000..069e75b --- /dev/null +++ b/scripts/check-db.mjs @@ -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(); + diff --git a/scripts/init-db.mjs b/scripts/init-db.mjs index 53d7302..6c8cff6 100755 --- a/scripts/init-db.mjs +++ b/scripts/init-db.mjs @@ -1,6 +1,6 @@ #!/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 */ @@ -59,7 +59,7 @@ async function initializeAdminUser() { } async function main() { - console.log("🔧 Initializing database..."); + console.log("🔧 Initializing SQLite database..."); try { await initializeAdminUser(); diff --git a/src/constants/errorMessages.ts b/src/constants/errorMessages.ts index 2a9c99f..e107de9 100644 --- a/src/constants/errorMessages.ts +++ b/src/constants/errorMessages.ts @@ -8,7 +8,7 @@ export const ERROR_MESSAGES: Record = { // MongoDB [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", // Auth diff --git a/src/lib/middleware-auth.ts b/src/lib/middleware-auth.ts index 42edd46..d333dc4 100644 --- a/src/lib/middleware-auth.ts +++ b/src/lib/middleware-auth.ts @@ -3,12 +3,14 @@ import { getToken } from "next-auth/jwt"; export async function getAuthSession(request: NextRequest) { try { + const cookieName = process.env.NODE_ENV === "production" + ? "__Secure-authjs.session-token" + : "authjs.session-token"; + const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET, - cookieName: process.env.NODE_ENV === "production" - ? "__Secure-authjs.session-token" - : "authjs.session-token" + cookieName }); if (!token) { diff --git a/src/lib/services/admin.service.ts b/src/lib/services/admin.service.ts index 9d9fd9b..c305b4a 100644 --- a/src/lib/services/admin.service.ts +++ b/src/lib/services/admin.service.ts @@ -55,6 +55,8 @@ export class AdminService { return { ...user, + id: user.id.toString(), + roles: user.roles as string[], hasKomgaConfig: !!komgaConfig, hasPreferences: !!preferences, }; @@ -76,10 +78,11 @@ export class AdminService { static async updateUserRoles(userId: string, roles: string[]): Promise { try { await requireAdmin(); + const userIdInt = parseInt(userId, 10); // Vérifier que l'utilisateur existe const user = await prisma.user.findUnique({ - where: { id: userId }, + where: { id: userIdInt }, }); if (!user) { @@ -88,7 +91,7 @@ export class AdminService { // Mettre à jour les rôles await prisma.user.update({ - where: { id: userId }, + where: { id: userIdInt }, data: { roles }, }); } catch (error) { @@ -105,6 +108,7 @@ export class AdminService { static async deleteUser(userId: string): Promise { try { const admin = await requireAdmin(); + const userIdInt = parseInt(userId, 10); // Empêcher la suppression de son propre compte if (admin.id === userId) { @@ -113,7 +117,7 @@ export class AdminService { // Vérifier que l'utilisateur existe const user = await prisma.user.findUnique({ - where: { id: userId }, + where: { id: userIdInt }, }); if (!user) { @@ -122,7 +126,7 @@ export class AdminService { // Supprimer l'utilisateur (cascade supprimera les relations) await prisma.user.delete({ - where: { id: userId }, + where: { id: userIdInt }, }); } catch (error) { if (error instanceof Error && error.message.includes("Forbidden")) { @@ -138,6 +142,7 @@ export class AdminService { static async resetUserPassword(userId: string, newPassword: string): Promise { try { const admin = await requireAdmin(); + const userIdInt = parseInt(userId, 10); // Empêcher la modification de son propre mot de passe via cette méthode if (admin.id === userId) { @@ -146,7 +151,7 @@ export class AdminService { // Vérifier que l'utilisateur existe const user = await prisma.user.findUnique({ - where: { id: userId }, + where: { id: userIdInt }, }); if (!user) { @@ -159,7 +164,7 @@ export class AdminService { // Mettre à jour le mot de passe await prisma.user.update({ - where: { id: userId }, + where: { id: userIdInt }, data: { password: hashedPassword }, }); } catch (error) { @@ -177,20 +182,21 @@ export class AdminService { try { await requireAdmin(); - const [totalUsers, totalAdmins, usersWithKomga, usersWithPreferences] = + const [totalUsers, usersWithKomga, usersWithPreferences] = await Promise.all([ prisma.user.count(), - prisma.user.count({ - where: { - roles: { - has: "ROLE_ADMIN", - }, - }, - }), prisma.komgaConfig.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 { totalUsers, totalAdmins, diff --git a/src/lib/services/auth-server.service.ts b/src/lib/services/auth-server.service.ts index 78c33e4..c84d33c 100644 --- a/src/lib/services/auth-server.service.ts +++ b/src/lib/services/auth-server.service.ts @@ -42,9 +42,9 @@ export class AuthServerService { }); const userData: UserData = { - id: user.id, + id: user.id.toString(), email: user.email, - roles: user.roles, + roles: user.roles as string[], authenticated: true, }; @@ -84,9 +84,9 @@ export class AuthServerService { } const userData: UserData = { - id: user.id, + id: user.id.toString(), email: user.email, - roles: user.roles, + roles: user.roles as string[], authenticated: true, }; diff --git a/src/lib/services/config-db.service.ts b/src/lib/services/config-db.service.ts index 40b5603..7dc7683 100644 --- a/src/lib/services/config-db.service.ts +++ b/src/lib/services/config-db.service.ts @@ -16,20 +16,21 @@ export class ConfigDBService { static async saveConfig(data: KomgaConfigData): Promise { try { const user: User | null = await this.getCurrentUser(); + const userId = parseInt(user.id, 10); const authHeader: string = Buffer.from(`${data.username}:${data.password}`).toString( "base64" ); const config = await prisma.komgaConfig.upsert({ - where: { userId: user.id }, + where: { userId }, update: { url: data.url, username: data.username, authHeader, }, create: { - userId: user.id, + userId, url: data.url, username: data.username, authHeader, @@ -48,11 +49,12 @@ export class ConfigDBService { static async getConfig(): Promise { try { const user: User | null = await this.getCurrentUser(); + const userId = parseInt(user.id, 10); const config = await prisma.komgaConfig.findUnique({ - where: { userId: user.id }, + where: { userId }, }); - return config as KomgaConfig | null; + return config; } catch (error) { if (error instanceof AppError) { throw error; @@ -64,9 +66,10 @@ export class ConfigDBService { static async getTTLConfig(): Promise { try { const user: User | null = await this.getCurrentUser(); + const userId = parseInt(user.id, 10); const config = await prisma.tTLConfig.findUnique({ - where: { userId: user.id }, + where: { userId }, }); return config as TTLConfig | null; } catch (error) { @@ -80,9 +83,10 @@ export class ConfigDBService { static async saveTTLConfig(data: TTLConfigData): Promise { try { const user: User | null = await this.getCurrentUser(); + const userId = parseInt(user.id, 10); const config = await prisma.tTLConfig.upsert({ - where: { userId: user.id }, + where: { userId }, update: { defaultTTL: data.defaultTTL, homeTTL: data.homeTTL, @@ -93,7 +97,7 @@ export class ConfigDBService { imageCacheMaxAge: data.imageCacheMaxAge, }, create: { - userId: user.id, + userId, defaultTTL: data.defaultTTL, homeTTL: data.homeTTL, librariesTTL: data.librariesTTL, diff --git a/src/lib/services/favorite.service.ts b/src/lib/services/favorite.service.ts index 9ce9dd6..74c790b 100644 --- a/src/lib/services/favorite.service.ts +++ b/src/lib/services/favorite.service.ts @@ -28,10 +28,11 @@ export class FavoriteService { static async isFavorite(seriesId: string): Promise { try { const user = await this.getCurrentUser(); + const userId = parseInt(user.id, 10); const favorite = await prisma.favorite.findFirst({ where: { - userId: user.id, + userId, seriesId: seriesId, }, }); @@ -48,17 +49,18 @@ export class FavoriteService { static async addToFavorites(seriesId: string): Promise { try { const user = await this.getCurrentUser(); + const userId = parseInt(user.id, 10); await prisma.favorite.upsert({ where: { userId_seriesId: { - userId: user.id, + userId, seriesId, }, }, update: {}, create: { - userId: user.id, + userId, seriesId, }, }); @@ -75,10 +77,11 @@ export class FavoriteService { static async removeFromFavorites(seriesId: string): Promise { try { const user = await this.getCurrentUser(); + const userId = parseInt(user.id, 10); await prisma.favorite.deleteMany({ where: { - userId: user.id, + userId, seriesId, }, }); @@ -94,9 +97,10 @@ export class FavoriteService { */ static async getAllFavoriteIds(): Promise { const user = await this.getCurrentUser(); + const userId = parseInt(user.id, 10); const favorites = await prisma.favorite.findMany({ - where: { userId: user.id }, + where: { userId }, select: { seriesId: true }, }); return favorites.map((favorite) => favorite.seriesId); @@ -104,17 +108,18 @@ export class FavoriteService { static async addFavorite(seriesId: string) { const user = await this.getCurrentUser(); + const userId = parseInt(user.id, 10); const favorite = await prisma.favorite.upsert({ where: { userId_seriesId: { - userId: user.id, + userId, seriesId, }, }, update: {}, create: { - userId: user.id, + userId, seriesId, }, }); @@ -123,10 +128,11 @@ export class FavoriteService { static async removeFavorite(seriesId: string): Promise { const user = await this.getCurrentUser(); + const userId = parseInt(user.id, 10); const result = await prisma.favorite.deleteMany({ where: { - userId: user.id, + userId, seriesId, }, }); diff --git a/src/lib/services/preferences.service.ts b/src/lib/services/preferences.service.ts index f210bfc..6e9a23a 100644 --- a/src/lib/services/preferences.service.ts +++ b/src/lib/services/preferences.service.ts @@ -19,8 +19,10 @@ export class PreferencesService { static async getPreferences(): Promise { try { const user = await this.getCurrentUser(); + const userId = parseInt(user.id, 10); + const preferences = await prisma.preferences.findUnique({ - where: { userId: user.id }, + where: { userId }, }); if (!preferences) { @@ -45,6 +47,7 @@ export class PreferencesService { static async updatePreferences(preferences: Partial): Promise { try { const user = await this.getCurrentUser(); + const userId = parseInt(user.id, 10); const updateData: Record = {}; if (preferences.showThumbnails !== undefined) updateData.showThumbnails = preferences.showThumbnails; @@ -54,15 +57,18 @@ export class PreferencesService { if (preferences.background !== undefined) updateData.background = preferences.background; const updatedPreferences = await prisma.preferences.upsert({ - where: { userId: user.id }, + where: { userId }, update: updateData, create: { - userId: user.id, + userId, showThumbnails: preferences.showThumbnails ?? defaultPreferences.showThumbnails, cacheMode: preferences.cacheMode ?? defaultPreferences.cacheMode, showOnlyUnread: preferences.showOnlyUnread ?? defaultPreferences.showOnlyUnread, displayMode: preferences.displayMode ?? defaultPreferences.displayMode, background: (preferences.background ?? defaultPreferences.background) as unknown as Prisma.InputJsonValue, + circuitBreakerConfig: {}, + komgaMaxConcurrentRequests: 2, + readerPrefetchCount: 5, }, }); diff --git a/src/lib/services/user.service.ts b/src/lib/services/user.service.ts index ae98a5f..c13c53f 100644 --- a/src/lib/services/user.service.ts +++ b/src/lib/services/user.service.ts @@ -21,9 +21,10 @@ export class UserService { if (!currentUser) { throw new AppError(ERROR_CODES.AUTH.UNAUTHENTICATED); } + const userId = parseInt(currentUser.id, 10); const user = await prisma.user.findUnique({ - where: { id: currentUser.id }, + where: { id: userId }, select: { id: true, email: true, @@ -37,7 +38,13 @@ export class UserService { 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) { if (error instanceof AppError) { throw error; @@ -55,10 +62,11 @@ export class UserService { if (!currentUser) { throw new AppError(ERROR_CODES.AUTH.UNAUTHENTICATED); } + const userId = parseInt(currentUser.id, 10); // Récupérer l'utilisateur avec son mot de passe const user = await prisma.user.findUnique({ - where: { id: currentUser.id }, + where: { id: userId }, }); if (!user) { @@ -76,7 +84,7 @@ export class UserService { // Mettre à jour le mot de passe await prisma.user.update({ - where: { id: currentUser.id }, + where: { id: userId }, data: { password: hashedPassword }, }); } catch (error) { @@ -93,16 +101,17 @@ export class UserService { if (!currentUser) { throw new AppError(ERROR_CODES.AUTH.UNAUTHENTICATED); } + const userId = parseInt(currentUser.id, 10); const [favoritesCount, preferences, komgaConfig] = await Promise.all([ prisma.favorite.count({ - where: { userId: currentUser.id }, + where: { userId }, }), prisma.preferences.findUnique({ - where: { userId: currentUser.id }, + where: { userId }, }), prisma.komgaConfig.findUnique({ - where: { userId: currentUser.id }, + where: { userId }, }), ]); diff --git a/src/types/komga.ts b/src/types/komga.ts index bfc341a..553494f 100644 --- a/src/types/komga.ts +++ b/src/types/komga.ts @@ -12,7 +12,7 @@ export interface KomgaConfigData { } export interface KomgaConfig extends KomgaConfigData { - userId: string; + userId: number; } export interface TTLConfigData { @@ -26,7 +26,7 @@ export interface TTLConfigData { } export interface TTLConfig extends TTLConfigData { - userId: string; + userId: number; } // Types liés à l'API Komga