487 lines
13 KiB
TypeScript
487 lines
13 KiB
TypeScript
/**
|
|
* Datenbank-Restore Script
|
|
*
|
|
* Stellt Daten aus einem JSON-Backup wieder her.
|
|
*
|
|
* Verwendung:
|
|
* npx ts-node prisma/restore-data.ts [backup-ordner]
|
|
*
|
|
* Beispiele:
|
|
* npx ts-node prisma/restore-data.ts # Letztes Backup
|
|
* npx ts-node prisma/restore-data.ts 2025-01-31_14-30-00 # Bestimmtes Backup
|
|
*
|
|
* WICHTIG: Führe vorher 'npx prisma migrate deploy' oder 'npx prisma db push' aus!
|
|
*/
|
|
|
|
import { PrismaClient } from '@prisma/client';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
// Hilfsfunktion: JSON-Datei lesen
|
|
function readJsonFile<T>(filePath: string): T[] {
|
|
if (!fs.existsSync(filePath)) {
|
|
return [];
|
|
}
|
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
return JSON.parse(content);
|
|
}
|
|
|
|
// Hilfsfunktion: Datum-Strings zu Date-Objekten konvertieren
|
|
function convertDates(obj: any): any {
|
|
if (obj === null || obj === undefined) return obj;
|
|
if (typeof obj === 'string') {
|
|
// ISO-Datumsformat erkennen
|
|
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(obj)) {
|
|
return new Date(obj);
|
|
}
|
|
return obj;
|
|
}
|
|
if (Array.isArray(obj)) {
|
|
return obj.map(convertDates);
|
|
}
|
|
if (typeof obj === 'object') {
|
|
const result: any = {};
|
|
for (const [key, value] of Object.entries(obj)) {
|
|
result[key] = convertDates(value);
|
|
}
|
|
return result;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
async function main() {
|
|
// Backup-Ordner bestimmen
|
|
const backupsDir = path.join(__dirname, 'backups');
|
|
let backupName = process.argv[2];
|
|
|
|
if (!backupName) {
|
|
// Neuestes Backup finden
|
|
if (!fs.existsSync(backupsDir)) {
|
|
console.error('❌ Kein Backup-Ordner gefunden!');
|
|
process.exit(1);
|
|
}
|
|
const backups = fs.readdirSync(backupsDir)
|
|
.filter(f => fs.statSync(path.join(backupsDir, f)).isDirectory())
|
|
.sort()
|
|
.reverse();
|
|
|
|
if (backups.length === 0) {
|
|
console.error('❌ Keine Backups gefunden!');
|
|
process.exit(1);
|
|
}
|
|
backupName = backups[0];
|
|
console.log(`📦 Verwende neuestes Backup: ${backupName}`);
|
|
}
|
|
|
|
const backupDir = path.join(backupsDir, backupName);
|
|
|
|
if (!fs.existsSync(backupDir)) {
|
|
console.error(`❌ Backup-Ordner nicht gefunden: ${backupDir}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
// Backup-Info lesen
|
|
const infoPath = path.join(backupDir, '_backup-info.json');
|
|
if (fs.existsSync(infoPath)) {
|
|
const info = JSON.parse(fs.readFileSync(infoPath, 'utf-8'));
|
|
console.log(`\n📅 Backup vom: ${new Date(info.timestamp).toLocaleString('de-DE')}`);
|
|
console.log(`📊 ${info.totalRecords} Datensätze in ${info.tables.filter((t: any) => t.count > 0).length} Tabellen\n`);
|
|
}
|
|
|
|
console.log(`🔄 Starte Wiederherstellung aus: ${backupDir}\n`);
|
|
|
|
// Foreign Key Checks deaktivieren für MySQL
|
|
await prisma.$executeRawUnsafe('SET FOREIGN_KEY_CHECKS = 0');
|
|
|
|
try {
|
|
// Tabellen in Abhängigkeitsreihenfolge wiederherstellen
|
|
const restoreOrder = [
|
|
// Level 0: Keine Abhängigkeiten
|
|
{
|
|
name: 'Permission',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.permission.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'Role',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.role.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'SalesPlatform',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.salesPlatform.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'ContractCategory',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.contractCategory.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'CancellationPeriod',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.cancellationPeriod.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'ContractDuration',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.contractDuration.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'AppSetting',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.appSetting.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'EmailProviderConfig',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.emailProviderConfig.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'EnergyProvider',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.energyProvider.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'TelecomProvider',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.telecomProvider.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
|
|
// Level 1
|
|
{
|
|
name: 'RolePermission',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.rolePermission.upsert({
|
|
where: { roleId_permissionId: { roleId: item.roleId, permissionId: item.permissionId } },
|
|
update: {},
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'User',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.user.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'Customer',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.customer.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'Tariff',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.tariff.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
|
|
// Level 2
|
|
{
|
|
name: 'UserRole',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.userRole.upsert({
|
|
where: { userId_roleId: { userId: item.userId, roleId: item.roleId } },
|
|
update: {},
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'CustomerRepresentative',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.customerRepresentative.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'StressfreiEmail',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.stressfreiEmail.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'Contract',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.contract.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'Meter',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.meter.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
|
|
// Level 3
|
|
{
|
|
name: 'CachedEmail',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.cachedEmail.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'ContractTask',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.contractTask.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'MeterReading',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.meterReading.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'ContractNote',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.contractNote.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'ContractDocument',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.contractDocument.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
|
|
// Level 4
|
|
{
|
|
name: 'ContractTaskSubtask',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.contractTaskSubtask.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
|
|
// Vertragsdetails
|
|
{
|
|
name: 'EnergyContractDetails',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.energyContractDetails.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'TelecomContractDetails',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.telecomContractDetails.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'CarInsuranceDetails',
|
|
restore: async (data: any[]) => {
|
|
for (const item of data) {
|
|
await prisma.carInsuranceDetails.upsert({
|
|
where: { id: item.id },
|
|
update: convertDates(item),
|
|
create: convertDates(item),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
];
|
|
|
|
let totalRestored = 0;
|
|
|
|
for (const table of restoreOrder) {
|
|
const filePath = path.join(backupDir, `${table.name}.json`);
|
|
const data = readJsonFile(filePath);
|
|
|
|
if (data.length === 0) {
|
|
console.log(`⚪ ${table.name}: Keine Daten`);
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
await table.restore(data);
|
|
totalRestored += data.length;
|
|
console.log(`✅ ${table.name}: ${data.length} Einträge wiederhergestellt`);
|
|
} catch (error: any) {
|
|
console.log(`⚠️ ${table.name}: Fehler - ${error.message?.slice(0, 80)}`);
|
|
}
|
|
}
|
|
|
|
console.log(`\n✅ Wiederherstellung abgeschlossen!`);
|
|
console.log(` 📊 ${totalRestored} Datensätze wiederhergestellt\n`);
|
|
|
|
} finally {
|
|
// Foreign Key Checks wieder aktivieren
|
|
await prisma.$executeRawUnsafe('SET FOREIGN_KEY_CHECKS = 1');
|
|
}
|
|
}
|
|
|
|
main()
|
|
.catch((e) => {
|
|
console.error('❌ Wiederherstellung fehlgeschlagen:', e);
|
|
process.exit(1);
|
|
})
|
|
.finally(async () => {
|
|
await prisma.$disconnect();
|
|
});
|