This commit is contained in:
Stefan Lazic committed 2026-08-04 09:57:58 +01:00
1 parent 546613e677
commit 4785510144
82 files changed
+4903 -287

No files matched your search

+35 -50
View File
@@ -4,10 +4,12 @@
* every relation in the schema once, not a full demo dataset.
*/
import "dotenv/config";
import { PrismaClient, Permission, Weekday, RewardType, NotificationType } from "../generated/prisma/client";
import { PrismaClient, Weekday, RewardType, NotificationType, InstallationStatus } from "../generated/prisma/client";
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
import { buildMysqlPoolConfig } from "../src/lib/db/mysql-pool-config";
import { notifySafely } from "../src/services/notifications/notification.service";
import { OWNER_PERMISSIONS, BARBER_PERMISSIONS, syncRolePermissions } from "../src/lib/roles/default-permissions";
import { INSTALLATION_SINGLETON_ID } from "../src/lib/installation/installation-state";
import bcrypt from "bcryptjs";
/**
@@ -29,52 +31,6 @@ function minutesFromNow(minutes: number): Date {
const adapter = new PrismaMariaDb(buildMysqlPoolConfig(process.env.DATABASE_URL));
const prisma = new PrismaClient({ adapter });
/**
* `role.upsert`'s `update: {}` deliberately does nothing on an existing
* role (see the two call sites below) — re-running the seed must still be
* safe to call repeatedly without duplicating anything, but a plain
* nested `permissions: { create: [...] }` only fires on the `create`
* branch, so a permission added to OWNER_PERMISSIONS/BARBER_PERMISSIONS
* after a business was first seeded would silently never reach an
* already-seeded database. This syncs RolePermission rows to exactly
* match the target list every run — found and fixed while adding Phase 6
* permissions to a database seeded back in Phase 2.
*/
async function syncRolePermissions(roleId: string, permissions: Permission[]): Promise<void> {
await prisma.rolePermission.deleteMany({ where: { roleId, permission: { notIn: permissions } } });
await prisma.rolePermission.createMany({
data: permissions.map((permission) => ({ roleId, permission })),
skipDuplicates: true,
});
}
const OWNER_PERMISSIONS: Permission[] = [
Permission.MANAGE_BUSINESS_PROFILE,
Permission.MANAGE_BOOKING_RULES,
Permission.MANAGE_SERVICES,
Permission.MANAGE_STAFF,
Permission.MANAGE_LOYALTY_PROGRAMME,
Permission.MANAGE_NOTIFICATION_SETTINGS,
Permission.VIEW_ALL_STAFF_APPOINTMENTS,
Permission.MANAGE_ALL_APPOINTMENTS,
Permission.MANAGE_OWN_APPOINTMENTS,
Permission.CHECK_IN_CUSTOMERS,
Permission.MANAGE_LOYALTY_STAMPS,
Permission.MANAGE_OWN_AVAILABILITY,
Permission.VIEW_CUSTOMER_DIRECTORY,
Permission.BOOK_ON_BEHALF_OF_CUSTOMER,
Permission.VIEW_REPORTING,
];
const BARBER_PERMISSIONS: Permission[] = [
Permission.MANAGE_OWN_APPOINTMENTS,
Permission.CHECK_IN_CUSTOMERS,
Permission.MANAGE_LOYALTY_STAMPS,
Permission.MANAGE_OWN_AVAILABILITY,
Permission.VIEW_CUSTOMER_DIRECTORY,
Permission.BOOK_ON_BEHALF_OF_CUSTOMER,
];
const WEEKDAYS: Weekday[] = [
Weekday.MONDAY,
Weekday.TUESDAY,
@@ -112,7 +68,13 @@ async function main() {
lateArrivalPolicy: "Arrivals more than 10 minutes late may need to be rescheduled.",
},
},
bookingRule: { create: {} }, // all defaults from the schema
// Guest booking defaults to false as of Phase 8 (see BookingRule in
// prisma/schema.prisma) — the dev/demo fixture explicitly opts back
// in so the guest-booking E2E journey and manual testing keep
// working; a real installed business starts with it off (see
// src/features/installation/installer.service.ts) until the owner
// enables it themselves.
bookingRule: { create: { allowGuestBooking: true } },
hours: {
create: [
...WEEKDAYS.map((dayOfWeek) => ({
@@ -146,7 +108,7 @@ async function main() {
permissions: { create: OWNER_PERMISSIONS.map((permission) => ({ permission })) },
},
});
await syncRolePermissions(ownerRole.id, OWNER_PERMISSIONS);
await syncRolePermissions(prisma, ownerRole.id, OWNER_PERMISSIONS);
const barberRole = await prisma.role.upsert({
where: { businessId_name: { businessId: business.id, name: "Barber" } },
@@ -158,7 +120,7 @@ async function main() {
permissions: { create: BARBER_PERMISSIONS.map((permission) => ({ permission })) },
},
});
await syncRolePermissions(barberRole.id, BARBER_PERMISSIONS);
await syncRolePermissions(prisma, barberRole.id, BARBER_PERMISSIONS);
const category = await prisma.serviceCategory.upsert({
where: { id: `${business.id}-senior-barber` },
@@ -1013,6 +975,29 @@ async function main() {
});
}
// Dev/demo fixtures bypass the installation wizard entirely (see
// src/features/installation/installer.service.ts for the real,
// production first-run path) — this upsert only exists so a freshly
// seeded dev/CI/E2E database is treated as "installed" by the routing
// gate in src/proxy.ts, exactly as if a real install had completed.
// Never run alongside a real installation: the two are mutually
// exclusive by construction (the installer's own transaction refuses
// to start once this row exists — see ApplicationInstallation's model
// header).
await prisma.applicationInstallation.upsert({
where: { id: INSTALLATION_SINGLETON_ID },
update: {},
create: {
id: INSTALLATION_SINGLETON_ID,
status: InstallationStatus.INSTALLED,
installedAt: new Date(),
installedVersion: "dev-seed",
initialAdminUserId: ownerUser.id,
primaryBusinessId: business.id,
metadata: { source: "prisma/seed.ts" },
},
});
console.log("Seed complete:", {
business: business.name,
roles: [ownerRole.name, barberRole.name],