HIGH PRIORITY: Fixes production 404 error on research inquiry form Research Inquiry API: - Add POST /api/research-inquiry endpoint for form submissions - Add admin endpoints for inquiry management (list, get, assign, respond, delete) - Create ResearchInquiry model with MongoDB integration - Add to moderation queue for human review (strategic quadrant) - Include rate limiting (5 req/min) and CSRF protection - Tested locally: endpoint responding, data saving to DB Umami Analytics (Privacy-First): - Add Docker Compose config for Umami + PostgreSQL - Create nginx reverse proxy config with SSL support - Implement privacy-first tracking script (DNT, opt-out, no cookies) - Integrate tracking across 26 public HTML pages - Exclude admin pages from tracking (privacy boundary) - Add comprehensive deployment guide (UMAMI_SETUP_GUIDE.md) - Environment variables added to .env.example Files Created (9): - src/models/ResearchInquiry.model.js - src/controllers/research.controller.js - src/routes/research.routes.js - public/js/components/umami-tracker.js - deployment-quickstart/nginx-analytics.conf - deployment-quickstart/UMAMI_SETUP_GUIDE.md - scripts/add-umami-tracking.sh - scripts/add-tracking-python.py - SESSION_SUMMARY_ANALYTICS_RESEARCH_INQUIRY.md Files Modified (29): - src/routes/index.js (research routes) - deployment-quickstart/docker-compose.yml (umami services) - deployment-quickstart/.env.example (umami config) - 26 public HTML pages (tracking script) Values Alignment: ✅ Privacy-First Design (cookie-free, DNT honored, opt-out available) ✅ Human Agency (research inquiries require human review) ✅ Data Sovereignty (self-hosted analytics, no third-party sharing) ✅ GDPR Compliance (no personal data in analytics) ✅ Transparency (open-source tools, documented setup) Testing Status: ✅ Research inquiry: Locally tested, data verified in MongoDB ⏳ Umami analytics: Pending production deployment Next Steps: 1. Deploy to production (./scripts/deploy.sh) 2. Test research form on live site 3. Deploy Umami following UMAMI_SETUP_GUIDE.md 4. Update umami-tracker.js with website ID after setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
132 lines
4 KiB
JavaScript
132 lines
4 KiB
JavaScript
/**
|
|
* Umami Analytics - Privacy-First Tracking
|
|
* No cookies, no personal data, GDPR-compliant
|
|
*
|
|
* Features:
|
|
* - Respects Do Not Track (DNT) browser setting
|
|
* - Honors user opt-out preference
|
|
* - Disabled in development environment
|
|
* - Lightweight async loading
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
// Configuration
|
|
const CONFIG = {
|
|
// NOTE: Replace this with actual website ID from Umami dashboard after setup
|
|
websiteId: 'REPLACE_WITH_ACTUAL_WEBSITE_ID',
|
|
domain: 'agenticgovernance.digital',
|
|
scriptSrc: 'https://analytics.agenticgovernance.digital/script.js',
|
|
autoTrack: true
|
|
};
|
|
|
|
// Development environment check
|
|
const isDevelopment =
|
|
window.location.hostname === 'localhost' ||
|
|
window.location.hostname === '127.0.0.1' ||
|
|
window.location.hostname === '' ||
|
|
window.location.port === '9000'; // Local dev server
|
|
|
|
if (isDevelopment) {
|
|
console.log('[Umami Analytics] Disabled in development environment');
|
|
return;
|
|
}
|
|
|
|
// Respect Do Not Track (DNT) browser setting
|
|
const dnt = navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack;
|
|
const dntEnabled = dnt === '1' || dnt === 'yes' || dnt === 'on';
|
|
|
|
if (dntEnabled) {
|
|
console.log('[Umami Analytics] Tracking disabled - Do Not Track enabled');
|
|
return;
|
|
}
|
|
|
|
// Check for user opt-out preference (localStorage)
|
|
try {
|
|
const optedOut = localStorage.getItem('umami.disabled') === 'true';
|
|
if (optedOut) {
|
|
console.log('[Umami Analytics] Tracking disabled - User opted out');
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
// localStorage may not be available (privacy mode, etc.)
|
|
console.warn('[Umami Analytics] Cannot check opt-out preference:', e);
|
|
}
|
|
|
|
// Website ID validation
|
|
if (CONFIG.websiteId === 'REPLACE_WITH_ACTUAL_WEBSITE_ID') {
|
|
console.warn('[Umami Analytics] Website ID not configured. Update umami-tracker.js after Umami setup.');
|
|
return;
|
|
}
|
|
|
|
// Load Umami tracking script
|
|
const script = document.createElement('script');
|
|
script.async = true;
|
|
script.defer = true;
|
|
script.src = CONFIG.scriptSrc;
|
|
script.setAttribute('data-website-id', CONFIG.websiteId);
|
|
script.setAttribute('data-domains', CONFIG.domain);
|
|
script.setAttribute('data-auto-track', CONFIG.autoTrack.toString());
|
|
|
|
// Error handling
|
|
script.onerror = function() {
|
|
console.error('[Umami Analytics] Failed to load tracking script from:', CONFIG.scriptSrc);
|
|
};
|
|
|
|
// Success callback
|
|
script.onload = function() {
|
|
console.log('[Umami Analytics] Tracking initialized (privacy-first, cookie-free)');
|
|
};
|
|
|
|
// Append script to head
|
|
document.head.appendChild(script);
|
|
|
|
// Expose opt-out function for privacy page
|
|
window.umamiOptOut = function() {
|
|
try {
|
|
localStorage.setItem('umami.disabled', 'true');
|
|
console.log('[Umami Analytics] User opted out successfully');
|
|
alert('Analytics tracking has been disabled. Reload the page to apply changes.');
|
|
return true;
|
|
} catch (e) {
|
|
console.error('[Umami Analytics] Failed to save opt-out preference:', e);
|
|
alert('Failed to save opt-out preference. Please ensure cookies/localStorage is enabled.');
|
|
return false;
|
|
}
|
|
};
|
|
|
|
// Expose opt-in function (to reverse opt-out)
|
|
window.umamiOptIn = function() {
|
|
try {
|
|
localStorage.removeItem('umami.disabled');
|
|
console.log('[Umami Analytics] User opted in successfully');
|
|
alert('Analytics tracking has been enabled. Reload the page to apply changes.');
|
|
return true;
|
|
} catch (e) {
|
|
console.error('[Umami Analytics] Failed to save opt-in preference:', e);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
// Expose status check function
|
|
window.umamiStatus = function() {
|
|
const status = {
|
|
enabled: true,
|
|
development: isDevelopment,
|
|
dnt: dntEnabled,
|
|
optedOut: false,
|
|
websiteId: CONFIG.websiteId
|
|
};
|
|
|
|
try {
|
|
status.optedOut = localStorage.getItem('umami.disabled') === 'true';
|
|
} catch (e) {
|
|
status.optedOut = null;
|
|
}
|
|
|
|
console.table(status);
|
|
return status;
|
|
};
|
|
|
|
})();
|