dvijaykrishnan commited on
Commit
2e35c2f
·
1 Parent(s): d03d74d

feat: implement bulk affiliate link generation in moderation queue

Browse files
src/features/marketplace/actions/trigger-bulk-marketplace-match.ts ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use server';
2
+
3
+ import { inngest } from '@/inngest/client';
4
+ import { db } from '@/lib/db';
5
+ import { detectedObjects, youtubeVideos, youtubeChannels } from '@/lib/db/schema';
6
+ import { eq, and, inArray } from 'drizzle-orm';
7
+ import { auth } from '@/lib/auth';
8
+ import { headers } from 'next/headers';
9
+
10
+ export async function triggerBulkMarketplaceMatchAction(detectedObjectIds: string[]) {
11
+ if (!detectedObjectIds || detectedObjectIds.length === 0) {
12
+ return { success: false, error: 'No IDs provided' };
13
+ }
14
+
15
+ const session = await auth.api.getSession({
16
+ headers: await headers(),
17
+ });
18
+
19
+ if (!session?.user?.id) {
20
+ throw new Error('Unauthorized');
21
+ }
22
+
23
+ const userId = session.user.id;
24
+ const userEmail = session.user.email;
25
+ const adminEmails = process.env.ADMIN_EMAILS?.split(',') || [];
26
+ const isAdmin = adminEmails.includes(userEmail);
27
+
28
+ // If not admin, verify object ownership for ALL objects
29
+ if (!isAdmin) {
30
+ const result = await db
31
+ .select({ id: detectedObjects.id })
32
+ .from(detectedObjects)
33
+ .innerJoin(youtubeVideos, eq(detectedObjects.videoId, youtubeVideos.id))
34
+ .innerJoin(youtubeChannels, eq(youtubeVideos.channelId, youtubeChannels.id))
35
+ .where(
36
+ and(
37
+ inArray(detectedObjects.id, detectedObjectIds),
38
+ eq(youtubeChannels.creatorId, userId)
39
+ )
40
+ );
41
+
42
+ if (result.length !== detectedObjectIds.length) {
43
+ throw new Error('Some objects not found or access denied');
44
+ }
45
+ }
46
+
47
+ // Trigger matching in bulk
48
+ await inngest.send({
49
+ name: 'discovery/objects.match-marketplace',
50
+ data: {
51
+ detectedObjectIds
52
+ },
53
+ });
54
+
55
+ return { success: true, count: detectedObjectIds.length };
56
+ }
src/features/moderation/components/moderation-queue.tsx CHANGED
@@ -13,10 +13,11 @@ import { approveDetection } from '../actions/approve-detection';
13
  import { rejectDetection } from '../actions/reject-detection';
14
  import { bulkApprove } from '../actions/bulk-approve';
15
  import { bulkReject } from '../actions/bulk-reject';
 
16
  import { resetDetection } from '../actions/edit-detection';
17
  import { EditDetectionDialog } from './edit-detection-dialog';
18
  import { AddProductDialog } from './add-product-dialog';
19
- import { Check, X, ShieldCheck, ShoppingCart, Filter, CheckCircle2, Clock, CheckCircle, ShieldAlert, MousePointerClick, Heart } from 'lucide-react';
20
  import Image from 'next/image';
21
 
22
  interface Detection {
@@ -159,6 +160,18 @@ export function ModerationQueue({ initialDetections, userId, videos, stats, init
159
  });
160
  };
161
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  const handleReset = async (id: string) => {
163
  startTransition(async () => {
164
  const result = await resetDetection(id);
@@ -310,6 +323,9 @@ export function ModerationQueue({ initialDetections, userId, videos, stats, init
310
  <Button size="sm" onClick={handleBulkApprove} disabled={isPending}>
311
  <Check className="w-4 h-4 mr-1" /> Approve
312
  </Button>
 
 
 
313
  <Button size="sm" variant="destructive" onClick={handleBulkReject} disabled={isPending}>
314
  <X className="w-4 h-4 mr-1" /> Reject
315
  </Button>
 
13
  import { rejectDetection } from '../actions/reject-detection';
14
  import { bulkApprove } from '../actions/bulk-approve';
15
  import { bulkReject } from '../actions/bulk-reject';
16
+ import { triggerBulkMarketplaceMatchAction } from '@/features/marketplace/actions/trigger-bulk-marketplace-match';
17
  import { resetDetection } from '../actions/edit-detection';
18
  import { EditDetectionDialog } from './edit-detection-dialog';
19
  import { AddProductDialog } from './add-product-dialog';
20
+ import { Check, X, ShieldCheck, ShoppingCart, Filter, CheckCircle2, Clock, CheckCircle, ShieldAlert, MousePointerClick, Heart, Sparkles } from 'lucide-react';
21
  import Image from 'next/image';
22
 
23
  interface Detection {
 
160
  });
161
  };
162
 
163
+ const handleBulkGenerateLinks = async () => {
164
+ startTransition(async () => {
165
+ const result = await triggerBulkMarketplaceMatchAction(selectedIds);
166
+ if (result.success) {
167
+ toast.success(`Generation triggered for ${result.count} items. Results will appear in a few moments.`);
168
+ setSelectedIds([]);
169
+ } else {
170
+ toast.error(result.error || 'Failed to trigger link generation');
171
+ }
172
+ });
173
+ };
174
+
175
  const handleReset = async (id: string) => {
176
  startTransition(async () => {
177
  const result = await resetDetection(id);
 
323
  <Button size="sm" onClick={handleBulkApprove} disabled={isPending}>
324
  <Check className="w-4 h-4 mr-1" /> Approve
325
  </Button>
326
+ <Button size="sm" variant="outline" className="bg-emerald-500/10 hover:bg-emerald-500/20 text-emerald-500 border-none" onClick={handleBulkGenerateLinks} disabled={isPending}>
327
+ <Sparkles className="w-4 h-4 mr-1" /> Generate Links
328
+ </Button>
329
  <Button size="sm" variant="destructive" onClick={handleBulkReject} disabled={isPending}>
330
  <X className="w-4 h-4 mr-1" /> Reject
331
  </Button>