504 lines
16 KiB
TypeScript
504 lines
16 KiB
TypeScript
import { PrismaClient } from '@prisma/client';
|
|
import bcrypt from 'bcryptjs';
|
|
import crypto from 'crypto';
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
async function main() {
|
|
console.log('Seeding database...');
|
|
|
|
// ==================== PERMISSIONS ====================
|
|
// Ressourcen mit ihren erlaubten Aktionen
|
|
const resourcePermissions: Record<string, string[]> = {
|
|
// Haupt-Ressourcen (CRUD)
|
|
customers: ['create', 'read', 'update', 'delete'],
|
|
contracts: ['create', 'read', 'update', 'delete'],
|
|
users: ['create', 'read', 'update', 'delete'],
|
|
platforms: ['create', 'read', 'update', 'delete'],
|
|
providers: ['create', 'read', 'update', 'delete'],
|
|
tariffs: ['create', 'read', 'update', 'delete'],
|
|
// Konfiguration (CRUD)
|
|
'cancellation-periods': ['create', 'read', 'update', 'delete'],
|
|
'contract-durations': ['create', 'read', 'update', 'delete'],
|
|
'contract-categories': ['create', 'read', 'update', 'delete'],
|
|
'email-providers': ['create', 'read', 'update', 'delete'],
|
|
// Einstellungen (nur lesen/ändern)
|
|
settings: ['read', 'update'],
|
|
// Spezial-Permissions
|
|
developer: ['access'],
|
|
emails: ['delete'],
|
|
// DSGVO & Audit
|
|
audit: ['read', 'export', 'admin'],
|
|
gdpr: ['export', 'delete', 'admin'],
|
|
};
|
|
|
|
const permissions: { resource: string; action: string }[] = [];
|
|
for (const [resource, actions] of Object.entries(resourcePermissions)) {
|
|
for (const action of actions) {
|
|
permissions.push({ resource, action });
|
|
}
|
|
}
|
|
|
|
for (const perm of permissions) {
|
|
await prisma.permission.upsert({
|
|
where: { resource_action: perm },
|
|
update: {},
|
|
create: perm,
|
|
});
|
|
}
|
|
|
|
console.log(`Permissions created (${permissions.length} total)`);
|
|
|
|
// Get all permissions
|
|
const allPermissions = await prisma.permission.findMany();
|
|
const customerReadPerm = allPermissions.find(
|
|
(p) => p.resource === 'customers' && p.action === 'read'
|
|
);
|
|
const contractReadPerm = allPermissions.find(
|
|
(p) => p.resource === 'contracts' && p.action === 'read'
|
|
);
|
|
const platformReadPerm = allPermissions.find(
|
|
(p) => p.resource === 'platforms' && p.action === 'read'
|
|
);
|
|
const providerReadPerm = allPermissions.find(
|
|
(p) => p.resource === 'providers' && p.action === 'read'
|
|
);
|
|
|
|
// Helper: Sync permissions for a role (adds missing, removes excess)
|
|
async function syncRolePermissions(roleId: number, permissionIds: number[]) {
|
|
const existing = await prisma.rolePermission.findMany({
|
|
where: { roleId },
|
|
select: { permissionId: true },
|
|
});
|
|
const existingIds = new Set(existing.map((e) => e.permissionId));
|
|
const targetIds = new Set(permissionIds);
|
|
|
|
// Add missing permissions
|
|
const missing = permissionIds.filter((id) => !existingIds.has(id));
|
|
if (missing.length > 0) {
|
|
await prisma.rolePermission.createMany({
|
|
data: missing.map((permissionId) => ({ roleId, permissionId })),
|
|
skipDuplicates: true,
|
|
});
|
|
console.log(` → ${missing.length} Permissions hinzugefügt für Rolle #${roleId}`);
|
|
}
|
|
|
|
// Remove excess permissions
|
|
const excess = existing.filter((e) => !targetIds.has(e.permissionId)).map((e) => e.permissionId);
|
|
if (excess.length > 0) {
|
|
await prisma.rolePermission.deleteMany({
|
|
where: { roleId, permissionId: { in: excess } },
|
|
});
|
|
console.log(` → ${excess.length} Permissions entfernt für Rolle #${roleId}`);
|
|
}
|
|
}
|
|
|
|
// Create roles
|
|
// Admin - all permissions EXCEPT developer:access and audit/gdpr (controlled separately via checkboxes)
|
|
const adminPermissions = allPermissions.filter(
|
|
(p) =>
|
|
!(p.resource === 'developer' && p.action === 'access') &&
|
|
p.resource !== 'audit' &&
|
|
p.resource !== 'gdpr'
|
|
);
|
|
const adminRole = await prisma.role.upsert({
|
|
where: { name: 'Admin' },
|
|
update: {},
|
|
create: {
|
|
name: 'Admin',
|
|
description: 'Voller Zugriff auf alle Funktionen',
|
|
permissions: {
|
|
create: adminPermissions.map((p) => ({ permissionId: p.id })),
|
|
},
|
|
},
|
|
});
|
|
await syncRolePermissions(adminRole.id, adminPermissions.map((p) => p.id));
|
|
|
|
// Developer - ALL permissions (developer:access + alles andere)
|
|
const developerPermissions = allPermissions;
|
|
const developerRole = await prisma.role.upsert({
|
|
where: { name: 'Developer' },
|
|
update: {},
|
|
create: {
|
|
name: 'Developer',
|
|
description: 'Voller Zugriff inkl. Entwickler-Tools',
|
|
permissions: {
|
|
create: developerPermissions.map((p) => ({ permissionId: p.id })),
|
|
},
|
|
},
|
|
});
|
|
await syncRolePermissions(developerRole.id, developerPermissions.map((p) => p.id));
|
|
|
|
// DSGVO - audit and gdpr permissions (hidden role, controlled via hasGdprAccess)
|
|
const gdprPermissions = allPermissions.filter(
|
|
(p) => p.resource === 'audit' || p.resource === 'gdpr'
|
|
);
|
|
const gdprRole = await prisma.role.upsert({
|
|
where: { name: 'DSGVO' },
|
|
update: {},
|
|
create: {
|
|
name: 'DSGVO',
|
|
description: 'DSGVO-Zugriff: Audit-Logs und Datenschutz-Verwaltung',
|
|
permissions: {
|
|
create: gdprPermissions.map((p) => ({ permissionId: p.id })),
|
|
},
|
|
},
|
|
});
|
|
await syncRolePermissions(gdprRole.id, gdprPermissions.map((p) => p.id));
|
|
|
|
// Employee - full access to customers, contracts, read access to lookup tables
|
|
const employeePermIds = allPermissions
|
|
.filter(
|
|
(p) =>
|
|
p.resource === 'customers' ||
|
|
p.resource === 'contracts' ||
|
|
// Read-only Zugriff auf Stammdaten und Konfiguration
|
|
(p.action === 'read' && [
|
|
'platforms',
|
|
'providers',
|
|
'tariffs',
|
|
'cancellation-periods',
|
|
'contract-durations',
|
|
'contract-categories',
|
|
].includes(p.resource))
|
|
)
|
|
.map((p) => p.id);
|
|
|
|
const employeeRole = await prisma.role.upsert({
|
|
where: { name: 'Mitarbeiter' },
|
|
update: {},
|
|
create: {
|
|
name: 'Mitarbeiter',
|
|
description: 'Kann Kunden und Verträge verwalten',
|
|
permissions: {
|
|
create: employeePermIds.map((id) => ({ permissionId: id })),
|
|
},
|
|
},
|
|
});
|
|
await syncRolePermissions(employeeRole.id, employeePermIds);
|
|
|
|
// Read-only employee - read access to main entities and lookup tables
|
|
const readOnlyResources = [
|
|
'customers',
|
|
'contracts',
|
|
'platforms',
|
|
'providers',
|
|
'tariffs',
|
|
'cancellation-periods',
|
|
'contract-durations',
|
|
'contract-categories',
|
|
];
|
|
const readOnlyPermIds = allPermissions
|
|
.filter((p) => p.action === 'read' && readOnlyResources.includes(p.resource))
|
|
.map((p) => p.id);
|
|
|
|
const readOnlyRole = await prisma.role.upsert({
|
|
where: { name: 'Mitarbeiter (Nur-Lesen)' },
|
|
update: {},
|
|
create: {
|
|
name: 'Mitarbeiter (Nur-Lesen)',
|
|
description: 'Kann nur lesen, keine Änderungen',
|
|
permissions: {
|
|
create: readOnlyPermIds.map((id) => ({ permissionId: id })),
|
|
},
|
|
},
|
|
});
|
|
await syncRolePermissions(readOnlyRole.id, readOnlyPermIds);
|
|
|
|
// Customer role - read own data only (handled in middleware)
|
|
const customerRole = await prisma.role.upsert({
|
|
where: { name: 'Kunde' },
|
|
update: {},
|
|
create: {
|
|
name: 'Kunde',
|
|
description: 'Kann nur eigene Daten lesen',
|
|
permissions: {
|
|
create: readOnlyPermIds.map((id) => ({ permissionId: id })),
|
|
},
|
|
},
|
|
});
|
|
await syncRolePermissions(customerRole.id, readOnlyPermIds);
|
|
|
|
console.log('Roles created');
|
|
|
|
// Create admin user
|
|
const hashedPassword = await bcrypt.hash('admin', 10);
|
|
|
|
const adminUser = await prisma.user.upsert({
|
|
where: { email: 'admin@admin.com' },
|
|
update: {},
|
|
create: {
|
|
email: 'admin@admin.com',
|
|
password: hashedPassword,
|
|
firstName: 'Admin',
|
|
lastName: 'User',
|
|
roles: {
|
|
create: [{ roleId: adminRole.id }],
|
|
},
|
|
},
|
|
});
|
|
|
|
console.log('Admin user created: admin@admin.com / admin');
|
|
|
|
// Create some sales platforms
|
|
const platforms = ['Moon Fachhandel', 'Verivox', 'Check24', 'Eigenvermittlung'];
|
|
for (const name of platforms) {
|
|
await prisma.salesPlatform.upsert({
|
|
where: { name },
|
|
update: {},
|
|
create: { name, isActive: true },
|
|
});
|
|
}
|
|
|
|
console.log('Sales platforms created');
|
|
|
|
// ==================== STANDARD PROVIDERS ====================
|
|
const providers = [
|
|
{
|
|
name: 'Vodafone',
|
|
portalUrl: 'https://www.vodafone.de/meinvodafone/account/login',
|
|
usernameFieldName: 'username',
|
|
passwordFieldName: 'password',
|
|
},
|
|
{
|
|
name: 'Klarmobil',
|
|
portalUrl: 'https://www.klarmobil.de/login',
|
|
usernameFieldName: 'username',
|
|
passwordFieldName: 'password',
|
|
},
|
|
{
|
|
name: 'Otelo',
|
|
portalUrl: 'https://www.otelo.de/mein-otelo/login',
|
|
usernameFieldName: 'username',
|
|
passwordFieldName: 'password',
|
|
},
|
|
{
|
|
name: 'Congstar',
|
|
portalUrl: 'https://www.congstar.de/login/',
|
|
usernameFieldName: 'username',
|
|
passwordFieldName: 'password',
|
|
},
|
|
{
|
|
name: 'Telekom',
|
|
portalUrl: 'https://www.telekom.de/kundencenter/startseite',
|
|
usernameFieldName: 'username',
|
|
passwordFieldName: 'password',
|
|
},
|
|
{
|
|
name: 'O2',
|
|
portalUrl: 'https://www.o2online.de/ecare/selfcare',
|
|
usernameFieldName: 'username',
|
|
passwordFieldName: 'password',
|
|
},
|
|
{
|
|
name: '1&1',
|
|
portalUrl: 'https://control-center.1und1.de/',
|
|
usernameFieldName: 'username',
|
|
passwordFieldName: 'password',
|
|
},
|
|
];
|
|
|
|
for (const provider of providers) {
|
|
await prisma.provider.upsert({
|
|
where: { name: provider.name },
|
|
update: {
|
|
portalUrl: provider.portalUrl,
|
|
usernameFieldName: provider.usernameFieldName,
|
|
passwordFieldName: provider.passwordFieldName,
|
|
},
|
|
create: { ...provider, isActive: true },
|
|
});
|
|
}
|
|
|
|
console.log('Providers created');
|
|
|
|
// Create contract categories (matching existing enum values)
|
|
const contractCategories = [
|
|
{ code: 'ELECTRICITY', name: 'Strom', icon: 'Zap', color: '#FFC107', sortOrder: 1 },
|
|
{ code: 'GAS', name: 'Gas', icon: 'Flame', color: '#FF5722', sortOrder: 2 },
|
|
{ code: 'DSL', name: 'DSL', icon: 'Wifi', color: '#2196F3', sortOrder: 3 },
|
|
{ code: 'FIBER', name: 'Glasfaser', icon: 'Cable', color: '#9C27B0', sortOrder: 4 },
|
|
{ code: 'CABLE', name: 'Kabel Internet (Coax)', icon: 'Cable', color: '#00BCD4', sortOrder: 5 },
|
|
{ code: 'MOBILE', name: 'Mobilfunk', icon: 'Smartphone', color: '#4CAF50', sortOrder: 6 },
|
|
{ code: 'TV', name: 'TV', icon: 'Tv', color: '#E91E63', sortOrder: 7 },
|
|
{ code: 'CAR_INSURANCE', name: 'KFZ-Versicherung', icon: 'Car', color: '#607D8B', sortOrder: 8 },
|
|
];
|
|
|
|
for (const category of contractCategories) {
|
|
await prisma.contractCategory.upsert({
|
|
where: { code: category.code },
|
|
update: { name: category.name, icon: category.icon, color: category.color, sortOrder: category.sortOrder },
|
|
create: category,
|
|
});
|
|
}
|
|
|
|
console.log('Contract categories created');
|
|
|
|
// ==================== CANCELLATION PERIODS ====================
|
|
const cancellationPeriods = [
|
|
{ code: '14D', description: '14 Tage' },
|
|
{ code: '1M', description: '1 Monat' },
|
|
{ code: '2M', description: '2 Monate' },
|
|
{ code: '3M', description: '3 Monate' },
|
|
{ code: '6M', description: '6 Monate' },
|
|
{ code: '12M', description: '12 Monate' },
|
|
{ code: '1W', description: '1 Woche' },
|
|
{ code: '2W', description: '2 Wochen' },
|
|
{ code: '4W', description: '4 Wochen' },
|
|
{ code: '6W', description: '6 Wochen' },
|
|
];
|
|
|
|
for (const period of cancellationPeriods) {
|
|
await prisma.cancellationPeriod.upsert({
|
|
where: { code: period.code },
|
|
update: { description: period.description },
|
|
create: period,
|
|
});
|
|
}
|
|
|
|
console.log('Cancellation periods created');
|
|
|
|
// ==================== CONTRACT DURATIONS ====================
|
|
const contractDurations = [
|
|
{ code: '1M', description: '1 Monat' },
|
|
{ code: '3M', description: '3 Monate' },
|
|
{ code: '6M', description: '6 Monate' },
|
|
{ code: '12M', description: '12 Monate' },
|
|
{ code: '24M', description: '24 Monate' },
|
|
{ code: '36M', description: '36 Monate' },
|
|
{ code: '1J', description: '1 Jahr' },
|
|
{ code: '2J', description: '2 Jahre' },
|
|
{ code: '3J', description: '3 Jahre' },
|
|
{ code: '4J', description: '4 Jahre' },
|
|
{ code: '5J', description: '5 Jahre' },
|
|
{ code: 'UNBEFRISTET', description: 'Unbefristet' },
|
|
];
|
|
|
|
for (const duration of contractDurations) {
|
|
await prisma.contractDuration.upsert({
|
|
where: { code: duration.code },
|
|
update: { description: duration.description },
|
|
create: duration,
|
|
});
|
|
}
|
|
|
|
console.log('Contract durations created');
|
|
|
|
// ==================== APP SETTINGS ====================
|
|
const appSettings = [
|
|
// Cockpit-Einstellungen (Fristen-Ampel)
|
|
{ key: 'deadlineCriticalDays', value: '14' }, // Rot: <= 14 Tage
|
|
{ key: 'deadlineWarningDays', value: '42' }, // Gelb: <= 42 Tage
|
|
{ key: 'deadlineOkDays', value: '90' }, // Grün: <= 90 Tage
|
|
// Allgemeine Einstellungen
|
|
{ key: 'companyName', value: 'OpenCRM' },
|
|
{ key: 'defaultEmailDomain', value: 'stressfrei-wechseln.de' },
|
|
];
|
|
|
|
for (const setting of appSettings) {
|
|
await prisma.appSetting.upsert({
|
|
where: { key: setting.key },
|
|
update: {}, // Bestehende Werte nicht überschreiben
|
|
create: setting,
|
|
});
|
|
}
|
|
|
|
console.log('App settings created');
|
|
|
|
// ==================== AUDIT RETENTION POLICIES (DSGVO) ====================
|
|
// Standard-Policy (ohne Sensitivity)
|
|
const existingDefault = await prisma.auditRetentionPolicy.findFirst({
|
|
where: { resourceType: '*', sensitivity: null },
|
|
});
|
|
if (!existingDefault) {
|
|
await prisma.auditRetentionPolicy.create({
|
|
data: {
|
|
resourceType: '*',
|
|
sensitivity: null,
|
|
retentionDays: 3650, // 10 Jahre
|
|
description: 'Standard-Aufbewahrungsfrist',
|
|
legalBasis: 'AO §147, HGB §257',
|
|
},
|
|
});
|
|
}
|
|
|
|
// Spezifische Policies mit Sensitivity
|
|
const specificPolicies = [
|
|
{
|
|
resourceType: 'Authentication',
|
|
sensitivity: 'CRITICAL' as const,
|
|
retentionDays: 730, // 2 Jahre
|
|
description: 'Login-Versuche und Authentifizierung',
|
|
legalBasis: 'Sicherheitsanforderungen',
|
|
},
|
|
{
|
|
resourceType: 'Customer',
|
|
sensitivity: 'HIGH' as const,
|
|
retentionDays: 3650, // 10 Jahre
|
|
description: 'Kundendaten-Zugriffe',
|
|
legalBasis: 'Steuerrecht (AO §147)',
|
|
},
|
|
{
|
|
resourceType: 'Contract',
|
|
sensitivity: 'MEDIUM' as const,
|
|
retentionDays: 3650, // 10 Jahre
|
|
description: 'Vertragsdaten-Zugriffe',
|
|
legalBasis: 'Steuerrecht (AO §147)',
|
|
},
|
|
{
|
|
resourceType: 'AppSetting',
|
|
sensitivity: 'LOW' as const,
|
|
retentionDays: 1095, // 3 Jahre
|
|
description: 'Allgemeine Einstellungen',
|
|
legalBasis: 'Verjährungsfrist (BGB §195)',
|
|
},
|
|
];
|
|
|
|
for (const policy of specificPolicies) {
|
|
await prisma.auditRetentionPolicy.upsert({
|
|
where: {
|
|
resourceType_sensitivity: {
|
|
resourceType: policy.resourceType,
|
|
sensitivity: policy.sensitivity,
|
|
},
|
|
},
|
|
update: {
|
|
retentionDays: policy.retentionDays,
|
|
description: policy.description,
|
|
legalBasis: policy.legalBasis,
|
|
},
|
|
create: policy,
|
|
});
|
|
}
|
|
|
|
console.log('Audit retention policies created');
|
|
|
|
// ==================== CONSENT HASH FÜR BESTEHENDE KUNDEN ====================
|
|
const customersWithoutHash = await prisma.customer.findMany({
|
|
where: { consentHash: null },
|
|
select: { id: true },
|
|
});
|
|
|
|
for (const c of customersWithoutHash) {
|
|
await prisma.customer.update({
|
|
where: { id: c.id },
|
|
data: { consentHash: crypto.randomUUID() },
|
|
});
|
|
}
|
|
|
|
if (customersWithoutHash.length > 0) {
|
|
console.log(`ConsentHash für ${customersWithoutHash.length} Kunden generiert`);
|
|
}
|
|
|
|
console.log('Seeding completed!');
|
|
}
|
|
|
|
main()
|
|
.catch((e) => {
|
|
console.error(e);
|
|
process.exit(1);
|
|
})
|
|
.finally(async () => {
|
|
await prisma.$disconnect();
|
|
});
|