hzgjuigik
This commit is contained in:
2026-01-27 21:06:48 +01:00
parent 18c11d27bc
commit 6da8ce1cbd
51 changed files with 6208 additions and 974 deletions

View File

@@ -4,11 +4,57 @@
*/
import express from 'express'
import { asyncHandler } from '../middleware/errorHandler.mjs'
import { asyncHandler, ValidationError } from '../middleware/errorHandler.mjs'
import { respond } from '../utils/response.mjs'
import { db, Collections } from '../services/database.mjs'
import { log } from '../middleware/logger.mjs'
const router = express.Router()
// Whitelist of allowed event types
const ALLOWED_EVENT_TYPES = [
'page_view',
'signup',
'trial_start',
'purchase',
'email_connected',
'onboarding_step',
'provider_connected',
'demo_used',
'suggested_rules_generated',
'rule_created',
'rules_applied',
'limit_reached',
'upgrade_clicked',
'referral_shared',
'sort_completed',
'account_deleted',
]
// Fields that should never be stored (PII)
const PII_FIELDS = ['email', 'password', 'emailContent', 'emailBody', 'subject', 'from', 'to', 'snippet', 'content']
function stripPII(metadata) {
if (!metadata || typeof metadata !== 'object') return {}
const cleaned = {}
for (const [key, value] of Object.entries(metadata)) {
if (PII_FIELDS.includes(key.toLowerCase())) {
continue // Skip PII fields
}
if (typeof value === 'string' && value.includes('@')) {
// Skip if looks like email
continue
}
if (typeof value === 'object' && value !== null) {
cleaned[key] = stripPII(value)
} else {
cleaned[key] = value
}
}
return cleaned
}
/**
* POST /api/analytics/track
* Track analytics events (page views, conversions, etc.)
@@ -39,29 +85,45 @@ router.post('/track', asyncHandler(async (req, res) => {
timestamp,
page,
referrer,
sessionId,
} = req.body
// Log analytics event (in production, send to analytics service)
// Validate event type
if (!type || !ALLOWED_EVENT_TYPES.includes(type)) {
throw new ValidationError(`Invalid event type. Allowed: ${ALLOWED_EVENT_TYPES.join(', ')}`)
}
// Strip PII from metadata
const cleanedMetadata = stripPII(metadata || {})
// Prepare event data
const eventData = {
userId: userId || null,
eventType: type,
metadataJson: JSON.stringify(cleanedMetadata),
timestamp: timestamp || new Date().toISOString(),
sessionId: sessionId || null,
}
// Store in database
try {
await db.create(Collections.ANALYTICS_EVENTS, eventData)
log.info(`Analytics event tracked: ${type}`, { userId, sessionId })
} catch (err) {
log.warn('Failed to store analytics event', { error: err.message, type })
// Don't fail the request if analytics storage fails
}
// Log in development
if (process.env.NODE_ENV === 'development') {
console.log('📊 Analytics Event:', {
type,
userId,
tracking,
metadata,
timestamp,
page,
referrer,
sessionId,
metadata: cleanedMetadata,
})
}
// TODO: Store in database for analytics dashboard
// For now, just log to console
// In production, you might want to:
// - Store in database
// - Send to Google Analytics / Plausible / etc.
// - Send to Mixpanel / Amplitude
// - Log to external analytics service
// Return success (client doesn't need to wait)
respond.success(res, { received: true })
}))