dvijaykrishnan's picture
feat: Implement a new product form for creating and editing product details, including image uploads and marketplace metadata fetching, and refine video analysis skipping logic.
931547f
Raw
History Blame Contribute Delete
8.04 kB
'use server';
import { db } from '@/lib/db';
import { detectedObjects, youtubeVideos, youtubeChannels, marketplaceMatches } from '@/lib/db/schema';
import { eq, and } from 'drizzle-orm';
import { auth } from '@/lib/auth';
import { headers } from 'next/headers';
import { revalidatePath } from 'next/cache';
import * as Sentry from '@sentry/nextjs';
import { isDemoDetection, DEMO_USER_ID } from '../utils/demo';
export interface NewMarketplaceMatch {
marketplace: 'amazon' | 'ebay' | 'etsy';
productName: string;
price: number;
affiliateUrl: string;
}
export interface EditDetectionData {
objectName?: string;
category?: 'Tech' | 'Fashion' | 'Furniture' | 'Audio' | 'Other';
thumbnailUrl?: string;
marketplaceLinkOverrides?: Record<string, string>; // matchId → new affiliateUrl
marketplaceMatchUpdates?: Record<string, {
marketplace?: 'amazon' | 'ebay' | 'etsy';
productName?: string;
price?: number;
affiliateUrl?: string;
}>;
newMarketplaceMatch?: NewMarketplaceMatch;
}
export async function editDetection(detectionId: string, data: EditDetectionData) {
console.log(`[editDetection] Input for ${detectionId}:`, JSON.stringify(data, null, 2));
console.log(`[editDetection] Starting update for ${detectionId}`, JSON.stringify(data, null, 2));
try {
const session = await auth.api.getSession({
headers: await headers(),
});
const isDemo = await isDemoDetection(detectionId);
if (!session?.user && !isDemo) {
return { success: false, error: 'Unauthorized' };
}
const effectiveUserId = session?.user?.id || DEMO_USER_ID;
// Verify user owns this detection
const detection = await db
.select({
id: detectedObjects.id,
creatorId: youtubeChannels.creatorId,
moderationMetadata: detectedObjects.moderationMetadata,
objectName: detectedObjects.objectName,
category: detectedObjects.category,
})
.from(detectedObjects)
.innerJoin(youtubeVideos, eq(detectedObjects.videoId, youtubeVideos.id))
.innerJoin(youtubeChannels, eq(youtubeVideos.channelId, youtubeChannels.id))
.where(eq(detectedObjects.id, detectionId))
.limit(1)
.then(res => res[0]);
if (!detection || detection.creatorId !== effectiveUserId) {
return { success: false, error: 'Detection not found or unauthorized' };
}
// Prepare metadata with history
const currentMetadata = (detection.moderationMetadata as any) || { editHistory: [] };
const historyEntry = {
previousName: detection.objectName,
previousCategory: detection.category,
marketplaceLinkChanges: data.marketplaceLinkOverrides || null,
editedAt: new Date().toISOString(),
editedBy: effectiveUserId,
};
const newMetadata = {
...currentMetadata,
editHistory: [...(currentMetadata.editHistory || []), historyEntry]
};
console.log(`[editDetection] Updating detection:`, {
objectName: data.objectName,
category: data.category,
thumbnailUrl: data.thumbnailUrl
});
// Update detection
await db
.update(detectedObjects)
.set({
objectName: data.objectName ?? detection.objectName,
category: (data.category as any) ?? detection.category,
thumbnailUrl: data.thumbnailUrl !== undefined ? data.thumbnailUrl : undefined,
moderationMetadata: newMetadata,
updatedAt: new Date(),
})
.where(eq(detectedObjects.id, detectionId));
// Update marketplace link overrides if provided
if (data.marketplaceLinkOverrides) {
for (const [matchId, affiliateUrl] of Object.entries(data.marketplaceLinkOverrides)) {
await db
.update(marketplaceMatches)
.set({ affiliateUrl })
.where(
and(
eq(marketplaceMatches.id, matchId),
eq(marketplaceMatches.objectId, detectionId)
)
);
}
}
// Update detection matches with full details
if (data.marketplaceMatchUpdates) {
for (const [matchId, updates] of Object.entries(data.marketplaceMatchUpdates)) {
await db
.update(marketplaceMatches)
.set({
...(updates.marketplace ? { marketplace: updates.marketplace } : {}),
...(updates.productName ? { productName: updates.productName } : {}),
...(updates.price !== undefined ? { price: updates.price } : {}),
...(updates.affiliateUrl ? { affiliateUrl: updates.affiliateUrl } : {}),
updatedAt: new Date(),
})
.where(
and(
eq(marketplaceMatches.id, matchId),
eq(marketplaceMatches.objectId, detectionId)
)
);
}
}
// Insert a new marketplace match if provided
if (data.newMarketplaceMatch) {
await db
.insert(marketplaceMatches)
.values({
objectId: detectionId,
marketplace: data.newMarketplaceMatch.marketplace,
productId: `manual-${Date.now()}`,
productName: data.newMarketplaceMatch.productName,
price: data.newMarketplaceMatch.price,
availabilityStatus: 'IN_STOCK',
affiliateUrl: data.newMarketplaceMatch.affiliateUrl,
});
}
revalidatePath('/dashboard/moderation');
return { success: true };
} catch (error) {
Sentry.captureException(error, {
tags: { action: 'edit-detection', detection_id: detectionId },
});
return { success: false, error: 'Failed to edit detection' };
}
}
export async function resetDetection(detectionId: string) {
try {
const session = await auth.api.getSession({
headers: await headers(),
});
const isDemo = await isDemoDetection(detectionId);
if (!session?.user && !isDemo) {
return { success: false, error: 'Unauthorized' };
}
const effectiveUserId = session?.user?.id || DEMO_USER_ID;
const detection = await db
.select({
id: detectedObjects.id,
creatorId: youtubeChannels.creatorId,
})
.from(detectedObjects)
.innerJoin(youtubeVideos, eq(detectedObjects.videoId, youtubeVideos.id))
.innerJoin(youtubeChannels, eq(youtubeVideos.channelId, youtubeChannels.id))
.where(eq(detectedObjects.id, detectionId))
.limit(1)
.then(res => res[0]);
if (!detection || detection.creatorId !== effectiveUserId) {
return { success: false, error: 'Detection not found or unauthorized' };
}
await db
.update(detectedObjects)
.set({
moderationStatus: 'PENDING',
moderatedAt: null,
moderatedBy: null,
})
.where(eq(detectedObjects.id, detectionId));
revalidatePath('/dashboard/moderation');
return { success: true };
} catch (error) {
Sentry.captureException(error, {
tags: { action: 'reset-detection', detection_id: detectionId },
});
return { success: false, error: 'Failed to reset detection' };
}
}