import { PrismaClient } from "./client"; type Folder = { id: string; name: string; slug: string; children: string[]; }; type Page = { path: string; id: string; name: string; title: string; meta: { description?: string; title?: string; // convert boolean to expression excludePageFromSearch?: any; socialImageAssetId?: string; socialImageUrl?: string; custom?: Array<{ property: string; content: string; }>; }; rootInstanceId: string; pathVariableId?: string; }; type ProjectMeta = { siteName?: string; faviconAssetId?: string; code?: string; }; type PageRedirect = { old: string; new: string; }; type ProjectSettings = { atomicStyles?: boolean; redirects?: PageRedirect[]; }; type Pages = { meta?: ProjectMeta; settings?: ProjectSettings; homePage: Page; pages: Page[]; folders: Folder[]; }; /** * convert all fields to js expression * string -> `"string"` * false -> `false` * undefined -> undefined */ const mutatePageMeta = (page: Page) => { page.title = JSON.stringify(page.title); page.meta.description = JSON.stringify(page.meta.description); page.meta.excludePageFromSearch = JSON.stringify( page.meta.excludePageFromSearch ); page.meta.socialImageUrl = JSON.stringify(page.meta.socialImageUrl); if (page.meta.custom) { for (const item of page.meta.custom) { item.content = JSON.stringify(item.content); } } }; export default async () => { const client = new PrismaClient({ // Uncomment to see the queries in console as the migration runs // log: ["query", "info", "warn", "error"], }); await client.$transaction( async (prisma) => { const chunkSize = 1000; let cursor: undefined | string = undefined; let hasNext = true; while (hasNext) { console.info("CHUNK", cursor); console.time("read"); const cursorOptions: {} = cursor ? { skip: 1, // Skip the cursor cursor: { id: cursor }, } : {}; const builds = await prisma.build.findMany({ select: { id: true, pages: true, }, take: chunkSize, orderBy: { id: "asc", }, where: { AND: [ { deployment: null, }, ], }, ...cursorOptions, }); console.timeEnd("read"); console.time("parse-change"); cursor = builds.at(-1)?.id; hasNext = builds.length === chunkSize; const changedBuilds: typeof builds = []; for (const build of builds) { const buildId = build.id; try { const pages: Pages = JSON.parse(build.pages); mutatePageMeta(pages.homePage); for (const page of pages.pages) { mutatePageMeta(page); } build.pages = JSON.stringify(pages); changedBuilds.push(build); } catch { console.info(`build ${buildId} cannot be converted`); } } console.timeEnd("parse-change"); console.info("changedBuilds.length", changedBuilds.length); console.time("update"); const sql = ` UPDATE "Build" SET "pages" = data."pages" FROM unnest(ARRAY[$1], ARRAY[$2]) as data(id, pages) WHERE "Build"."id" = data."id" `; if (changedBuilds.length === 0) { return; } const res = await prisma.$executeRawUnsafe( sql, changedBuilds.map((changedBuild) => changedBuild.id), changedBuilds.map((changedBuild) => changedBuild.pages) ); console.timeEnd("update"); console.info("res", res); } }, { timeout: 3600000 } ); };