Update package dependencies, enhance README for clarity, and implement new features in the admin panel and script detail pages. Added support for collections, improved script submission previews, and refactored comment handling in the script detail view.
This commit is contained in:
274
src/lib/api/analytics.ts
Normal file
274
src/lib/api/analytics.ts
Normal file
@ -0,0 +1,274 @@
|
||||
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 {
|
||||
const eventRecord = await db.insert(scriptAnalytics).values({
|
||||
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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user