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.
Some checks failed
Deploy with Docker Compose / deploy (push) Failing after 2m25s

This commit is contained in:
Julien Froidefond
2025-12-17 12:29:13 +01:00
parent 20c3043572
commit 5875813f2f
10 changed files with 64 additions and 696 deletions

View File

@@ -21,5 +21,6 @@ jobs:
PRISMA_DATA_PATH: ${{ vars.PRISMA_DATA_PATH }} PRISMA_DATA_PATH: ${{ vars.PRISMA_DATA_PATH }}
UPLOADS_PATH: ${{ vars.UPLOADS_PATH }} UPLOADS_PATH: ${{ vars.UPLOADS_PATH }}
POSTGRES_DATA_PATH: ${{ vars.POSTGRES_DATA_PATH }} POSTGRES_DATA_PATH: ${{ vars.POSTGRES_DATA_PATH }}
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
run: | run: |
docker compose up -d --build docker compose up -d --build

View File

@@ -32,7 +32,7 @@ WORKDIR /app
ENV NODE_ENV=production ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1 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 && \ RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs adduser --system --uid 1001 nextjs

View File

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

View File

@@ -29,21 +29,24 @@ Créez un fichier `.env` à la racine du projet avec les variables suivantes :
```env ```env
NEXTAUTH_SECRET=your-secret-key-here NEXTAUTH_SECRET=your-secret-key-here
NEXTAUTH_URL=http://localhost:3000 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 ## 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. Les migrations Prisma sont appliquées automatiquement au démarrage du conteneur.
Pour appliquer manuellement les migrations : Pour appliquer manuellement les migrations :
```bash ```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 ### Images uploadées

View File

@@ -41,5 +41,5 @@ pnpm start
- React 18 - React 18
- TypeScript - TypeScript
- Tailwind CSS - Tailwind CSS
- Prisma (SQLite) - Prisma (PostgreSQL)
- NextAuth.js - NextAuth.js

View File

@@ -39,8 +39,6 @@ services:
- NEXTAUTH_URL=${NEXTAUTH_URL:-http://localhost:3000} - NEXTAUTH_URL=${NEXTAUTH_URL:-http://localhost:3000}
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET:-change-this-secret-in-production} - NEXTAUTH_SECRET=${NEXTAUTH_SECRET:-change-this-secret-in-production}
volumes: volumes:
# Persist SQLite database (pour migration)
- ${PRISMA_DATA_PATH:-./data}:/app/data
# Persist uploaded images (avatars and backgrounds) # Persist uploaded images (avatars and backgrounds)
- ${UPLOADS_PATH:-./public/uploads}:/app/public/uploads - ${UPLOADS_PATH:-./public/uploads}:/app/public/uploads
- ./prisma/migrations:/app/prisma/migrations - ./prisma/migrations:/app/prisma/migrations

View File

