diff --git a/influence/app/controllers/emails.js b/influence/app/controllers/emails.js index 84ac4b2..67cac38 100644 --- a/influence/app/controllers/emails.js +++ b/influence/app/controllers/emails.js @@ -1,5 +1,6 @@ const emailService = require('../services/email'); const nocoDB = require('../services/nocodb'); +const crypto = require('crypto'); class EmailsController { async sendEmail(req, res, next) { @@ -211,6 +212,206 @@ class EmailsController { }); } } + + async initiateEmailToCampaign(req, res, next) { + try { + const { email, subject, message, postalCode, senderName } = req.body; + + // Check if email verification is enabled + const verificationEnabled = process.env.EMAIL_VERIFICATION_ENABLED !== 'false'; + if (!verificationEnabled) { + return res.status(400).json({ + success: false, + error: 'Email verification is not enabled' + }); + } + + // Generate verification token + const token = crypto.randomBytes(32).toString('hex'); + const expiryHours = parseInt(process.env.EMAIL_VERIFICATION_EXPIRY) || 24; + const expiresAt = new Date(Date.now() + expiryHours * 60 * 60 * 1000); + + // Store token and campaign data + await nocoDB.createEmailVerification({ + token, + email, + temp_campaign_data: JSON.stringify({ + subject, + message, + postalCode, + senderName + }), + created_at: new Date().toISOString(), + expires_at: expiresAt.toISOString(), + used: false + }); + + // Send verification email + const appUrl = process.env.APP_URL || 'http://localhost:3333'; + const verificationUrl = `${appUrl}/verify-email.html?token=${token}`; + await emailService.sendEmailVerification(email, verificationUrl, senderName || 'there'); + + res.json({ + success: true, + message: 'Verification email sent. Please check your inbox.' + }); + } catch (error) { + console.error('Email to campaign conversion error:', error); + res.status(500).json({ + success: false, + error: 'Failed to initiate campaign conversion', + message: error.message + }); + } + } + + async verifyEmailToken(req, res, next) { + try { + const { token } = req.params; + + // Find verification record + const verification = await nocoDB.getEmailVerificationByToken(token); + + if (!verification) { + return res.status(404).json({ + success: false, + error: 'Invalid or expired verification link' + }); + } + + // Check if already used + if (verification.used) { + return res.status(400).json({ + success: false, + error: 'This verification link has already been used' + }); + } + + // Check if expired + const now = new Date(); + const expiresAt = new Date(verification.expires_at || verification.expiresAt || verification['Expires At']); + if (now > expiresAt) { + return res.status(400).json({ + success: false, + error: 'This verification link has expired' + }); + } + + // Mark as used + const verificationId = verification.id || verification.Id || verification.ID; + await nocoDB.updateEmailVerification(verificationId, { used: true }); + + // Parse campaign data + const campaignDataStr = verification.temp_campaign_data || verification.tempCampaignData || verification['Temp Campaign Data']; + const campaignData = JSON.parse(campaignDataStr); + + // Check if user exists + const userEmail = verification.email || verification.Email; + const existingUser = await nocoDB.getUserByEmail(userEmail); + + if (existingUser) { + // User exists, log them in automatically + req.session.authenticated = true; + req.session.userId = existingUser.ID || existingUser.Id || existingUser.id; + req.session.userEmail = existingUser.Email || existingUser.email; + req.session.userName = existingUser.Name || existingUser.name; + req.session.isAdmin = existingUser.Admin || existingUser.admin || false; + req.session.userType = existingUser['User Type'] || existingUser.UserType || existingUser.userType || 'user'; + + req.session.save((err) => { + if (err) { + console.error('Session save error:', err); + return res.status(500).json({ + success: false, + error: 'Session error' + }); + } + + res.json({ + success: true, + needsAccount: false, + campaignData: campaignData, + redirectTo: '/dashboard.html' + }); + }); + } else { + // User doesn't exist - create a new user account automatically + try { + // Generate a temporary password (user can change it later) + const tempPassword = crypto.randomBytes(16).toString('hex'); + + // Extract name from campaign data or use email prefix + const userName = campaignData.senderName || userEmail.split('@')[0]; + + // Create new user + const newUser = await nocoDB.createUser({ + 'Name': userName, + 'Email': userEmail, + 'Password': tempPassword, + 'Admin': false, + 'User Type': 'user' + }); + + const userId = newUser.ID || newUser.Id || newUser.id || newUser; + + // Send login credentials email to the new user + try { + await emailService.sendLoginDetails({ + Name: userName, + Email: userEmail, + Password: tempPassword, + admin: false + }); + console.log('Welcome email with credentials sent to:', userEmail); + } catch (emailError) { + console.error('Failed to send welcome email:', emailError); + // Don't fail the whole process if email sending fails + } + + // Log the new user in automatically + req.session.authenticated = true; + req.session.userId = userId; + req.session.userEmail = userEmail; + req.session.userName = userName; + req.session.isAdmin = false; + req.session.userType = 'user'; + + req.session.save((err) => { + if (err) { + console.error('Session save error:', err); + return res.status(500).json({ + success: false, + error: 'Session error' + }); + } + + res.json({ + success: true, + needsAccount: false, + isNewUser: true, + campaignData: campaignData, + redirectTo: '/dashboard.html', + message: 'Account created successfully! Check your email for login credentials.' + }); + }); + } catch (createError) { + console.error('Error creating user account:', createError); + return res.status(500).json({ + success: false, + error: 'Failed to create user account', + message: createError.message + }); + } + } + } catch (error) { + console.error('Email verification error:', error); + res.status(500).json({ + success: false, + error: 'Failed to verify email', + message: error.message + }); + } + } } module.exports = new EmailsController(); \ No newline at end of file diff --git a/influence/app/public/js/dashboard.js b/influence/app/public/js/dashboard.js index 9d0c41e..42429db 100644 --- a/influence/app/public/js/dashboard.js +++ b/influence/app/public/js/dashboard.js @@ -28,6 +28,9 @@ class UserDashboard { this.setupEventListeners(); this.loadUserCampaigns(); this.loadAnalytics(); + + // Check for pending campaign from email verification + this.checkPendingCampaign(); } setupUserInterface() { @@ -405,6 +408,94 @@ class UserDashboard { } } + checkPendingCampaign() { + // Check for pending campaign data from email verification + const pendingCampaignData = sessionStorage.getItem('pendingCampaign'); + + if (pendingCampaignData) { + try { + const campaignData = JSON.parse(pendingCampaignData); + + // Clear the session storage + sessionStorage.removeItem('pendingCampaign'); + + // Pre-populate the create campaign form + this.populateCampaignFormFromEmail(campaignData); + + // Switch to create tab + this.switchTab('create'); + + // Show helpful message + this.showMessage( + '🎉 Your email content has been loaded! Please add a campaign title and description, then publish your campaign.', + 'success', + 10000 + ); + + // Show additional info for new users + if (this.user && !this.user.lastLogin) { + setTimeout(() => { + this.showMessage( + 'Tip: You can update your password anytime in the Account Settings tab.', + 'info', + 8000 + ); + }, 2000); + } + } catch (error) { + console.error('Error loading pending campaign:', error); + } + } + } + + populateCampaignFormFromEmail(campaignData) { + // Pre-fill form fields with email data + const titleField = document.getElementById('create-title'); + const descriptionField = document.getElementById('create-description'); + const subjectField = document.getElementById('create-email-subject'); + const bodyField = document.getElementById('create-email-body'); + + // Auto-generate title from subject + if (titleField && campaignData.subject) { + const suggestedTitle = campaignData.subject.length > 50 + ? campaignData.subject.substring(0, 47) + '...' + : campaignData.subject; + titleField.value = suggestedTitle; + } + + // Add helpful description template + if (descriptionField && campaignData.postalCode) { + descriptionField.value = `Join others from ${campaignData.postalCode} and across the region in sending this important message to our representatives.`; + } + + // Fill in email content + if (subjectField && campaignData.subject) { + subjectField.value = campaignData.subject; + } + + if (bodyField && campaignData.message) { + bodyField.value = campaignData.message; + } + + // Add a subtle highlight to show pre-filled fields + [titleField, descriptionField, subjectField, bodyField].forEach(field => { + if (field && field.value) { + field.style.backgroundColor = '#fffacd'; // Light yellow + + // Remove highlight on focus + field.addEventListener('focus', function() { + this.style.backgroundColor = ''; + }, { once: true }); + } + }); + + // Scroll to top of form + const createTab = document.getElementById('create-tab'); + if (createTab) { + createTab.scrollTop = 0; + } + } + async loadUserCampaigns() { const loadingDiv = document.getElementById('campaigns-loading'); const listDiv = document.getElementById('campaigns-list'); diff --git a/influence/app/public/js/email-composer.js b/influence/app/public/js/email-composer.js index eef834f..1c6fddb 100644 --- a/influence/app/public/js/email-composer.js +++ b/influence/app/public/js/email-composer.js @@ -572,6 +572,9 @@ class EmailComposer { if (result.success) { window.messageDisplay.show('Email sent successfully! Your representative will receive your message.', 'success', 7000); + // Show campaign conversion prompt if user is not authenticated + this.showCampaignConversionPrompt(this.currentEmailData); + // Close the inline composer after successful send this.closeInlineComposer(); this.currentEmailData = null; @@ -851,6 +854,10 @@ class EmailComposer { if (result.success) { window.messageDisplay.show('Email sent successfully! Your representative will receive your message.', 'success'); + + // Show campaign conversion prompt if user is not authenticated + this.showCampaignConversionPrompt(this.currentEmailData); + this.closePreviewModal(); this.currentEmailData = null; } else { @@ -927,6 +934,132 @@ Respectfully, return templates[type] || templates.general; } + + // Campaign conversion prompt + showCampaignConversionPrompt(emailData) { + // Only show if user is not authenticated + if (window.authManager && window.authManager.isAuthenticated) { + return; + } + + // Create the conversion prompt + const promptHTML = ` +
+ + +

🚀 Turn Your Message Into a Campaign!

+

+ Want others to join your cause? Create a campaign so more people can send similar messages. +

+ +
+ + +
+
+ + + `; + + // Insert the prompt + const promptDiv = document.createElement('div'); + promptDiv.innerHTML = promptHTML; + document.body.appendChild(promptDiv); + + // Attach event listeners + const convertBtn = promptDiv.querySelector('#convert-to-campaign'); + const skipBtn = promptDiv.querySelector('#skip-conversion'); + const closeBtn = promptDiv.querySelector('.close-prompt'); + + const removePrompt = () => { + promptDiv.remove(); + }; + + convertBtn.addEventListener('click', () => { + this.initiateConversion(emailData); + removePrompt(); + }); + + skipBtn.addEventListener('click', removePrompt); + closeBtn.addEventListener('click', removePrompt); + + // Auto-remove after 30 seconds + setTimeout(removePrompt, 30000); + } + + async initiateConversion(emailData) { + if (!emailData) { + window.messageDisplay.show('No email data available for conversion', 'error'); + return; + } + + try { + // Show loading message + window.messageDisplay.show('Sending verification email...', 'info'); + + const conversionData = { + email: emailData.senderEmail, + senderName: emailData.senderName, + subject: emailData.subject, + message: emailData.message, + postalCode: emailData.postalCode + }; + + const response = await window.apiClient.post('/emails/convert-to-campaign', conversionData); + + if (response.success) { + window.messageDisplay.show( + 'Verification email sent! Please check your inbox to complete campaign creation.', + 'success', + 10000 + ); + } else { + throw new Error(response.error || 'Failed to send verification email'); + } + } catch (error) { + console.error('Campaign conversion error:', error); + window.messageDisplay.show( + `Failed to initiate campaign creation: ${error.message}`, + 'error', + 8000 + ); + } + } } // Initialize when DOM is loaded diff --git a/influence/app/public/js/verify-email.js b/influence/app/public/js/verify-email.js new file mode 100644 index 0000000..bfdac3d --- /dev/null +++ b/influence/app/public/js/verify-email.js @@ -0,0 +1,89 @@ +// Email Verification Handler +document.addEventListener('DOMContentLoaded', async () => { + const statusDiv = document.getElementById('verification-status'); + + // Get token from URL query parameters + const urlParams = new URLSearchParams(window.location.search); + const token = urlParams.get('token'); + + if (!token) { + showError('Invalid verification link - no token provided'); + return; + } + + try { + // Call API to verify token + const response = await fetch(`/api/verify-email/${token}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + }, + credentials: 'include' // Important for session management + }); + + const data = await response.json(); + + if (data.success) { + // Store campaign data in session storage for dashboard + if (data.campaignData) { + sessionStorage.setItem('pendingCampaign', JSON.stringify(data.campaignData)); + } + + // Show success and redirect + if (data.isNewUser) { + showSuccess( + '✅ Welcome! Your account has been created successfully. Check your email for login credentials. Your email content is ready - just add a campaign title and description!', + '/dashboard.html', + 'Go to Dashboard', + 4000 + ); + } else if (data.needsAccount) { + showSuccess( + 'Email verified! Please create your account to continue.', + '/login.html', + 'Go to Login', + 3000 + ); + } else { + showSuccess( + 'Email verified! Redirecting to campaign creation...', + '/dashboard.html', + 'Go to Dashboard', + 2000 + ); + } + } else { + showError(data.error || 'Verification failed'); + } + } catch (error) { + console.error('Verification error:', error); + showError('An error occurred during verification. Please try again or contact support.'); + } +}); + +function showSuccess(message, redirectUrl, buttonText, autoRedirectDelay = 3000) { + const statusDiv = document.getElementById('verification-status'); + + statusDiv.innerHTML = ` +
+

Verification Successful!

+

${message}

+ ${buttonText} + `; + + // Auto-redirect after delay + setTimeout(() => { + window.location.href = redirectUrl; + }, autoRedirectDelay); +} + +function showError(errorMessage) { + const statusDiv = document.getElementById('verification-status'); + + statusDiv.innerHTML = ` +
+

Verification Failed

+

${errorMessage}

+ Return to Home + `; +} diff --git a/influence/app/public/verify-email.html b/influence/app/public/verify-email.html new file mode 100644 index 0000000..901a526 --- /dev/null +++ b/influence/app/public/verify-email.html @@ -0,0 +1,102 @@ + + + + + + Email Verification - BNKops Influence + + + + + +
+
+ + +
+
+
+

Verifying your email...

+
+
+
+
+ + + + + diff --git a/influence/app/routes/api.js b/influence/app/routes/api.js index 6414c59..0d1962b 100644 --- a/influence/app/routes/api.js +++ b/influence/app/routes/api.js @@ -90,6 +90,26 @@ router.post( emailsController.sendTestEmail ); +// Email-to-campaign conversion endpoints +router.post( + '/emails/convert-to-campaign', + rateLimiter.general, + [ + body('email').isEmail().withMessage('Valid email is required'), + body('subject').notEmpty().withMessage('Subject is required'), + body('message').notEmpty().withMessage('Message is required'), + body('postalCode').matches(/^[A-Za-z]\d[A-Za-z]\s?\d[A-Za-z]\d$/).withMessage('Invalid postal code format') + ], + handleValidationErrors, + emailsController.initiateEmailToCampaign +); + +router.get( + '/verify-email/:token', + rateLimiter.general, + emailsController.verifyEmailToken +); + router.get( '/emails/logs', requireAdmin, diff --git a/influence/app/services/email.js b/influence/app/services/email.js index 1c5fabd..1dade3d 100644 --- a/influence/app/services/email.js +++ b/influence/app/services/email.js @@ -386,6 +386,29 @@ class EmailService { throw error; } } + + async sendEmailVerification(recipientEmail, verificationUrl, userName) { + try { + const templateVariables = { + USER_NAME: userName || 'there', + VERIFICATION_URL: verificationUrl + }; + + const emailOptions = { + to: recipientEmail, + from: { + email: process.env.SMTP_FROM_EMAIL, + name: process.env.SMTP_FROM_NAME + }, + subject: 'Verify Your Email to Create Your Campaign' + }; + + return await this.sendTemplatedEmail('email-verification', templateVariables, emailOptions); + } catch (error) { + console.error('Failed to send email verification:', error); + throw error; + } + } } module.exports = new EmailService(); \ No newline at end of file diff --git a/influence/app/services/nocodb.js b/influence/app/services/nocodb.js index 0c87bc1..b3af67f 100644 --- a/influence/app/services/nocodb.js +++ b/influence/app/services/nocodb.js @@ -28,7 +28,8 @@ class NocoDBService { users: process.env.NOCODB_TABLE_USERS, calls: process.env.NOCODB_TABLE_CALLS, representativeResponses: process.env.NOCODB_TABLE_REPRESENTATIVE_RESPONSES, - responseUpvotes: process.env.NOCODB_TABLE_RESPONSE_UPVOTES + responseUpvotes: process.env.NOCODB_TABLE_RESPONSE_UPVOTES, + emailVerifications: process.env.NOCODB_TABLE_EMAIL_VERIFICATIONS }; // Validate that all table IDs are set @@ -877,6 +878,83 @@ class NocoDBService { created_at: data.CreatedAt || data.created_at }; } + + // Email verification methods + async createEmailVerification(verificationData) { + if (!this.tableIds.emailVerifications) { + throw new Error('Email verifications table not configured'); + } + + const data = { + 'Token': verificationData.token, + 'Email': verificationData.email, + 'Temp Campaign Data': verificationData.temp_campaign_data, + 'Created At': verificationData.created_at, + 'Expires At': verificationData.expires_at, + 'Used': verificationData.used || false + }; + + return await this.create(this.tableIds.emailVerifications, data); + } + + async getEmailVerificationByToken(token) { + if (!this.tableIds.emailVerifications) { + throw new Error('Email verifications table not configured'); + } + + try { + const response = await this.getAll(this.tableIds.emailVerifications, { + where: `(Token,eq,${token})`, + limit: 1 + }); + + return response.list?.[0] || null; + } catch (error) { + console.error('Error in getEmailVerificationByToken:', error.message); + throw error; + } + } + + async updateEmailVerification(verificationId, updateData) { + if (!this.tableIds.emailVerifications) { + throw new Error('Email verifications table not configured'); + } + + const data = {}; + if (updateData.used !== undefined) { + data['Used'] = updateData.used; + } + + return await this.update(this.tableIds.emailVerifications, verificationId, data); + } + + async deleteExpiredEmailVerifications() { + if (!this.tableIds.emailVerifications) { + throw new Error('Email verifications table not configured'); + } + + try { + const now = new Date().toISOString(); + const response = await this.getAll(this.tableIds.emailVerifications, { + where: `(Expires At,lt,${now})` + }); + + if (response.list && response.list.length > 0) { + for (const verification of response.list) { + const id = verification.ID || verification.Id || verification.id; + if (id) { + await this.client.delete(`${this.getTableUrl(this.tableIds.emailVerifications)}/${id}`); + } + } + return { success: true, deletedCount: response.list.length }; + } + + return { success: true, deletedCount: 0 }; + } catch (error) { + console.error('Error deleting expired email verifications:', error.message); + return { success: false, error: error.message }; + } + } } module.exports = new NocoDBService(); diff --git a/influence/app/templates/email/email-verification.html b/influence/app/templates/email/email-verification.html new file mode 100644 index 0000000..ad7cd6d --- /dev/null +++ b/influence/app/templates/email/email-verification.html @@ -0,0 +1,113 @@ + + + + + Verify Your Email to Create a Campaign + + + +
+
+ +
+
+

🚀 Verify Your Email to Create a Campaign

+

Hi {{USER_NAME}},

+

You're one step away from turning your message into a powerful campaign!

+ +
+ What happens next? +
    +
  • Click the verification button below
  • +
  • Create your account (if you don't have one)
  • +
  • Your email content will be pre-filled
  • +
  • Add campaign details and publish!
  • +
+
+ +

+ Verify Email & Create Campaign +

+ +
+ ⏰ Important: This link expires in 24 hours. +
+ +

+ If you didn't request this, you can safely ignore this email. +

+ +

+ If the button doesn't work, copy and paste this link into your browser:
+ {{VERIFICATION_URL}} +

+
+ +
+ + diff --git a/influence/app/templates/email/email-verification.txt b/influence/app/templates/email/email-verification.txt new file mode 100644 index 0000000..85b756b --- /dev/null +++ b/influence/app/templates/email/email-verification.txt @@ -0,0 +1,24 @@ +{{APP_NAME}} + +Verify Your Email to Create a Campaign + +Hi {{USER_NAME}}, + +You're one step away from turning your message into a powerful campaign! + +What happens next? +- Click the verification link below +- Create your account (if you don't have one) +- Your email content will be pre-filled +- Add campaign details and publish! + +Verify your email by clicking this link: +{{VERIFICATION_URL}} + +⏰ Important: This link expires in 24 hours. + +If you didn't request this, you can safely ignore this email. + +--- +{{APP_NAME}} - Empowering civic engagement +{{TIMESTAMP}} diff --git a/influence/example.env b/influence/example.env index 75d0369..31232f2 100644 --- a/influence/example.env +++ b/influence/example.env @@ -39,6 +39,11 @@ NODE_ENV=development EMAIL_TEST_MODE=true TEST_EMAIL_RECIPIENT=your-test-email@domain.com +# Email Verification Configuration +# For email-to-campaign conversion feature +EMAIL_VERIFICATION_ENABLED=true +EMAIL_VERIFICATION_EXPIRY=24 + # NocoDB Table IDs # These will be auto-generated when you run build-nocodb.sh # DO NOT modify these manually - they are set by the setup script @@ -48,6 +53,10 @@ NOCODB_TABLE_POSTAL_CODES= NOCODB_TABLE_CAMPAIGN_EMAILS= NOCODB_TABLE_CAMPAIGNS= NOCODB_TABLE_USERS= +NOCODB_TABLE_CALLS= +NOCODB_TABLE_REPRESENTATIVE_RESPONSES= +NOCODB_TABLE_RESPONSE_UPVOTES= +NOCODB_TABLE_EMAIL_VERIFICATIONS= # Optional: Development Mode Settings # Uncomment and modify these for local development with MailHog diff --git a/influence/scripts/build-nocodb.sh b/influence/scripts/build-nocodb.sh index 1e8bb2f..c43a5b0 100755 --- a/influence/scripts/build-nocodb.sh +++ b/influence/scripts/build-nocodb.sh @@ -1615,6 +1615,65 @@ create_users_table() { create_table "$base_id" "influence_users" "$table_data" "User authentication and management" } +# Function to create the email verifications table +create_email_verifications_table() { + local base_id=$1 + + local table_data='{ + "table_name": "influence_email_verifications", + "title": "Influence Email Verifications", + "columns": [ + { + "column_name": "id", + "title": "ID", + "uidt": "ID", + "pk": true, + "ai": true, + "rqd": true + }, + { + "column_name": "token", + "title": "Token", + "uidt": "SingleLineText", + "rqd": true + }, + { + "column_name": "email", + "title": "Email", + "uidt": "Email", + "rqd": true + }, + { + "column_name": "temp_campaign_data", + "title": "Temp Campaign Data", + "uidt": "LongText", + "rqd": false + }, + { + "column_name": "created_at", + "title": "Created At", + "uidt": "DateTime", + "rqd": false + }, + { + "column_name": "expires_at", + "title": "Expires At", + "uidt": "DateTime", + "rqd": true + }, + { + "column_name": "used", + "title": "Used", + "uidt": "Checkbox", + "rqd": false, + "cdf": "false" + } + ] + }' + + create_table "$base_id" "influence_email_verifications" "$table_data" "Email verification tokens for campaign conversion" +} + # Function to create a new base create_base() { local base_data='{ @@ -1654,6 +1713,7 @@ update_env_with_table_ids() { local call_logs_table_id=$8 local representative_responses_table_id=$9 local response_upvotes_table_id=${10} + local email_verifications_table_id=${11} print_status "Updating .env file with NocoDB project and table IDs..." @@ -1691,6 +1751,7 @@ update_env_with_table_ids() { update_env_var "NOCODB_TABLE_CALLS" "$call_logs_table_id" update_env_var "NOCODB_TABLE_REPRESENTATIVE_RESPONSES" "$representative_responses_table_id" update_env_var "NOCODB_TABLE_RESPONSE_UPVOTES" "$response_upvotes_table_id" + update_env_var "NOCODB_TABLE_EMAIL_VERIFICATIONS" "$email_verifications_table_id" print_success "Successfully updated .env file with all table IDs" @@ -1828,8 +1889,15 @@ main() { exit 1 fi + # Create email verifications table + EMAIL_VERIFICATIONS_TABLE_ID=$(create_email_verifications_table "$BASE_ID") + if [[ $? -ne 0 ]]; then + print_error "Failed to create email verifications table" + exit 1 + fi + # Validate all table IDs were created successfully - if ! validate_table_ids "$REPRESENTATIVES_TABLE_ID" "$EMAIL_LOGS_TABLE_ID" "$POSTAL_CODES_TABLE_ID" "$CAMPAIGNS_TABLE_ID" "$CAMPAIGN_EMAILS_TABLE_ID" "$USERS_TABLE_ID" "$CALL_LOGS_TABLE_ID" "$REPRESENTATIVE_RESPONSES_TABLE_ID" "$RESPONSE_UPVOTES_TABLE_ID"; then + if ! validate_table_ids "$REPRESENTATIVES_TABLE_ID" "$EMAIL_LOGS_TABLE_ID" "$POSTAL_CODES_TABLE_ID" "$CAMPAIGNS_TABLE_ID" "$CAMPAIGN_EMAILS_TABLE_ID" "$USERS_TABLE_ID" "$CALL_LOGS_TABLE_ID" "$REPRESENTATIVE_RESPONSES_TABLE_ID" "$RESPONSE_UPVOTES_TABLE_ID" "$EMAIL_VERIFICATIONS_TABLE_ID"; then print_error "One or more table IDs are invalid" exit 1 fi @@ -1853,6 +1921,7 @@ main() { table_mapping["influence_call_logs"]="$CALL_LOGS_TABLE_ID" table_mapping["influence_representative_responses"]="$REPRESENTATIVE_RESPONSES_TABLE_ID" table_mapping["influence_response_upvotes"]="$RESPONSE_UPVOTES_TABLE_ID" + table_mapping["influence_email_verifications"]="$EMAIL_VERIFICATIONS_TABLE_ID" # Get source table information local source_tables_response @@ -1907,6 +1976,7 @@ main() { print_status " - influence_call_logs (ID: $CALL_LOGS_TABLE_ID)" print_status " - influence_representative_responses (ID: $REPRESENTATIVE_RESPONSES_TABLE_ID)" print_status " - influence_response_upvotes (ID: $RESPONSE_UPVOTES_TABLE_ID)" + print_status " - influence_email_verifications (ID: $EMAIL_VERIFICATIONS_TABLE_ID)" # Automatically update .env file with new project ID print_status "" @@ -1929,7 +1999,7 @@ main() { fi # Update .env file with table IDs - update_env_with_table_ids "$BASE_ID" "$REPRESENTATIVES_TABLE_ID" "$EMAIL_LOGS_TABLE_ID" "$POSTAL_CODES_TABLE_ID" "$CAMPAIGNS_TABLE_ID" "$CAMPAIGN_EMAILS_TABLE_ID" "$USERS_TABLE_ID" "$CALL_LOGS_TABLE_ID" "$REPRESENTATIVE_RESPONSES_TABLE_ID" "$RESPONSE_UPVOTES_TABLE_ID" + update_env_with_table_ids "$BASE_ID" "$REPRESENTATIVES_TABLE_ID" "$EMAIL_LOGS_TABLE_ID" "$POSTAL_CODES_TABLE_ID" "$CAMPAIGNS_TABLE_ID" "$CAMPAIGN_EMAILS_TABLE_ID" "$USERS_TABLE_ID" "$CALL_LOGS_TABLE_ID" "$REPRESENTATIVE_RESPONSES_TABLE_ID" "$RESPONSE_UPVOTES_TABLE_ID" "$EMAIL_VERIFICATIONS_TABLE_ID" print_status "" print_status "============================================================"