Pushing the new influence app in its current state
This commit is contained in:
159
influence/app/controllers/authController.js
Normal file
159
influence/app/controllers/authController.js
Normal file
@@ -0,0 +1,159 @@
|
||||
const nocodbService = require('../services/nocodb');
|
||||
|
||||
class AuthController {
|
||||
async login(req, res) {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
// Validate input
|
||||
if (!email || !password) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Email and password are required'
|
||||
});
|
||||
}
|
||||
|
||||
// Validate email format
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Invalid email format'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Login attempt:', {
|
||||
email,
|
||||
ip: req.ip,
|
||||
userAgent: req.headers['user-agent']
|
||||
});
|
||||
|
||||
// Fetch user from NocoDB
|
||||
const user = await nocodbService.getUserByEmail(email);
|
||||
|
||||
if (!user) {
|
||||
console.warn(`No user found with email: ${email}`);
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
// Check password
|
||||
if (user.Password !== password && user.password !== password) {
|
||||
console.warn(`Invalid password for email: ${email}`);
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
// Update last login time
|
||||
try {
|
||||
const userId = user.Id || user.id;
|
||||
await nocodbService.updateUser(userId, {
|
||||
'Last Login': new Date().toISOString()
|
||||
});
|
||||
} catch (updateError) {
|
||||
console.warn('Failed to update last login time:', updateError.message);
|
||||
// Don't fail the login
|
||||
}
|
||||
|
||||
// Set session
|
||||
req.session.authenticated = true;
|
||||
req.session.userId = 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;
|
||||
|
||||
console.log('User logged in successfully:', {
|
||||
email: req.session.userEmail,
|
||||
isAdmin: req.session.isAdmin
|
||||
});
|
||||
|
||||
// Force session save
|
||||
req.session.save((err) => {
|
||||
if (err) {
|
||||
console.error('Session save error:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: 'Session error. Please try again.'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
user: {
|
||||
id: req.session.userId,
|
||||
email: req.session.userEmail,
|
||||
name: req.session.userName,
|
||||
isAdmin: req.session.isAdmin
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Server error. Please try again later.'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async logout(req, res) {
|
||||
try {
|
||||
const userEmail = req.session?.userEmail;
|
||||
|
||||
req.session.destroy((err) => {
|
||||
if (err) {
|
||||
console.error('Session destroy error:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: 'Logout failed'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('User logged out:', userEmail);
|
||||
res.json({ success: true });
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Server error during logout'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async checkSession(req, res) {
|
||||
try {
|
||||
const isAuthenticated = (req.session && req.session.authenticated) ||
|
||||
(req.session && req.session.userId && req.session.userEmail);
|
||||
|
||||
if (isAuthenticated) {
|
||||
res.json({
|
||||
authenticated: true,
|
||||
user: {
|
||||
id: req.session.userId,
|
||||
email: req.session.userEmail,
|
||||
name: req.session.userName,
|
||||
isAdmin: req.session.isAdmin
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.json({
|
||||
authenticated: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Session check error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Session check failed'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new AuthController();
|
||||
500
influence/app/controllers/campaigns.js
Normal file
500
influence/app/controllers/campaigns.js
Normal file
@@ -0,0 +1,500 @@
|
||||
const nocoDB = require('../services/nocodb');
|
||||
const emailService = require('../services/email');
|
||||
const representAPI = require('../services/represent-api');
|
||||
const { generateSlug, validateSlug } = require('../utils/validators');
|
||||
|
||||
class CampaignsController {
|
||||
// Get all campaigns (for admin panel)
|
||||
async getAllCampaigns(req, res, next) {
|
||||
try {
|
||||
const campaigns = await nocoDB.getAllCampaigns();
|
||||
|
||||
// Get email counts for each campaign
|
||||
const campaignsWithCounts = await Promise.all(campaigns.map(async (campaign) => {
|
||||
const id = campaign.Id ?? campaign.id;
|
||||
let emailCount = 0;
|
||||
if (id != null) {
|
||||
emailCount = await nocoDB.getCampaignEmailCount(id);
|
||||
}
|
||||
// Normalize id property for frontend
|
||||
return {
|
||||
id,
|
||||
...campaign,
|
||||
emailCount
|
||||
};
|
||||
}));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
campaigns: campaignsWithCounts
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Get campaigns error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to retrieve campaigns',
|
||||
message: error.message,
|
||||
details: error.response?.data || null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get single campaign by ID (for admin)
|
||||
async getCampaignById(req, res, next) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const campaign = await nocoDB.getCampaignById(id);
|
||||
|
||||
if (!campaign) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Campaign not found'
|
||||
});
|
||||
}
|
||||
|
||||
const normalizedId = campaign.Id ?? campaign.id ?? id;
|
||||
const emailCount = await nocoDB.getCampaignEmailCount(normalizedId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
campaign: {
|
||||
id: normalizedId,
|
||||
...campaign,
|
||||
emailCount
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Get campaign error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to retrieve campaign',
|
||||
message: error.message,
|
||||
details: error.response?.data || null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get campaign by slug (for public access)
|
||||
async getCampaignBySlug(req, res, next) {
|
||||
try {
|
||||
const { slug } = req.params;
|
||||
const campaign = await nocoDB.getCampaignBySlug(slug);
|
||||
|
||||
if (!campaign) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Campaign not found'
|
||||
});
|
||||
}
|
||||
|
||||
if (campaign.status !== 'active') {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Campaign is not currently active'
|
||||
});
|
||||
}
|
||||
|
||||
// Get email count if enabled
|
||||
let emailCount = null;
|
||||
if (campaign.show_email_count) {
|
||||
const id = campaign.Id ?? campaign.id;
|
||||
if (id != null) {
|
||||
emailCount = await nocoDB.getCampaignEmailCount(id);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
campaign: {
|
||||
id: campaign.id,
|
||||
slug: campaign.slug,
|
||||
title: campaign.title,
|
||||
description: campaign.description,
|
||||
call_to_action: campaign.call_to_action,
|
||||
email_subject: campaign.email_subject,
|
||||
email_body: campaign.email_body,
|
||||
allow_smtp_email: campaign.allow_smtp_email,
|
||||
allow_mailto_link: campaign.allow_mailto_link,
|
||||
collect_user_info: campaign.collect_user_info,
|
||||
show_email_count: campaign.show_email_count,
|
||||
target_government_levels: Array.isArray(campaign.target_government_levels)
|
||||
? campaign.target_government_levels
|
||||
: (typeof campaign.target_government_levels === 'string' && campaign.target_government_levels.length > 0
|
||||
? campaign.target_government_levels.split(',').map(s => s.trim())
|
||||
: []),
|
||||
emailCount
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Get campaign by slug error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to retrieve campaign',
|
||||
message: error.message,
|
||||
details: error.response?.data || null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create new campaign
|
||||
async createCampaign(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
email_subject,
|
||||
email_body,
|
||||
call_to_action,
|
||||
allow_smtp_email = true,
|
||||
allow_mailto_link = true,
|
||||
collect_user_info = true,
|
||||
show_email_count = true,
|
||||
target_government_levels = ['Federal', 'Provincial', 'Municipal']
|
||||
} = req.body;
|
||||
|
||||
// Generate slug from title
|
||||
let slug = generateSlug(title);
|
||||
|
||||
// Ensure slug is unique
|
||||
let counter = 1;
|
||||
let originalSlug = slug;
|
||||
while (await nocoDB.getCampaignBySlug(slug)) {
|
||||
slug = `${originalSlug}-${counter}`;
|
||||
counter++;
|
||||
}
|
||||
|
||||
const campaignData = {
|
||||
slug,
|
||||
title,
|
||||
description,
|
||||
email_subject,
|
||||
email_body,
|
||||
call_to_action,
|
||||
status: 'draft',
|
||||
allow_smtp_email,
|
||||
allow_mailto_link,
|
||||
collect_user_info,
|
||||
show_email_count,
|
||||
// NocoDB MultiSelect expects an array of values
|
||||
target_government_levels: Array.isArray(target_government_levels)
|
||||
? target_government_levels
|
||||
: (typeof target_government_levels === 'string' && target_government_levels.length > 0
|
||||
? target_government_levels.split(',').map(s => s.trim())
|
||||
: [])
|
||||
};
|
||||
|
||||
const campaign = await nocoDB.createCampaign(campaignData);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
campaign: {
|
||||
id: campaign.Id ?? campaign.id,
|
||||
...campaign
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Create campaign error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to create campaign',
|
||||
message: error.message,
|
||||
details: error.response?.data || null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update campaign
|
||||
async updateCampaign(req, res, next) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const updates = req.body;
|
||||
|
||||
// If title is being updated, regenerate slug
|
||||
if (updates.title) {
|
||||
let slug = generateSlug(updates.title);
|
||||
|
||||
// Ensure slug is unique (but allow current campaign to keep its slug)
|
||||
const existingCampaign = await nocoDB.getCampaignBySlug(slug);
|
||||
if (existingCampaign && existingCampaign.id !== parseInt(id)) {
|
||||
let counter = 1;
|
||||
let originalSlug = slug;
|
||||
while (await nocoDB.getCampaignBySlug(slug)) {
|
||||
slug = `${originalSlug}-${counter}`;
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
updates.slug = slug;
|
||||
}
|
||||
|
||||
// Ensure target_government_levels remains an array for MultiSelect
|
||||
if (updates.target_government_levels) {
|
||||
updates.target_government_levels = Array.isArray(updates.target_government_levels)
|
||||
? updates.target_government_levels
|
||||
: (typeof updates.target_government_levels === 'string'
|
||||
? updates.target_government_levels.split(',').map(s => s.trim())
|
||||
: []);
|
||||
}
|
||||
|
||||
updates.updated_at = new Date().toISOString();
|
||||
|
||||
const campaign = await nocoDB.updateCampaign(id, updates);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
campaign: {
|
||||
id: campaign.Id ?? campaign.id ?? id,
|
||||
...campaign
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Update campaign error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to update campaign',
|
||||
message: error.message,
|
||||
details: error.response?.data || null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Delete campaign
|
||||
async deleteCampaign(req, res, next) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
await nocoDB.deleteCampaign(id);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Campaign deleted successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Delete campaign error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to delete campaign',
|
||||
message: error.message,
|
||||
details: error.response?.data || null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Send campaign email
|
||||
async sendCampaignEmail(req, res, next) {
|
||||
try {
|
||||
const { slug } = req.params;
|
||||
const {
|
||||
userEmail,
|
||||
userName,
|
||||
postalCode,
|
||||
recipientEmail,
|
||||
recipientName,
|
||||
recipientTitle,
|
||||
recipientLevel,
|
||||
emailMethod = 'smtp'
|
||||
} = req.body;
|
||||
|
||||
// Get campaign
|
||||
const campaign = await nocoDB.getCampaignBySlug(slug);
|
||||
if (!campaign) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Campaign not found'
|
||||
});
|
||||
}
|
||||
|
||||
if (campaign.status !== 'active') {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Campaign is not currently active'
|
||||
});
|
||||
}
|
||||
|
||||
// Check if the requested email method is allowed
|
||||
if (emailMethod === 'smtp' && !campaign.allow_smtp_email) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'SMTP email sending is not enabled for this campaign'
|
||||
});
|
||||
}
|
||||
|
||||
if (emailMethod === 'mailto' && !campaign.allow_mailto_link) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Mailto links are not enabled for this campaign'
|
||||
});
|
||||
}
|
||||
|
||||
const subject = campaign.email_subject;
|
||||
const message = campaign.email_body;
|
||||
|
||||
let emailResult = { success: true };
|
||||
|
||||
// Send email if SMTP method
|
||||
if (emailMethod === 'smtp') {
|
||||
emailResult = await emailService.sendEmail({
|
||||
to: recipientEmail,
|
||||
from: {
|
||||
email: process.env.SMTP_FROM_EMAIL,
|
||||
name: process.env.SMTP_FROM_NAME
|
||||
},
|
||||
replyTo: userEmail,
|
||||
subject: subject,
|
||||
text: message,
|
||||
html: `
|
||||
<p>${message.replace(/\n/g, '<br>')}</p>
|
||||
<hr>
|
||||
<p><small>This message was sent via the Alberta Influence Campaign Tool by ${userName || 'A constituent'} (${userEmail}) from postal code ${postalCode} as part of the "${campaign.title}" campaign.</small></p>
|
||||
`
|
||||
});
|
||||
}
|
||||
|
||||
// Log the campaign email
|
||||
await nocoDB.logCampaignEmail({
|
||||
campaign_id: campaign.Id ?? campaign.id,
|
||||
campaign_slug: slug,
|
||||
user_email: userEmail,
|
||||
user_name: userName,
|
||||
user_postal_code: postalCode,
|
||||
recipient_email: recipientEmail,
|
||||
recipient_name: recipientName,
|
||||
recipient_title: recipientTitle,
|
||||
recipient_level: recipientLevel,
|
||||
email_method: emailMethod,
|
||||
subject: subject,
|
||||
message: message,
|
||||
status: emailMethod === 'mailto' ? 'clicked' : (emailResult.success ? 'sent' : 'failed'),
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
if (emailMethod === 'smtp') {
|
||||
if (emailResult.success) {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Email sent successfully'
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to send email',
|
||||
message: emailResult.error
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// For mailto, just return success since we're tracking the click
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Email action tracked'
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Send campaign email error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to process campaign email',
|
||||
message: error.message,
|
||||
details: error.response?.data || null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get representatives for postal code (for campaign use)
|
||||
async getRepresentativesForCampaign(req, res, next) {
|
||||
try {
|
||||
const { slug, postalCode } = req.params;
|
||||
|
||||
// Get campaign to check target levels
|
||||
const campaign = await nocoDB.getCampaignBySlug(slug);
|
||||
if (!campaign) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Campaign not found'
|
||||
});
|
||||
}
|
||||
|
||||
if (campaign.status !== 'active') {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Campaign is not currently active'
|
||||
});
|
||||
}
|
||||
|
||||
// Get representatives
|
||||
const result = await representAPI.getRepresentativesByPostalCode(postalCode);
|
||||
|
||||
if (!result.success) {
|
||||
return res.status(result.status || 500).json(result);
|
||||
}
|
||||
|
||||
// Filter representatives by target government levels
|
||||
const targetLevels = Array.isArray(campaign.target_government_levels)
|
||||
? campaign.target_government_levels
|
||||
: (typeof campaign.target_government_levels === 'string' && campaign.target_government_levels.length > 0
|
||||
? campaign.target_government_levels.split(',').map(level => level.trim())
|
||||
: ['Federal', 'Provincial', 'Municipal']);
|
||||
|
||||
const filteredRepresentatives = result.representatives.filter(rep => {
|
||||
const repLevel = rep.elected_office ? rep.elected_office.toLowerCase() : 'other';
|
||||
|
||||
return targetLevels.some(targetLevel => {
|
||||
const target = targetLevel.toLowerCase();
|
||||
|
||||
if (target === 'federal' && (repLevel.includes('mp') || repLevel.includes('member of parliament'))) {
|
||||
return true;
|
||||
}
|
||||
if (target === 'provincial' && (repLevel.includes('mla') || repLevel.includes('legislative assembly'))) {
|
||||
return true;
|
||||
}
|
||||
if (target === 'municipal' && (repLevel.includes('mayor') || repLevel.includes('councillor') || repLevel.includes('council'))) {
|
||||
return true;
|
||||
}
|
||||
if (target === 'school board' && repLevel.includes('school')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
representatives: filteredRepresentatives,
|
||||
location: result.location
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Get representatives for campaign error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to get representatives',
|
||||
message: error.message,
|
||||
details: error.response?.data || null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get campaign analytics
|
||||
async getCampaignAnalytics(req, res, next) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const analytics = await nocoDB.getCampaignAnalytics(id);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
analytics
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Get campaign analytics error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to get campaign analytics',
|
||||
message: error.message,
|
||||
details: error.response?.data || null
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new CampaignsController();
|
||||
61
influence/app/controllers/emails.js
Normal file
61
influence/app/controllers/emails.js
Normal file
@@ -0,0 +1,61 @@
|
||||
const emailService = require('../services/email');
|
||||
const nocoDB = require('../services/nocodb');
|
||||
|
||||
class EmailsController {
|
||||
async sendEmail(req, res, next) {
|
||||
try {
|
||||
const { recipientEmail, senderName, senderEmail, subject, message, postalCode } = req.body;
|
||||
|
||||
// Send the email
|
||||
const emailResult = await emailService.sendEmail({
|
||||
to: recipientEmail,
|
||||
from: {
|
||||
email: process.env.SMTP_FROM_EMAIL,
|
||||
name: process.env.SMTP_FROM_NAME
|
||||
},
|
||||
replyTo: senderEmail,
|
||||
subject: subject,
|
||||
text: message,
|
||||
html: `
|
||||
<p>${message.replace(/\n/g, '<br>')}</p>
|
||||
<hr>
|
||||
<p><small>This message was sent via the Alberta Influence Campaign Tool by ${senderName} (${senderEmail}) from postal code ${postalCode}.</small></p>
|
||||
`
|
||||
});
|
||||
|
||||
// Log the email send event
|
||||
await nocoDB.logEmailSend({
|
||||
recipientEmail,
|
||||
senderName,
|
||||
senderEmail,
|
||||
subject,
|
||||
postalCode,
|
||||
status: emailResult.success ? 'sent' : 'failed',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
if (emailResult.success) {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Email sent successfully',
|
||||
messageId: emailResult.messageId
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to send email',
|
||||
message: emailResult.error
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Send email error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to send email',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new EmailsController();
|
||||
170
influence/app/controllers/representatives.js
Normal file
170
influence/app/controllers/representatives.js
Normal file
@@ -0,0 +1,170 @@
|
||||
const representAPI = require('../services/represent-api');
|
||||
const nocoDB = require('../services/nocodb');
|
||||
|
||||
// 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
|
||||
await nocoDB.storeRepresentatives(postalCode, representatives);
|
||||
|
||||
console.log(`Successfully cached representatives for ${postalCode}`);
|
||||
} catch (error) {
|
||||
console.log(`Failed to cache representatives for ${postalCode}:`, error.message);
|
||||
// Don't throw - caching is optional
|
||||
}
|
||||
}
|
||||
|
||||
class RepresentativesController {
|
||||
async testConnection(req, res, next) {
|
||||
try {
|
||||
const result = await representAPI.testConnection();
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Test connection error:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to test connection',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getByPostalCode(req, res, next) {
|
||||
try {
|
||||
const { postalCode } = req.params;
|
||||
const formattedPostalCode = postalCode.replace(/\s/g, '').toUpperCase();
|
||||
|
||||
// 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) {
|
||||
return res.json({
|
||||
source: 'cache',
|
||||
postalCode: formattedPostalCode,
|
||||
representatives: cachedData
|
||||
});
|
||||
}
|
||||
} catch (cacheError) {
|
||||
console.log(`Cache unavailable for ${formattedPostalCode}, proceeding with API call:`, cacheError.message);
|
||||
}
|
||||
|
||||
// If not in cache, fetch from Represent API
|
||||
console.log(`Fetching representatives from Represent API for ${postalCode}`);
|
||||
const representData = await representAPI.getRepresentativesByPostalCode(postalCode);
|
||||
|
||||
if (!representData) {
|
||||
return res.json({
|
||||
success: false,
|
||||
message: 'No data found for this postal code',
|
||||
data: {
|
||||
postalCode,
|
||||
location: null,
|
||||
representatives: []
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Process representatives from both concordance and centroid
|
||||
let representatives = [];
|
||||
|
||||
// Add concordance representatives (if any)
|
||||
if (representData.boundaries_concordance && representData.boundaries_concordance.length > 0) {
|
||||
representatives = representatives.concat(representData.boundaries_concordance);
|
||||
}
|
||||
|
||||
// Add centroid representatives (if any) - these are the actual elected officials
|
||||
if (representData.representatives_centroid && representData.representatives_centroid.length > 0) {
|
||||
representatives = representatives.concat(representData.representatives_centroid);
|
||||
}
|
||||
|
||||
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}`);
|
||||
console.log(`Total representatives found: ${representatives.length}`);
|
||||
|
||||
if (representatives.length === 0) {
|
||||
return res.json({
|
||||
success: false,
|
||||
message: 'No representatives found for this postal code',
|
||||
data: {
|
||||
postalCode,
|
||||
location: {
|
||||
city: representData.city,
|
||||
province: representData.province
|
||||
},
|
||||
representatives: []
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Try to cache the results (will fail gracefully if NocoDB is down)
|
||||
console.log(`Attempting to cache ${representatives.length} representatives for ${postalCode}`);
|
||||
await cacheRepresentatives(postalCode, representatives, representData);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
postalCode,
|
||||
location: {
|
||||
city: representData.city,
|
||||
province: representData.province
|
||||
},
|
||||
representatives
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Get representatives error:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to fetch representatives',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async refreshPostalCode(req, res, next) {
|
||||
try {
|
||||
const { postalCode } = req.params;
|
||||
const formattedPostalCode = postalCode.replace(/\s/g, '').toUpperCase();
|
||||
|
||||
// Clear cached data
|
||||
await nocoDB.clearRepresentativesByPostalCode(formattedPostalCode);
|
||||
|
||||
// Fetch fresh data from API
|
||||
const representData = await representAPI.getRepresentativesByPostalCode(formattedPostalCode);
|
||||
|
||||
if (!representData || !representData.representatives_concordance) {
|
||||
return res.status(404).json({
|
||||
error: 'No representatives found for this postal code',
|
||||
postalCode: formattedPostalCode
|
||||
});
|
||||
}
|
||||
|
||||
// Cache the fresh results
|
||||
await nocoDB.storeRepresentatives(formattedPostalCode, representData.representatives_concordance);
|
||||
|
||||
res.json({
|
||||
source: 'refreshed',
|
||||
postalCode: formattedPostalCode,
|
||||
representatives: representData.representatives_concordance,
|
||||
city: representData.city,
|
||||
province: representData.province
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Refresh representatives error:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to refresh representatives',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new RepresentativesController();
|
||||
Reference in New Issue
Block a user