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
Some checks failed
Deploy with Docker Compose / deploy (push) Failing after 2m25s
This commit is contained in:
@@ -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`
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user