From 5875813f2fe9a85c78937ee39bdb8087ace7f831 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Wed, 17 Dec 2025 12:29:13 +0100 Subject: [PATCH] Refactor Docker configuration for PostgreSQL migration: Remove SQLite volume from docker-compose.yml, update Dockerfile to eliminate SQLite dependencies, and adjust README files to reflect PostgreSQL setup. Delete migration script and related documentation as part of the transition to PostgreSQL. --- .gitea/workflows/deploy.yml | 1 + Dockerfile | 2 +- MIGRATION_POSTGRES.md | 82 ----- README.docker.md | 11 +- README.md | 2 +- docker-compose.yml | 2 - package.json | 6 +- pnpm-lock.yaml | 95 ++--- scripts/README-MIGRATION.md | 70 ---- scripts/migrate-sqlite-to-postgres.ts | 489 -------------------------- 10 files changed, 64 insertions(+), 696 deletions(-) delete mode 100644 MIGRATION_POSTGRES.md delete mode 100644 scripts/README-MIGRATION.md delete mode 100644 scripts/migrate-sqlite-to-postgres.ts diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index e90a608..9c2c33c 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -21,5 +21,6 @@ jobs: PRISMA_DATA_PATH: ${{ vars.PRISMA_DATA_PATH }} UPLOADS_PATH: ${{ vars.UPLOADS_PATH }} POSTGRES_DATA_PATH: ${{ vars.POSTGRES_DATA_PATH }} + POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} run: | docker compose up -d --build diff --git a/Dockerfile b/Dockerfile index 0276fd7..0c863e8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,7 +32,7 @@ WORKDIR /app ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 -RUN apk add --no-cache python3 make g++ sqlite +RUN apk add --no-cache python3 make g++ RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 nextjs diff --git a/MIGRATION_POSTGRES.md b/MIGRATION_POSTGRES.md deleted file mode 100644 index c48dcdb..0000000 --- a/MIGRATION_POSTGRES.md +++ /dev/null @@ -1,82 +0,0 @@ -# Migration vers PostgreSQL - -## Changements effectués - -✅ Schema Prisma modifié pour PostgreSQL -✅ Code retiré Better SQLite adapter -✅ Docker Compose configuré avec service PostgreSQL -✅ Dockerfile mis à jour -✅ Dépendances retirées (better-sqlite3) - -## Prochaines étapes - -### 1. Démarrer PostgreSQL en local (pour dev) - -```bash -# Option 1: Docker Compose -docker-compose up postgres -d - -# Option 2: PostgreSQL local -# Installer PostgreSQL puis créer la DB -createdb gotgaming -``` - -### 2. Configurer DATABASE_URL - -Créer un fichier `.env.local` avec : -``` -DATABASE_URL="postgresql://gotgaming:password@localhost:5432/gotgaming?schema=public" -``` - -### 3. Créer la migration initiale - -```bash -pnpm prisma migrate dev --name init_postgres -``` - -### 4. Migrer les données SQLite → PostgreSQL (si nécessaire) - -Si tu as des données existantes dans SQLite : - -```bash -# Exporter SQLite -sqlite3 data/dev.db .dump > sqlite_dump.sql - -# Adapter le dump pour PostgreSQL (changer les types, syntaxe) -# Puis importer dans PostgreSQL -psql gotgaming < adapted_dump.sql -``` - -Ou utiliser un outil comme `pgloader` : -```bash -pgloader sqlite://data/dev.db postgresql://gotgaming:password@localhost:5432/gotgaming -``` - -### 5. En production (Docker Compose) - -```bash -# Démarrer tous les services -docker-compose up -d - -# Les migrations seront appliquées automatiquement au démarrage via entrypoint.sh -``` - -## Variables d'environnement Docker - -Ajouter dans ton `.env` ou docker-compose.yml : - -```env -POSTGRES_USER=gotgaming -POSTGRES_PASSWORD=change-this-in-production -POSTGRES_DB=gotgaming -POSTGRES_DATA_PATH=./data/postgres -``` - -## Avantages de PostgreSQL - -- ✅ Pool de connexions natif (plus de timeout après inactivité) -- ✅ Concurrence réelle (pas de verrous de fichier) -- ✅ Meilleures performances en production -- ✅ Backups plus simples (`pg_dump`) -- ✅ Scaling horizontal possible - diff --git a/README.docker.md b/README.docker.md index c2b14a8..b12017e 100644 --- a/README.docker.md +++ b/README.docker.md @@ -29,21 +29,24 @@ Créez un fichier `.env` à la racine du projet avec les variables suivantes : ```env NEXTAUTH_SECRET=your-secret-key-here NEXTAUTH_URL=http://localhost:3000 -DATABASE_URL=file:./prisma/dev.db +POSTGRES_USER=gotgaming +POSTGRES_PASSWORD=change-this-in-production +POSTGRES_DB=gotgaming +DATABASE_URL=postgresql://gotgaming:change-this-in-production@got-postgres:5432/gotgaming?schema=public ``` ## Volumes persistants -### Base de données +### Base de données PostgreSQL -La base de données SQLite est persistée via un volume Docker. Par défaut, elle est stockée dans `/Volumes/EXTERNAL_USB/sites/got-gaming/data`, mais vous pouvez la personnaliser avec la variable d'environnement `PRISMA_DATA_PATH`. +La base de données PostgreSQL est persistée via un volume Docker. Par défaut, elle est stockée dans `./data/postgres`, mais vous pouvez la personnaliser avec la variable d'environnement `POSTGRES_DATA_PATH`. Les migrations Prisma sont appliquées automatiquement au démarrage du conteneur. Pour appliquer manuellement les migrations : ```bash -docker-compose exec got-app node node_modules/.bin/prisma migrate deploy +docker-compose exec got-app pnpm dlx prisma migrate deploy ``` ### Images uploadées diff --git a/README.md b/README.md index 49a60a8..1501d49 100644 --- a/README.md +++ b/README.md @@ -41,5 +41,5 @@ pnpm start - React 18 - TypeScript - Tailwind CSS -- Prisma (SQLite) +- Prisma (PostgreSQL) - NextAuth.js diff --git a/docker-compose.yml b/docker-compose.yml index e6d6364..5e389e2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,8 +39,6 @@ services: - NEXTAUTH_URL=${NEXTAUTH_URL:-http://localhost:3000} - NEXTAUTH_SECRET=${NEXTAUTH_SECRET:-change-this-secret-in-production} volumes: - # Persist SQLite database (pour migration) - - ${PRISMA_DATA_PATH:-./data}:/app/data # Persist uploaded images (avatars and backgrounds) - ${UPLOADS_PATH:-./public/uploads}:/app/public/uploads - ./prisma/migrations:/app/prisma/migrations diff --git a/package.json b/package.json index 089f10b..0220547 100644 --- a/package.json +++ b/package.json @@ -17,16 +17,13 @@ "pnpm": { "onlyBuiltDependencies": [ "prisma", - "@prisma/engines", - "better-sqlite3" + "@prisma/engines" ] }, "dependencies": { "@prisma/adapter-pg": "^7.1.0", - "@prisma/adapter-better-sqlite3": "^7.1.0", "@prisma/client": "^7.1.0", "bcryptjs": "^3.0.3", - "better-sqlite3": "^12.5.0", "next": "15.5.9", "next-auth": "5.0.0-beta.30", "pg": "^8.16.3", @@ -36,7 +33,6 @@ "devDependencies": { "@eslint/js": "^9.39.1", "@types/bcryptjs": "^3.0.0", - "@types/better-sqlite3": "^7.6.13", "@types/node": "^22.0.0", "@types/pg": "^8.16.0", "@types/react": "^19.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ff7a97..74765f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,6 @@ importers: .: dependencies: - '@prisma/adapter-better-sqlite3': - specifier: ^7.1.0 - version: 7.1.0 '@prisma/adapter-pg': specifier: ^7.1.0 version: 7.1.0 @@ -20,9 +17,6 @@ importers: bcryptjs: specifier: ^3.0.3 version: 3.0.3 - better-sqlite3: - specifier: ^12.5.0 - version: 12.5.0 next: specifier: 15.5.9 version: 15.5.9(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -45,9 +39,6 @@ importers: '@types/bcryptjs': specifier: ^3.0.0 version: 3.0.0 - '@types/better-sqlite3': - specifier: ^7.6.13 - version: 7.6.13 '@types/node': specifier: ^22.0.0 version: 22.19.1 @@ -677,9 +668,6 @@ packages: '@panva/hkdf@1.2.1': resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} - '@prisma/adapter-better-sqlite3@7.1.0': - resolution: {integrity: sha512-Ex4CimAONWMoUrhU27lpGXb4MdX/59qj+4PBTIuPVJLXZfTxSWuU8KowlRtq1w5iE91WiwMgU1KgeBOKJ81nEA==} - '@prisma/adapter-pg@7.1.0': resolution: {integrity: sha512-DSAnUwkKfX4bUzhkrjGN4IBQzwg0nvFw2W17H0Oa532I5w9nLtTJ9mAEGDs1nUBEGRAsa0c7qsf8CSgfJ4DsBQ==} @@ -754,9 +742,6 @@ packages: resolution: {integrity: sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg==} deprecated: This is a stub types definition. bcryptjs provides its own type definitions, so you do not need this installed. - '@types/better-sqlite3@7.6.13': - resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} - '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -3160,11 +3145,6 @@ snapshots: '@panva/hkdf@1.2.1': {} - '@prisma/adapter-better-sqlite3@7.1.0': - dependencies: - '@prisma/driver-adapter-utils': 7.1.0 - better-sqlite3: 12.5.0 - '@prisma/adapter-pg@7.1.0': dependencies: '@prisma/driver-adapter-utils': 7.1.0 @@ -3269,10 +3249,6 @@ snapshots: dependencies: bcryptjs: 3.0.3 - '@types/better-sqlite3@7.6.13': - dependencies: - '@types/node': 22.19.1 - '@types/estree@1.0.8': {} '@types/json-schema@7.0.15': {} @@ -3570,7 +3546,8 @@ snapshots: balanced-match@1.0.2: {} - base64-js@1.5.1: {} + base64-js@1.5.1: + optional: true baseline-browser-mapping@2.9.5: {} @@ -3580,18 +3557,21 @@ snapshots: dependencies: bindings: 1.5.0 prebuild-install: 7.1.3 + optional: true binary-extensions@2.3.0: {} bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 + optional: true bl@4.1.0: dependencies: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 + optional: true brace-expansion@1.1.12: dependencies: @@ -3618,6 +3598,7 @@ snapshots: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + optional: true c12@3.1.0: dependencies: @@ -3689,7 +3670,8 @@ snapshots: dependencies: readdirp: 4.1.2 - chownr@1.1.4: {} + chownr@1.1.4: + optional: true citty@0.1.6: dependencies: @@ -3754,8 +3736,10 @@ snapshots: decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 + optional: true - deep-extend@0.6.0: {} + deep-extend@0.6.0: + optional: true deep-is@0.1.4: {} @@ -3779,7 +3763,8 @@ snapshots: destr@2.0.5: {} - detect-libc@2.1.2: {} + detect-libc@2.1.2: + optional: true didyoumean@1.2.2: {} @@ -3813,6 +3798,7 @@ snapshots: end-of-stream@1.4.5: dependencies: once: 1.4.0 + optional: true es-abstract@1.24.0: dependencies: @@ -4151,7 +4137,8 @@ snapshots: esutils@2.0.3: {} - expand-template@2.0.3: {} + expand-template@2.0.3: + optional: true exsolve@1.0.8: {} @@ -4193,7 +4180,8 @@ snapshots: dependencies: flat-cache: 4.0.1 - file-uri-to-path@1.0.0: {} + file-uri-to-path@1.0.0: + optional: true fill-range@7.1.1: dependencies: @@ -4222,7 +4210,8 @@ snapshots: fraction.js@5.3.4: {} - fs-constants@1.0.0: {} + fs-constants@1.0.0: + optional: true fsevents@2.3.3: optional: true @@ -4287,7 +4276,8 @@ snapshots: nypm: 0.6.2 pathe: 2.0.3 - github-from-package@0.0.0: {} + github-from-package@0.0.0: + optional: true glob-parent@5.1.2: dependencies: @@ -4350,7 +4340,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 - ieee754@1.2.1: {} + ieee754@1.2.1: + optional: true ignore@5.3.2: {} @@ -4363,9 +4354,11 @@ snapshots: imurmurhash@0.1.4: {} - inherits@2.0.4: {} + inherits@2.0.4: + optional: true - ini@1.3.8: {} + ini@1.3.8: + optional: true internal-slot@1.1.0: dependencies: @@ -4587,7 +4580,8 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 - mimic-response@3.1.0: {} + mimic-response@3.1.0: + optional: true minimatch@3.1.2: dependencies: @@ -4599,7 +4593,8 @@ snapshots: minimist@1.2.8: {} - mkdirp-classic@0.5.3: {} + mkdirp-classic@0.5.3: + optional: true ms@2.1.3: {} @@ -4627,7 +4622,8 @@ snapshots: nanoid@3.3.11: {} - napi-build-utils@2.0.0: {} + napi-build-utils@2.0.0: + optional: true napi-postinstall@0.3.4: {} @@ -4665,6 +4661,7 @@ snapshots: node-abi@3.85.0: dependencies: semver: 7.7.3 + optional: true node-fetch-native@1.6.7: {} @@ -4733,6 +4730,7 @@ snapshots: once@1.4.0: dependencies: wrappy: 1.0.2 + optional: true optionator@0.9.4: dependencies: @@ -4902,6 +4900,7 @@ snapshots: simple-get: 4.0.1 tar-fs: 2.1.4 tunnel-agent: 0.6.0 + optional: true prelude-ls@1.2.1: {} @@ -4940,6 +4939,7 @@ snapshots: dependencies: end-of-stream: 1.4.5 once: 1.4.0 + optional: true punycode@2.3.1: {} @@ -4958,6 +4958,7 @@ snapshots: ini: 1.3.8 minimist: 1.2.8 strip-json-comments: 2.0.1 + optional: true react-dom@19.2.1(react@19.2.1): dependencies: @@ -4977,6 +4978,7 @@ snapshots: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 + optional: true readdirp@3.6.0: dependencies: @@ -5042,7 +5044,8 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 - safe-buffer@5.2.1: {} + safe-buffer@5.2.1: + optional: true safe-push-apply@1.0.0: dependencies: @@ -5157,13 +5160,15 @@ snapshots: signal-exit@4.1.0: {} - simple-concat@1.0.1: {} + simple-concat@1.0.1: + optional: true simple-get@4.0.1: dependencies: decompress-response: 6.0.0 once: 1.4.0 simple-concat: 1.0.1 + optional: true source-map-js@1.2.1: {} @@ -5233,10 +5238,12 @@ snapshots: string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 + optional: true strip-bom@3.0.0: {} - strip-json-comments@2.0.1: {} + strip-json-comments@2.0.1: + optional: true strip-json-comments@3.1.1: {} @@ -5297,6 +5304,7 @@ snapshots: mkdirp-classic: 0.5.3 pump: 3.0.3 tar-stream: 2.2.0 + optional: true tar-stream@2.2.0: dependencies: @@ -5305,6 +5313,7 @@ snapshots: fs-constants: 1.0.0 inherits: 2.0.4 readable-stream: 3.6.2 + optional: true thenify-all@1.6.0: dependencies: @@ -5350,6 +5359,7 @@ snapshots: tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 + optional: true type-check@0.4.0: dependencies: @@ -5499,7 +5509,8 @@ snapshots: word-wrap@1.2.5: {} - wrappy@1.0.2: {} + wrappy@1.0.2: + optional: true xtend@4.0.2: {} diff --git a/scripts/README-MIGRATION.md b/scripts/README-MIGRATION.md deleted file mode 100644 index b672b54..0000000 --- a/scripts/README-MIGRATION.md +++ /dev/null @@ -1,70 +0,0 @@ -# Migration SQLite → PostgreSQL - -Script pour migrer les données de SQLite vers PostgreSQL. - -## Prérequis - -1. Le container `got-app` doit être démarré -2. Le container `got-postgres` doit être démarré et accessible -3. Le fichier SQLite doit être accessible dans le container (par défaut: `/app/data/dev.db`) - -## Utilisation - -### Dans le container Docker - -```bash -# IMPORTANT: Le script doit être exécuté depuis la racine du projet (/app) - -# Option 1: Exécuter directement depuis l'extérieur du container -docker-compose exec got-app sh -c "cd /app && pnpm dlx tsx scripts/migrate-sqlite-to-postgres.ts" - -# Option 2: Se connecter au container puis exécuter -docker-compose exec got-app sh -# Dans le container: -cd /app -pnpm dlx tsx scripts/migrate-sqlite-to-postgres.ts -``` - -### Variables d'environnement - -Le script utilise les variables d'environnement suivantes : - -- `SQLITE_DB_PATH` : Chemin vers le fichier SQLite (défaut: `/app/data/dev.db`) -- `DATABASE_URL` : URL de connexion PostgreSQL (défaut: depuis les variables d'env du container) - -### Exemple avec variables personnalisées - -```bash -docker-compose exec -e SQLITE_DB_PATH=/app/data/old.db got-app sh -c "cd /app && pnpm dlx tsx scripts/migrate-sqlite-to-postgres.ts" -``` - -## Ce que fait le script - -1. **Se connecte** à SQLite (lecture seule) et PostgreSQL -2. **Migre les données** dans l'ordre des dépendances : - - Users - - UserPreferences - - Events - - EventRegistrations - - EventFeedbacks - - SitePreferences - - Challenges -3. **Utilise `upsert`** pour éviter les doublons (idempotent) -4. **Affiche la progression** en temps réel -5. **Affiche un résumé** à la fin avec les statistiques - -## Notes importantes - -- Le script est **idempotent** : tu peux le relancer plusieurs fois sans problème -- Les données existantes dans PostgreSQL seront **mises à jour** si elles existent déjà -- Les **erreurs** sont affichées mais n'arrêtent pas la migration -- Le script utilise `better-sqlite3` en **lecture seule** pour SQLite - -## En cas d'erreur - -Si le script échoue : - -1. Vérifie que les containers sont démarrés : `docker-compose ps` -2. Vérifie que le fichier SQLite existe : `docker-compose exec got-app ls -la /app/data/dev.db` -3. Vérifie les logs PostgreSQL : `docker-compose logs got-postgres` -4. Vérifie la connexion PostgreSQL : `docker-compose exec got-app pnpm prisma db pull` diff --git a/scripts/migrate-sqlite-to-postgres.ts b/scripts/migrate-sqlite-to-postgres.ts deleted file mode 100644 index 0e961ee..0000000 --- a/scripts/migrate-sqlite-to-postgres.ts +++ /dev/null @@ -1,489 +0,0 @@ -#!/usr/bin/env tsx -/* eslint-disable @typescript-eslint/no-explicit-any */ -/** - * Script de migration des données SQLite vers PostgreSQL - * - * Usage dans le container: - * docker-compose exec got-app sh -c "cd /app && pnpm dlx tsx scripts/migrate-sqlite-to-postgres.ts" - * - * Variables d'environnement: - * SQLITE_DB_PATH - Chemin vers le fichier SQLite (défaut: /app/data/dev.db) - * DATABASE_URL - URL de connexion PostgreSQL (défaut: depuis env) - */ - -import { PrismaPg } from "@prisma/adapter-pg"; -import { Pool } from "pg"; -import Database from "better-sqlite3"; -import { fileURLToPath } from "url"; -import { dirname, join } from "path"; -import { existsSync } from "fs"; - -// Résoudre le chemin vers le module Prisma -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -const projectRoot = join(__dirname, ".."); - -const SQLITE_DB_PATH = process.env.SQLITE_DB_PATH || "/app/data/dev.db"; -const POSTGRES_URL = process.env.DATABASE_URL; - -if (!POSTGRES_URL) { - console.error("❌ DATABASE_URL est requis"); - process.exit(1); -} - -// Variables globales pour les clients (initialisées dans main) -let sqliteDb: Database.Database; -let pgPool: Pool; -let prismaPG: any; // PrismaClient - typé dynamiquement - -interface MigrationStats { - users: number; - userPreferences: number; - events: number; - eventRegistrations: number; - eventFeedbacks: number; - sitePreferences: number; - challenges: number; - errors: number; -} - -const stats: MigrationStats = { - users: 0, - userPreferences: 0, - events: 0, - eventRegistrations: 0, - eventFeedbacks: 0, - sitePreferences: 0, - challenges: 0, - errors: 0, -}; - -function readSQLite(table: string): any[] { - try { - const rows = sqliteDb.prepare(`SELECT * FROM "${table}"`).all() as any[]; - return rows; - } catch (error) { - console.error(`Erreur lecture table ${table}:`, error); - return []; - } -} - -async function migrateUsers() { - console.log("\n📦 Migration des Users..."); - const users = readSQLite("User"); - - for (const user of users) { - try { - await prismaPG.user.upsert({ - where: { id: user.id }, - update: { - email: user.email, - password: user.password, - username: user.username, - role: user.role, - score: user.score, - level: user.level, - hp: user.hp, - maxHp: user.maxHp, - xp: user.xp, - maxXp: user.maxXp, - avatar: user.avatar, - bio: user.bio, - characterClass: user.characterClass, - createdAt: new Date(user.createdAt), - updatedAt: new Date(user.updatedAt), - }, - create: { - id: user.id, - email: user.email, - password: user.password, - username: user.username, - role: user.role, - score: user.score, - level: user.level, - hp: user.hp, - maxHp: user.maxHp, - xp: user.xp, - maxXp: user.maxXp, - avatar: user.avatar, - bio: user.bio, - characterClass: user.characterClass, - createdAt: new Date(user.createdAt), - updatedAt: new Date(user.updatedAt), - }, - }); - stats.users++; - process.stdout.write( - `\r ✅ ${stats.users}/${users.length} users migrés` - ); - } catch (error) { - stats.errors++; - console.error(`\n ❌ Erreur sur user ${user.id}:`, error); - } - } - console.log(`\n ✅ ${stats.users} users migrés avec succès`); -} - -async function migrateUserPreferences() { - console.log("\n📦 Migration des UserPreferences..."); - const preferences = readSQLite("UserPreferences"); - - for (const pref of preferences) { - try { - await prismaPG.userPreferences.upsert({ - where: { id: pref.id }, - update: { - userId: pref.userId, - homeBackground: pref.homeBackground, - eventsBackground: pref.eventsBackground, - leaderboardBackground: pref.leaderboardBackground, - theme: pref.theme, - createdAt: new Date(pref.createdAt), - updatedAt: new Date(pref.updatedAt), - }, - create: { - id: pref.id, - userId: pref.userId, - homeBackground: pref.homeBackground, - eventsBackground: pref.eventsBackground, - leaderboardBackground: pref.leaderboardBackground, - theme: pref.theme, - createdAt: new Date(pref.createdAt), - updatedAt: new Date(pref.updatedAt), - }, - }); - stats.userPreferences++; - process.stdout.write( - `\r ✅ ${stats.userPreferences}/${preferences.length} préférences migrées` - ); - } catch (error) { - stats.errors++; - console.error(`\n ❌ Erreur sur préférence ${pref.id}:`, error); - } - } - console.log( - `\n ✅ ${stats.userPreferences} préférences migrées avec succès` - ); -} - -async function migrateEvents() { - console.log("\n📦 Migration des Events..."); - const events = readSQLite("Event"); - - for (const event of events) { - try { - await prismaPG.event.upsert({ - where: { id: event.id }, - update: { - date: new Date(event.date), - name: event.name, - description: event.description, - type: event.type, - room: event.room, - time: event.time, - maxPlaces: event.maxPlaces, - createdAt: new Date(event.createdAt), - updatedAt: new Date(event.updatedAt), - }, - create: { - id: event.id, - date: new Date(event.date), - name: event.name, - description: event.description, - type: event.type, - room: event.room, - time: event.time, - maxPlaces: event.maxPlaces, - createdAt: new Date(event.createdAt), - updatedAt: new Date(event.updatedAt), - }, - }); - stats.events++; - process.stdout.write( - `\r ✅ ${stats.events}/${events.length} événements migrés` - ); - } catch (error) { - stats.errors++; - console.error(`\n ❌ Erreur sur event ${event.id}:`, error); - } - } - console.log(`\n ✅ ${stats.events} événements migrés avec succès`); -} - -async function migrateEventRegistrations() { - console.log("\n📦 Migration des EventRegistrations..."); - const registrations = readSQLite("EventRegistration"); - - for (const reg of registrations) { - try { - await prismaPG.eventRegistration.upsert({ - where: { - userId_eventId: { - userId: reg.userId, - eventId: reg.eventId, - }, - }, - update: { - createdAt: new Date(reg.createdAt), - }, - create: { - id: reg.id, - userId: reg.userId, - eventId: reg.eventId, - createdAt: new Date(reg.createdAt), - }, - }); - stats.eventRegistrations++; - process.stdout.write( - `\r ✅ ${stats.eventRegistrations}/${registrations.length} inscriptions migrées` - ); - } catch (error) { - stats.errors++; - console.error(`\n ❌ Erreur sur registration ${reg.id}:`, error); - } - } - console.log( - `\n ✅ ${stats.eventRegistrations} inscriptions migrées avec succès` - ); -} - -async function migrateEventFeedbacks() { - console.log("\n📦 Migration des EventFeedbacks..."); - const feedbacks = readSQLite("EventFeedback"); - - for (const feedback of feedbacks) { - try { - await prismaPG.eventFeedback.upsert({ - where: { - userId_eventId: { - userId: feedback.userId, - eventId: feedback.eventId, - }, - }, - update: { - rating: feedback.rating, - comment: feedback.comment, - isRead: feedback.isRead ? true : false, - createdAt: new Date(feedback.createdAt), - updatedAt: new Date(feedback.updatedAt), - }, - create: { - id: feedback.id, - userId: feedback.userId, - eventId: feedback.eventId, - rating: feedback.rating, - comment: feedback.comment, - isRead: feedback.isRead ? true : false, - createdAt: new Date(feedback.createdAt), - updatedAt: new Date(feedback.updatedAt), - }, - }); - stats.eventFeedbacks++; - process.stdout.write( - `\r ✅ ${stats.eventFeedbacks}/${feedbacks.length} feedbacks migrés` - ); - } catch (error) { - stats.errors++; - console.error(`\n ❌ Erreur sur feedback ${feedback.id}:`, error); - } - } - console.log(`\n ✅ ${stats.eventFeedbacks} feedbacks migrés avec succès`); -} - -async function migrateSitePreferences() { - console.log("\n📦 Migration des SitePreferences..."); - const sitePrefs = readSQLite("SitePreferences"); - - for (const pref of sitePrefs) { - try { - await prismaPG.sitePreferences.upsert({ - where: { id: pref.id }, - update: { - homeBackground: pref.homeBackground, - eventsBackground: pref.eventsBackground, - leaderboardBackground: pref.leaderboardBackground, - challengesBackground: pref.challengesBackground, - eventRegistrationPoints: pref.eventRegistrationPoints, - eventFeedbackPoints: pref.eventFeedbackPoints, - createdAt: new Date(pref.createdAt), - updatedAt: new Date(pref.updatedAt), - }, - create: { - id: pref.id, - homeBackground: pref.homeBackground, - eventsBackground: pref.eventsBackground, - leaderboardBackground: pref.leaderboardBackground, - challengesBackground: pref.challengesBackground, - eventRegistrationPoints: pref.eventRegistrationPoints, - eventFeedbackPoints: pref.eventFeedbackPoints, - createdAt: new Date(pref.createdAt), - updatedAt: new Date(pref.updatedAt), - }, - }); - stats.sitePreferences++; - } catch (error) { - stats.errors++; - console.error(`\n ❌ Erreur sur site preferences ${pref.id}:`, error); - } - } - console.log( - ` ✅ ${stats.sitePreferences} préférences site migrées avec succès` - ); -} - -async function migrateChallenges() { - console.log("\n📦 Migration des Challenges..."); - const challenges = readSQLite("Challenge"); - - for (const challenge of challenges) { - try { - await prismaPG.challenge.upsert({ - where: { id: challenge.id }, - update: { - challengerId: challenge.challengerId, - challengedId: challenge.challengedId, - title: challenge.title, - description: challenge.description, - pointsReward: challenge.pointsReward, - status: challenge.status, - adminId: challenge.adminId, - adminComment: challenge.adminComment, - winnerId: challenge.winnerId, - createdAt: new Date(challenge.createdAt), - acceptedAt: challenge.acceptedAt - ? new Date(challenge.acceptedAt) - : null, - completedAt: challenge.completedAt - ? new Date(challenge.completedAt) - : null, - updatedAt: new Date(challenge.updatedAt), - }, - create: { - id: challenge.id, - challengerId: challenge.challengerId, - challengedId: challenge.challengedId, - title: challenge.title, - description: challenge.description, - pointsReward: challenge.pointsReward, - status: challenge.status, - adminId: challenge.adminId, - adminComment: challenge.adminComment, - winnerId: challenge.winnerId, - createdAt: new Date(challenge.createdAt), - acceptedAt: challenge.acceptedAt - ? new Date(challenge.acceptedAt) - : null, - completedAt: challenge.completedAt - ? new Date(challenge.completedAt) - : null, - updatedAt: new Date(challenge.updatedAt), - }, - }); - stats.challenges++; - process.stdout.write( - `\r ✅ ${stats.challenges}/${challenges.length} défis migrés` - ); - } catch (error) { - stats.errors++; - console.error(`\n ❌ Erreur sur challenge ${challenge.id}:`, error); - } - } - console.log(`\n ✅ ${stats.challenges} défis migrés avec succès`); -} - -async function main() { - console.log("🚀 Démarrage de la migration SQLite → PostgreSQL"); - console.log(`📂 SQLite: ${SQLITE_DB_PATH}`); - console.log(`🐘 PostgreSQL: ${POSTGRES_URL?.replace(/:[^:@]+@/, ":****@")}`); - - try { - // Import dynamique du PrismaClient depuis la racine du projet - const prismaModule = await import( - join(projectRoot, "prisma/generated/prisma/client") - ); - const { PrismaClient } = prismaModule; - - // Vérifier que le fichier SQLite existe - if (!existsSync(SQLITE_DB_PATH)) { - console.error(`\n❌ Le fichier SQLite n'existe pas: ${SQLITE_DB_PATH}`); - console.error(`\n💡 Vérifications:`); - console.error(` 1. Le volume est-il monté dans docker-compose.yml ?`); - console.error(` 2. Le fichier existe-t-il sur l'hôte ?`); - console.error(` 3. Le chemin est-il correct ?`); - console.error(`\n Pour vérifier dans le container:`); - console.error(` docker-compose exec got-app ls -la /app/data/`); - throw new Error(`Fichier SQLite introuvable: ${SQLITE_DB_PATH}`); - } - - console.log(` ✅ Fichier SQLite trouvé: ${SQLITE_DB_PATH}`); - - // Initialiser les clients - sqliteDb = new Database(SQLITE_DB_PATH, { readonly: true }); - - pgPool = new Pool({ - connectionString: POSTGRES_URL, - }); - const pgAdapter = new PrismaPg(pgPool); - prismaPG = new PrismaClient({ - adapter: pgAdapter, - log: ["error"], - }); - - // Vérifier les connexions - console.log("\n🔍 Vérification des connexions..."); - if (!sqliteDb.open) { - throw new Error("Impossible d'ouvrir la base SQLite"); - } - console.log(" ✅ SQLite connecté"); - - await prismaPG.$connect(); - console.log(" ✅ PostgreSQL connecté"); - - // Migration dans l'ordre des dépendances - await migrateUsers(); - await migrateUserPreferences(); - await migrateEvents(); - await migrateEventRegistrations(); - await migrateEventFeedbacks(); - await migrateSitePreferences(); - await migrateChallenges(); - - // Résumé - console.log("\n" + "=".repeat(50)); - console.log("📊 Résumé de la migration:"); - console.log("=".repeat(50)); - console.log(` Users: ${stats.users}`); - console.log(` UserPreferences: ${stats.userPreferences}`); - console.log(` Events: ${stats.events}`); - console.log(` EventRegistrations: ${stats.eventRegistrations}`); - console.log(` EventFeedbacks: ${stats.eventFeedbacks}`); - console.log(` SitePreferences: ${stats.sitePreferences}`); - console.log(` Challenges: ${stats.challenges}`); - console.log(` Erreurs: ${stats.errors}`); - console.log("=".repeat(50)); - - if (stats.errors > 0) { - console.log( - "\n⚠️ Certaines erreurs sont survenues. Vérifiez les logs ci-dessus." - ); - process.exit(1); - } else { - console.log("\n✅ Migration terminée avec succès!"); - } - } catch (error) { - console.error("\n❌ Erreur fatale:", error); - process.exit(1); - } finally { - if (prismaPG) { - await prismaPG.$disconnect(); - } - if (sqliteDb) { - sqliteDb.close(); - } - if (pgPool) { - await pgPool.end(); - } - } -} - -main();