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" }); }