import { type MigrateDownArgs, type MigrateUpArgs, sql } from '@payloadcms/db-sqlite' import { homeContent } from '@/spa/homeContent' type MigrationDB = MigrateUpArgs['db'] const ident = (value: string) => `\`${value.replace(/`/g, '``')}\`` const literal = (value: string) => `'${value.replace(/'/g, "''")}'` const snake = (value: string) => value.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`) const sectionScalars = [ ['eyebrow', homeContent.application.eyebrow], ['heading', homeContent.application.heading], ['body', homeContent.application.body], ['awardImageAlt', homeContent.application.awardImageAlt], ['formEyebrow', homeContent.application.formEyebrow], ['formTitle', homeContent.application.formTitle], ['footnote', homeContent.application.footnote], ['footnoteStrong', homeContent.application.footnoteStrong], ] as const const topLevelFormScalars = [ ['stepCompleteLabel', homeContent.form.stepCompleteLabel], ['backLabel', homeContent.form.backLabel], ['nextLabel', homeContent.form.nextLabel], ['submitLabel', homeContent.form.submitLabel], ['yesLabel', homeContent.form.yesLabel], ['noLabel', homeContent.form.noLabel], ['requiredSuffix', homeContent.form.requiredSuffix], ] as const const formGroups = { validation: homeContent.form.validation, typeStep: homeContent.form.typeStep, eligibility: homeContent.form.eligibility, selfContact: homeContent.form.selfContact, selfSummary: homeContent.form.selfSummary, nominationCompany: homeContent.form.nominationCompany, nominationContact: homeContent.form.nominationContact, nominationSummary: homeContent.form.nominationSummary, success: homeContent.form.success, } as const const mainScalarDefinitions = [ ...sectionScalars.map(([name, value]) => [`section_${snake(name)}`, value] as const), ...topLevelFormScalars.map(([name, value]) => [`form_${snake(name)}`, value] as const), ...Object.entries(formGroups).flatMap(([group, values]) => Object.entries(values).map(([name, value]) => [`form_${snake(group)}_${snake(name)}`, value] as const), ), ] async function tableExists(db: MigrationDB, tableName: string) { const rows = (await db.all( sql.raw(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ${literal(tableName)}`), )) as Array<{ name: string }> return rows.length > 0 } async function columnExists(db: MigrationDB, tableName: string, columnName: string) { if (!(await tableExists(db, tableName))) return false const columns = (await db.all(sql.raw(`PRAGMA table_info(${ident(tableName)})`))) as Array<{ name: string }> return columns.some((column) => column.name === columnName) } async function createArrayTable( db: MigrationDB, tableName: string, fields: Array<{ name: string; defaultValue?: string }>, ) { const fieldDefinitions = fields .map((field) => `${ident(field.name)} text${field.defaultValue ? ` DEFAULT ${literal(field.defaultValue)}` : ''}`) .join(',\n') await db.run(sql.raw(` CREATE TABLE IF NOT EXISTS ${ident(tableName)} ( \`_order\` integer NOT NULL, \`_parent_id\` integer NOT NULL, \`id\` text PRIMARY KEY NOT NULL, ${fieldDefinitions}, FOREIGN KEY (\`_parent_id\`) REFERENCES \`application_form\`(\`id\`) ON UPDATE no action ON DELETE cascade ); `)) await db.run(sql.raw(`CREATE INDEX IF NOT EXISTS ${ident(`${tableName}_order_idx`)} ON ${ident(tableName)} (\`_order\`);`)) await db.run(sql.raw(`CREATE INDEX IF NOT EXISTS ${ident(`${tableName}_parent_id_idx`)} ON ${ident(tableName)} (\`_parent_id\`);`)) } async function seedArrayFromPage( db: MigrationDB, target: string, source: string, columns: string[], defaults: ReadonlyArray>, ) { const sourceExists = await tableExists(db, source) if (sourceExists) { const columnList = columns.map(ident).join(', ') await db.run(sql.raw(` INSERT OR IGNORE INTO ${ident(target)} (\`_order\`, \`_parent_id\`, \`id\`, ${columnList}) SELECT source.\`_order\`, 1, lower(hex(randomblob(16))), ${columns.map((column) => `source.${ident(column)}`).join(', ')} FROM ${ident(source)} source INNER JOIN \`pages\` page ON page.\`id\` = source.\`_parent_id\` WHERE page.\`spa_path\` = '/' OR page.\`slug\` IN ('startseite', 'home') ORDER BY source.\`_order\`; `)) } const count = (await db.all(sql.raw(`SELECT COUNT(*) AS count FROM ${ident(target)} WHERE \`_parent_id\` = 1;`))) as Array<{ count: number }> if (Number(count[0]?.count || 0) > 0) return for (const [index, item] of defaults.entries()) { const values = columns.map((column) => literal(String(item[column] ?? ''))).join(', ') await db.run(sql.raw(` INSERT INTO ${ident(target)} (\`_order\`, \`_parent_id\`, \`id\`, ${columns.map(ident).join(', ')}) VALUES (${index + 1}, 1, lower(hex(randomblob(16))), ${values}); `)) } } export async function up({ db }: MigrateUpArgs): Promise { const scalarColumns = mainScalarDefinitions .map(([name, value]) => `${ident(name)} text DEFAULT ${literal(value)}`) .join(',\n') await db.run(sql.raw(` CREATE TABLE IF NOT EXISTS \`application_form\` ( \`id\` integer PRIMARY KEY NOT NULL, ${scalarColumns}, \`section_award_image_id\` integer, \`updated_at\` text, \`created_at\` text, FOREIGN KEY (\`section_award_image_id\`) REFERENCES \`media\`(\`id\`) ON UPDATE no action ON DELETE set null ); `)) await db.run(sql.raw('CREATE INDEX IF NOT EXISTS `application_form_section_section_award_image_idx` ON `application_form` (`section_award_image_id`);')) await createArrayTable(db, 'application_form_benefits', [ { name: 'num' }, { name: 'label' }, { name: 'desc' }, ]) await createArrayTable(db, 'application_form_self_steps', [ { name: 'label' }, { name: 'icon', defaultValue: 'trophy' }, ]) await createArrayTable(db, 'application_form_nom_steps', [ { name: 'label' }, { name: 'icon', defaultValue: 'trophy' }, ]) await createArrayTable(db, 'application_form_emp_opts', [ { name: 'value' }, { name: 'label' }, ]) await db.run(sql.raw(` INSERT OR IGNORE INTO \`application_form\` (\`id\`, \`updated_at\`, \`created_at\`) VALUES (1, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), strftime('%Y-%m-%dT%H:%M:%fZ', 'now')); `)) if (await columnExists(db, 'pages', 'home_application_eyebrow')) { const assignments = [ ...sectionScalars.map(([name]) => [`section_${snake(name)}`, `home_application_${snake(name)}`] as const), ...topLevelFormScalars.map(([name]) => [`form_${snake(name)}`, `home_form_${snake(name)}`] as const), ...Object.keys(formGroups).flatMap((group) => Object.keys(formGroups[group as keyof typeof formGroups]).map((name) => [ `form_${snake(group)}_${snake(name)}`, `home_form_${snake(group)}_${snake(name)}`, ] as const), ), ] .filter(([, source]) => source !== 'home_application_award_image') .map(([target, source]) => `${ident(target)} = COALESCE((SELECT ${ident(source)} FROM \`pages\` WHERE \`spa_path\` = '/' OR \`slug\` IN ('startseite', 'home') ORDER BY CASE WHEN \`spa_path\` = '/' THEN 0 ELSE 1 END LIMIT 1), ${ident(target)})`) if (await columnExists(db, 'pages', 'home_application_award_image_id')) { assignments.push("`section_award_image_id` = (SELECT `home_application_award_image_id` FROM `pages` WHERE `spa_path` = '/' OR `slug` IN ('startseite', 'home') ORDER BY CASE WHEN `spa_path` = '/' THEN 0 ELSE 1 END LIMIT 1)") } await db.run(sql.raw(`UPDATE \`application_form\` SET ${assignments.join(',\n')} WHERE \`id\` = 1;`)) } await seedArrayFromPage(db, 'application_form_benefits', 'pages_home_application_benefits', ['num', 'label', 'desc'], homeContent.application.benefits) await seedArrayFromPage(db, 'application_form_self_steps', 'home_self_steps', ['label', 'icon'], homeContent.form.selfSteps) await seedArrayFromPage(db, 'application_form_nom_steps', 'home_nom_steps', ['label', 'icon'], homeContent.form.nominationSteps) await seedArrayFromPage(db, 'application_form_emp_opts', 'home_emp_opts', ['value', 'label'], homeContent.form.employeeOptions) } export async function down({ db }: MigrateDownArgs): Promise { await db.run(sql.raw('DROP TABLE IF EXISTS `application_form_emp_opts`;')) await db.run(sql.raw('DROP TABLE IF EXISTS `application_form_nom_steps`;')) await db.run(sql.raw('DROP TABLE IF EXISTS `application_form_self_steps`;')) await db.run(sql.raw('DROP TABLE IF EXISTS `application_form_benefits`;')) await db.run(sql.raw('DROP TABLE IF EXISTS `application_form`;')) }