返回 oh-my-ppt
backfill-design-contract-fonts.ts
根目录 / src / main / db / patch / backfill-design-contract-fonts.ts
1 import type { createClient } from '@libsql/client'
2 import type { DesignContract } from '@shared/generation'
3 import { createDefaultDesignContract } from '../../presentation/design-contract'
4
5 type LibSqlClient = ReturnType<typeof createClient>
6
7 const FALLBACK_TITLE_FONT = 'Inter'
8 const FALLBACK_BODY_FONT = 'Noto Sans SC'
9
10 const getRowValue = (row: unknown, key: string): unknown => {
11 if (row && typeof row === 'object' && !Array.isArray(row) && key in row) {
12 return (row as Record<string, unknown>)[key]
13 }
14 return undefined
15 }
16
17 const parseDesignContract = (value: unknown): DesignContract | null => {
18 if (typeof value !== 'string' || value.trim().length === 0) return null
19 try {
20 const parsed = JSON.parse(value) as unknown
21 if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return null
22 return parsed as DesignContract
23 } catch {
24 return null
25 }
26 }
27
28 const normalizeFont = (value: unknown): string => String(value ?? '').replace(/\s+/g, ' ').trim()
29
30 const withFontFallback = (contract: DesignContract): DesignContract => {
31 const titleFont = normalizeFont(contract.titleFont) || FALLBACK_TITLE_FONT
32 const bodyFont = normalizeFont(contract.bodyFont) || FALLBACK_BODY_FONT
33 if (titleFont === contract.titleFont && bodyFont === contract.bodyFont) return contract
34 return { ...contract, titleFont, bodyFont }
35 }
36
37 export const patchDesignContractFonts = async (client: LibSqlClient): Promise<void> => {
38 await client.execute({
39 sql: `
40 UPDATE sessions
41 SET design_contract = ?, updated_at = ?
42 WHERE provider = 'import'
43 AND model IN ('session-file-import', 'pptx-import')
44 AND (design_contract IS NULL OR TRIM(design_contract) = '')
45 `,
46 args: [JSON.stringify(createDefaultDesignContract()), Math.floor(Date.now() / 1000)]
47 })
48
49 const result = await client.execute(`
50 SELECT id, design_contract
51 FROM sessions
52 WHERE design_contract IS NOT NULL
53 AND TRIM(design_contract) <> ''
54 `)
55
56 for (const row of result.rows || []) {
57 const sessionId = String(getRowValue(row, 'id') || '').trim()
58 const rawContract = getRowValue(row, 'design_contract')
59 if (!sessionId || typeof rawContract !== 'string') continue
60
61 const contract = parseDesignContract(rawContract)
62 if (!contract) continue
63
64 const patched = withFontFallback(contract)
65 if (patched.titleFont === contract.titleFont && patched.bodyFont === contract.bodyFont) {
66 continue
67 }
68
69 await client.execute({
70 sql: 'UPDATE sessions SET design_contract = ? WHERE id = ? AND design_contract = ?',
71 args: [JSON.stringify(patched), sessionId, rawContract]
72 })
73 }
74 }
75
75 lines TYPESCRIPT