- Introduced a new configuration file for the application form with various fields and validation messages. - Created a migration script to set up the database schema for the application form, including necessary tables and default values. - Added a migration for supporting awards copy to enhance the pages with additional columns. - Implemented a new SPA component for the application form section, integrating form logic and UI elements.
192 lines
8.5 KiB
TypeScript
192 lines
8.5 KiB
TypeScript
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<Record<string, unknown>>,
|
|
) {
|
|
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<void> {
|
|
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<void> {
|
|
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`;'))
|
|
}
|