armand0e commited on
Commit
710ace6
·
1 Parent(s): 9a41ad7

Fix Supabase mutations + require service role for admin edits

Browse files
src/app/page.tsx CHANGED
@@ -77,7 +77,7 @@ const SOURCE_MODELS = [
77
  "deepseek/deepseek-r1",
78
  "Qwen/Qwen2.5-72B-Instruct",
79
  "meta-llama/Llama-3.1-8B-Instruct",
80
- "Other",
81
  ];
82
 
83
  const REASONING_DEPTHS = ["low", "medium", "high"];
@@ -248,7 +248,8 @@ export default function Home() {
248
 
249
  async function handleDatasetSubmit(e: React.FormEvent) {
250
  e.preventDefault();
251
- const resolvedSourceModel = sourceModel === "Other" ? sourceModelOther.trim() : sourceModel;
 
252
  if (!resolvedSourceModel) {
253
  toast({ title: "Error", description: "Please select a source model", variant: "destructive" });
254
  return;
@@ -584,7 +585,7 @@ export default function Home() {
584
  value={sourceModel}
585
  onValueChange={(v) => {
586
  setSourceModel(v);
587
- if (v !== "Other") {
588
  setSourceModelOther("");
589
  setHfModelQuery("");
590
  setHuggingfaceModels([]);
 
77
  "deepseek/deepseek-r1",
78
  "Qwen/Qwen2.5-72B-Instruct",
79
  "meta-llama/Llama-3.1-8B-Instruct",
80
+ "Other (Huggingface Models)",
81
  ];
82
 
83
  const REASONING_DEPTHS = ["low", "medium", "high"];
 
248
 
249
  async function handleDatasetSubmit(e: React.FormEvent) {
250
  e.preventDefault();
251
+ const resolvedSourceModel =
252
+ sourceModel === "Other (Huggingface Models)" ? sourceModelOther.trim() : sourceModel;
253
  if (!resolvedSourceModel) {
254
  toast({ title: "Error", description: "Please select a source model", variant: "destructive" });
255
  return;
 
585
  value={sourceModel}
586
  onValueChange={(v) => {
587
  setSourceModel(v);
588
+ if (v !== "Other (Huggingface Models)") {
589
  setSourceModelOther("");
590
  setHfModelQuery("");
591
  setHuggingfaceModels([]);
src/lib/store.ts CHANGED
@@ -129,20 +129,20 @@ export async function getDatasetRequests(): Promise<DatasetRequest[]> {
129
 
130
  export async function getRequest(type: RequestType, id: string): Promise<DistillationRequest | DatasetRequest | null> {
131
  const table = type === "distillation" ? "distillation_requests" : "dataset_requests";
132
- const { data, error } = await supabaseRest<any>(`/rest/v1/${table}`, {
133
  query: {
134
  select: "*",
135
  id: `eq.${id}`,
136
  },
137
- acceptObject: true,
138
  });
139
 
140
  if (error) {
141
  throw new Error(error.message);
142
  }
143
 
144
- if (!data) return null;
145
- return type === "distillation" ? mapDistillationRow(data) : mapDatasetRow(data);
 
146
  }
147
 
148
  export async function updateDistillationRequest(
@@ -154,19 +154,20 @@ export async function updateDistillationRequest(
154
  if (typeof updates.studentModel === "string") body.student_model = updates.studentModel;
155
  if (typeof updates.additionalNotes === "string") body.additional_notes = updates.additionalNotes;
156
 
157
- const { data, error } = await supabaseRest<any>("/rest/v1/distillation_requests", {
158
  method: "PATCH",
159
  body: JSON.stringify(body),
160
  query: { select: "*", id: `eq.${id}` },
161
  preferReturn: "representation",
162
- acceptObject: true,
163
  });
164
 
165
  if (error) {
166
  throw new Error(error.message);
167
  }
168
 
169
- return data ? mapDistillationRow(data) : null;
 
170
  }
171
 
172
  export async function updateDatasetRequest(
@@ -180,19 +181,20 @@ export async function updateDatasetRequest(
180
  if (Array.isArray(updates.topics)) body.topics = updates.topics.map(String);
181
  if (typeof updates.additionalNotes === "string") body.additional_notes = updates.additionalNotes;
182
 
183
- const { data, error } = await supabaseRest<any>("/rest/v1/dataset_requests", {
184
  method: "PATCH",
185
  body: JSON.stringify(body),
186
  query: { select: "*", id: `eq.${id}` },
187
  preferReturn: "representation",
188
- acceptObject: true,
189
  });
190
 
191
  if (error) {
192
  throw new Error(error.message);
193
  }
194
 
195
- return data ? mapDatasetRow(data) : null;
 
196
  }
197
 
198
  export async function updateRequestStatus(
@@ -201,19 +203,19 @@ export async function updateRequestStatus(
201
  status: "pending" | "in_progress" | "completed"
202
  ): Promise<boolean> {
203
  const table = type === "distillation" ? "distillation_requests" : "dataset_requests";
204
- const { data, error } = await supabaseRest<any>(`/rest/v1/${table}`, {
205
  method: "PATCH",
206
  body: JSON.stringify({ status }),
207
  query: { select: "id", id: `eq.${id}` },
208
  preferReturn: "representation",
209
- acceptObject: true,
210
  });
211
 
212
  if (error) {
213
  throw new Error(error.message);
214
  }
215
 
216
- return Boolean(data?.id);
217
  }
218
 
219
  export async function deleteRequest(type: RequestType, id: string): Promise<boolean> {
@@ -223,22 +225,23 @@ export async function deleteRequest(type: RequestType, id: string): Promise<bool
223
  request_type: `eq.${type}`,
224
  request_id: `eq.${id}`,
225
  },
 
226
  });
227
 
228
  const table = type === "distillation" ? "distillation_requests" : "dataset_requests";
229
- const { data, error } = await supabaseRest<any>(`/rest/v1/${table}`, {
230
  method: "DELETE",
231
  body: null,
232
  query: { select: "id", id: `eq.${id}` },
233
  preferReturn: "representation",
234
- acceptObject: true,
235
  });
236
 
237
  if (error) {
238
  throw new Error(error.message);
239
  }
240
 
241
- return Boolean(data?.id);
242
  }
243
 
244
  export async function getThread(type: RequestType, id: string): Promise<DiscussionThread> {
@@ -265,7 +268,7 @@ export async function getThread(type: RequestType, id: string): Promise<Discussi
265
  }
266
 
267
  export async function addAdminComment(type: RequestType, id: string, body: string, ownerId: string): Promise<DiscussionComment> {
268
- const { data, error } = await supabaseRest<any>("/rest/v1/request_comments", {
269
  method: "POST",
270
  body: JSON.stringify({
271
  request_type: type,
@@ -277,18 +280,19 @@ export async function addAdminComment(type: RequestType, id: string, body: strin
277
  }),
278
  query: { select: "*" },
279
  preferReturn: "representation",
280
- acceptObject: true,
281
  });
282
 
283
  if (error) {
284
  throw new Error(error.message);
285
  }
286
 
287
- if (!data) {
 
288
  throw new Error("Failed to create comment");
289
  }
290
 
291
- return mapCommentRow(data);
292
  }
293
 
294
  export async function addUserComment(
@@ -330,7 +334,7 @@ export async function updateComment(
330
  commentId: string,
331
  body: string
332
  ): Promise<DiscussionComment | null> {
333
- const { data, error } = await supabaseRest<any>("/rest/v1/request_comments", {
334
  method: "PATCH",
335
  body: JSON.stringify({ body, edited_at: new Date().toISOString() }),
336
  query: {
@@ -340,14 +344,15 @@ export async function updateComment(
340
  request_id: `eq.${requestId}`,
341
  },
342
  preferReturn: "representation",
343
- acceptObject: true,
344
  });
345
 
346
  if (error) {
347
  throw new Error(error.message);
348
  }
349
 
350
- return data ? mapCommentRow(data) : null;
 
351
  }
352
 
353
  export async function deleteComment(
@@ -355,7 +360,7 @@ export async function deleteComment(
355
  requestId: string,
356
  commentId: string
357
  ): Promise<boolean> {
358
- const { data, error } = await supabaseRest<any>("/rest/v1/request_comments", {
359
  method: "DELETE",
360
  body: null,
361
  query: {
@@ -365,14 +370,14 @@ export async function deleteComment(
365
  request_id: `eq.${requestId}`,
366
  },
367
  preferReturn: "representation",
368
- acceptObject: true,
369
  });
370
 
371
  if (error) {
372
  throw new Error(error.message);
373
  }
374
 
375
- return Boolean(data?.id);
376
  }
377
 
378
  export async function addDistillationRequest(
 
129
 
130
  export async function getRequest(type: RequestType, id: string): Promise<DistillationRequest | DatasetRequest | null> {
131
  const table = type === "distillation" ? "distillation_requests" : "dataset_requests";
132
+ const { data, error } = await supabaseRest<any[]>(`/rest/v1/${table}`, {
133
  query: {
134
  select: "*",
135
  id: `eq.${id}`,
136
  },
 
137
  });
138
 
139
  if (error) {
140
  throw new Error(error.message);
141
  }
142
 
143
+ const row = Array.isArray(data) ? data[0] : null;
144
+ if (!row) return null;
145
+ return type === "distillation" ? mapDistillationRow(row) : mapDatasetRow(row);
146
  }
147
 
148
  export async function updateDistillationRequest(
 
154
  if (typeof updates.studentModel === "string") body.student_model = updates.studentModel;
155
  if (typeof updates.additionalNotes === "string") body.additional_notes = updates.additionalNotes;
156
 
157
+ const { data, error } = await supabaseRest<any[]>("/rest/v1/distillation_requests", {
158
  method: "PATCH",
159
  body: JSON.stringify(body),
160
  query: { select: "*", id: `eq.${id}` },
161
  preferReturn: "representation",
162
+ requireServiceRole: true,
163
  });
164
 
165
  if (error) {
166
  throw new Error(error.message);
167
  }
168
 
169
+ const row = Array.isArray(data) ? data[0] : null;
170
+ return row ? mapDistillationRow(row) : null;
171
  }
172
 
173
  export async function updateDatasetRequest(
 
181
  if (Array.isArray(updates.topics)) body.topics = updates.topics.map(String);
182
  if (typeof updates.additionalNotes === "string") body.additional_notes = updates.additionalNotes;
183
 
184
+ const { data, error } = await supabaseRest<any[]>("/rest/v1/dataset_requests", {
185
  method: "PATCH",
186
  body: JSON.stringify(body),
187
  query: { select: "*", id: `eq.${id}` },
188
  preferReturn: "representation",
189
+ requireServiceRole: true,
190
  });
191
 
192
  if (error) {
193
  throw new Error(error.message);
194
  }
195
 
196
+ const row = Array.isArray(data) ? data[0] : null;
197
+ return row ? mapDatasetRow(row) : null;
198
  }
199
 
200
  export async function updateRequestStatus(
 
203
  status: "pending" | "in_progress" | "completed"
204
  ): Promise<boolean> {
205
  const table = type === "distillation" ? "distillation_requests" : "dataset_requests";
206
+ const { data, error } = await supabaseRest<any[]>(`/rest/v1/${table}`, {
207
  method: "PATCH",
208
  body: JSON.stringify({ status }),
209
  query: { select: "id", id: `eq.${id}` },
210
  preferReturn: "representation",
211
+ requireServiceRole: true,
212
  });
213
 
214
  if (error) {
215
  throw new Error(error.message);
216
  }
217
 
218
+ return Array.isArray(data) ? Boolean(data[0]?.id) : false;
219
  }
220
 
221
  export async function deleteRequest(type: RequestType, id: string): Promise<boolean> {
 
225
  request_type: `eq.${type}`,
226
  request_id: `eq.${id}`,
227
  },
228
+ requireServiceRole: true,
229
  });
230
 
231
  const table = type === "distillation" ? "distillation_requests" : "dataset_requests";
232
+ const { data, error } = await supabaseRest<any[]>(`/rest/v1/${table}`, {
233
  method: "DELETE",
234
  body: null,
235
  query: { select: "id", id: `eq.${id}` },
236
  preferReturn: "representation",
237
+ requireServiceRole: true,
238
  });
239
 
240
  if (error) {
241
  throw new Error(error.message);
242
  }
243
 
244
+ return Array.isArray(data) ? Boolean(data[0]?.id) : false;
245
  }
246
 
247
  export async function getThread(type: RequestType, id: string): Promise<DiscussionThread> {
 
268
  }
269
 
270
  export async function addAdminComment(type: RequestType, id: string, body: string, ownerId: string): Promise<DiscussionComment> {
271
+ const { data, error } = await supabaseRest<any[]>("/rest/v1/request_comments", {
272
  method: "POST",
273
  body: JSON.stringify({
274
  request_type: type,
 
280
  }),
281
  query: { select: "*" },
282
  preferReturn: "representation",
283
+ requireServiceRole: true,
284
  });
285
 
286
  if (error) {
287
  throw new Error(error.message);
288
  }
289
 
290
+ const row = Array.isArray(data) ? data[0] : null;
291
+ if (!row) {
292
  throw new Error("Failed to create comment");
293
  }
294
 
295
+ return mapCommentRow(row);
296
  }
297
 
298
  export async function addUserComment(
 
334
  commentId: string,
335
  body: string
336
  ): Promise<DiscussionComment | null> {
337
+ const { data, error } = await supabaseRest<any[]>("/rest/v1/request_comments", {
338
  method: "PATCH",
339
  body: JSON.stringify({ body, edited_at: new Date().toISOString() }),
340
  query: {
 
344
  request_id: `eq.${requestId}`,
345
  },
346
  preferReturn: "representation",
347
+ requireServiceRole: true,
348
  });
349
 
350
  if (error) {
351
  throw new Error(error.message);
352
  }
353
 
354
+ const row = Array.isArray(data) ? data[0] : null;
355
+ return row ? mapCommentRow(row) : null;
356
  }
357
 
358
  export async function deleteComment(
 
360
  requestId: string,
361
  commentId: string
362
  ): Promise<boolean> {
363
+ const { data, error } = await supabaseRest<any[]>("/rest/v1/request_comments", {
364
  method: "DELETE",
365
  body: null,
366
  query: {
 
370
  request_id: `eq.${requestId}`,
371
  },
372
  preferReturn: "representation",
373
+ requireServiceRole: true,
374
  });
375
 
376
  if (error) {
377
  throw new Error(error.message);
378
  }
379
 
380
+ return Array.isArray(data) ? Boolean(data[0]?.id) : false;
381
  }
382
 
383
  export async function addDistillationRequest(
src/lib/supabaseRest.ts CHANGED
@@ -30,10 +30,14 @@ export async function supabaseRest<T>(
30
  preferReturn?: "representation" | "minimal";
31
  acceptObject?: boolean;
32
  count?: "exact" | "planned" | "estimated";
 
33
  } = {}
34
  ): Promise<{ data: T | null; error: SupabaseRestError | null; status: number; count: number | null }> {
35
  const { url, anonKey, serviceRoleKey } = getSupabaseEnv();
36
- const apiKey = serviceRoleKey || anonKey;
 
 
 
37
 
38
  const endpoint = new URL(path.startsWith("/") ? path : `/${path}`, url);
39
  if (init.query) {
 
30
  preferReturn?: "representation" | "minimal";
31
  acceptObject?: boolean;
32
  count?: "exact" | "planned" | "estimated";
33
+ requireServiceRole?: boolean;
34
  } = {}
35
  ): Promise<{ data: T | null; error: SupabaseRestError | null; status: number; count: number | null }> {
36
  const { url, anonKey, serviceRoleKey } = getSupabaseEnv();
37
+ if (init.requireServiceRole && !serviceRoleKey) {
38
+ throw new Error("Supabase env var missing: SUPABASE_SERVICE_ROLE_KEY (required for this operation)");
39
+ }
40
+ const apiKey = init.requireServiceRole ? (serviceRoleKey as string) : anonKey;
41
 
42
  const endpoint = new URL(path.startsWith("/") ? path : `/${path}`, url);
43
  if (init.query) {
supabase/schema.sql CHANGED
@@ -68,12 +68,12 @@ begin
68
  end if;
69
 
70
  if voter_ip = any(current_ips) then
71
- update public.distillation_requests
72
  set
73
- voted_ips = array_remove(voted_ips, voter_ip),
74
- upvotes = greatest(upvotes - 1, 0)
75
- where id = request_id
76
- returning public.distillation_requests.upvotes into upvotes;
77
 
78
  success := true;
79
  action := 'unvoted';
@@ -81,12 +81,12 @@ begin
81
  return;
82
  end if;
83
 
84
- update public.distillation_requests
85
  set
86
- voted_ips = array_append(voted_ips, voter_ip),
87
- upvotes = upvotes + 1
88
- where id = request_id
89
- returning public.distillation_requests.upvotes into upvotes;
90
 
91
  success := true;
92
  action := 'upvoted';
@@ -118,12 +118,12 @@ begin
118
  end if;
119
 
120
  if voter_ip = any(current_ips) then
121
- update public.dataset_requests
122
  set
123
- voted_ips = array_remove(voted_ips, voter_ip),
124
- upvotes = greatest(upvotes - 1, 0)
125
- where id = request_id
126
- returning public.dataset_requests.upvotes into upvotes;
127
 
128
  success := true;
129
  action := 'unvoted';
@@ -131,12 +131,12 @@ begin
131
  return;
132
  end if;
133
 
134
- update public.dataset_requests
135
  set
136
- voted_ips = array_append(voted_ips, voter_ip),
137
- upvotes = upvotes + 1
138
- where id = request_id
139
- returning public.dataset_requests.upvotes into upvotes;
140
 
141
  success := true;
142
  action := 'upvoted';
 
68
  end if;
69
 
70
  if voter_ip = any(current_ips) then
71
+ update public.distillation_requests as r
72
  set
73
+ voted_ips = array_remove(r.voted_ips, voter_ip),
74
+ upvotes = greatest(r.upvotes - 1, 0)
75
+ where r.id = request_id
76
+ returning r.upvotes into upvotes;
77
 
78
  success := true;
79
  action := 'unvoted';
 
81
  return;
82
  end if;
83
 
84
+ update public.distillation_requests as r
85
  set
86
+ voted_ips = array_append(r.voted_ips, voter_ip),
87
+ upvotes = r.upvotes + 1
88
+ where r.id = request_id
89
+ returning r.upvotes into upvotes;
90
 
91
  success := true;
92
  action := 'upvoted';
 
118
  end if;
119
 
120
  if voter_ip = any(current_ips) then
121
+ update public.dataset_requests as r
122
  set
123
+ voted_ips = array_remove(r.voted_ips, voter_ip),
124
+ upvotes = greatest(r.upvotes - 1, 0)
125
+ where r.id = request_id
126
+ returning r.upvotes into upvotes;
127
 
128
  success := true;
129
  action := 'unvoted';
 
131
  return;
132
  end if;
133
 
134
+ update public.dataset_requests as r
135
  set
136
+ voted_ips = array_append(r.voted_ips, voter_ip),
137
+ upvotes = r.upvotes + 1
138
+ where r.id = request_id
139
+ returning r.upvotes into upvotes;
140
 
141
  success := true;
142
  action := 'upvoted';