import type { Transaction } from 'sequelize'; import db from '@/db/models'; import { PER_TENANT_CONTENT_TYPES, SCHOOL_SCOPED_CONTENT_TYPES, ORG_SCOPED_CONTENT_TYPES, } from '@/shared/constants/content-catalog'; import { CONTENT_CATALOG_DEFAULT_ROWS } from '@/db/seeders/content-catalog-data/content-catalog-seed-payloads'; export type TenantSeedLevel = 'organization' | 'school' | 'campus'; export interface TenantSeedContext { level: TenantSeedLevel; organizationId: string; schoolId?: string | null; campusId?: string | null; } interface OwnerStamp { organizationId: string | null; schoolId: string | null; campusId: string | null; } /** * The owning-tenant ids a content type takes when the given tenant level is * created — or `null` if this type is not preset at this level. Per-tenant * org-scoped content such as the safety quiz exists only at organization level; * dashboard + parent templates exist at org/school/campus; school-scoped only * at school; truly global types are seeded once (with no tenant) when the first * org is created. */ function stampForLevel( contentType: string, ctx: TenantSeedContext, ): OwnerStamp | null { const org = ctx.organizationId; if (ORG_SCOPED_CONTENT_TYPES.has(contentType)) { return ctx.level === 'organization' ? { organizationId: org, schoolId: null, campusId: null } : null; } if (SCHOOL_SCOPED_CONTENT_TYPES.has(contentType)) { return ctx.level === 'school' && ctx.schoolId ? { organizationId: org, schoolId: ctx.schoolId, campusId: null } : null; } if (PER_TENANT_CONTENT_TYPES.has(contentType)) { if (ctx.level === 'organization') { return { organizationId: org, schoolId: null, campusId: null }; } if (ctx.level === 'school') { return ctx.schoolId ? { organizationId: org, schoolId: ctx.schoolId, campusId: null } : null; } return ctx.campusId ? { organizationId: org, schoolId: null, campusId: ctx.campusId } : null; } // Truly global/shared content: seeded once (no tenant) at org creation. return ctx.level === 'organization' ? { organizationId: null, schoolId: null, campusId: null } : null; } /** * Copies the default content a newly-created tenant owns into `content_catalog`, * once (idempotent — skips rows that already exist for that tenant). Called from * the org/school/campus creation flows so new tenants start with editable * defaults from the single defaults source. */ export async function seedDefaultContentForTenant( ctx: TenantSeedContext, transaction?: Transaction, ): Promise { for (const row of CONTENT_CATALOG_DEFAULT_ROWS) { const stamp = stampForLevel(row.content_type, ctx); if (!stamp) { continue; } const where = { content_type: row.content_type, organizationId: stamp.organizationId, schoolId: stamp.schoolId, campusId: stamp.campusId, classId: null, }; const existing = await db.content_catalog.findOne({ where, paranoid: false, transaction, }); if (existing) { continue; } await db.content_catalog.create( { content_type: row.content_type, payload: row.payload, active: true, importHash: null, ...stamp, classId: null, }, { transaction }, ); } }