File size: 4,115 Bytes
ceb943f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
110
111
112
113
114
115
116
117
118
"use server";

import { db } from "@/lib/db";
import {
    detectedObjects,
    adminModeration,
    marketplaceMatches,
} from "@/lib/db/schema";
import { eq, and } from "drizzle-orm";
import { getAdminSession } from "@/lib/admin";
import { revalidatePath } from "next/cache";
import * as Sentry from "@sentry/nextjs";

export interface AdminCorrectionData {
    objectName?: string;
    category?: string;
    marketplaceLinkOverrides?: { matchId: string; affiliateUrl: string }[];
}

export async function adminCorrectDetection(
    detectionId: string,
    data: AdminCorrectionData
) {
    try {
        const session = await getAdminSession();
        if (!session) {
            return { success: false, error: "Unauthorized: admin access required" };
        }

        // 1. Fetch current detection for audit snapshot
        const current = await db.query.detectedObjects.findFirst({
            where: eq(detectedObjects.id, detectionId),
        });

        if (!current) {
            return { success: false, error: "Detection not found" };
        }

        // Fetch current marketplace matches for complete audit snapshot
        const currentMatches = await db
            .select({
                id: marketplaceMatches.id,
                affiliateUrl: marketplaceMatches.affiliateUrl,
                marketplace: marketplaceMatches.marketplace,
            })
            .from(marketplaceMatches)
            .where(eq(marketplaceMatches.objectId, detectionId));

        const originalValues = {
            objectName: current.objectName,
            category: current.category,
            status: current.status,
            marketplaceMatches: currentMatches.map(m => ({
                id: m.id,
                affiliateUrl: m.affiliateUrl,
                marketplace: m.marketplace,
            })),
        };

        // 2. Start transaction for atomicity
        return await db.transaction(async (tx) => {
            // 3. Update detected_objects
            await tx
                .update(detectedObjects)
                .set({
                    objectName: data.objectName ?? current.objectName,
                    category: (data.category as any) ?? current.category,
                    status: "pending_review", // Moves to creator queue
                    updatedAt: new Date(),
                })
                .where(eq(detectedObjects.id, detectionId));

            // 4. Update marketplace links if provided
            if (data.marketplaceLinkOverrides && data.marketplaceLinkOverrides.length > 0) {
                for (const override of data.marketplaceLinkOverrides) {
                    await tx
                        .update(marketplaceMatches)
                        .set({
                            affiliateUrl: override.affiliateUrl,
                            updatedAt: new Date()
                        })
                        .where(
                            and(
                                eq(marketplaceMatches.id, override.matchId),
                                eq(marketplaceMatches.objectId, detectionId)
                            )
                        );
                }
            }

            // 5. Insert audit log
            await tx.insert(adminModeration).values({
                id: crypto.randomUUID(),
                detectionId,
                adminId: session.user.id,
                action: "corrected",
                originalValues,
                correctedValues: {
                    ...data,
                    correctedAt: new Date().toISOString(),
                },
                trainAiFlag: true,
            });

            revalidatePath("/admin/moderation");
            revalidatePath("/dashboard/moderation");

            return { success: true };
        });
    } catch (error) {
        Sentry.captureException(error, {
            tags: { action: "admin-correct-detection", detection_id: detectionId },
        });
        console.error("Admin correction failed:", error);
        return { success: false, error: "Failed to apply admin correction" };
    }
}