Updated campagin cover photos

This commit is contained in:
admin 2025-10-01 12:20:55 -06:00
parent ba246d5dc8
commit 8915299707
14 changed files with 804 additions and 292 deletions

2
.gitignore vendored
View File

@ -14,3 +14,5 @@
.excalidraw
/.VSCodeCounter
/influence/app/public/uploads

View File

@ -2,6 +2,45 @@ const nocoDB = require('../services/nocodb');
const emailService = require('../services/email');
const representAPI = require('../services/represent-api');
const { generateSlug, validateSlug } = require('../utils/validators');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
// Configure multer for file uploads
const storage = multer.diskStorage({
destination: function (req, file, cb) {
const uploadDir = path.join(__dirname, '../public/uploads');
// Ensure the upload directory exists
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
cb(null, uploadDir);
},
filename: function (req, file, cb) {
// Generate unique filename with timestamp
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, 'cover-' + uniqueSuffix + path.extname(file.originalname));
}
});
const upload = multer({
storage: storage,
limits: {
fileSize: 5 * 1024 * 1024 // 5MB limit
},
fileFilter: function (req, file, cb) {
// Accept only image files
const allowedTypes = /jpeg|jpg|png|gif|webp/;
const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
const mimetype = allowedTypes.test(file.mimetype);
if (mimetype && extname) {
return cb(null, true);
} else {
cb(new Error('Only image files are allowed (jpeg, jpg, png, gif, webp)'));
}
}
});
const VALID_CAMPAIGN_STATUSES = ['draft', 'active', 'paused', 'archived'];
@ -76,6 +115,7 @@ class CampaignsController {
email_subject: campaign['Email Subject'] || campaign.email_subject,
email_body: campaign['Email Body'] || campaign.email_body,
call_to_action: campaign['Call to Action'] || campaign.call_to_action,
cover_photo: campaign['Cover Photo'] || campaign.cover_photo,
status: normalizeStatus(campaign['Status'] || campaign.status),
allow_smtp_email: campaign['Allow SMTP Email'] || campaign.allow_smtp_email,
allow_mailto_link: campaign['Allow Mailto Link'] || campaign.allow_mailto_link,
@ -157,6 +197,7 @@ class CampaignsController {
email_subject: campaign['Email Subject'] || campaign.email_subject,
email_body: campaign['Email Body'] || campaign.email_body,
call_to_action: campaign['Call to Action'] || campaign.call_to_action,
cover_photo: campaign['Cover Photo'] || campaign.cover_photo,
status: normalizeStatus(campaign['Status'] || campaign.status),
allow_smtp_email: campaign['Allow SMTP Email'] || campaign.allow_smtp_email,
allow_mailto_link: campaign['Allow Mailto Link'] || campaign.allow_mailto_link,
@ -214,6 +255,10 @@ class CampaignsController {
}
}
// Debug cover photo value
const coverPhoto = campaign['Cover Photo'] || campaign.cover_photo;
console.log('Raw cover_photo from NocoDB:', coverPhoto, 'Type:', typeof coverPhoto);
res.json({
success: true,
campaign: {
@ -222,6 +267,7 @@ class CampaignsController {
title: campaign['Campaign Title'] || campaign.title,
description: campaign['Description'] || campaign.description,
call_to_action: campaign['Call to Action'] || campaign.call_to_action,
cover_photo: coverPhoto || null,
email_subject: campaign['Email Subject'] || campaign.email_subject,
email_body: campaign['Email Body'] || campaign.email_body,
allow_smtp_email: campaign['Allow SMTP Email'] || campaign.allow_smtp_email,
@ -294,7 +340,9 @@ class CampaignsController {
// Add user ownership data
created_by_user_id: ownerUserId,
created_by_user_email: ownerEmail,
created_by_user_name: ownerName
created_by_user_name: ownerName,
// Add cover photo if uploaded
cover_photo: req.file ? req.file.filename : null
};
const campaign = await nocoDB.createCampaign(campaignData);
@ -310,6 +358,7 @@ class CampaignsController {
email_subject: campaign['Email Subject'] || campaign.email_subject,
email_body: campaign['Email Body'] || campaign.email_body,
call_to_action: campaign['Call to Action'] || campaign.call_to_action,
cover_photo: campaign['Cover Photo'] || campaign.cover_photo,
status: normalizeStatus(campaign['Status'] || campaign.status),
allow_smtp_email: campaign['Allow SMTP Email'] || campaign.allow_smtp_email,
allow_mailto_link: campaign['Allow Mailto Link'] || campaign.allow_mailto_link,
@ -390,6 +439,16 @@ class CampaignsController {
updates.target_government_levels = normalizeTargetLevels(updates.target_government_levels);
}
// Handle cover photo upload
if (req.file) {
console.log('Cover photo file received:', req.file.filename);
updates.cover_photo = req.file.filename;
} else {
console.log('No cover photo file in request');
}
console.log('Updates object before saving:', updates);
if (updates.status !== undefined) {
const sanitizedStatus = normalizeStatus(updates.status, null);
if (!sanitizedStatus) {
@ -423,6 +482,7 @@ class CampaignsController {
email_subject: campaign['Email Subject'] || campaign.email_subject,
email_body: campaign['Email Body'] || campaign.email_body,
call_to_action: campaign['Call to Action'] || campaign.call_to_action,
cover_photo: campaign['Cover Photo'] || campaign.cover_photo,
status: normalizeStatus(campaign['Status'] || campaign.status),
allow_smtp_email: campaign['Allow SMTP Email'] || campaign.allow_smtp_email,
allow_mailto_link: campaign['Allow Mailto Link'] || campaign.allow_mailto_link,
@ -834,4 +894,8 @@ class CampaignsController {
}
}
module.exports = new CampaignsController();
// Export controller instance and upload middleware
const controller = new CampaignsController();
controller.upload = upload;
module.exports = controller;

View File

@ -27,7 +27,8 @@
"axios": "^1.5.0",
"nodemailer": "^6.9.4",
"express-session": "^1.17.3",
"bcryptjs": "^2.4.3"
"bcryptjs": "^2.4.3",
"multer": "^1.4.5-lts.1"
},
"devDependencies": {
"nodemon": "^3.0.1",

View File

@ -84,13 +84,55 @@
gap: 0.5rem;
}
.campaign-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.campaign-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1rem;
padding: 0;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
transition: transform 0.2s, box-shadow 0.2s;
}
.campaign-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.campaign-card-cover {
background-size: cover;
background-position: center;
min-height: 180px;
position: relative;
display: flex;
align-items: flex-end;
}
.campaign-card-cover-overlay {
background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0.4) 60%, transparent 100%);
width: 100%;
padding: 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.campaign-card-cover-overlay h3 {
margin: 0;
color: white;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
}
.campaign-card > .campaign-meta,
.campaign-card > .campaign-actions {
padding: 1.5rem;
}
.campaign-header {
@ -98,6 +140,7 @@
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
padding: 1.5rem;
}
.campaign-header h3 {
@ -150,7 +193,7 @@
}
.btn {
padding: 0.75rem 1.5rem;
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
@ -700,7 +743,7 @@
<p>Loading campaigns...</p>
</div>
<div id="campaigns-list" class="campaign-list">
<div id="campaigns-list" class="campaign-grid">
<!-- Campaigns will be loaded here -->
</div>
</div>
@ -770,6 +813,12 @@ Sincerely,
placeholder="Join thousands of Albertans in protecting our provincial parks. Send an email to your representatives today!"></textarea>
</div>
<div class="form-group">
<label for="create-cover-photo">🖼️ Cover Photo</label>
<input type="file" id="create-cover-photo" name="cover_photo" accept="image/jpeg,image/jpg,image/png,image/gif,image/webp">
<small style="color: #666; font-size: 0.9em;">Upload a cover image for your campaign (max 5MB, jpeg/jpg/png/gif/webp)</small>
</div>
<div class="section-header">⚙️ Campaign Settings</div>
<div class="form-group">
<div class="checkbox-group">
@ -880,6 +929,13 @@ Sincerely,
<textarea id="edit-call-to-action" name="call_to_action" rows="3"></textarea>
</div>
<div class="form-group">
<label for="edit-cover-photo">🖼️ Cover Photo</label>
<div id="current-cover-photo" style="margin-bottom: 0.5rem;"></div>
<input type="file" id="edit-cover-photo" name="cover_photo" accept="image/jpeg,image/jpg,image/png,image/gif,image/webp">
<small style="color: #666; font-size: 0.9em;">Upload a new cover image (max 5MB, jpeg/jpg/png/gif/webp) or leave empty to keep current</small>
</div>
<div class="form-group">
<label>Campaign Settings</label>
<div class="checkbox-group">

View File

@ -12,11 +12,40 @@
padding: 3rem 0;
text-align: center;
margin-bottom: 2rem;
position: relative;
overflow: hidden;
}
.campaign-header.has-cover {
background-size: cover;
background-position: center;
background-repeat: no-repeat;
min-height: 350px;
display: flex;
align-items: center;
justify-content: center;
}
.campaign-header.has-cover::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1;
}
.campaign-header > * {
position: relative;
z-index: 2;
}
.campaign-header h1 {
font-size: 2.5rem;
margin-bottom: 1rem;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
}
.campaign-stats {

View File

@ -85,10 +85,11 @@
.campaign-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1.5rem;
padding: 0;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: transform 0.2s, box-shadow 0.2s;
overflow: hidden;
}
.campaign-card:hover {
@ -96,11 +97,42 @@
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.campaign-card-cover {
background-size: cover;
background-position: center;
min-height: 180px;
position: relative;
display: flex;
align-items: flex-end;
}
.campaign-card-cover-overlay {
background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0.4) 60%, transparent 100%);
width: 100%;
padding: 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.campaign-card-cover-overlay h3 {
margin: 0;
color: white;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
font-size: 1.2rem;
}
.campaign-card > .campaign-meta,
.campaign-card > .campaign-actions {
padding: 1.5rem;
}
.campaign-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
padding: 1.5rem;
}
.campaign-header h3 {

View File

@ -467,10 +467,19 @@ class AdminPanel {
listDiv.innerHTML = this.campaigns.map(campaign => `
<div class="campaign-card" data-campaign-id="${campaign.id}">
${campaign.cover_photo ? `
<div class="campaign-card-cover" style="background-image: url('/uploads/${campaign.cover_photo}');">
<div class="campaign-card-cover-overlay">
<h3>${this.escapeHtml(campaign.title)}</h3>
<span class="status-badge status-${campaign.status}">${campaign.status}</span>
</div>
</div>
` : `
<div class="campaign-header">
<h3>${this.escapeHtml(campaign.title)}</h3>
<span class="status-badge status-${campaign.status}">${campaign.status}</span>
</div>
`}
<div class="campaign-meta">
<p><strong>Slug:</strong> <code>/campaign/${campaign.slug}</code></p>
@ -531,22 +540,34 @@ class AdminPanel {
e.preventDefault();
const formData = new FormData(e.target);
const campaignData = {
title: formData.get('title'),
description: formData.get('description'),
email_subject: formData.get('email_subject'),
email_body: formData.get('email_body'),
call_to_action: formData.get('call_to_action'),
status: formData.get('status'),
allow_smtp_email: formData.get('allow_smtp_email') === 'on',
allow_mailto_link: formData.get('allow_mailto_link') === 'on',
collect_user_info: formData.get('collect_user_info') === 'on',
show_email_count: formData.get('show_email_count') === 'on',
target_government_levels: Array.from(formData.getAll('target_government_levels'))
};
// Convert checkboxes to boolean values
const campaignFormData = new FormData();
campaignFormData.append('title', formData.get('title'));
campaignFormData.append('description', formData.get('description') || '');
campaignFormData.append('email_subject', formData.get('email_subject'));
campaignFormData.append('email_body', formData.get('email_body'));
campaignFormData.append('call_to_action', formData.get('call_to_action') || '');
campaignFormData.append('status', formData.get('status'));
campaignFormData.append('allow_smtp_email', formData.get('allow_smtp_email') === 'on');
campaignFormData.append('allow_mailto_link', formData.get('allow_mailto_link') === 'on');
campaignFormData.append('collect_user_info', formData.get('collect_user_info') === 'on');
campaignFormData.append('show_email_count', formData.get('show_email_count') === 'on');
// Handle target_government_levels array
const targetLevels = Array.from(formData.getAll('target_government_levels'));
targetLevels.forEach(level => {
campaignFormData.append('target_government_levels', level);
});
// Handle cover photo file upload
const coverPhotoFile = formData.get('cover_photo');
if (coverPhotoFile && coverPhotoFile.size > 0) {
campaignFormData.append('cover_photo', coverPhotoFile);
}
try {
const response = await window.apiClient.post('/admin/campaigns', campaignData);
const response = await window.apiClient.postFormData('/admin/campaigns', campaignFormData);
if (response.success) {
this.showMessage('Campaign created successfully!', 'success');
@ -584,6 +605,19 @@ class AdminPanel {
// Status select
form.querySelector('[name="status"]').value = campaign.status || 'draft';
// Show current cover photo if exists
const currentCoverDiv = document.getElementById('current-cover-photo');
if (campaign.cover_photo) {
currentCoverDiv.innerHTML = `
<div style="margin-bottom: 0.5rem;">
<small style="color: #666;">Current cover photo:</small><br>
<img src="/uploads/${campaign.cover_photo}" alt="Current cover" style="max-width: 200px; max-height: 150px; border-radius: 4px; margin-top: 0.25rem;">
</div>
`;
} else {
currentCoverDiv.innerHTML = '<small style="color: #999;">No cover photo uploaded</small>';
}
// Checkboxes
form.querySelector('[name="allow_smtp_email"]').checked = campaign.allow_smtp_email;
form.querySelector('[name="allow_mailto_link"]').checked = campaign.allow_mailto_link;
@ -612,25 +646,34 @@ class AdminPanel {
if (!this.currentCampaign) return;
const formData = new FormData(e.target);
const updates = {
title: formData.get('title'),
description: formData.get('description'),
email_subject: formData.get('email_subject'),
email_body: formData.get('email_body'),
call_to_action: formData.get('call_to_action'),
status: formData.get('status'),
allow_smtp_email: formData.get('allow_smtp_email') === 'on',
allow_mailto_link: formData.get('allow_mailto_link') === 'on',
collect_user_info: formData.get('collect_user_info') === 'on',
show_email_count: formData.get('show_email_count') === 'on',
target_government_levels: Array.from(formData.getAll('target_government_levels'))
};
// Convert checkboxes to boolean values and build FormData for upload
const updateFormData = new FormData();
updateFormData.append('title', formData.get('title'));
updateFormData.append('description', formData.get('description') || '');
updateFormData.append('email_subject', formData.get('email_subject'));
updateFormData.append('email_body', formData.get('email_body'));
updateFormData.append('call_to_action', formData.get('call_to_action') || '');
updateFormData.append('status', formData.get('status'));
updateFormData.append('allow_smtp_email', formData.get('allow_smtp_email') === 'on');
updateFormData.append('allow_mailto_link', formData.get('allow_mailto_link') === 'on');
updateFormData.append('collect_user_info', formData.get('collect_user_info') === 'on');
updateFormData.append('show_email_count', formData.get('show_email_count') === 'on');
// Handle target_government_levels array
const targetLevels = Array.from(formData.getAll('target_government_levels'));
targetLevels.forEach(level => {
updateFormData.append('target_government_levels', level);
});
// Handle cover photo file upload if a new one is selected
const coverPhotoFile = formData.get('cover_photo');
if (coverPhotoFile && coverPhotoFile.size > 0) {
updateFormData.append('cover_photo', coverPhotoFile);
}
try {
const response = await window.apiClient.makeRequest(`/admin/campaigns/${this.currentCampaign.id}`, {
method: 'PUT',
body: JSON.stringify(updates)
});
const response = await window.apiClient.putFormData(`/admin/campaigns/${this.currentCampaign.id}`, updateFormData);
if (response.success) {
this.showMessage('Campaign updated successfully!', 'success');

View File

@ -45,6 +45,56 @@ class APIClient {
});
}
async postFormData(endpoint, formData) {
// Don't set Content-Type header - browser will set it with boundary for multipart/form-data
const config = {
method: 'POST',
body: formData
};
try {
const response = await fetch(`${this.baseURL}${endpoint}`, config);
const data = await response.json();
if (!response.ok) {
const error = new Error(data.error || data.message || `HTTP ${response.status}`);
error.status = response.status;
error.data = data;
throw error;
}
return data;
} catch (error) {
console.error('API request failed:', error);
throw error;
}
}
async putFormData(endpoint, formData) {
// Don't set Content-Type header - browser will set it with boundary for multipart/form-data
const config = {
method: 'PUT',
body: formData
};
try {
const response = await fetch(`${this.baseURL}${endpoint}`, config);
const data = await response.json();
if (!response.ok) {
const error = new Error(data.error || data.message || `HTTP ${response.status}`);
error.status = response.status;
error.data = data;
throw error;
}
return data;
} catch (error) {
console.error('API request failed:', error);
throw error;
}
}
// Health check
async checkHealth() {
return this.get('/health');

View File

@ -39,6 +39,8 @@ class CampaignPage {
}
this.campaign = data.campaign;
console.log('Campaign data loaded:', this.campaign);
console.log('Cover photo value:', this.campaign.cover_photo);
this.renderCampaign();
} catch (error) {
this.showError('Failed to load campaign: ' + error.message);
@ -54,6 +56,25 @@ class CampaignPage {
document.getElementById('campaign-title').textContent = this.campaign.title;
document.getElementById('campaign-description').textContent = this.campaign.description;
// Add cover photo if available
const headerElement = document.querySelector('.campaign-header');
if (this.campaign.cover_photo) {
// Clean the cover_photo value - it should just be a filename
const coverPhotoFilename = String(this.campaign.cover_photo).trim();
console.log('Cover photo filename:', coverPhotoFilename);
if (coverPhotoFilename && coverPhotoFilename !== 'null' && coverPhotoFilename !== 'undefined') {
headerElement.classList.add('has-cover');
headerElement.style.backgroundImage = `url('/uploads/${coverPhotoFilename}')`;
} else {
headerElement.classList.remove('has-cover');
headerElement.style.backgroundImage = '';
}
} else {
headerElement.classList.remove('has-cover');
headerElement.style.backgroundImage = '';
}
// Show email count if enabled
if (this.campaign.show_email_count && this.campaign.emailCount !== null) {
document.getElementById('email-count').textContent = this.campaign.emailCount;

View File

@ -482,10 +482,19 @@ class UserDashboard {
listDiv.innerHTML = this.campaigns.map(campaign => `
<div class="campaign-card" data-campaign-id="${campaign.id}">
${campaign.cover_photo ? `
<div class="campaign-card-cover" style="background-image: url('/uploads/${campaign.cover_photo}');">
<div class="campaign-card-cover-overlay">
<h3>${this.escapeHtml(campaign.title)}</h3>
<span class="status-badge status-${campaign.status}">${campaign.status}</span>
</div>
</div>
` : `
<div class="campaign-header">
<h3>${this.escapeHtml(campaign.title)}</h3>
<span class="status-badge status-${campaign.status}">${campaign.status}</span>
</div>
`}
<div class="campaign-meta">
<p><strong>Slug:</strong> <code>/campaign/${campaign.slug}</code></p>

View File

@ -101,6 +101,7 @@ router.get('/admin/campaigns/:id', requireAdmin, rateLimiter.general, campaignsC
router.post(
'/admin/campaigns',
requireAdmin,
campaignsController.upload.single('cover_photo'),
rateLimiter.general,
[
body('title').notEmpty().withMessage('Campaign title is required'),
@ -110,7 +111,7 @@ router.post(
handleValidationErrors,
campaignsController.createCampaign
);
router.put('/admin/campaigns/:id', requireAdmin, rateLimiter.general, campaignsController.updateCampaign);
router.put('/admin/campaigns/:id', requireAdmin, campaignsController.upload.single('cover_photo'), rateLimiter.general, campaignsController.updateCampaign);
router.delete('/admin/campaigns/:id', requireAdmin, rateLimiter.general, campaignsController.deleteCampaign);
router.get('/admin/campaigns/:id/analytics', requireAdmin, rateLimiter.general, campaignsController.getCampaignAnalytics);
@ -119,6 +120,7 @@ router.get('/campaigns', requireAuth, rateLimiter.general, campaignsController.g
router.post(
'/campaigns',
requireNonTemp,
campaignsController.upload.single('cover_photo'),
rateLimiter.general,
[
body('title').notEmpty().withMessage('Campaign title is required'),
@ -131,6 +133,7 @@ router.post(
router.put(
'/campaigns/:id',
requireNonTemp,
campaignsController.upload.single('cover_photo'),
rateLimiter.general,
campaignsController.updateCampaign
);

View File

@ -424,6 +424,7 @@ class NocoDBService {
'Email Subject': campaignData.email_subject,
'Email Body': campaignData.email_body,
'Call to Action': campaignData.call_to_action,
'Cover Photo': campaignData.cover_photo,
'Status': campaignData.status,
'Allow SMTP Email': campaignData.allow_smtp_email,
'Allow Mailto Link': campaignData.allow_mailto_link,
@ -455,6 +456,7 @@ class NocoDBService {
if (updates.email_subject !== undefined) mappedUpdates['Email Subject'] = updates.email_subject;
if (updates.email_body !== undefined) mappedUpdates['Email Body'] = updates.email_body;
if (updates.call_to_action !== undefined) mappedUpdates['Call to Action'] = updates.call_to_action;
if (updates.cover_photo !== undefined) mappedUpdates['Cover Photo'] = updates.cover_photo;
if (updates.status !== undefined) mappedUpdates['Status'] = updates.status;
if (updates.allow_smtp_email !== undefined) mappedUpdates['Allow SMTP Email'] = updates.allow_smtp_email;
if (updates.allow_mailto_link !== undefined) mappedUpdates['Allow Mailto Link'] = updates.allow_mailto_link;

View File

@ -2,332 +2,527 @@
This document explains the purpose and functionality of each file in the BNKops Influence Campaign Tool.
## Overview
The BNKops Influence Campaign Tool is a comprehensive political engagement platform that allows users to create and participate in advocacy campaigns, contact their elected representatives, and track campaign analytics. The application features campaign management, user authentication, representative lookup via the Represent API, email composition and sending, and detailed analytics tracking.
## Authentication System
The application includes a complete authentication system for admin panel access, following the same patterns used in the map application. Authentication is implemented using NocoDB as the user database and express-session for session management.
The application includes a complete authentication system supporting both admin and regular user access. Authentication is implemented using NocoDB as the user database, bcryptjs for password hashing, and express-session for session management. The system supports temporary users with expiration dates for campaign-specific access.
## Cover Photo Feature
Campaign cover photos enhance visual engagement and branding for advocacy campaigns. This feature allows administrators and authorized users to upload custom images that display as hero banners on campaign landing pages.
**Implementation Details:**
- **Backend:** Multer middleware handles file uploads with validation (JPEG/PNG/GIF/WebP, max 5MB)
- **Storage:** Files stored in `/app/public/uploads/` with unique timestamp-based filenames
- **Database:** NocoDB `cover_photo` field stores filename references in campaigns table
- **Frontend:** Dynamic background-image rendering with responsive CSS and overlay effects
- **API Routes:** FormData multipart/form-data support in create/update campaign endpoints
- **Security:** File type validation, size limits, and sanitized filenames prevent malicious uploads
- **Fallback:** Gradient background displays when no cover photo is uploaded
**User Experience:**
- Admin interface includes file input with current photo preview
- Campaign pages display cover photos as full-width hero backgrounds with semi-transparent overlays
- Mobile-responsive scaling ensures optimal display across all devices
- Optional feature - campaigns function normally without cover photos
## Root Directory Files
### Configuration Files
- **`.env`** - Environment variables (database URLs, SMTP config, API keys, email testing config)
- **`.env.development`** - Development environment configuration with MailHog SMTP settings
- **`.env.example`** - Template for environment configuration
- **`docker-compose.yml`** - Docker container orchestration with MailHog for email testing
- **`Dockerfile`** - Container definition for the Node.js application
- **`package.json`** - Node.js dependencies and scripts
- **`package-lock.json`** - Locked dependency versions for reproducible builds
- **`.env`** - Environment configuration containing NocoDB API credentials, SMTP settings, session secrets, API tokens, and table URLs for database integration
- **`.env.backup.20250930_105913`** - Automated backup of environment configuration from September 30, 2025 at 10:59:13
- **`.env.backup.20250930_163827`** - Automated backup of environment configuration from September 30, 2025 at 16:38:27
- **`example.env`** - Template for environment configuration with placeholder values and documentation
- **`docker-compose.yml`** - Docker container orchestration with MailHog for email testing in development mode
- **`fix-campaigns-table.sh`** - Database repair script for fixing campaign table structure and data integrity issues
### Documentation
- **`README.MD`** - Main project documentation with setup and usage instructions
- **`files-explainer.md`** - This file, explaining the project structure
- **`instruct.md`** - Implementation instructions and development notes
- **`API Reference Represent Elected Officials and Electoral Districts API for Canada.md`** - Complete API documentation for the Represent OpenNorth API used for representative lookup
- **`README.MD`** - Main project documentation with features overview, setup instructions, and usage guidelines
- **`files-explainer.md`** - This file, providing comprehensive documentation of the project structure and file purposes
- **`instruct.md`** - Development guidelines, implementation notes, and contribution instructions
- **`influence-campaign-setup.md`** - Detailed setup guide for deploying and configuring the influence campaign tool
## Application Structure (`app/`)
### Entry Point
- **`server.js`** - Express.js application entry point, middleware setup, route mounting
- **`server.js`** - Express.js application entry point with comprehensive middleware setup, route mounting, security configuration, session management, and static file serving. Includes protected routes for admin and user areas, error handling, and environment-specific configurations
- **`Dockerfile`** - Container definition for the Node.js application with multi-stage build, security optimizations, and production configurations
- **`package.json`** - Node.js project manifest defining dependencies including Express.js, authentication libraries (bcryptjs, express-session), email services (nodemailer), API clients (axios), security middleware (helmet, cors), validation libraries, and development tools
### Controllers (`app/controllers/`)
Business logic layer that handles HTTP requests and responses:
- **`authController.js`** - Authentication controller for admin login/logout
- `login()` - Handles admin login with email/password validation
- `logout()` - Destroys user session and logs out
- `checkSession()` - Verifies current authentication status
- Integrates with NocoDB users table for credential verification
- Updates last login timestamps and manages session state
- **`authController.js`** - Authentication controller managing user login/logout operations
- `login()` - Handles user authentication with email/password validation, bcryptjs password verification, and session creation
- `logout()` - Destroys user sessions and clears authentication state
- `checkSession()` - Verifies current authentication status and returns user information
- `register()` - User registration with password hashing and account creation
- Integrates with NocoDB users table for credential storage and verification
- Updates last login timestamps and manages session persistence
- **`representatives.js`** - Core functionality for postal code lookups
- `getByPostalCode()` - Main endpoint for finding representatives
- `refreshPostalCode()` - Force refresh of cached data
- `testConnection()` - Health check for Represent API
- Handles caching logic and fallback to API when cache fails
- **`campaigns.js`** - Core campaign management functionality with comprehensive CRUD operations
- `getAllCampaigns()` - Retrieves campaigns with filtering, pagination, and user permissions
- `createCampaign()` - Creates new campaigns with validation, slug generation, and cover photo upload handling using multer
- `updateCampaign()` - Updates campaign details, status management, and cover photo processing
- `deleteCampaign()` - Removes campaigns with data cleanup and permission checks
- `getCampaignBySlug()` - Public campaign access for landing pages and participation
- `participateInCampaign()` - Handles user participation, representative lookup, and email sending
- `getCampaignAnalytics()` - Generates campaign performance metrics and participation statistics
- Cover photo feature: Supports image uploads (JPEG, PNG, GIF, WebP) up to 5MB, stores files in `/public/uploads/` with unique filenames, and saves filename references to NocoDB
- Advanced features: attachment URL normalization, representative caching, email templating, and analytics tracking
- File upload middleware configuration using multer with diskStorage, file size limits, and image type validation
- **`emails.js`** - Email composition and sending functionality
- `sendEmail()` - Process and send emails to representatives with test mode support
- `previewEmail()` - Generate email preview without sending for testing purposes
- `sendTestEmail()` - Send test emails to configured test recipient
- `getEmailLogs()` - Retrieve email history with filtering and pagination
- **`representatives.js`** - Representative lookup and caching functionality
- `getByPostalCode()` - Main endpoint for finding representatives by postal code using Represent API
- `refreshPostalCode()` - Force refresh of cached representative data
- `testConnection()` - Health check for Represent API connectivity
- `searchRepresentatives()` - Search functionality for finding specific representatives
- Handles intelligent caching logic, fallback to API when cache fails, and data normalization
- **`emails.js`** - Email composition and delivery management
- `sendEmail()` - Process and send emails to representatives with template support and test mode
- `previewEmail()` - Generate email previews without sending for content review
- `sendTestEmail()` - Send test emails to configured recipients for system verification
- `sendCampaignEmail()` - Campaign-specific email sending with participant tracking
- `getEmailLogs()` - Retrieve email history with filtering, pagination, and analytics
- `testSMTPConnection()` - Test SMTP server connectivity for diagnostics
- Integrates with SMTP service and logs to database with test mode tracking
- Integrates with email templates, logs all activity to database, and supports both HTML and text formats
- **`usersController.js`** - User management and administration
- `getAll()` - Retrieve all users with role filtering and pagination
- `create()` - Create new users with password generation and email notification
- `update()` - Update user details, roles, and account settings
- `delete()` - Remove users with data cleanup and session invalidation
- `sendLoginDetails()` - Email login credentials to users with secure password handling
- `broadcastEmail()` - Send mass emails to user groups with template support
- `getExpiredUsers()` - Identify and manage temporary user account expiration
- Features temporary user support, role-based access control, and comprehensive user lifecycle management
### Routes (`app/routes/`)
API endpoint definitions and request validation:
- **`auth.js`** - Authentication API routes
- POST `/api/auth/login` - Admin login endpoint
- POST `/api/auth/logout` - Admin logout endpoint
- GET `/api/auth/session` - Session verification endpoint
- Handles authentication requests with proper validation and error handling
- **`auth.js`** - Authentication API routes with comprehensive security
- POST `/api/auth/login` - User login endpoint with rate limiting and validation
- POST `/api/auth/logout` - User logout endpoint with session cleanup
- GET `/api/auth/session` - Session verification and user information endpoint
- POST `/api/auth/register` - User registration endpoint with password requirements
- Handles authentication requests with proper validation, error handling, and security measures
- **`api.js`** - Main API routes with validation middleware
- Representatives endpoints with postal code validation
- Email endpoints with input sanitization and test mode support
- Email testing endpoints: `/api/emails/preview`, `/api/emails/test`, `/api/emails/logs`
- SMTP testing endpoint: `/api/test-smtp` for connection diagnostics
- Health check and testing endpoints
- Rate limiting and error handling middleware
- **`api.js`** - Main API routes with extensive validation middleware
- Campaign management endpoints: CRUD operations for campaigns, participation, analytics
- Representative endpoints with postal code validation and caching
- Email endpoints with input sanitization, template support, and test mode
- Email management: `/api/emails/preview`, `/api/emails/send`, `/api/emails/logs`, `/api/emails/test`
- File upload endpoints for campaign attachments and media
- Health check and system testing endpoints
- Comprehensive rate limiting, input validation, and error handling middleware
- **`users.js`** - User management API routes (admin access)
- GET `/api/users` - List all users with filtering and pagination
- POST `/api/users` - Create new users with role assignment
- PUT `/api/users/:id` - Update user details and permissions
- DELETE `/api/users/:id` - Remove users with data cleanup
- POST `/api/users/broadcast` - Send broadcast emails to user groups
- Includes admin authorization checks and comprehensive user lifecycle management
- **`dashboard.js`** - User dashboard and analytics API routes
- GET `/api/dashboard/stats` - User-specific dashboard statistics
- GET `/api/dashboard/campaigns` - User's campaign participation history
- GET `/api/dashboard/analytics` - Personal analytics and activity tracking
- Provides personalized data views with role-based access control
### Services (`app/services/`)
External system integrations and data access layer:
- **`nocodb.js`** - NocoDB database integration
- User management methods: `getUserByEmail()`, `createUser()`, `updateUser()`, `getAllUsers()`
/* Lines 76-80 omitted */
- Caching logic with automatic retry mechanisms
- **`nocodb.js`** - Comprehensive NocoDB database integration service
- User management: `getUserByEmail()`, `createUser()`, `updateUser()`, `deleteUser()`, `getAllUsers()`
- Campaign operations: `createCampaign()`, `updateCampaign()`, `getCampaigns()`, `deleteCampaign()`
- Representative caching: `getRepresentativesByPostalCode()`, `storeRepresentatives()`, `cacheRepresentatives()`
- Email logging: `logEmail()`, `getEmailLogs()`, `updateEmailStatus()`
- Generic CRUD operations: `create()`, `read()`, `update()`, `delete()`, `getAll()` with filtering and pagination
- Advanced features: automatic table URL resolution, attachment handling, file upload support, and connection pooling
- Robust error handling with automatic retry mechanisms and fallback strategies
- **`represent-api.js`** - Represent OpenNorth API integration
- Postal code lookup against Canadian electoral data
/* Lines 84-86 omitted */
- Support for both concordance and centroid representative data
- **`represent-api.js`** - Represent OpenNorth API integration for Canadian electoral data
- `getRepresentativesByPostalCode()` - Fetch representatives for postal codes with multiple data sources
- `getElectoralDistricts()` - Retrieve electoral district information and boundaries
- `searchRepresentatives()` - Search functionality for finding specific representatives
- `validatePostalCode()` - Canadian postal code format validation and normalization
- Support for both concordance and centroid representative data with intelligent merging
- Comprehensive error handling, rate limiting, and response caching
- **`emailTemplates.js`** - Email template service for managing HTML/text email templates
- Template loading and caching system with support for conditional blocks
- Variable replacement and template processing for dynamic content
- Template rendering for both HTML and text formats with fallback support
- Available templates: representative-contact, campaign-email, test-email
- **`emailTemplates.js`** - Advanced email template management service
- Template loading and caching system with support for conditional blocks and dynamic content
- Variable replacement and template processing for personalized emails
- Multi-format support: HTML and text templates with automatic fallback
- Template rendering with context-aware variable substitution
- Available templates: representative-contact, campaign-email, test-email, login-details, user-broadcast
- Template validation, error handling, and performance optimization through caching
- **`email.js`** - SMTP email service with comprehensive testing support
- **`email.js`** - Comprehensive SMTP email service with extensive features
- Template-based email composition using emailTemplates service
- Legacy sendEmail method for backward compatibility
- New templated methods: sendRepresentativeEmail(), sendCampaignEmail(), sendTestEmail()
- Email preview functionality with template support
- SMTP connection testing for diagnostics and troubleshooting
- Multiple sending methods: `sendRepresentativeEmail()`, `sendCampaignEmail()`, `sendTestEmail()`, `sendLoginDetails()`
- Email preview functionality with full template rendering without sending
- SMTP connection testing and diagnostics for troubleshooting
- Support for multiple SMTP providers with automatic failover
- Email logging, delivery tracking, and bounce handling
- Development mode with MailHog integration for local testing
### Utilities (`app/utils/`)
Helper functions and shared utilities:
- **`validation.js`** - Input validation and sanitization
- Postal code format validation (Alberta T-prefix)
- Email address validation
- HTML content sanitization for security
- **`validators.js`** - Comprehensive input validation and sanitization
- Postal code format validation for Canadian addresses (including Alberta T-prefix)
- Email address validation with domain checking and format verification
- Campaign slug generation and validation for URL-safe identifiers
- HTML content sanitization for security and XSS prevention
- Form input validation with detailed error messaging
- Data type validation and conversion utilities
- **`rate-limiter.js`** - API rate limiting configuration
- Different limits for various endpoint types
- IP-based rate limiting with sliding windows
- Configurable limits via environment variables
- **`rate-limiter.js`** - Advanced API rate limiting configuration
- Endpoint-specific rate limiting with different limits for various operations
- IP-based rate limiting with sliding windows and burst protection
- User-based rate limiting for authenticated requests
- Configurable limits via environment variables for different deployment environments
- Integration with express-rate-limit for production-ready protection
- **`helpers.js`** - General utility functions and data processing helpers
- User data sanitization for removing sensitive information from API responses
- ID extraction and normalization utilities for database operations
- Date and time formatting functions for consistent display
- String manipulation utilities for text processing and cleanup
- Data transformation helpers for API response formatting
- Error handling utilities and logging helpers
### Middleware (`app/middleware/`)
Express.js middleware functions:
Express.js middleware functions for request processing:
- **`auth.js`** - Authentication middleware for protecting routes
- `requireAuth()` - Ensures user is logged in
- `requireAdmin()` - Ensures user is logged in and has admin privileges
- Redirects unauthenticated requests to login page
- Sets up `req.user` object for authenticated requests
- Standardized error response formatting
- Error logging and classification
- Production vs development error detail levels
- **`auth.js`** - Comprehensive authentication middleware for route protection
- `requireAuth()` - Ensures user is logged in with valid session
- `requireAdmin()` - Ensures user has administrative privileges
- `requireOwnership()` - Ensures user can only access their own resources
- `checkTempUser()` - Handles temporary user restrictions and expiration
- Redirects unauthenticated requests to appropriate login pages
- Sets up `req.user` object for authenticated requests with full user context
- Session validation and renewal functionality
- Standardized error response formatting with consistent structure
- Comprehensive error logging and security event tracking
- Development vs production error detail levels for appropriate information disclosure
### Email Templates (`app/templates/email/`)
Professional HTML and text email templates with variable substitution:
- **`representative-contact.html/.txt`** - Template for direct constituent communications
- **`representative-contact.html/.txt`** - Template for direct constituent communications to representatives
- Variables: MESSAGE, SENDER_NAME, SENDER_EMAIL, POSTAL_CODE, APP_NAME, TIMESTAMP
- Professional styling with constituent information display and platform branding
- Responsive design for various email clients and devices
- **`campaign-email.html/.txt`** - Template for organized campaign emails
- **`campaign-email.html/.txt`** - Template for organized campaign participation emails
- Variables: MESSAGE, USER_NAME, USER_EMAIL, POSTAL_CODE, CAMPAIGN_TITLE, RECIPIENT_NAME, RECIPIENT_LEVEL
- Campaign-specific styling with participant information and campaign branding
- Conditional recipient information display
- Conditional recipient information display and campaign context
- Call-to-action buttons and engagement tracking
- **`test-email.html/.txt`** - Template for email system testing
- **`test-email.html/.txt`** - Template for email system testing and diagnostics
- Variables: MESSAGE, APP_NAME, TIMESTAMP
- Warning styling and test indicators for system verification emails
- Development-friendly formatting for debugging email delivery
- **`login-details.html/.txt`** - Template for sending user credentials and account information
- Variables: USER_NAME, EMAIL, PASSWORD, LOGIN_URL, APP_NAME
- Secure credential display with clear login instructions
- Security warnings and best practices for password management
- **`user-broadcast.html/.txt`** - Template for administrator mass communications
- Variables: MESSAGE, RECIPIENT_NAME, SENDER_NAME, APP_NAME, TIMESTAMP
- Professional broadcast styling for announcements and updates
- Rich text support for formatted content and multimedia
### Frontend Assets (`app/public/`)
#### HTML
- **`index.html`** - Main application interface
#### HTML Pages
- **`index.html`** - Main application landing page and campaign discovery interface
- Campaign listing with search and filtering capabilities
- Featured campaigns display with engaging visuals
- User registration and login integration
- Responsive design with mobile-first approach
- **`login.html`** - Admin login page
- Clean, professional login interface for admin access
- Email/password form with client-side validation
- Integration with authentication API
- Automatic redirect to admin panel on successful login
- Session persistence and auto-login detection
- **`login.html`** - User authentication page with comprehensive login functionality
- Clean, professional login interface for user access
- Email/password form with client-side validation and security features
- Integration with authentication API and session management
- Automatic redirect to dashboard on successful login
- Session persistence and auto-login detection for returning users
- Password reset functionality and security messaging
- **`admin.html`** - Campaign management admin panel (protected)
- Requires admin authentication to access
- Includes authentication checks and user info display
- Logout functionality integrated into interface
- Postal code input form with validation
- Representatives display sections
- Email composition modal
- Responsive design with accessibility features
- **`admin.html`** - Administrative panel for campaign and user management (protected)
- Requires admin authentication with role verification
- Campaign creation and management interface with rich text editing and cover photo uploads
- Cover photo upload: File input with image validation (jpeg/jpg/png/gif/webp, max 5MB)
- Current cover photo preview in edit form with thumbnail display
- User management with role assignment and account lifecycle controls
- Email composition tools for broadcast communications
- System analytics dashboard with campaign performance metrics
- Representative data management and caching controls
- File upload interface for campaign assets and attachments using FormData for multipart uploads
- **`dashboard.html`** - User dashboard for campaign participation and personal analytics
- Personalized campaign recommendations and participation history
- User profile management with account settings
- Campaign creation tools for non-admin users (if permitted)
- Email history and communication tracking
- Personal analytics and engagement metrics
- Responsive interface optimized for various screen sizes
- **`campaign.html`** - Dynamic campaign landing pages for public participation
- Campaign-specific branding and visual design with cover photo hero sections
- Cover photo display: Dynamically applies uploaded cover images as hero backgrounds with overlay effects
- Responsive cover photo sizing with background-size cover and center positioning
- Fallback gradient background for campaigns without cover photos
- Representative lookup integration with postal code input
- Email composition interface for campaign participation
- Social sharing functionality and engagement tracking
- Mobile-optimized interface for accessibility
- Progress tracking and participation confirmation
- **`email-test.html`** - Comprehensive email testing interface (admin-only)
- Protected admin interface for email testing and diagnostics
- Quick test email functionality with one-click testing
- Email preview system to see emails before sending
- Email composition form with real-time preview
- Email logs viewer with filtering by test/live mode
- SMTP connection testing and diagnostics
- Current configuration display showing test mode status
- Real-time feedback and error handling for all operations
- Protected admin interface for email system testing and diagnostics
- Quick test email functionality with predefined templates
- Email preview system with real-time rendering for content review
- Email composition form with WYSIWYG editing capabilities
- Email delivery logs with filtering and search functionality
- SMTP connection testing and configuration validation
- Test mode configuration and environment switching
- **`terms.html`** - Terms of service and privacy policy page
- Legal documentation for user agreement and data handling
- Privacy policy with data collection and usage transparency
- User rights and responsibilities outlined clearly
- Contact information for legal and privacy inquiries
#### Stylesheets (`app/public/css/`)
- **`styles.css`** - Complete application styling
- Responsive grid layouts for representative cards
- Photo display with fallback styling
- Modal and form styling
- Mobile-first responsive design
- Loading states and error message styling
- **`styles.css`** - Comprehensive application styling with modern design principles
- Responsive grid layouts for campaign and representative cards
- Professional form styling with validation states and user feedback
- Modal dialog systems for email composition and content editing
- Mobile-first responsive design with progressive enhancement
- Loading states, progress indicators, and skeleton screens
- Error message styling and user notification systems
- Campaign-specific theming and branding customization
- Accessibility features including focus management and color contrast
- Print-friendly styles for campaign materials and representative information
#### JavaScript (`app/public/js/`)
- **`main.js`** - Application initialization and utilities
- Global error handling and message display system
- Utility functions (postal code formatting, sanitization)
- Mobile detection and responsive behavior
- **`main.js`** - Core application initialization and shared utilities
- Global error handling and user message display system
- Utility functions including postal code formatting and data sanitization
- Mobile device detection and responsive behavior management
- Application-wide constants and configuration management
- Event delegation and global event handling
- **`api-client.js`** - Frontend API communication
- HTTP client with error handling
- API endpoint wrappers for all backend services
- Request/response processing and error propagation
- **`postal-lookup.js`** - Postal code search functionality
- Form handling and input validation
- API integration for representative lookup
- Loading states and error display
- Results processing and display coordination
- **`representatives-display.js`** - Representative data presentation
- Dynamic HTML generation for representative cards
- Photo display with fallback to initials
- Government level categorization and sorting
- Contact information formatting and display
- **`api-client.js`** - Frontend API communication layer
- HTTP client with comprehensive error handling and retry logic
- API endpoint wrappers for all backend services with consistent interfaces
- Request/response processing with automatic data transformation
- Authentication token management and automatic session renewal
- Error propagation and user-friendly error message generation
- FormData upload methods: `postFormData()` and `putFormData()` for file uploads
- Automatic content-type handling for multipart/form-data requests
- **`auth.js`** - Client-side authentication management
- `AuthManager` class for handling login/logout operations
- Session state management and persistence
- UI updates based on authentication status
- Auto-redirect for protected pages requiring authentication
- Integration with login forms and logout buttons
- `AuthManager` class for handling complete authentication lifecycle
- Login/logout operations with secure session management
- Session state persistence across browser sessions
- UI updates based on authentication status and user roles
- Automatic redirect for protected pages requiring authentication
- Integration with login forms, logout buttons, and protected routes
- Role-based UI element visibility and feature access control
- **`email-composer.js`** - Email composition interface
- Modal-based email composition form
- Pre-population of recipient data
- Form validation and submission handling
- Success/error feedback to users
- **`campaign.js`** - Campaign participation and interaction management
- `CampaignPage` class managing campaign landing page functionality
- Dynamic campaign loading with slug-based routing
- Cover photo rendering: Dynamically applies cover images as background with `.has-cover` class
- Responsive cover photo display with overlay effects and text shadowing for readability
- Fallback to gradient background when no cover photo exists
- User information collection and validation for campaign participation
- Representative lookup integration with postal code processing
- Email composition interface with campaign context and template integration
- Progress tracking through campaign participation workflow
- Social sharing and engagement tracking functionality
- **`dashboard.js`** - User dashboard and analytics interface
- `UserDashboard` class managing personalized user experience
- Campaign management interface for user-created campaigns
- Personal analytics display with charts and metrics
- Campaign participation history and tracking
- User profile management and account settings
- Campaign creation wizard for authorized users
- **`admin.js`** - Administrative interface management
- `AdminPanel` class for comprehensive system administration
- Campaign management with CRUD operations, advanced editing, and cover photo upload handling
- Cover photo management: FormData-based file uploads with postFormData/putFormData methods
- Current cover photo preview with thumbnail display in edit forms
- File validation client-side before upload (image types, size limits)
- User management with role assignment and account lifecycle
- System analytics and performance monitoring
- Email broadcast functionality with template selection
- File upload management for campaign assets with multipart/form-data encoding
- **`postal-lookup.js`** - Postal code search and representative discovery
- Form handling and input validation for Canadian postal codes
- API integration for representative lookup with caching
- Loading states and error display for user feedback
- Results processing and display coordination with visual feedback
- Representative data presentation with government level organization
- **`representatives-display.js`** - Representative data presentation and interaction
- Dynamic HTML generation for representative cards with rich information display
- Photo display with intelligent fallback to generated initials
- Government level categorization and hierarchical sorting
- Contact information formatting with clickable actions
- Representative profile modal displays with detailed information
- **`representatives-map.js`** - Interactive map integration for representative boundaries
- Map visualization of electoral districts and representative coverage
- Interactive boundary display with postal code correlation
- Representative location plotting with contextual information
- Geographic search functionality and boundary visualization
- **`email-composer.js`** - Advanced email composition interface
- Modal-based email composition with rich text editing capabilities
- Template selection and dynamic content generation
- Recipient data pre-population with representative information
- Form validation and submission handling with progress tracking
- Email preview functionality and template rendering
- Attachment handling and file upload integration
- **`email-testing.js`** - Comprehensive email testing interface management
- `EmailTesting` class managing all testing functionality
- Quick test email sending with default content
- SMTP connection testing and diagnostics
- Email preview generation with real-time rendering
- Test email sending with form validation
- Email logs management with filtering and pagination
- Configuration status display and management
- Real-time UI updates and error handling
- Integration with authentication system for admin protection
- `EmailTesting` class managing all diagnostic functionality
- Quick test email sending with predefined content and templates
- SMTP connection testing and configuration validation
- Email preview generation with real-time template rendering
- Test email composition with form validation and sending
- Email delivery logs management with filtering and pagination
- Configuration status display and troubleshooting tools
- Real-time UI updates and comprehensive error handling
- **`login.js`** - Login page functionality and user experience
- Login form handling with client-side validation
- Integration with authentication API and session management
- Password visibility toggles and security features
- Auto-redirect logic for authenticated users
- Error display and user feedback for authentication issues
## Scripts Directory (`scripts/`)
- **`build-nocodb.sh`** - Database setup automation
- Now includes users table creation for authentication system
- Creates influence_users table with email, name, password, admin flag, and last_login fields
- Updates .env file with NOCODB_TABLE_USERS configuration
- Creates NocoDB base and tables using v2 API
- Configures table schemas for representatives, emails, postal codes
- Handles authentication and error recovery
- Updates environment with new base ID
- **`build-nocodb.sh`** - Comprehensive database setup automation with migration capabilities
- Creates complete NocoDB base with all required tables for the influence campaign system
- Table creation includes: users, campaigns, representatives, emails, postal_codes, campaign_emails
- Advanced data migration support with `--migrate-data` flag for preserving existing data
- Interactive source base and table selection for migration operations
- Preserves original data while creating new base with improved schema
- Automatic .env file updates with new base ID and table URLs
- Comprehensive dependency checking (requires jq, curl, and valid NocoDB credentials)
- Robust error handling with rollback capabilities and user feedback
- User authentication table with email, name, password hash, admin flag, and activity tracking
- Campaign management tables with full metadata, attachments, and analytics support
## Data Flow Architecture
### Request Processing Flow
1. **User Input** → Postal code entered in frontend form
2. **Validation** → Client-side and server-side validation
3. **Cache Check** → NocoDB queried for existing data
4. **API Fallback** → Represent API called if cache miss or NocoDB down
5. **Data Processing** → Representative data normalized and processed
6. **Caching** → Results stored in NocoDB for future requests
7. **Response** → Formatted data returned to frontend
8. **Display** → Representatives rendered with photos and contact info
### Campaign Participation Flow
1. **Campaign Discovery** → Users browse campaigns on main page or access via direct links
2. **Campaign Access** → Dynamic campaign pages loaded based on URL slug with branding
3. **User Information** → Postal code collection and user details for representative lookup
4. **Representative Lookup** → API call to Represent service with intelligent caching
5. **Email Composition** → Template-based email composition with campaign context
6. **Email Delivery** → SMTP sending with comprehensive logging and tracking
7. **Analytics Tracking** → Participation metrics and campaign performance data collection
### Email Flow
1. **Composition** → User fills email form in modal
2. **Validation** → Input sanitization and validation
3. **Processing** → Email formatted and prepared for sending
4. **SMTP Delivery** → Email sent via configured SMTP service
5. **Logging** → Email attempt logged to database
6. **Confirmation** → User notified of success/failure
### Campaign Management Flow
1. **Campaign Creation** → Admin/user creates campaign with details, messaging, and targeting
2. **Content Management** → Rich text editing, file uploads, and visual customization
3. **Publication** → Campaign status management (draft, active, paused, archived)
4. **Monitoring** → Real-time analytics, participation tracking, and performance metrics
5. **Communication** → Broadcast messaging to participants and stakeholder updates
### Error Handling Strategy
- **Graceful Degradation** → App works even if NocoDB is down
- **User Feedback** → Clear error messages and recovery suggestions
- **Logging** → Comprehensive error logging for debugging
- **Fallbacks** → Multiple data sources and retry mechanisms
### Authentication and Session Management
1. **User Registration** → Account creation with role assignment and email verification
2. **Login Process** → Secure authentication with bcryptjs password verification
3. **Session Management** → Express-session with secure cookie configuration and expiration
4. **Role-Based Access** → Middleware-enforced permissions for admin, user, and temporary access
5. **Account Lifecycle** → User management including temporary accounts with expiration dates
### Email System Architecture
1. **Template Selection** → Dynamic template selection based on email type and context
2. **Content Generation** → Variable substitution and personalization with user/campaign data
3. **SMTP Delivery** → Configurable SMTP providers with failover and retry logic
4. **Delivery Tracking** → Comprehensive logging with status tracking and analytics
5. **Test Mode** → Development-friendly email redirection and preview capabilities
### Representative Data Management
1. **Postal Code Input** → User provides postal code with client-side validation
2. **Cache Check** → NocoDB queried for existing representative data with TTL management
3. **API Fallback** → Represent API integration when cache miss or data refresh needed
4. **Data Processing** → Representative data normalization and government level organization
5. **Caching Strategy** → Intelligent caching with automatic invalidation and refresh policies
6. **Display Rendering** → Rich representative cards with photos, contact information, and actions
## Key Integration Points
### Authentication System
- **Admin Login** → Email/password authentication for admin panel access
- **Session Management** → Express-session with secure cookie configuration
- **Route Protection** → Middleware-based protection for admin endpoints and pages
- **User Management** → NocoDB-based user storage with admin role support
- **Security** → Password validation, session expiration, and automatic logout
### Advanced Authentication System
- **Multi-Level Access** → Support for admin, regular users, and temporary campaign participants
- **Session Security** → HttpOnly cookies, CSRF protection, and automatic session expiration
- **Password Management** → Bcryptjs hashing with secure password generation and recovery
- **Role-Based UI** → Dynamic interface adaptation based on user permissions and account type
- **Account Expiration** → Temporary user support with automatic cleanup and lifecycle management
### NocoDB Integration
- **Authentication** → Token-based API authentication
- **User Storage** → Users table with email, password, admin flag, and login tracking
- **Table Management** → Dynamic table ID resolution
- **Error Recovery** → Automatic fallback to API when database unavailable
- **Performance** → Efficient caching with intelligent cache invalidation
### Comprehensive Campaign Management
- **Rich Content Creation** → WYSIWYG editing, file uploads, and multimedia attachment support
- **URL-Safe Routing** → Automatic slug generation with validation and conflict resolution
- **Status Workflow** → Campaign lifecycle management from draft through archive
- **Analytics Integration** → Built-in tracking for participation, engagement, and conversion metrics
- **Social Features** → Sharing capabilities and viral growth mechanisms
### Represent API Integration
- **Data Sources** → Both concordance and centroid representative data
- **Rate Limiting** → Respectful API usage with built-in rate limiting
- **Error Handling** → Robust error handling with user-friendly messages
- **Data Processing** → Normalization of API responses for consistent display
### Enhanced NocoDB Integration
- **Advanced Schema Management** → Dynamic table creation with complex relationship support
- **File Upload Handling** → Attachment management with URL normalization and CDN support
- **Bulk Operations** → Efficient data migration and batch processing capabilities
- **Connection Resilience** → Automatic retry logic and graceful degradation strategies
- **Performance Optimization** → Query optimization, caching layers, and connection pooling
### SMTP Integration
- **Security** → Secure authentication and encrypted connections
- **Reliability** → Error handling and delivery confirmation
- **Logging** → Complete audit trail of email activity with test mode tracking
- **Configuration** → Flexible SMTP provider support with development/production modes
- **Testing** → Comprehensive test mode with email redirection and preview capabilities
### Sophisticated Email Infrastructure
- **Multi-Template System** → Context-aware template selection with rich variable support
- **SMTP Flexibility** → Multiple provider support with automatic failover and load balancing
- **Development Tools** → MailHog integration for local testing and email preview capabilities
- **Delivery Assurance** → Comprehensive logging, bounce handling, and retry mechanisms
- **Personalization Engine** → Advanced variable substitution with conditional content blocks
### Email Testing System
- **Test Mode** → Automatic email redirection to configured test recipient
- **Preview System** → Generate email previews without sending for content review
- **SMTP Diagnostics** → Connection testing and troubleshooting tools
- **Email Logging** → Complete audit trail with test/live mode classification
- **Development Tools** → MailHog integration for local email catching and review
- **Admin Interface** → Dedicated testing interface accessible only to authenticated admins
### Representative Data Excellence
- **Multi-Source Integration** → Represent API with intelligent data merging and validation
- **Geographic Accuracy** → Postal code validation with boundary checking and error correction
- **Performance Caching** → Multi-level caching with intelligent invalidation strategies
- **Data Enrichment** → Photo fetching, contact validation, and information completeness scoring
- **Real-Time Updates** → Automatic data refresh with change detection and notification
### Docker Configuration
- **Production Mode** → Standard application container with external SMTP
- **Development Mode** → Application + MailHog containers for local email testing
- **Profile-based Deployment** → MailHog only runs in development profile
- **Email Catching** → All development emails caught by MailHog web interface at port 8025
- **Environment Flexibility** → Easy switching between development and production SMTP settings
### Security and Compliance Framework
- **Input Validation** → Comprehensive sanitization and validation across all user inputs
- **Rate Limiting** → Advanced rate limiting with user-based and IP-based protection
- **CSRF Protection** → Cross-site request forgery prevention with token validation
- **Content Security** → HTML sanitization and XSS prevention throughout the application
- **Audit Logging** → Comprehensive activity logging for security monitoring and compliance
## Development Patterns
### Deployment and Operations
- **Docker Integration** → Production-ready containerization with multi-stage builds
- **Environment Management** → Flexible configuration with development/production modes
- **Monitoring Capabilities** → Built-in health checks and performance monitoring
- **Backup Automation** → Automated environment backups with restoration capabilities
- **Scaling Support** → Load balancer compatibility and horizontal scaling preparation
### Error Handling
- All async operations wrapped in try-catch blocks
- Consistent error response formatting across APIs
- User-friendly error messages with technical details logged
- Graceful degradation when external services unavailable
### Data Validation
- Input validation at both client and server levels
- Sanitization to prevent XSS and injection attacks
- Type checking and format validation throughout
- Clear validation error messages for users
### Performance Optimization
- Smart caching with configurable TTL
- Lazy loading of images and non-critical resources
- Efficient database queries with minimal data transfer
- Client-side debouncing for user inputs
### Security Measures
- **Authentication Protection** → Admin panel and API endpoints protected by login
- **Session Security** → HttpOnly cookies, secure session configuration, automatic expiration
- Rate limiting to prevent abuse
- Input sanitization and validation
- Secure SMTP configuration
- Environment variable protection of sensitive data
This architecture provides a robust, scalable, and maintainable solution for connecting Alberta residents with their elected representatives.
This architecture provides a robust, scalable, and secure solution for political engagement and advocacy campaign management, enabling Alberta residents and organizations to effectively connect with their elected representatives and coordinate grassroots political action.

View File

@ -1010,6 +1010,11 @@ create_campaigns_table() {
"title": "Call to Action",
"uidt": "LongText"
},
{
"column_name": "cover_photo",
"title": "Cover Photo",
"uidt": "SingleLineText"
},
{
"column_name": "status",
"title": "Status",