@@ -17,16 +17,13 @@
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
"prisma", "prisma",
"@prisma/engines", "@prisma/engines"
"better-sqlite3"
] ]
}, },
"dependencies": { "dependencies": {
"@prisma/adapter-pg": "^7.1.0", "@prisma/adapter-pg": "^7.1.0",
"@prisma/adapter-better-sqlite3": "^7.1.0",
"@prisma/client": "^7.1.0", "@prisma/client": "^7.1.0",
"bcryptjs": "^3.0.3", "bcryptjs": "^3.0.3",
"better-sqlite3": "^12.5.0",
"next": "15.5.9", "next": "15.5.9",
"next-auth": "5.0.0-beta.30", "next-auth": "5.0.0-beta.30",
"pg": "^8.16.3", "pg": "^8.16.3",
@@ -36,7 +33,6 @@
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.39.1", "@eslint/js": "^9.39.1",
"@types/bcryptjs": "^3.0.0", "@types/bcryptjs": "^3.0.0",
"@types/better-sqlite3": "^7.6.13",
"@types/node": "^22.0.0", "@types/node": "^22.0.0",
"@types/pg": "^8.16.0", "@types/pg": "^8.16.0",
"@types/react": "^19.0.0", "@types/react": "^19.0.0",

95
pnpm-lock.yaml generated
View File

@@ -8,9 +8,6 @@ importers:
.: .:
dependencies: dependencies:
'@prisma/adapter-better-sqlite3':
specifier: ^7.1.0
version: 7.1.0
'@prisma/adapter-pg': '@prisma/adapter-pg':
specifier: ^7.1.0 specifier: ^7.1.0
version: 7.1.0 version: 7.1.0
@@ -20,9 +17,6 @@ importers:
bcryptjs: bcryptjs:
specifier: ^3.0.3 specifier: ^3.0.3
version: 3.0.3 version: 3.0.3
better-sqlite3:
specifier: ^12.5.0
version: 12.5.0
next: next:
specifier: 15.5.9 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) 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': '@types/bcryptjs':
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.0.0 version: 3.0.0
'@types/better-sqlite3':
specifier: ^7.6.13
version: 7.6.13
'@types/node': '@types/node':
specifier: ^22.0.0 specifier: ^22.0.0
version: 22.19.1 version: 22.19.1
@@ -677,9 +668,6 @@ packages:
'@panva/hkdf@1.2.1': '@panva/hkdf@1.2.1':
resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} 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': '@prisma/adapter-pg@7.1.0':
resolution: {integrity: sha512-DSAnUwkKfX4bUzhkrjGN4IBQzwg0nvFw2W17H0Oa532I5w9nLtTJ9mAEGDs1nUBEGRAsa0c7qsf8CSgfJ4DsBQ==} resolution: {integrity: sha512-DSAnUwkKfX4bUzhkrjGN4IBQzwg0nvFw2W17H0Oa532I5w9nLtTJ9mAEGDs1nUBEGRAsa0c7qsf8CSgfJ4DsBQ==}
@@ -754,9 +742,6 @@ packages:
resolution: {integrity: sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg==} 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. 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': '@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
@@ -3160,11 +3145,6 @@ snapshots:
'@panva/hkdf@1.2.1': {} '@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': '@prisma/adapter-pg@7.1.0':
dependencies: dependencies:
'@prisma/driver-adapter-utils': 7.1.0 '@prisma/driver-adapter-utils': 7.1.0
@@ -3269,10 +3249,6 @@ snapshots:
dependencies: dependencies:
bcryptjs: 3.0.3 bcryptjs: 3.0.3
'@types/better-sqlite3@7.6.13':
dependencies:
'@types/node': 22.19.1
'@types/estree@1.0.8': {} '@types/estree@1.0.8': {}
'@types/json-schema@7.0.15': {} '@types/json-schema@7.0.15': {}
@@ -3570,7 +3546,8 @@ snapshots:
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
base64-js@1.5.1: {} base64-js@1.5.1:
optional: true
baseline-browser-mapping@2.9.5: {} baseline-browser-mapping@2.9.5: {}
@@ -3580,18 +3557,21 @@ snapshots:
dependencies: dependencies:
bindings: 1.5.0 bindings: 1.5.0
prebuild-install: 7.1.3 prebuild-install: 7.1.3
optional: true
binary-extensions@2.3.0: {} binary-extensions@2.3.0: {}
bindings@1.5.0: bindings@1.5.0:
dependencies: dependencies:
file-uri-to-path: 1.0.0 file-uri-to-path: 1.0.0
optional: true
bl@4.1.0: bl@4.1.0:
dependencies: dependencies:
buffer: 5.7.1 buffer: 5.7.1
inherits: 2.0.4 inherits: 2.0.4
readable-stream: 3.6.2 readable-stream: 3.6.2
optional: true
brace-expansion@1.1.12: brace-expansion@1.1.12:
dependencies: dependencies:
@@ -3618,6 +3598,7 @@ snapshots:
dependencies: dependencies:
base64-js: 1.5.1 base64-js: 1.5.1
ieee754: 1.2.1 ieee754: 1.2.1
optional: true
c12@3.1.0: c12@3.1.0:
dependencies: dependencies:
@@ -3689,7 +3670,8 @@ snapshots:
dependencies: dependencies:
readdirp: 4.1.2 readdirp: 4.1.2
chownr@1.1.4: {} chownr@1.1.4:
optional: true
citty@0.1.6: citty@0.1.6:
dependencies: dependencies:
@@ -3754,8 +3736,10 @@ snapshots:
decompress-response@6.0.0: decompress-response@6.0.0:
dependencies: dependencies:
mimic-response: 3.1.0 mimic-response: 3.1.0
optional: true
deep-extend@0.6.0: {} deep-extend@0.6.0:
optional: true
deep-is@0.1.4: {} deep-is@0.1.4: {}
@@ -3779,7 +3763,8 @@ snapshots:
destr@2.0.5: {} destr@2.0.5: {}
detect-libc@2.1.2: {} detect-libc@2.1.2:
optional: true
didyoumean@1.2.2: {} didyoumean@1.2.2: {}
@@ -3813,6 +3798,7 @@ snapshots:
end-of-stream@1.4.5: end-of-stream@1.4.5:
dependencies: dependencies:
once: 1.4.0 once: 1.4.0
optional: true
es-abstract@1.24.0: es-abstract@1.24.0:
dependencies: dependencies:
@@ -4151,7 +4137,8 @@ snapshots:
esutils@2.0.3: {} esutils@2.0.3: {}
expand-template@2.0.3: {} expand-template@2.0.3:
optional: true
exsolve@1.0.8: {} exsolve@1.0.8: {}
@@ -4193,7 +4180,8 @@ snapshots:
dependencies: dependencies:
flat-cache: 4.0.1 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: fill-range@7.1.1:
dependencies: dependencies:
@@ -4222,7 +4210,8 @@ snapshots:
fraction.js@5.3.4: {} fraction.js@5.3.4: {}
fs-constants@1.0.0: {} fs-constants@1.0.0:
optional: true
fsevents@2.3.3: fsevents@2.3.3:
optional: true optional: true
@@ -4287,7 +4276,8 @@ snapshots:
nypm: 0.6.2 nypm: 0.6.2
pathe: 2.0.3 pathe: 2.0.3
github-from-package@0.0.0: {} github-from-package@0.0.0:
optional: true
glob-parent@5.1.2: glob-parent@5.1.2:
dependencies: dependencies:
@@ -4350,7 +4340,8 @@ snapshots:
dependencies: dependencies:
safer-buffer: 2.1.2 safer-buffer: 2.1.2
ieee754@1.2.1: {} ieee754@1.2.1:
optional: true
ignore@5.3.2: {} ignore@5.3.2: {}
@@ -4363,9 +4354,11 @@ snapshots:
imurmurhash@0.1.4: {} 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: internal-slot@1.1.0:
dependencies: dependencies:
@@ -4587,7 +4580,8 @@ snapshots:
braces: 3.0.3 braces: 3.0.3
picomatch: 2.3.1 picomatch: 2.3.1
mimic-response@3.1.0: {} mimic-response@3.1.0:
optional: true
minimatch@3.1.2: minimatch@3.1.2:
dependencies: dependencies:
@@ -4599,7 +4593,8 @@ snapshots:
minimist@1.2.8: {} minimist@1.2.8: {}
mkdirp-classic@0.5.3: {} mkdirp-classic@0.5.3:
optional: true
ms@2.1.3: {} ms@2.1.3: {}
@@ -4627,7 +4622,8 @@ snapshots:
nanoid@3.3.11: {} nanoid@3.3.11: {}
napi-build-utils@2.0.0: {} napi-build-utils@2.0.0:
optional: true
napi-postinstall@0.3.4: {} napi-postinstall@0.3.4: {}
@@ -4665,6 +4661,7 @@ snapshots:
node-abi@3.85.0: node-abi@3.85.0:
dependencies: dependencies:
semver: 7.7.3 semver: 7.7.3
optional: true
node-fetch-native@1.6.7: {} node-fetch-native@1.6.7: {}
@@ -4733,6 +4730,7 @@ snapshots:
once@1.4.0: once@1.4.0:
dependencies: dependencies:
wrappy: 1.0.2 wrappy: 1.0.2
optional: true
optionator@0.9.4: optionator@0.9.4:
dependencies: dependencies:
@@ -4902,6 +4900,7 @@ snapshots:
simple-get: 4.0.1 simple-get: 4.0.1
tar-fs: 2.1.4 tar-fs: 2.1.4
tunnel-agent: 0.6.0 tunnel-agent: 0.6.0
optional: true
prelude-ls@1.2.1: {} prelude-ls@1.2.1: {}
@@ -4940,6 +4939,7 @@ snapshots:
dependencies: dependencies:
end-of-stream: 1.4.5 end-of-stream: 1.4.5
once: 1.4.0 once: 1.4.0
optional: true
punycode@2.3.1: {} punycode@2.3.1: {}
@@ -4958,6 +4958,7 @@ snapshots:
ini: 1.3.8 ini: 1.3.8
minimist: 1.2.8 minimist: 1.2.8
strip-json-comments: 2.0.1 strip-json-comments: 2.0.1
optional: true
react-dom@19.2.1(react@19.2.1): react-dom@19.2.1(react@19.2.1):
dependencies: dependencies:
@@ -4977,6 +4978,7 @@ snapshots:
inherits: 2.0.4 inherits: 2.0.4
string_decoder: 1.3.0 string_decoder: 1.3.0
util-deprecate: 1.0.2 util-deprecate: 1.0.2
optional: true
readdirp@3.6.0: readdirp@3.6.0:
dependencies: dependencies:
@@ -5042,7 +5044,8 @@ snapshots:
has-symbols: 1.1.0 has-symbols: 1.1.0
isarray: 2.0.5 isarray: 2.0.5
safe-buffer@5.2.1: {} safe-buffer@5.2.1:
optional: true
safe-push-apply@1.0.0: safe-push-apply@1.0.0:
dependencies: dependencies:
@@ -5157,13 +5160,15 @@ snapshots:
signal-exit@4.1.0: {} signal-exit@4.1.0: {}
simple-concat@1.0.1: {} simple-concat@1.0.1:
optional: true
simple-get@4.0.1: simple-get@4.0.1:
dependencies: dependencies:
decompress-response: 6.0.0 decompress-response: 6.0.0
once: 1.4.0 once: 1.4.0
simple-concat: 1.0.1 simple-concat: 1.0.1
optional: true
source-map-js@1.2.1: {} source-map-js@1.2.1: {}
@@ -5233,10 +5238,12 @@ snapshots:
string_decoder@1.3.0: string_decoder@1.3.0:
dependencies: dependencies:
safe-buffer: 5.2.1 safe-buffer: 5.2.1
optional: true
strip-bom@3.0.0: {} 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: {} strip-json-comments@3.1.1: {}
@@ -5297,6 +5304,7 @@ snapshots:
mkdirp-classic: 0.5.3 mkdirp-classic: 0.5.3
pump: 3.0.3 pump: 3.0.3
tar-stream: 2.2.0 tar-stream: 2.2.0
optional: true
tar-stream@2.2.0: tar-stream@2.2.0:
dependencies: dependencies:
@@ -5305,6 +5313,7 @@ snapshots:
fs-constants: 1.0.0 fs-constants: 1.0.0
inherits: 2.0.4 inherits: 2.0.4
readable-stream: 3.6.2 readable-stream: 3.6.2
optional: true
thenify-all@1.6.0: thenify-all@1.6.0:
dependencies: dependencies:
@@ -5350,6 +5359,7 @@ snapshots:
tunnel-agent@0.6.0: tunnel-agent@0.6.0:
dependencies: dependencies:
safe-buffer: 5.2.1 safe-buffer: 5.2.1
optional: true
type-check@0.4.0: type-check@0.4.0:
dependencies: dependencies:
@@ -5499,7 +5509,8 @@ snapshots:
word-wrap@1.2.5: {} word-wrap@1.2.5: {}
wrappy@1.0.2: {} wrappy@1.0.2:
optional: true
xtend@4.0.2: {} xtend@4.0.2: {}

View File

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

View File

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