Bunch of buag fixes and updates

This commit is contained in:
2025-09-24 12:37:26 -06:00
parent feb2e72c9a
commit f35a1f4be5
33 changed files with 3085 additions and 200 deletions

View File

@@ -50,10 +50,23 @@ class AuthController {
// Update last login time
try {
const userId = user.Id || user.id;
await nocodbService.updateUser(userId, {
'Last Login': new Date().toISOString()
// Debug: Log user object structure
console.log('User object keys:', Object.keys(user));
console.log('User ID candidates:', {
ID: user.ID,
Id: user.Id,
id: user.id
});
const userId = user.ID || user.Id || user.id;
if (userId) {
await nocodbService.updateUser(userId, {
'Last Login': new Date().toISOString()
});
} else {
console.warn('No valid user ID found for updating last login time');
}
} catch (updateError) {
console.warn('Failed to update last login time:', updateError.message);
// Don't fail the login
@@ -61,7 +74,7 @@ class AuthController {
// Set session
req.session.authenticated = true;
req.session.userId = user.Id || user.id;
req.session.userId = user.ID || user.Id || user.id;
req.session.userEmail = user.Email || user.email;
req.session.userName = user.Name || user.name;
req.session.isAdmin = user.Admin || user.admin || false;

View File

@@ -3,6 +3,30 @@ const emailService = require('../services/email');
const representAPI = require('../services/represent-api');
const { generateSlug, validateSlug } = require('../utils/validators');
// Helper function to cache representatives
async function cacheRepresentatives(postalCode, representatives, representData) {
try {
// Cache the postal code info
await nocoDB.storePostalCodeInfo({
postal_code: postalCode,
city: representData.city,
province: representData.province
});
// Cache representatives using the existing method
const result = await nocoDB.storeRepresentatives(postalCode, representatives);
if (result.success) {
console.log(`Successfully cached ${result.count} representatives for ${postalCode}`);
} else {
console.log(`Partial success caching representatives for ${postalCode}: ${result.error || 'unknown error'}`);
}
} catch (error) {
console.log(`Failed to cache representatives for ${postalCode}:`, error.message);
// Don't throw - caching is optional and should never break the main flow
}
}
class CampaignsController {
// Get all campaigns (for admin panel)
async getAllCampaigns(req, res, next) {
@@ -31,6 +55,7 @@ class CampaignsController {
allow_mailto_link: campaign['Allow Mailto Link'] || campaign.allow_mailto_link,
collect_user_info: campaign['Collect User Info'] || campaign.collect_user_info,
show_email_count: campaign['Show Email Count'] || campaign.show_email_count,
allow_email_editing: campaign['Allow Email Editing'] || campaign.allow_email_editing,
target_government_levels: campaign['Target Government Levels'] || campaign.target_government_levels,
created_at: campaign.CreatedAt || campaign.created_at,
updated_at: campaign.UpdatedAt || campaign.updated_at,
@@ -91,6 +116,7 @@ class CampaignsController {
allow_mailto_link: campaign['Allow Mailto Link'] || campaign.allow_mailto_link,
collect_user_info: campaign['Collect User Info'] || campaign.collect_user_info,
show_email_count: campaign['Show Email Count'] || campaign.show_email_count,
allow_email_editing: campaign['Allow Email Editing'] || campaign.allow_email_editing,
target_government_levels: campaign['Target Government Levels'] || campaign.target_government_levels,
created_at: campaign.CreatedAt || campaign.created_at,
updated_at: campaign.UpdatedAt || campaign.updated_at,
@@ -153,6 +179,7 @@ class CampaignsController {
allow_mailto_link: campaign['Allow Mailto Link'] || campaign.allow_mailto_link,
collect_user_info: campaign['Collect User Info'] || campaign.collect_user_info,
show_email_count: campaign['Show Email Count'] || campaign.show_email_count,
allow_email_editing: campaign['Allow Email Editing'] || campaign.allow_email_editing,
target_government_levels: Array.isArray(campaign['Target Government Levels'] || campaign.target_government_levels)
? (campaign['Target Government Levels'] || campaign.target_government_levels)
: (typeof (campaign['Target Government Levels'] || campaign.target_government_levels) === 'string' && (campaign['Target Government Levels'] || campaign.target_government_levels).length > 0
@@ -181,10 +208,12 @@ class CampaignsController {
email_subject,
email_body,
call_to_action,
status = 'draft',
allow_smtp_email = true,
allow_mailto_link = true,
collect_user_info = true,
show_email_count = true,
allow_email_editing = false,
target_government_levels = ['Federal', 'Provincial', 'Municipal']
} = req.body;
@@ -206,11 +235,12 @@ class CampaignsController {
email_subject,
email_body,
call_to_action,
status: 'draft',
status,
allow_smtp_email,
allow_mailto_link,
collect_user_info,
show_email_count,
allow_email_editing,
// NocoDB MultiSelect expects an array of values
target_government_levels: Array.isArray(target_government_levels)
? target_government_levels
@@ -237,6 +267,7 @@ class CampaignsController {
allow_mailto_link: campaign['Allow Mailto Link'] || campaign.allow_mailto_link,
collect_user_info: campaign['Collect User Info'] || campaign.collect_user_info,
show_email_count: campaign['Show Email Count'] || campaign.show_email_count,
allow_email_editing: campaign['Allow Email Editing'] || campaign.allow_email_editing,
target_government_levels: campaign['Target Government Levels'] || campaign.target_government_levels,
created_at: campaign.CreatedAt || campaign.created_at,
updated_at: campaign.UpdatedAt || campaign.updated_at
@@ -356,7 +387,9 @@ class CampaignsController {
recipientName,
recipientTitle,
recipientLevel,
emailMethod = 'smtp'
emailMethod = 'smtp',
customEmailSubject,
customEmailBody
} = req.body;
// Get campaign
@@ -392,8 +425,14 @@ class CampaignsController {
});
}
const subject = campaign['Email Subject'] || campaign.email_subject;
const message = campaign['Email Body'] || campaign.email_body;
// Use custom email content if provided (when email editing is enabled), otherwise use campaign defaults
const allowEmailEditing = campaign['Allow Email Editing'] || campaign.allow_email_editing;
const subject = (allowEmailEditing && customEmailSubject)
? customEmailSubject
: (campaign['Email Subject'] || campaign.email_subject);
const message = (allowEmailEditing && customEmailBody)
? customEmailBody
: (campaign['Email Body'] || campaign.email_body);
let emailResult = { success: true };
@@ -539,20 +578,46 @@ class CampaignsController {
});
}
// Get representatives
const result = await representAPI.getRepresentativesByPostalCode(postalCode);
// Process representatives from both concordance and centroid
// First check cache for representatives
const formattedPostalCode = postalCode.replace(/\s/g, '').toUpperCase();
let representatives = [];
let result = null;
// Add concordance representatives (if any)
if (result.representatives_concordance && result.representatives_concordance.length > 0) {
representatives = representatives.concat(result.representatives_concordance);
// Try to check cached data first, but don't fail if NocoDB is down
let cachedData = [];
try {
cachedData = await nocoDB.getRepresentativesByPostalCode(formattedPostalCode);
console.log(`Cache check for ${formattedPostalCode}: found ${cachedData.length} records`);
if (cachedData && cachedData.length > 0) {
representatives = cachedData;
console.log(`Using cached representatives for ${formattedPostalCode}`);
}
} catch (cacheError) {
console.log(`Cache unavailable for ${formattedPostalCode}, proceeding with API call:`, cacheError.message);
}
// Add centroid representatives (if any) - these are the actual elected officials
if (result.representatives_centroid && result.representatives_centroid.length > 0) {
representatives = representatives.concat(result.representatives_centroid);
// If not in cache, fetch from Represent API
if (representatives.length === 0) {
console.log(`Fetching representatives from Represent API for ${formattedPostalCode}`);
result = await representAPI.getRepresentativesByPostalCode(postalCode);
// Process representatives from both concordance and centroid
// Add concordance representatives (if any)
if (result.representatives_concordance && result.representatives_concordance.length > 0) {
representatives = representatives.concat(result.representatives_concordance);
}
// Add centroid representatives (if any) - these are the actual elected officials
if (result.representatives_centroid && result.representatives_centroid.length > 0) {
representatives = representatives.concat(result.representatives_centroid);
}
// Cache the results if we got them from the API
if (representatives.length > 0 && result) {
console.log(`Attempting to cache ${representatives.length} representatives for ${formattedPostalCode}`);
await cacheRepresentatives(formattedPostalCode, representatives, result);
}
}
if (representatives.length === 0) {
@@ -561,8 +626,8 @@ class CampaignsController {
message: 'No representatives found for this postal code',
representatives: [],
location: {
city: result.city,
province: result.province
city: result?.city || 'Alberta',
province: result?.province || 'AB'
}
});
}
@@ -602,8 +667,8 @@ class CampaignsController {
success: true,
representatives: filteredRepresentatives,
location: {
city: result.city,
province: result.province
city: result?.city || cachedData[0]?.city || 'Alberta',
province: result?.province || cachedData[0]?.province || 'AB'
}
});

View File

@@ -4,7 +4,7 @@ const nocoDB = require('../services/nocodb');
class EmailsController {
async sendEmail(req, res, next) {
try {
const { recipientEmail, senderName, senderEmail, subject, message, postalCode } = req.body;
const { recipientEmail, senderName, senderEmail, subject, message, postalCode, recipientName } = req.body;
// Send the email using template system
const emailResult = await emailService.sendRepresentativeEmail(
@@ -13,7 +13,8 @@ class EmailsController {
senderEmail,
subject,
message,
postalCode
postalCode,
recipientName
);
// Log the email send event
@@ -22,6 +23,7 @@ class EmailsController {
senderName,
senderEmail,
subject,
message,
postalCode,
status: emailResult.success ? 'sent' : 'failed',
timestamp: new Date().toISOString(),
@@ -53,13 +55,14 @@ class EmailsController {
async previewEmail(req, res, next) {
try {
const { recipientEmail, subject, message, senderName, senderEmail, postalCode } = req.body;
const { recipientEmail, subject, message, senderName, senderEmail, postalCode, recipientName } = req.body;
const templateVariables = {
MESSAGE: message,
SENDER_NAME: senderName || 'Anonymous',
SENDER_EMAIL: senderEmail || 'unknown@example.com',
POSTAL_CODE: postalCode || 'Unknown'
POSTAL_CODE: postalCode || 'Unknown',
RECIPIENT_NAME: recipientName || 'Representative'
};
const emailOptions = {
@@ -74,6 +77,23 @@ class EmailsController {
const preview = await emailService.previewTemplatedEmail('representative-contact', templateVariables, emailOptions);
// Log the email preview event (non-blocking)
try {
await nocoDB.logEmailPreview({
recipientEmail,
senderName,
senderEmail,
subject,
message,
postalCode,
timestamp: new Date().toISOString(),
senderIP: req.ip || req.connection.remoteAddress
});
} catch (loggingError) {
console.error('Failed to log email preview:', loggingError);
// Don't fail the preview if logging fails
}
res.json({
success: true,
preview: preview,

View File

@@ -12,12 +12,16 @@ async function cacheRepresentatives(postalCode, representatives, representData)
});
// Cache representatives using the existing method
await nocoDB.storeRepresentatives(postalCode, representatives);
const result = await nocoDB.storeRepresentatives(postalCode, representatives);
console.log(`Successfully cached representatives for ${postalCode}`);
if (result.success) {
console.log(`Successfully cached ${result.count} representatives for ${postalCode}`);
} else {
console.log(`Partial success caching representatives for ${postalCode}: ${result.error || 'unknown error'}`);
}
} catch (error) {
console.log(`Failed to cache representatives for ${postalCode}:`, error.message);
// Don't throw - caching is optional
// Don't throw - caching is optional and should never break the main flow
}
}
@@ -48,9 +52,16 @@ class RepresentativesController {
if (cachedData && cachedData.length > 0) {
return res.json({
source: 'cache',
postalCode: formattedPostalCode,
representatives: cachedData
success: true,
source: 'Local Cache',
data: {
postalCode: formattedPostalCode,
location: {
city: cachedData[0]?.city || 'Alberta',
province: 'AB'
},
representatives: cachedData
}
});
}
} catch (cacheError) {
@@ -85,6 +96,9 @@ class RepresentativesController {
if (representData.representatives_centroid && representData.representatives_centroid.length > 0) {
representatives = representatives.concat(representData.representatives_centroid);
}
// Representatives already include office information, no need for additional API calls
console.log('Using representatives data with existing office information');
console.log(`Representatives concordance count: ${representData.boundaries_concordance ? representData.boundaries_concordance.length : 0}`);
console.log(`Representatives centroid count: ${representData.representatives_centroid ? representData.representatives_centroid.length : 0}`);
@@ -111,6 +125,7 @@ class RepresentativesController {
res.json({
success: true,
source: 'Open North',
data: {
postalCode,
location: {
@@ -151,7 +166,7 @@ class RepresentativesController {
await nocoDB.storeRepresentatives(formattedPostalCode, representData.representatives_concordance);
res.json({
source: 'refreshed',
source: 'Open North',
postalCode: formattedPostalCode,
representatives: representData.representatives_concordance,
city: representData.city,