/** * Realtime Subscription Utilities * * Helper functions for processing realtime subscription events in the moderation queue. */ import type { ModerationItem, EntityFilter, StatusFilter } from '@/types/moderation'; interface SubmissionContent { name?: string; [key: string]: any; } /** * Check if a submission matches the entity filter */ export function matchesEntityFilter( submission: { submission_type: string }, entityFilter: EntityFilter ): boolean { if (entityFilter === 'all') return true; if (entityFilter === 'photos') { return submission.submission_type === 'photo'; } if (entityFilter === 'submissions') { return submission.submission_type !== 'photo'; } if (entityFilter === 'reviews') { return submission.submission_type === 'review'; } return false; } /** * Check if a submission matches the status filter */ export function matchesStatusFilter( submission: { status: string }, statusFilter: StatusFilter ): boolean { if (statusFilter === 'all') return true; if (statusFilter === 'pending') { return ['pending', 'partially_approved'].includes(submission.status); } return statusFilter === submission.status; } /** * Deep comparison of ModerationItem fields to detect actual changes */ export function hasItemChanged( current: ModerationItem, updated: ModerationItem ): boolean { // Check critical fields if ( current.status !== updated.status || current.reviewed_at !== updated.reviewed_at || current.reviewer_notes !== updated.reviewer_notes || current.assigned_to !== updated.assigned_to || current.locked_until !== updated.locked_until || current.escalated !== updated.escalated ) { return true; } // Check submission_items if (current.submission_items?.length !== updated.submission_items?.length) { return true; } // Check content (one level deep for performance) if (current.content && updated.content) { // Compare content reference first if (current.content !== updated.content) { const currentKeys = Object.keys(current.content).sort(); const updatedKeys = Object.keys(updated.content).sort(); // Different number of keys = changed if (currentKeys.length !== updatedKeys.length) { return true; } // Different key names = changed if (!currentKeys.every((key, i) => key === updatedKeys[i])) { return true; } // Check each key's value for (const key of currentKeys) { if (current.content[key] !== updated.content[key]) { return true; } } } } return false; } /** * Extract only changed fields for minimal updates */ export function extractChangedFields( current: ModerationItem, updated: ModerationItem ): Partial { const changes: Partial = {}; if (current.status !== updated.status) { changes.status = updated.status; } if (current.reviewed_at !== updated.reviewed_at) { changes.reviewed_at = updated.reviewed_at; } if (current.reviewer_notes !== updated.reviewer_notes) { changes.reviewer_notes = updated.reviewer_notes; } if (current.assigned_to !== updated.assigned_to) { changes.assigned_to = updated.assigned_to; } if (current.locked_until !== updated.locked_until) { changes.locked_until = updated.locked_until; } if (current.escalated !== updated.escalated) { changes.escalated = updated.escalated; } // Check content changes if (current.content !== updated.content) { changes.content = updated.content; } // Check submission_items if (updated.submission_items) { changes.submission_items = updated.submission_items; } return changes; } /** * Build a full ModerationItem from submission data */ export function buildModerationItem( submission: any, profile?: any, entityName?: string, parkName?: string ): ModerationItem { return { id: submission.id, type: 'content_submission', content: submission.content, // Handle both created_at (from view) and submitted_at (from realtime) created_at: submission.created_at || submission.submitted_at, submitted_at: submission.submitted_at, // Support both user_id and submitter_id user_id: submission.user_id || submission.submitter_id, submitter_id: submission.submitter_id || submission.user_id, status: submission.status, submission_type: submission.submission_type, // Use new profile structure from view if available submitter_profile: submission.submitter_profile || (profile ? { user_id: submission.user_id || submission.submitter_id, username: profile.username, display_name: profile.display_name, avatar_url: profile.avatar_url, } : undefined), reviewer_profile: submission.reviewer_profile, assigned_profile: submission.assigned_profile, // Legacy support: create user_profile from submitter_profile user_profile: submission.submitter_profile ? { username: submission.submitter_profile.username, display_name: submission.submitter_profile.display_name, avatar_url: submission.submitter_profile.avatar_url, } : (profile ? { username: profile.username, display_name: profile.display_name, avatar_url: profile.avatar_url, } : undefined), entity_name: entityName || (submission.content as SubmissionContent)?.name || 'Unknown', park_name: parkName, reviewed_at: submission.reviewed_at || undefined, reviewer_notes: submission.reviewer_notes || undefined, escalated: submission.escalated || false, assigned_to: submission.assigned_to || undefined, locked_until: submission.locked_until || undefined, submission_items: submission.submission_items || undefined, }; }