feat: Implement submission workflow tracking

This commit is contained in:
gpt-engineer-app[bot]
2025-10-17 20:33:20 +00:00
parent 9f5e19922c
commit db84e99746
2 changed files with 256 additions and 3 deletions

View File

@@ -15,7 +15,11 @@ export type ActivityType =
| 'user_banned'
| 'user_unbanned'
| 'review_created'
| 'review_deleted';
| 'review_deleted'
| 'submission_created'
| 'submission_claimed'
| 'submission_escalated'
| 'submission_reassigned';
export interface ActivityActor {
id: string;
@@ -99,6 +103,20 @@ export interface ReviewLifecycleDetails {
was_moderated?: boolean;
}
export interface SubmissionWorkflowDetails {
submission_id: string;
submission_type: string;
user_id?: string;
username?: string;
assigned_to?: string;
assigned_username?: string;
escalation_reason?: string;
from_moderator?: string;
from_moderator_username?: string;
to_moderator?: string;
to_moderator_username?: string;
}
export type ActivityDetails =
| EntityChangeDetails
| AdminActionDetails
@@ -107,7 +125,8 @@ export type ActivityDetails =
| ReviewModerationDetails
| PhotoApprovalDetails
| AccountLifecycleDetails
| ReviewLifecycleDetails;
| ReviewLifecycleDetails
| SubmissionWorkflowDetails;
export interface SystemActivity {
id: string;
@@ -588,6 +607,87 @@ export async function fetchSystemActivities(
}
}
// Fetch submission workflow events (recent 7 days)
// 1. Submission creations
const { data: newSubmissions, error: newSubmissionsError } = await supabase
.from('content_submissions')
.select('id, user_id, submission_type, submitted_at')
.gte('submitted_at', sevenDaysAgo.toISOString())
.order('submitted_at', { ascending: false })
.limit(Math.ceil(limit / 2));
if (!newSubmissionsError && newSubmissions) {
for (const submission of newSubmissions) {
activities.push({
id: `submission-created-${submission.id}`,
type: 'submission_created',
timestamp: submission.submitted_at,
actor_id: submission.user_id,
action: 'created submission',
details: {
submission_id: submission.id,
submission_type: submission.submission_type,
user_id: submission.user_id,
} as SubmissionWorkflowDetails,
});
}
}
// 2. Submission claims/assignments
const { data: claimedSubmissions, error: claimsError } = await supabase
.from('content_submissions')
.select('id, submission_type, assigned_to, assigned_at, user_id')
.not('assigned_at', 'is', null)
.gte('assigned_at', sevenDaysAgo.toISOString())
.order('assigned_at', { ascending: false })
.limit(Math.ceil(limit / 2));
if (!claimsError && claimedSubmissions) {
for (const submission of claimedSubmissions) {
activities.push({
id: `submission-claimed-${submission.id}`,
type: 'submission_claimed',
timestamp: submission.assigned_at!,
actor_id: submission.assigned_to,
action: 'claimed submission',
details: {
submission_id: submission.id,
submission_type: submission.submission_type,
user_id: submission.user_id,
assigned_to: submission.assigned_to,
} as SubmissionWorkflowDetails,
});
}
}
// 3. Submission escalations
const { data: escalatedSubmissions, error: escalationsError } = await supabase
.from('content_submissions')
.select('id, submission_type, escalated_by, escalated_at, escalation_reason, user_id')
.eq('escalated', true)
.not('escalated_at', 'is', null)
.gte('escalated_at', sevenDaysAgo.toISOString())
.order('escalated_at', { ascending: false })
.limit(Math.ceil(limit / 2));
if (!escalationsError && escalatedSubmissions) {
for (const submission of escalatedSubmissions) {
activities.push({
id: `submission-escalated-${submission.id}`,
type: 'submission_escalated',
timestamp: submission.escalated_at!,
actor_id: submission.escalated_by,
action: 'escalated submission',
details: {
submission_id: submission.id,
submission_type: submission.submission_type,
user_id: submission.user_id,
escalation_reason: submission.escalation_reason || undefined,
} as SubmissionWorkflowDetails,
});
}
}
// Fetch review lifecycle events
// 1. Review creations (recent 7 days)
const { data: newReviews, error: newReviewsError } = await supabase
@@ -780,6 +880,48 @@ export async function fetchSystemActivities(
}
}
// Enrich submission workflow users
const submissionWorkflowUserIds = filteredActivities
.filter(a => ['submission_created', 'submission_claimed', 'submission_escalated', 'submission_reassigned'].includes(a.type))
.flatMap(a => {
const details = a.details as SubmissionWorkflowDetails;
return [details.user_id, details.assigned_to, details.from_moderator, details.to_moderator].filter(Boolean);
})
.filter(Boolean) as string[];
if (submissionWorkflowUserIds.length > 0) {
const { data: submissionProfiles } = await supabase
.from('profiles')
.select('user_id, username')
.in('user_id', submissionWorkflowUserIds);
if (submissionProfiles) {
const submissionProfileMap = new Map(submissionProfiles.map(p => [p.user_id, p]));
for (const activity of filteredActivities) {
if (['submission_created', 'submission_claimed', 'submission_escalated', 'submission_reassigned'].includes(activity.type)) {
const details = activity.details as SubmissionWorkflowDetails;
if (details.user_id && !details.username) {
const profile = submissionProfileMap.get(details.user_id);
if (profile) details.username = profile.username;
}
if (details.assigned_to && !details.assigned_username) {
const profile = submissionProfileMap.get(details.assigned_to);
if (profile) details.assigned_username = profile.username;
}
if (details.from_moderator && !details.from_moderator_username) {
const profile = submissionProfileMap.get(details.from_moderator);
if (profile) details.from_moderator_username = profile.username;
}
if (details.to_moderator && !details.to_moderator_username) {
const profile = submissionProfileMap.get(details.to_moderator);
if (profile) details.to_moderator_username = profile.username;
}
}
}
}
}
// Enrich review entity names
const parkReviewIds = filteredActivities
.filter(a => ['review_created', 'review_deleted'].includes(a.type) && (a.details as ReviewLifecycleDetails).entity_type === 'park')