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
+
+
+
+
+
+
+
+
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 "============================================================"