|
| 1 | +import { connect } from "framer-api"; |
| 2 | +import { config } from "./config"; |
| 3 | +import { extractFieldData, isDeleted, type NotionAutomationPayload } from "./notion"; |
| 4 | + |
| 5 | +async function handleWebhook(request: Request, env: Env): Promise<Response> { |
| 6 | + if (request.method !== "POST") { |
| 7 | + return new Response("Method Not Allowed", { status: 405 }); |
| 8 | + } |
| 9 | + |
| 10 | + const token = request.headers.get("Authorization"); |
| 11 | + if (token !== env.WEBHOOK_TOKEN) { |
| 12 | + return new Response("Unauthorized", { status: 401 }); |
| 13 | + } |
| 14 | + |
| 15 | + const payload = await request.json<NotionAutomationPayload>(); |
| 16 | + |
| 17 | + try { |
| 18 | + const pageId = payload.data.id.replace(/-/gu, ""); |
| 19 | + |
| 20 | + if (payload.data.in_trash || payload.data.archived) { |
| 21 | + return json({ success: true, action: "skipped", reason: "trashed or archived" }); |
| 22 | + } |
| 23 | + |
| 24 | + const parent = payload.data.parent; |
| 25 | + if (env.NOTION_DATABASE_ID && "database_id" in parent && parent.database_id) { |
| 26 | + const normalize = (id: string) => id.replace(/-/gu, ""); |
| 27 | + if (normalize(parent.database_id) !== normalize(env.NOTION_DATABASE_ID)) { |
| 28 | + return json({ skipped: true, reason: "Different database" }); |
| 29 | + } |
| 30 | + } |
| 31 | + |
| 32 | + using framer = await connect(env.FRAMER_PROJECT_URL, env.FRAMER_API_KEY); |
| 33 | + |
| 34 | + const collections = await framer.getManagedCollections(); |
| 35 | + const collection = collections.find((c) => c.name === env.FRAMER_COLLECTION_NAME); |
| 36 | + |
| 37 | + if (!collection) { |
| 38 | + return json( |
| 39 | + { |
| 40 | + error: `${env.FRAMER_COLLECTION_NAME} collection not found`, |
| 41 | + available: collections.map((c) => c.name), |
| 42 | + }, |
| 43 | + 404, |
| 44 | + ); |
| 45 | + } |
| 46 | + |
| 47 | + if (isDeleted(payload.data.properties, config.TOMBSTONE_PROPERTY)) { |
| 48 | + await collection.removeItems([pageId]); |
| 49 | + await publishAndDeploy(framer); |
| 50 | + console.log(`Deleted page ${pageId}`); |
| 51 | + return json({ success: true, action: "deleted", id: pageId, published: config.AUTO_PUBLISH }); |
| 52 | + } |
| 53 | + |
| 54 | + const fieldData = extractFieldData(payload.data.properties, config.FIELD_MAPPING); |
| 55 | + const slug = `item-${pageId}`; |
| 56 | + |
| 57 | + await collection.addItems([{ id: pageId, slug, fieldData }]); |
| 58 | + await publishAndDeploy(framer); |
| 59 | + console.log(`Upserted page ${pageId} → ${slug}`); |
| 60 | + |
| 61 | + return json({ success: true, action: "upserted", id: pageId, slug, published: config.AUTO_PUBLISH }); |
| 62 | + } catch (error) { |
| 63 | + console.error(`Error processing page:`, error); |
| 64 | + return json({ error: error instanceof Error ? error.message : "Unknown error" }, 500); |
| 65 | + } |
| 66 | +} |
| 67 | + |
| 68 | +async function publishAndDeploy(framer: Awaited<ReturnType<typeof connect>>) { |
| 69 | + if (!config.AUTO_PUBLISH) return null; |
| 70 | + const { deployment } = await framer.publish(); |
| 71 | + const hostnames = await framer.deploy(deployment.id); |
| 72 | + return { deploymentId: deployment.id, hostnames }; |
| 73 | +} |
| 74 | + |
| 75 | +const json = (data: unknown, status = 200) => |
| 76 | + new Response(JSON.stringify(data), { status, headers: { "Content-Type": "application/json" } }); |
| 77 | + |
| 78 | +export default { fetch: handleWebhook } satisfies ExportedHandler<Env>; |
0 commit comments