Files
got-gaming/scripts/migrate-sqlite-to-postgres.ts

490 lines
15 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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();