-
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathroute.ts
More file actions
109 lines (97 loc) · 3.23 KB
/
Copy pathroute.ts
File metadata and controls
109 lines (97 loc) · 3.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import { checkVisibleEventDuplicates } from "@/lib/events/check-duplicates";
import { sanitizeEventCategorySlugs } from "@/lib/events/categories";
import { ForbiddenError, handleError } from "@/lib/errors";
import { revalidateEventsListing } from "@/lib/revalidate-public";
import { assertAuthenticated } from "@/lib/supabase/authentication";
import { createClient } from "@/lib/supabase/server";
import { NextResponse } from "next/server";
export async function POST(request: Request) {
try {
const supabase = await createClient();
const profile = await assertAuthenticated(supabase);
if (profile.role !== "admin") {
throw new ForbiddenError("Admin access required");
}
const body = await request.json();
const { title, description, start_time, end_time, city, url, bannerUrl, allowPotentialDuplicate } = body;
const categorySlugs = sanitizeEventCategorySlugs(body.categorySlugs);
// Validate required fields
if (!title || !description || !start_time || !city) {
return NextResponse.json({ error: "Missing required fields" }, { status: 400 });
}
const { duplicateCandidates, hasBlockingDuplicate } = await checkVisibleEventDuplicates(supabase, {
title,
description,
start_time,
city,
url,
});
if (duplicateCandidates.length > 0 && (hasBlockingDuplicate || !allowPotentialDuplicate)) {
return NextResponse.json(
{
error: hasBlockingDuplicate
? "This event already appears to be published."
: "This event may already be published.",
duplicateCandidates,
severity: hasBlockingDuplicate ? "block" : "warning",
},
{ status: 409 },
);
}
// Admin instant-publish path: explicitly mark approved so the row
// bypasses the moderation queue that defaults new submissions to
// status='pending'. Stamping reviewed_by/reviewed_at keeps the audit
// trail consistent with the review-queue path.
const now = new Date().toISOString();
const { data, error } = await supabase
.from("events")
.insert([
{
title,
description,
start_time,
end_time: end_time ?? null,
city,
url,
banner_url: bannerUrl,
status: "approved",
submitted_by: profile.id,
submitter_email: profile.email,
submitted_at: now,
reviewed_by: profile.id,
reviewed_at: now,
},
])
.select()
.single();
if (error) {
console.error("Error creating event:", error);
return NextResponse.json({ error: "Failed to create event" }, { status: 500 });
}
const { error: categoriesError } =
categorySlugs.length > 0
? await supabase.from("event_category_assignments").insert(
categorySlugs.map((categorySlug) => ({
event_id: data.id,
category_slug: categorySlug,
})),
)
: { error: null };
if (categoriesError) {
console.error("Error assigning event categories:", categoriesError);
await supabase.from("events").delete().eq("id", data.id);
return NextResponse.json({ error: "Failed to assign event categories" }, { status: 500 });
}
revalidateEventsListing();
return NextResponse.json({
message: "Event created successfully",
event: {
...data,
categorySlugs,
},
});
} catch (error) {
console.error("Error in POST /api/events:", error);
return handleError(error);
}
}