Files
thrilltrack-explorer/src-old/lib/moderation/realtime.ts

210 lines
5.8 KiB
TypeScript

/**
* 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<ModerationItem> {
const changes: Partial<ModerationItem> = {};
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,
};
}