2025-08-15 20:29:02 +01:00
|
|
|
import { db } from '@/lib/db';
|
|
|
|
import { scriptAnalytics, scripts } from '@/lib/db/schema';
|
|
|
|
import { eq, and, gte, lte, desc, count, sql } from 'drizzle-orm';
|
|
|
|
import { generateId, ApiError } from './index';
|
|
|
|
|
|
|
|
export interface TrackEventData {
|
|
|
|
scriptId: string;
|
|
|
|
eventType: 'view' | 'download' | 'share';
|
|
|
|
userId?: string;
|
|
|
|
userAgent?: string;
|
|
|
|
ipAddress?: string;
|
|
|
|
referrer?: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface AnalyticsFilters {
|
|
|
|
scriptId?: string;
|
|
|
|
eventType?: string;
|
|
|
|
startDate?: Date;
|
|
|
|
endDate?: Date;
|
|
|
|
userId?: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Track an analytics event
|
|
|
|
export async function trackEvent(data: TrackEventData) {
|
|
|
|
try {
|
2025-08-15 22:35:15 +01:00
|
|
|
await db.insert(scriptAnalytics).values({
|
2025-08-15 20:29:02 +01:00
|
|
|
id: generateId(),
|
|
|
|
scriptId: data.scriptId,
|
|
|
|
eventType: data.eventType,
|
|
|
|
userId: data.userId,
|
|
|
|
userAgent: data.userAgent,
|
|
|
|
ipAddress: data.ipAddress,
|
|
|
|
referrer: data.referrer,
|
|
|
|
createdAt: new Date(),
|
|
|
|
});
|
|
|
|
|
|
|
|
// Update script counters based on event type
|
|
|
|
if (data.eventType === 'view') {
|
|
|
|
await db
|
|
|
|
.update(scripts)
|
|
|
|
.set({
|
|
|
|
viewCount: sql`${scripts.viewCount} + 1`,
|
|
|
|
})
|
|
|
|
.where(eq(scripts.id, data.scriptId));
|
|
|
|
} else if (data.eventType === 'download') {
|
|
|
|
await db
|
|
|
|
.update(scripts)
|
|
|
|
.set({
|
|
|
|
downloadCount: sql`${scripts.downloadCount} + 1`,
|
|
|
|
})
|
|
|
|
.where(eq(scripts.id, data.scriptId));
|
|
|
|
}
|
|
|
|
|
|
|
|
return { success: true };
|
|
|
|
} catch (error) {
|
|
|
|
throw new ApiError(`Failed to track event: ${error}`, 500);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get analytics events with filters
|
|
|
|
export async function getAnalyticsEvents(filters: AnalyticsFilters = {}) {
|
|
|
|
try {
|
|
|
|
let query = db.select().from(scriptAnalytics);
|
|
|
|
let conditions: any[] = [];
|
|
|
|
|
|
|
|
if (filters.scriptId) {
|
|
|
|
conditions.push(eq(scriptAnalytics.scriptId, filters.scriptId));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (filters.eventType) {
|
|
|
|
conditions.push(eq(scriptAnalytics.eventType, filters.eventType));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (filters.userId) {
|
|
|
|
conditions.push(eq(scriptAnalytics.userId, filters.userId));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (filters.startDate) {
|
|
|
|
conditions.push(gte(scriptAnalytics.createdAt, filters.startDate));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (filters.endDate) {
|
|
|
|
conditions.push(lte(scriptAnalytics.createdAt, filters.endDate));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (conditions.length > 0) {
|
|
|
|
query = query.where(and(...conditions)) as any;
|
|
|
|
}
|
|
|
|
|
|
|
|
const events = await query.orderBy(desc(scriptAnalytics.createdAt));
|
|
|
|
return events;
|
|
|
|
} catch (error) {
|
|
|
|
throw new ApiError(`Failed to get analytics events: ${error}`, 500);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get analytics summary for a script
|
|
|
|
export async function getScriptAnalytics(scriptId: string, days: number = 30) {
|
|
|
|
try {
|
|
|
|
const startDate = new Date();
|
|
|
|
startDate.setDate(startDate.getDate() - days);
|
|
|
|
|
|
|
|
// Get event counts by type
|
|
|
|
const eventCounts = await db
|
|
|
|
.select({
|
|
|
|
eventType: scriptAnalytics.eventType,
|
|
|
|
count: count(scriptAnalytics.id),
|
|
|
|
})
|
|
|
|
.from(scriptAnalytics)
|
|
|
|
.where(
|
|
|
|
and(
|
|
|
|
eq(scriptAnalytics.scriptId, scriptId),
|
|
|
|
gte(scriptAnalytics.createdAt, startDate)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.groupBy(scriptAnalytics.eventType);
|
|
|
|
|
|
|
|
// Get daily activity
|
|
|
|
const dailyActivity = await db
|
|
|
|
.select({
|
|
|
|
date: sql<string>`DATE(${scriptAnalytics.createdAt})`,
|
|
|
|
eventType: scriptAnalytics.eventType,
|
|
|
|
count: count(scriptAnalytics.id),
|
|
|
|
})
|
|
|
|
.from(scriptAnalytics)
|
|
|
|
.where(
|
|
|
|
and(
|
|
|
|
eq(scriptAnalytics.scriptId, scriptId),
|
|
|
|
gte(scriptAnalytics.createdAt, startDate)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.groupBy(sql`DATE(${scriptAnalytics.createdAt})`, scriptAnalytics.eventType);
|
|
|
|
|
|
|
|
// Get referrer statistics
|
|
|
|
const referrers = await db
|
|
|
|
.select({
|
|
|
|
referrer: scriptAnalytics.referrer,
|
|
|
|
count: count(scriptAnalytics.id),
|
|
|
|
})
|
|
|
|
.from(scriptAnalytics)
|
|
|
|
.where(
|
|
|
|
and(
|
|
|
|
eq(scriptAnalytics.scriptId, scriptId),
|
|
|
|
gte(scriptAnalytics.createdAt, startDate)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.groupBy(scriptAnalytics.referrer)
|
|
|
|
.orderBy(desc(count(scriptAnalytics.id)))
|
|
|
|
.limit(10);
|
|
|
|
|
|
|
|
return {
|
|
|
|
eventCounts,
|
|
|
|
dailyActivity,
|
|
|
|
referrers,
|
|
|
|
periodDays: days,
|
|
|
|
};
|
|
|
|
} catch (error) {
|
|
|
|
throw new ApiError(`Failed to get script analytics: ${error}`, 500);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get platform-wide analytics (admin only)
|
|
|
|
export async function getPlatformAnalytics(days: number = 30) {
|
|
|
|
try {
|
|
|
|
const startDate = new Date();
|
|
|
|
startDate.setDate(startDate.getDate() - days);
|
|
|
|
|
|
|
|
// Total scripts and activity
|
|
|
|
const [totals] = await db
|
|
|
|
.select({
|
|
|
|
totalScripts: count(scripts.id),
|
|
|
|
approvedScripts: sql<number>`SUM(CASE WHEN ${scripts.isApproved} = 1 THEN 1 ELSE 0 END)`,
|
|
|
|
pendingScripts: sql<number>`SUM(CASE WHEN ${scripts.isApproved} = 0 THEN 1 ELSE 0 END)`,
|
|
|
|
})
|
|
|
|
.from(scripts);
|
|
|
|
|
|
|
|
// Activity by event type
|
|
|
|
const activityByType = await db
|
|
|
|
.select({
|
|
|
|
eventType: scriptAnalytics.eventType,
|
|
|
|
count: count(scriptAnalytics.id),
|
|
|
|
})
|
|
|
|
.from(scriptAnalytics)
|
|
|
|
.where(gte(scriptAnalytics.createdAt, startDate))
|
|
|
|
.groupBy(scriptAnalytics.eventType);
|
|
|
|
|
|
|
|
// Most popular scripts
|
|
|
|
const popularScripts = await db
|
|
|
|
.select({
|
|
|
|
scriptId: scriptAnalytics.scriptId,
|
|
|
|
scriptName: scripts.name,
|
|
|
|
views: count(scriptAnalytics.id),
|
|
|
|
})
|
|
|
|
.from(scriptAnalytics)
|
|
|
|
.innerJoin(scripts, eq(scriptAnalytics.scriptId, scripts.id))
|
|
|
|
.where(
|
|
|
|
and(
|
|
|
|
eq(scriptAnalytics.eventType, 'view'),
|
|
|
|
gte(scriptAnalytics.createdAt, startDate)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.groupBy(scriptAnalytics.scriptId, scripts.name)
|
|
|
|
.orderBy(desc(count(scriptAnalytics.id)))
|
|
|
|
.limit(10);
|
|
|
|
|
|
|
|
// Daily activity trends
|
|
|
|
const dailyTrends = await db
|
|
|
|
.select({
|
|
|
|
date: sql<string>`DATE(${scriptAnalytics.createdAt})`,
|
|
|
|
views: sql<number>`SUM(CASE WHEN ${scriptAnalytics.eventType} = 'view' THEN 1 ELSE 0 END)`,
|
|
|
|
downloads: sql<number>`SUM(CASE WHEN ${scriptAnalytics.eventType} = 'download' THEN 1 ELSE 0 END)`,
|
|
|
|
})
|
|
|
|
.from(scriptAnalytics)
|
|
|
|
.where(gte(scriptAnalytics.createdAt, startDate))
|
|
|
|
.groupBy(sql`DATE(${scriptAnalytics.createdAt})`)
|
|
|
|
.orderBy(sql`DATE(${scriptAnalytics.createdAt})`);
|
|
|
|
|
|
|
|
return {
|
|
|
|
totals,
|
|
|
|
activityByType,
|
|
|
|
popularScripts,
|
|
|
|
dailyTrends,
|
|
|
|
periodDays: days,
|
|
|
|
};
|
|
|
|
} catch (error) {
|
|
|
|
throw new ApiError(`Failed to get platform analytics: ${error}`, 500);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get user analytics
|
|
|
|
export async function getUserAnalytics(userId: string, days: number = 30) {
|
|
|
|
try {
|
|
|
|
const startDate = new Date();
|
|
|
|
startDate.setDate(startDate.getDate() - days);
|
|
|
|
|
|
|
|
// User's scripts performance
|
|
|
|
const userScriptsAnalytics = await db
|
|
|
|
.select({
|
|
|
|
scriptId: scripts.id,
|
|
|
|
scriptName: scripts.name,
|
|
|
|
views: scripts.viewCount,
|
|
|
|
downloads: scripts.downloadCount,
|
|
|
|
rating: scripts.rating,
|
|
|
|
ratingCount: scripts.ratingCount,
|
|
|
|
})
|
|
|
|
.from(scripts)
|
|
|
|
.where(eq(scripts.authorId, userId))
|
|
|
|
.orderBy(desc(scripts.viewCount));
|
|
|
|
|
|
|
|
// Recent activity on user's scripts
|
|
|
|
const recentActivity = await db
|
|
|
|
.select({
|
|
|
|
eventType: scriptAnalytics.eventType,
|
|
|
|
count: count(scriptAnalytics.id),
|
|
|
|
})
|
|
|
|
.from(scriptAnalytics)
|
|
|
|
.innerJoin(scripts, eq(scriptAnalytics.scriptId, scripts.id))
|
|
|
|
.where(
|
|
|
|
and(
|
|
|
|
eq(scripts.authorId, userId),
|
|
|
|
gte(scriptAnalytics.createdAt, startDate)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.groupBy(scriptAnalytics.eventType);
|
|
|
|
|
|
|
|
return {
|
|
|
|
userScripts: userScriptsAnalytics,
|
|
|
|
recentActivity,
|
|
|
|
periodDays: days,
|
|
|
|
};
|
|
|
|
} catch (error) {
|
|
|
|
throw new ApiError(`Failed to get user analytics: ${error}`, 500);
|
|
|
|
}
|
|
|
|
}
|