|
1 | 1 | import { internalMutation, mutation } from "../_generated/server"; |
2 | 2 | import { v } from "convex/values"; |
3 | 3 | import { getCurrentUser } from "../lib/ids"; |
| 4 | +import type { Id } from "../_generated/dataModel"; |
| 5 | + |
| 6 | +async function requireChallengeAdminForActivity( |
| 7 | + ctx: { db: any; auth: any }, |
| 8 | + activityId: Id<"activities">, |
| 9 | +) { |
| 10 | + const user = await getCurrentUser(ctx as any); |
| 11 | + if (!user) { |
| 12 | + throw new Error("Not authenticated"); |
| 13 | + } |
| 14 | + |
| 15 | + const activity = await ctx.db.get(activityId); |
| 16 | + if (!activity || activity.deletedAt) { |
| 17 | + throw new Error("Activity not found"); |
| 18 | + } |
| 19 | + |
| 20 | + const challenge = await ctx.db.get(activity.challengeId); |
| 21 | + if (!challenge) { |
| 22 | + throw new Error("Challenge not found"); |
| 23 | + } |
| 24 | + |
| 25 | + const isGlobalAdmin = user.role === "admin"; |
| 26 | + const isCreator = challenge.creatorId === user._id; |
| 27 | + const participation = await ctx.db |
| 28 | + .query("userChallenges") |
| 29 | + .withIndex("userChallengeUnique", (q: any) => |
| 30 | + q.eq("userId", user._id).eq("challengeId", activity.challengeId), |
| 31 | + ) |
| 32 | + .first(); |
| 33 | + const isChallengeAdmin = participation?.role === "admin"; |
| 34 | + |
| 35 | + if (!isGlobalAdmin && !isCreator && !isChallengeAdmin) { |
| 36 | + throw new Error("Not authorized - challenge admin required"); |
| 37 | + } |
| 38 | + |
| 39 | + return { user, activity }; |
| 40 | +} |
4 | 41 |
|
5 | 42 | // Internal mutation to delete a challenge and all related data (for scripts/migrations) |
6 | 43 | export const deleteChallenge = internalMutation({ |
@@ -84,28 +121,7 @@ export const updateFlagResolution = mutation({ |
84 | 121 | notes: v.optional(v.string()), |
85 | 122 | }, |
86 | 123 | handler: async (ctx, args) => { |
87 | | - const user = await getCurrentUser(ctx); |
88 | | - if (!user || user.role !== "admin") { |
89 | | - throw new Error("Not authorized - admin only"); |
90 | | - } |
91 | | - |
92 | | - const activity = await ctx.db.get(args.activityId); |
93 | | - if (!activity || activity.deletedAt) { |
94 | | - throw new Error("Activity not found"); |
95 | | - } |
96 | | - |
97 | | - // Check if user can manage this challenge |
98 | | - const challenge = await ctx.db.get(activity.challengeId); |
99 | | - if (!challenge) { |
100 | | - throw new Error("Challenge not found"); |
101 | | - } |
102 | | - |
103 | | - const canManage = |
104 | | - user.role === "admin" || challenge.creatorId === user._id; |
105 | | - |
106 | | - if (!canManage) { |
107 | | - throw new Error("Not authorized to manage this challenge"); |
108 | | - } |
| 124 | + const { user } = await requireChallengeAdminForActivity(ctx, args.activityId); |
109 | 125 |
|
110 | 126 | const now = Date.now(); |
111 | 127 |
|
@@ -143,28 +159,10 @@ export const addAdminComment = mutation({ |
143 | 159 | visibility: v.union(v.literal("internal"), v.literal("participant")), |
144 | 160 | }, |
145 | 161 | handler: async (ctx, args) => { |
146 | | - const user = await getCurrentUser(ctx); |
147 | | - if (!user || user.role !== "admin") { |
148 | | - throw new Error("Not authorized - admin only"); |
149 | | - } |
150 | | - |
151 | | - const activity = await ctx.db.get(args.activityId); |
152 | | - if (!activity || activity.deletedAt) { |
153 | | - throw new Error("Activity not found"); |
154 | | - } |
155 | | - |
156 | | - // Check if user can manage this challenge |
157 | | - const challenge = await ctx.db.get(activity.challengeId); |
158 | | - if (!challenge) { |
159 | | - throw new Error("Challenge not found"); |
160 | | - } |
161 | | - |
162 | | - const canManage = |
163 | | - user.role === "admin" || challenge.creatorId === user._id; |
164 | | - |
165 | | - if (!canManage) { |
166 | | - throw new Error("Not authorized to manage this challenge"); |
167 | | - } |
| 162 | + const { user, activity } = await requireChallengeAdminForActivity( |
| 163 | + ctx, |
| 164 | + args.activityId, |
| 165 | + ); |
168 | 166 |
|
169 | 167 | const now = Date.now(); |
170 | 168 |
|
@@ -216,28 +214,10 @@ export const adminEditActivity = mutation({ |
216 | 214 | metrics: v.optional(v.any()), |
217 | 215 | }, |
218 | 216 | handler: async (ctx, args) => { |
219 | | - const user = await getCurrentUser(ctx); |
220 | | - if (!user || user.role !== "admin") { |
221 | | - throw new Error("Not authorized - admin only"); |
222 | | - } |
223 | | - |
224 | | - const activity = await ctx.db.get(args.activityId); |
225 | | - if (!activity || activity.deletedAt) { |
226 | | - throw new Error("Activity not found"); |
227 | | - } |
228 | | - |
229 | | - // Check if user can manage this challenge |
230 | | - const challenge = await ctx.db.get(activity.challengeId); |
231 | | - if (!challenge) { |
232 | | - throw new Error("Challenge not found"); |
233 | | - } |
234 | | - |
235 | | - const canManage = |
236 | | - user.role === "admin" || challenge.creatorId === user._id; |
237 | | - |
238 | | - if (!canManage) { |
239 | | - throw new Error("Not authorized to manage this challenge"); |
240 | | - } |
| 217 | + const { user, activity } = await requireChallengeAdminForActivity( |
| 218 | + ctx, |
| 219 | + args.activityId, |
| 220 | + ); |
241 | 221 |
|
242 | 222 | const now = Date.now(); |
243 | 223 | const updates: Record<string, unknown> = { |
|
0 commit comments