From 85f31792ce8ca0d87aa25dfd70998c1aa5d0bb0e Mon Sep 17 00:00:00 2001 From: Akshay Date: Wed, 19 Nov 2025 16:03:10 +0530 Subject: [PATCH] feat: Add analytics tracking for event and hackathon creation and publication --- lib/services/analytics-service.ts | 2 +- lib/services/events.ts | 37 ++++++++++++++++--------- lib/services/hackathons.ts | 43 ++++++++++++++++++++---------- lib/services/moderation-service.ts | 29 ++++++++++++++++++++ 4 files changed, 84 insertions(+), 27 deletions(-) diff --git a/lib/services/analytics-service.ts b/lib/services/analytics-service.ts index f212891c..623a2e55 100644 --- a/lib/services/analytics-service.ts +++ b/lib/services/analytics-service.ts @@ -149,7 +149,7 @@ export class AnalyticsService { /** * Increment a specific field in company analytics */ - private static async incrementCompanyAnalytics( + static async incrementCompanyAnalytics( companyId: string, field: string, increment: number diff --git a/lib/services/events.ts b/lib/services/events.ts index be3febc8..be166f0f 100644 --- a/lib/services/events.ts +++ b/lib/services/events.ts @@ -2,6 +2,7 @@ import { createClient } from '@/lib/supabase/server' import { Event, EventsFilters, EventsResponse } from '@/types/events' import { companyService } from './company-service' +import { AnalyticsService } from './analytics-service' // Re-export types for convenience export type { Event, EventsFilters, EventsResponse } @@ -57,7 +58,7 @@ class EventsService { } const supabase = await createClient() - + let query = supabase .from('events') .select(` @@ -160,7 +161,7 @@ class EventsService { } const supabase = await createClient() - + const { data: event, error } = await supabase .from('events') .select(` @@ -204,7 +205,7 @@ class EventsService { } const supabase = await createClient() - + const { data: event, error } = await supabase .from('events') .select(` @@ -244,7 +245,7 @@ class EventsService { try { const supabase = await createClient() - + const { data: events, error } = await supabase .from('events') .select(` @@ -356,12 +357,12 @@ class EventsService { // Payment constraint: if price is "Free", payment must be "Not Required" // if price is not "Free", payment must be "Required" const paymentValue = eventData.price === 'Free' ? 'Not Required' : (eventData.payment || 'Required') - + // Remove status field - events start as draft by default // They can be submitted for approval later via submitForApproval // eslint-disable-next-line @typescript-eslint/no-unused-vars const { status, ...eventDataWithoutStatus } = eventData - + const eventPayload = { ...eventDataWithoutStatus, company_id: companyId, @@ -393,6 +394,18 @@ class EventsService { throw new EventError('Failed to create event', EventErrorCodes.NOT_FOUND, 500) } + // Track analytics for event creation + try { + await AnalyticsService.incrementCompanyAnalytics( + companyId, + 'events_created', + 1 + ) + } catch (analyticsError) { + console.error('Error tracking event creation analytics:', analyticsError) + // Don't fail the event creation if analytics tracking fails + } + clearCache() return event } @@ -438,7 +451,7 @@ class EventsService { // Check if this is an approved event being edited // If so, reset to pending status for re-approval const needsReapproval = existingEvent.approval_status === 'approved' - + // Update the updated_at timestamp const updatePayload: Record = { ...updateData, @@ -479,17 +492,17 @@ class EventsService { // Import notification service dynamically to avoid circular dependencies const { NotificationService } = await import('./notification-service') const { moderationService } = await import('./moderation-service') - + // Log the edit action await moderationService.logModerationAction('edited', id, undefined, userId, 'Event edited after approval - requires re-approval') - + // Notify admins about the updated event // Get all admin users const { data: adminUsers } = await supabase .from('profiles') .select('id') .eq('role', 'admin') - + if (adminUsers && adminUsers.length > 0) { // Create notifications for all admins const notifications = adminUsers.map(admin => ({ @@ -506,11 +519,11 @@ class EventsService { event_slug: existingEvent.slug } })) - + await supabase.from('notifications').insert(notifications) console.log(`📧 Notified ${adminUsers.length} admin(s) about updated event`) } - + // Notify company members about the status change if (existingEvent.company_id) { await NotificationService.notifyCompanyMembers(existingEvent.company_id, { diff --git a/lib/services/hackathons.ts b/lib/services/hackathons.ts index 8eedba63..10bbd78c 100644 --- a/lib/services/hackathons.ts +++ b/lib/services/hackathons.ts @@ -1,6 +1,7 @@ // Server-side service for hackathons import { createClient } from '@/lib/supabase/server' import { Hackathon, HackathonsFilters, HackathonsResponse } from '@/types/hackathons' +import { AnalyticsService } from './analytics-service' // Re-export types for convenience export type { Hackathon, HackathonsFilters, HackathonsResponse } @@ -30,7 +31,7 @@ class HackathonsService { } const supabase = await createClient() - + let query = supabase .from('hackathons') .select(` @@ -118,7 +119,7 @@ class HackathonsService { } const supabase = await createClient() - + const { data: hackathon, error } = await supabase .from('hackathons') .select(` @@ -149,7 +150,7 @@ class HackathonsService { try { const supabase = await createClient() - + const { data: hackathons, error } = await supabase .from('hackathons') .select(` @@ -180,7 +181,7 @@ class HackathonsService { async createHackathon(hackathonData: Omit): Promise { const supabase = await createClient() - + const { data: hackathon, error } = await supabase .from('hackathons') .insert([hackathonData]) @@ -192,18 +193,32 @@ class HackathonsService { throw new Error('Failed to create hackathon') } + // Track analytics for hackathon creation + if (hackathonData.company_id) { + try { + await AnalyticsService.incrementCompanyAnalytics( + hackathonData.company_id, + 'hackathons_created', + 1 + ) + } catch (analyticsError) { + console.error('Error tracking hackathon creation analytics:', analyticsError) + // Don't fail the hackathon creation if analytics tracking fails + } + } + // Clear cache after creating new hackathon cache.clear() return hackathon } async updateHackathon( - slug: string, + slug: string, hackathonData: Partial>, userId?: string ): Promise { const supabase = await createClient() - + // Get existing hackathon first const existingHackathon = await this.getHackathonBySlug(slug) if (!existingHackathon) { @@ -212,7 +227,7 @@ class HackathonsService { // Check if this is an approved hackathon being edited const needsReapproval = existingHackathon.approval_status === 'approved' - + // Prepare update payload const updatePayload: Record = { ...hackathonData, @@ -253,16 +268,16 @@ class HackathonsService { // Import services dynamically to avoid circular dependencies const { NotificationService } = await import('./notification-service') const { moderationService } = await import('./moderation-service') - + // Log the edit action await moderationService.logModerationAction('edited', undefined, hackathonId, userId, 'Hackathon edited after approval - requires re-approval') - + // Notify admins about the updated hackathon const { data: adminUsers } = await supabase .from('profiles') .select('id') .eq('role', 'admin') - + if (adminUsers && adminUsers.length > 0) { const notifications = adminUsers.map(admin => ({ user_id: admin.id, @@ -278,11 +293,11 @@ class HackathonsService { hackathon_slug: existingHackathon.slug } })) - + await supabase.from('notifications').insert(notifications) console.log(`📧 Notified ${adminUsers.length} admin(s) about updated hackathon`) } - + // Notify company members about the status change if (existingHackathon.company_id) { await NotificationService.notifyCompanyMembers(existingHackathon.company_id, { @@ -309,9 +324,9 @@ class HackathonsService { async deleteHackathon(slug: string) { const supabase = await createClient() - + console.log('🗑️ Deleting hackathon with slug:', slug) - + const { data, error } = await supabase .from('hackathons') .delete() diff --git a/lib/services/moderation-service.ts b/lib/services/moderation-service.ts index d765b917..a1bcb120 100644 --- a/lib/services/moderation-service.ts +++ b/lib/services/moderation-service.ts @@ -4,6 +4,7 @@ import { Event } from '@/types/events' import { Hackathon } from '@/types/hackathons' import { companyService } from './company-service' import { SUBSCRIPTION_LIMITS } from '@/types/company' +import { AnalyticsService } from './analytics-service' // Moderation log entry interface export interface ModerationLog { @@ -391,6 +392,20 @@ class ModerationService { // Log the moderation action await this.logModerationAction('approved', eventId, undefined, adminId, notes) + // Track analytics for event publication + if (updatedEvent.company_id) { + try { + await AnalyticsService.incrementCompanyAnalytics( + updatedEvent.company_id, + 'events_published', + 1 + ) + } catch (analyticsError) { + console.error('Error tracking event publication analytics:', analyticsError) + // Don't fail the approval if analytics tracking fails + } + } + return updatedEvent as Event } @@ -619,6 +634,20 @@ class ModerationService { // Log the moderation action await this.logModerationAction('approved', undefined, hackathonId, adminId, notes) + // Track analytics for hackathon publication + if (updatedHackathon.company_id) { + try { + await AnalyticsService.incrementCompanyAnalytics( + updatedHackathon.company_id, + 'hackathons_published', + 1 + ) + } catch (analyticsError) { + console.error('Error tracking hackathon publication analytics:', analyticsError) + // Don't fail the approval if analytics tracking fails + } + } + return updatedHackathon as Hackathon }