Full analytics platform with MaxMind GeoLite2 IP-to-location resolution, cross-module dashboard (docs, video, photo), user drill-down, volunteer self-service stats, and ANALYTICS_ADMIN role with feature flag controls. - ANALYTICS_ADMIN role + ANALYTICS_ROLES group across backend and frontend - GeoIP service (MaxMind GeoLite2, lazy-loaded, graceful degradation) - Geo fields (country, region, city, lat/lng) on DocsPageView, VideoView, PhotoView - IP resolved to geo before SHA-256 hashing (privacy-preserving) - Unified analytics module: overview, geo, content, user engagement endpoints - 4 admin dashboard pages: Overview, Geography (Leaflet map), Content, Users - Volunteer MyAnalyticsPage for self-service activity stats - Settings UI: enableAnalytics, analyticsGeoEnabled, trackAuthenticatedUsers, retentionDays - Scheduled cleanup job respecting configurable retention period - config.sh: Analytics + MaxMind prompt in configure_features() - Control panel: enableAnalytics flag, template, discovery, wizard, detail page - Docker: geoip volume mount, MaxMind env vars, entrypoint auto-download - Nginx: X-Forwarded-For fix ($proxy_add_x_forwarded_for) for real client IP - Express trust proxy set to 2 for Pangolin/Newt tunnel chain - CORS updated for docs origin (cmlite.org + docs.cmlite.org) - Lander page: added docs-analytics tracking snippet - Prisma migration: 20260402100000_add_analytics_system Bunker Admin
24 lines
732 B
TypeScript
24 lines
732 B
TypeScript
import { Router } from 'express';
|
|
import { authenticate } from '../../middleware/auth.middleware';
|
|
import { validate } from '../../middleware/validate';
|
|
import { analyticsService } from './analytics.service';
|
|
import { userDetailQuerySchema } from './analytics.schemas';
|
|
|
|
export const analyticsUserRouter = Router();
|
|
analyticsUserRouter.use(authenticate);
|
|
|
|
// GET /api/analytics/my-activity?days=30
|
|
analyticsUserRouter.get(
|
|
'/my-activity',
|
|
validate(userDetailQuerySchema, 'query'),
|
|
async (req, res, next) => {
|
|
try {
|
|
const days = Number(req.query.days) || 30;
|
|
const activity = await analyticsService.getMyActivity(req.user!.id, days);
|
|
res.json(activity);
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
},
|
|
);
|