Files
web-app-launcher/prisma/seed.ts
T
alexei.dolgolyov e6b50fb4f1 feat(mvp): phase 8 - integration, testing & deployment
Fix all build/type/lint errors (zod 3.25 compat wrapper, Svelte 5 fixes),
write 115 unit tests across 10 test files, expand seed script with demo
data, update Docker config with migration on startup.
2026-03-24 22:09:17 +03:00

353 lines
8.4 KiB
TypeScript

import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs';
const prisma = new PrismaClient();
async function main() {
console.log('Seeding database...');
// --- System Settings ---
const settings = await prisma.systemSettings.upsert({
where: { id: 'singleton' },
update: {},
create: {
id: 'singleton',
authMode: 'local',
registrationEnabled: true,
defaultTheme: 'dark',
defaultPrimaryColor: '#6366f1',
healthcheckDefaults: JSON.stringify({
interval: 300,
timeout: 5000,
method: 'GET',
expectedStatus: 200
})
}
});
console.log(' Created system settings:', settings.id);
// --- Admin User ---
const adminPassword = await bcrypt.hash('admin123', 12);
const admin = await prisma.user.upsert({
where: { email: 'admin@localhost' },
update: {},
create: {
email: 'admin@localhost',
password: adminPassword,
displayName: 'Administrator',
role: 'admin',
authProvider: 'local'
}
});
console.log(' Created admin user:', admin.email);
// --- Regular User ---
const userPassword = await bcrypt.hash('user123', 12);
const regularUser = await prisma.user.upsert({
where: { email: 'user@localhost' },
update: {},
create: {
email: 'user@localhost',
password: userPassword,
displayName: 'Demo User',
role: 'user',
authProvider: 'local'
}
});
console.log(' Created regular user:', regularUser.email);
// --- Groups ---
const adminGroup = await prisma.group.upsert({
where: { name: 'admin' },
update: {},
create: {
name: 'admin',
description: 'Administrators with full system access',
isDefault: false
}
});
console.log(' Created group:', adminGroup.name);
const userGroup = await prisma.group.upsert({
where: { name: 'user' },
update: {},
create: {
name: 'user',
description: 'Default group for all registered users',
isDefault: true
}
});
console.log(' Created group:', userGroup.name);
// --- User-Group memberships ---
await prisma.userGroup.upsert({
where: { userId_groupId: { userId: admin.id, groupId: adminGroup.id } },
update: {},
create: { userId: admin.id, groupId: adminGroup.id }
});
await prisma.userGroup.upsert({
where: { userId_groupId: { userId: admin.id, groupId: userGroup.id } },
update: {},
create: { userId: admin.id, groupId: userGroup.id }
});
await prisma.userGroup.upsert({
where: { userId_groupId: { userId: regularUser.id, groupId: userGroup.id } },
update: {},
create: { userId: regularUser.id, groupId: userGroup.id }
});
console.log(' Added users to groups');
// --- Sample Apps ---
const appDefinitions = [
{
name: 'Plex',
url: 'http://plex.local:32400',
icon: 'plex',
iconType: 'simple',
description: 'Media server for streaming movies, TV shows, and music',
category: 'Media',
tags: 'media,streaming,movies,tv',
healthcheckEnabled: true
},
{
name: 'Nextcloud',
url: 'http://nextcloud.local',
icon: 'nextcloud',
iconType: 'simple',
description: 'Self-hosted file sync, sharing, and collaboration platform',
category: 'Productivity',
tags: 'files,sync,cloud,office',
healthcheckEnabled: true
},
{
name: 'Gitea',
url: 'http://gitea.local:3000',
icon: 'gitea',
iconType: 'simple',
description: 'Lightweight self-hosted Git service',
category: 'Development',
tags: 'git,code,development,ci',
healthcheckEnabled: true
},
{
name: 'Home Assistant',
url: 'http://homeassistant.local:8123',
icon: 'homeassistant',
iconType: 'simple',
description: 'Open-source home automation platform',
category: 'Home Automation',
tags: 'home,automation,iot,smart-home',
healthcheckEnabled: true
},
{
name: 'Grafana',
url: 'http://grafana.local:3000',
icon: 'grafana',
iconType: 'simple',
description: 'Analytics and monitoring dashboards',
category: 'Monitoring',
tags: 'monitoring,analytics,dashboards,metrics',
healthcheckEnabled: true
},
{
name: 'Portainer',
url: 'http://portainer.local:9000',
icon: 'portainer',
iconType: 'simple',
description: 'Container management UI for Docker and Kubernetes',
category: 'Infrastructure',
tags: 'docker,containers,kubernetes,management',
healthcheckEnabled: true
},
{
name: 'Pi-hole',
url: 'http://pihole.local/admin',
icon: 'pihole',
iconType: 'simple',
description: 'Network-wide ad blocking DNS sinkhole',
category: 'Network',
tags: 'dns,adblock,network,privacy',
healthcheckEnabled: true
}
];
// Create apps using create (delete existing first for idempotency)
const createdApps = [];
for (const appData of appDefinitions) {
// Delete existing app with same name if present (for re-seeding)
await prisma.app.deleteMany({ where: { name: appData.name } });
const app = await prisma.app.create({
data: {
...appData,
createdById: admin.id
}
});
createdApps.push(app);
console.log(' Created app:', app.name);
}
// --- Default Board ---
const board = await prisma.board.upsert({
where: { id: 'default-board' },
update: {},
create: {
id: 'default-board',
name: 'Dashboard',
icon: 'layout-dashboard',
description: 'Default application dashboard',
isDefault: true,
isGuestAccessible: true,
createdById: admin.id
}
});
console.log(' Created board:', board.name);
// --- Sections ---
const mediaSection = await prisma.section.upsert({
where: { id: 'section-media' },
update: {},
create: {
id: 'section-media',
boardId: board.id,
title: 'Media & Entertainment',
icon: 'tv',
order: 0,
isExpandedByDefault: true
}
});
console.log(' Created section:', mediaSection.title);
const infraSection = await prisma.section.upsert({
where: { id: 'section-infra' },
update: {},
create: {
id: 'section-infra',
boardId: board.id,
title: 'Infrastructure & Tools',
icon: 'server',
order: 1,
isExpandedByDefault: true
}
});
console.log(' Created section:', infraSection.title);
const networkSection = await prisma.section.upsert({
where: { id: 'section-network' },
update: {},
create: {
id: 'section-network',
boardId: board.id,
title: 'Network & Security',
icon: 'shield',
order: 2,
isExpandedByDefault: true
}
});
console.log(' Created section:', networkSection.title);
// --- Widgets ---
// Delete existing seed widgets for idempotency
const seedWidgetIds = [
'widget-plex',
'widget-nextcloud',
'widget-gitea',
'widget-homeassistant',
'widget-grafana',
'widget-portainer',
'widget-pihole'
];
await prisma.widget.deleteMany({ where: { id: { in: seedWidgetIds } } });
// Media section widgets
await prisma.widget.create({
data: {
id: 'widget-plex',
sectionId: mediaSection.id,
type: 'app',
order: 0,
appId: createdApps[0].id,
config: JSON.stringify({ showStatus: true, openInNewTab: true })
}
});
// Infrastructure section widgets
await prisma.widget.create({
data: {
id: 'widget-nextcloud',
sectionId: infraSection.id,
type: 'app',
order: 0,
appId: createdApps[1].id,
config: JSON.stringify({ showStatus: true, openInNewTab: true })
}
});
await prisma.widget.create({
data: {
id: 'widget-gitea',
sectionId: infraSection.id,
type: 'app',
order: 1,
appId: createdApps[2].id,
config: JSON.stringify({ showStatus: true, openInNewTab: true })
}
});
await prisma.widget.create({
data: {
id: 'widget-homeassistant',
sectionId: infraSection.id,
type: 'app',
order: 2,
appId: createdApps[3].id,
config: JSON.stringify({ showStatus: true, openInNewTab: true })
}
});
await prisma.widget.create({
data: {
id: 'widget-grafana',
sectionId: infraSection.id,
type: 'app',
order: 3,
appId: createdApps[4].id,
config: JSON.stringify({ showStatus: true, openInNewTab: true })
}
});
await prisma.widget.create({
data: {
id: 'widget-portainer',
sectionId: infraSection.id,
type: 'app',
order: 4,
appId: createdApps[5].id,
config: JSON.stringify({ showStatus: true, openInNewTab: true })
}
});
// Network section widgets
await prisma.widget.create({
data: {
id: 'widget-pihole',
sectionId: networkSection.id,
type: 'app',
order: 0,
appId: createdApps[6].id,
config: JSON.stringify({ showStatus: true, openInNewTab: true })
}
});
console.log(' Created widgets for all apps');
console.log('Seeding complete!');
}
main()
.catch((e) => {
console.error('Seed error:', e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});