From dc4e52562ccdc4aac4cfcb5a25aab56b41c29a1b Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Thu, 13 Nov 2025 06:33:29 +0000 Subject: [PATCH] Create /api/community/posts endpoint for user post CRUD operations cgen-eab4fedec4ac4d819a32e5c5d1328c4d --- api/community/posts.ts | 262 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 api/community/posts.ts diff --git a/api/community/posts.ts b/api/community/posts.ts new file mode 100644 index 00000000..97f7fc82 --- /dev/null +++ b/api/community/posts.ts @@ -0,0 +1,262 @@ +export const config = { + runtime: "nodejs", +}; + +import { createClient } from "@supabase/supabase-js"; + +const supabaseUrl = process.env.VITE_SUPABASE_URL; +const supabaseServiceRole = process.env.SUPABASE_SERVICE_ROLE; + +if (!supabaseUrl || !supabaseServiceRole) { + throw new Error("Missing Supabase configuration"); +} + +const supabase = createClient(supabaseUrl, supabaseServiceRole); + +const VALID_ARMS = ["labs", "gameforge", "corp", "foundation", "devlink", "nexus", "staff"]; + +export default async function handler(req: any, res: any) { + if (req.method === "POST") { + try { + const { title, content, arm_affiliation, author_id, tags, category } = req.body; + + // Validate required fields + if (!title || !content || !arm_affiliation || !author_id) { + return res.status(400).json({ + error: "Missing required fields: title, content, arm_affiliation, author_id", + }); + } + + // Validate arm_affiliation + if (!VALID_ARMS.includes(arm_affiliation)) { + return res.status(400).json({ + error: `Invalid arm_affiliation. Must be one of: ${VALID_ARMS.join(", ")}`, + }); + } + + // Validate content length + if (title.trim().length === 0 || title.length > 500) { + return res + .status(400) + .json({ error: "Title must be between 1 and 500 characters" }); + } + + if (content.trim().length === 0 || content.length > 5000) { + return res + .status(400) + .json({ error: "Content must be between 1 and 5000 characters" }); + } + + // Validate author exists + const { data: author, error: authorError } = await supabase + .from("user_profiles") + .select("id") + .eq("id", author_id) + .single(); + + if (authorError || !author) { + return res.status(401).json({ error: "User not found" }); + } + + // Insert the post (published by default for users) + const { data, error } = await supabase + .from("community_posts") + .insert({ + title: title.trim(), + content: content.trim(), + arm_affiliation, + author_id, + tags: tags || [], + category: category || null, + is_published: true, + likes_count: 0, + comments_count: 0, + }) + .select( + ` + id, + title, + content, + arm_affiliation, + author_id, + created_at, + updated_at, + is_published, + likes_count, + comments_count, + tags, + category, + user_profiles!community_posts_author_id_fkey ( + id, + username, + full_name, + avatar_url + ) + ` + ); + + if (error) { + console.error("[Community Posts API] Insert error:", error); + return res.status(500).json({ error: error.message }); + } + + return res.status(201).json({ + post: data?.[0], + }); + } catch (error: any) { + console.error("[Community Posts API POST] Unexpected error:", error); + return res + .status(500) + .json({ error: error.message || "Internal server error" }); + } + } + + if (req.method === "PUT") { + try { + const { id, title, content, arm_affiliation, category, tags, user_id } = req.body; + + if (!id || !user_id) { + return res.status(400).json({ error: "Missing id or user_id" }); + } + + // Get the post to verify ownership + const { data: post, error: fetchError } = await supabase + .from("community_posts") + .select("author_id") + .eq("id", id) + .single(); + + if (fetchError) { + console.error("[Community Posts API] Fetch error:", fetchError); + return res.status(404).json({ error: "Post not found" }); + } + + if (post.author_id !== user_id) { + return res + .status(403) + .json({ error: "You can only edit your own posts" }); + } + + // Validate updates + if (title && (title.trim().length === 0 || title.length > 500)) { + return res + .status(400) + .json({ error: "Title must be between 1 and 500 characters" }); + } + + if (content && (content.trim().length === 0 || content.length > 5000)) { + return res + .status(400) + .json({ error: "Content must be between 1 and 5000 characters" }); + } + + if (arm_affiliation && !VALID_ARMS.includes(arm_affiliation)) { + return res + .status(400) + .json({ + error: `Invalid arm_affiliation. Must be one of: ${VALID_ARMS.join(", ")}`, + }); + } + + // Build update object + const updateData: any = {}; + if (title) updateData.title = title.trim(); + if (content) updateData.content = content.trim(); + if (arm_affiliation) updateData.arm_affiliation = arm_affiliation; + if (category !== undefined) updateData.category = category; + if (tags) updateData.tags = tags; + + const { data, error } = await supabase + .from("community_posts") + .update(updateData) + .eq("id", id) + .select( + ` + id, + title, + content, + arm_affiliation, + author_id, + created_at, + updated_at, + is_published, + likes_count, + comments_count, + tags, + category, + user_profiles!community_posts_author_id_fkey ( + id, + username, + full_name, + avatar_url + ) + ` + ); + + if (error) { + console.error("[Community Posts API] Update error:", error); + return res.status(500).json({ error: error.message }); + } + + return res.status(200).json({ + post: data?.[0], + }); + } catch (error: any) { + console.error("[Community Posts API PUT] Unexpected error:", error); + return res + .status(500) + .json({ error: error.message || "Internal server error" }); + } + } + + if (req.method === "DELETE") { + try { + const { id, user_id } = req.body; + + if (!id || !user_id) { + return res.status(400).json({ error: "Missing id or user_id" }); + } + + // Get the post to verify ownership + const { data: post, error: fetchError } = await supabase + .from("community_posts") + .select("author_id") + .eq("id", id) + .single(); + + if (fetchError) { + console.error("[Community Posts API] Fetch error:", fetchError); + return res.status(404).json({ error: "Post not found" }); + } + + if (post.author_id !== user_id) { + return res + .status(403) + .json({ error: "You can only delete your own posts" }); + } + + // Delete the post (cascade will delete likes and comments) + const { error } = await supabase + .from("community_posts") + .delete() + .eq("id", id); + + if (error) { + console.error("[Community Posts API] Delete error:", error); + return res.status(500).json({ error: error.message }); + } + + return res.status(200).json({ + success: true, + message: "Post deleted successfully", + }); + } catch (error: any) { + console.error("[Community Posts API DELETE] Unexpected error:", error); + return res + .status(500) + .json({ error: error.message || "Internal server error" }); + } + } + + return res.status(405).json({ error: "Method not allowed" }); +}