huhuih
hzgjuigik
This commit is contained in:
@@ -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 })
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user