From c779ebc44ce8e00f39b133018aa267524a046a9f Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Sat, 15 Nov 2025 20:04:48 +0000 Subject: [PATCH] Create blog publish API endpoint cgen-a4beb225c4d14bddab119d2b5b7eed91 --- api/blog/publish.ts | 108 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 api/blog/publish.ts diff --git a/api/blog/publish.ts b/api/blog/publish.ts new file mode 100644 index 00000000..351821d0 --- /dev/null +++ b/api/blog/publish.ts @@ -0,0 +1,108 @@ +import { createClient } from "@supabase/supabase-js"; +import { publishPostToGhost, updatePostInGhost } from "@/server/ghost-admin-api"; + +const supabaseUrl = process.env.SUPABASE_URL || ""; +const supabaseServiceRole = process.env.SUPABASE_SERVICE_ROLE || ""; + +const supabase = + supabaseUrl && supabaseServiceRole + ? createClient(supabaseUrl, supabaseServiceRole) + : null; + +async function isUserAdminOrStaff(userId: string): Promise { + if (!supabase) return false; + + try { + const { data, error } = await supabase + .from("user_roles") + .select("role") + .eq("user_id", userId) + .in("role", ["admin", "staff"]) + .single(); + + return !error && data; + } catch { + return false; + } +} + +export default async function handler(req: any, res: any) { + if (req.method !== "POST") { + return res.status(405).json({ error: "Method not allowed" }); + } + + try { + // Get user from session or auth header + const userId = req.user?.id || req.query.user_id; + + if (!userId) { + return res.status(401).json({ error: "Unauthorized" }); + } + + // Check if user is admin or staff + const isAuthorized = await isUserAdminOrStaff(userId); + if (!isAuthorized) { + return res.status(403).json({ error: "Forbidden: Admin/Staff access required" }); + } + + const { + title, + excerpt, + html, + slug, + feature_image, + published_at, + status = "published", + tags, + meta_description, + meta_title, + post_id, // for updates + } = req.body; + + // Validate required fields + if (!title || !html) { + return res.status(400).json({ error: "Title and body are required" }); + } + + // Publish or update post + let result; + if (post_id) { + result = await updatePostInGhost(post_id, { + title, + excerpt, + html, + feature_image, + published_at, + status, + tags: tags?.map((tag: string) => ({ name: tag })) || [], + meta_description, + meta_title, + }); + } else { + result = await publishPostToGhost({ + title, + excerpt, + html, + slug, + feature_image, + published_at, + status, + tags: tags?.map((tag: string) => ({ name: tag })) || [], + meta_description, + meta_title, + }); + } + + return res.status(200).json({ + success: true, + postId: result.id, + url: result.url, + }); + } catch (error: any) { + console.error("Blog publish API error:", error); + return res.status(500).json({ + error: "Failed to publish post", + message: error?.message || "Unknown error", + }); + } +}