Create /api/community/posts endpoint for user post CRUD operations
cgen-eab4fedec4ac4d819a32e5c5d1328c4d
This commit is contained in:
parent
f35c5c892a
commit
dc4e52562c
1 changed files with 262 additions and 0 deletions
262
api/community/posts.ts
Normal file
262
api/community/posts.ts
Normal file
|
|
@ -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" });
|
||||
}
|
||||
Loading…
Reference in a new issue