Spaces:
Runtime error
Runtime error
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 | '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' }; | |
| } | |
| } | |