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.
This commit is contained in:
2026-03-24 22:09:17 +03:00
parent 0bd30c5e17
commit e6b50fb4f1
36 changed files with 1634 additions and 99 deletions
+108 -31
View File
@@ -41,6 +41,21 @@ async function main() {
});
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' },
@@ -75,10 +90,15 @@ async function main() {
update: {},
create: { userId: admin.id, groupId: userGroup.id }
});
console.log(' Added admin to groups');
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 apps = [
const appDefinitions = [
{
name: 'Plex',
url: 'http://plex.local:32400',
@@ -128,15 +148,36 @@ async function main() {
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 apps) {
const app = await prisma.app.upsert({
where: { id: appData.name.toLowerCase().replace(/\s+/g, '-') },
update: {},
create: {
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
}
@@ -190,12 +231,36 @@ async function main() {
});
console.log(' Created section:', infraSection.title);
// --- Widgets ---
// Plex widget in media section
await prisma.widget.upsert({
where: { id: 'widget-plex' },
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',
@@ -205,11 +270,9 @@ async function main() {
}
});
// Nextcloud widget in infra section
await prisma.widget.upsert({
where: { id: 'widget-nextcloud' },
update: {},
create: {
// Infrastructure section widgets
await prisma.widget.create({
data: {
id: 'widget-nextcloud',
sectionId: infraSection.id,
type: 'app',
@@ -219,11 +282,8 @@ async function main() {
}
});
// Gitea widget in infra section
await prisma.widget.upsert({
where: { id: 'widget-gitea' },
update: {},
create: {
await prisma.widget.create({
data: {
id: 'widget-gitea',
sectionId: infraSection.id,
type: 'app',
@@ -233,11 +293,8 @@ async function main() {
}
});
// Home Assistant widget in infra section
await prisma.widget.upsert({
where: { id: 'widget-homeassistant' },
update: {},
create: {
await prisma.widget.create({
data: {
id: 'widget-homeassistant',
sectionId: infraSection.id,
type: 'app',
@@ -247,11 +304,8 @@ async function main() {
}
});
// Grafana widget in infra section
await prisma.widget.upsert({
where: { id: 'widget-grafana' },
update: {},
create: {
await prisma.widget.create({
data: {
id: 'widget-grafana',
sectionId: infraSection.id,
type: 'app',
@@ -261,6 +315,29 @@ async function main() {
}
});
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!');
}