import { randomUUID } from "node:crypto"; import { z } from "zod"; import { transaction } from "./db"; import { works } from "../content/registry"; export const postInput=z.object({ slug:z.string().regex(/^[a-z0-9][a-z0-9-]{0,119}$/).optional(), kind:z.enum(["image","video","audio","article","motion"]),title:z.string().trim().min(1).max(200), assets:z.array(z.object({id:z.uuid(),role:z.enum(["content","caption"]).default("content")})).max(100).default([]), text:z.string().trim().max(100000).optional(),tags:z.array(z.uuid()).max(50).default([]), coverAssetId:z.uuid().optional(),pageKey:z.string().max(120).optional(), }); export async function createPost(input:z.infer) { const id=randomUUID(); const slug=input.slug||id; if(["article","motion"].includes(input.kind)&&(!input.pageKey||works[input.pageKey]?.kind!==input.kind)) throw new Error("Register the coded page before creating its post"); return transaction(async client=>{ const ids=[...new Set(input.assets.map(a=>a.id))]; const assets=ids.length?(await client.query('SELECT id,kind FROM assets WHERE id=ANY($1::uuid[])',[ids])).rows:[]; if(assets.length!==ids.length) throw new Error("Referenced asset does not exist"); if(["image","video","audio"].includes(input.kind)&&!input.assets.some(link=>link.role==='content'&&assets.some(a=>a.id===link.id&&a.kind===input.kind))) throw new Error("A media post needs matching content"); if(input.coverAssetId && !(await client.query("SELECT 1 FROM assets WHERE id=$1 AND kind='image'",[input.coverAssetId])).rowCount) throw new Error("Cover must reference an image"); await client.query('INSERT INTO posts(id,slug,kind,title,page_key,cover_asset_id) VALUES($1,$2,$3,$4,$5,$6)',[id,slug,input.kind,input.title,input.pageKey||null,input.coverAssetId||null]); for(const [position,a] of input.assets.entries()) await client.query('INSERT INTO post_assets(post_id,asset_id,position,role) VALUES($1,$2,$3,$4)',[id,a.id,position,a.role]); if(input.text){const textId=randomUUID();await client.query("INSERT INTO assets(id,kind,body) VALUES($1,'text',$2)",[textId,input.text]);await client.query("INSERT INTO post_assets(post_id,asset_id,position,role) VALUES($1,$2,$3,'caption')",[id,textId,input.assets.length]);} for(const tag of new Set(input.tags)) await client.query('INSERT INTO post_tags(post_id,tag_id) VALUES($1,$2)',[id,tag]); return {id,slug}; }); }