new system for creating campaigns from the main site dashboard
This commit is contained in:
parent
b71a6e4ff3
commit
06ecffaf4d
@ -1,5 +1,6 @@
|
|||||||
const emailService = require('../services/email');
|
const emailService = require('../services/email');
|
||||||
const nocoDB = require('../services/nocodb');
|
const nocoDB = require('../services/nocodb');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
class EmailsController {
|
class EmailsController {
|
||||||
async sendEmail(req, res, next) {
|
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();
|
module.exports = new EmailsController();
|
||||||
@ -28,6 +28,9 @@ class UserDashboard {
|
|||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
this.loadUserCampaigns();
|
this.loadUserCampaigns();
|
||||||
this.loadAnalytics();
|
this.loadAnalytics();
|
||||||
|
|
||||||
|
// Check for pending campaign from email verification
|
||||||
|
this.checkPendingCampaign();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupUserInterface() {
|
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() {
|
async loadUserCampaigns() {
|
||||||
const loadingDiv = document.getElementById('campaigns-loading');
|
const loadingDiv = document.getElementById('campaigns-loading');
|
||||||
const listDiv = document.getElementById('campaigns-list');
|
const listDiv = document.getElementById('campaigns-list');
|
||||||
|
|||||||
@ -572,6 +572,9 @@ class EmailComposer {
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
window.messageDisplay.show('Email sent successfully! Your representative will receive your message.', 'success', 7000);
|
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
|
// Close the inline composer after successful send
|
||||||
this.closeInlineComposer();
|
this.closeInlineComposer();
|
||||||
this.currentEmailData = null;
|
this.currentEmailData = null;
|
||||||
@ -851,6 +854,10 @@ class EmailComposer {
|
|||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
window.messageDisplay.show('Email sent successfully! Your representative will receive your message.', '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.closePreviewModal();
|
||||||
this.currentEmailData = null;
|
this.currentEmailData = null;
|
||||||
} else {
|
} else {
|
||||||
@ -927,6 +934,132 @@ Respectfully,
|
|||||||
|
|
||||||
return templates[type] || templates.general;
|
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 = `
|
||||||
|
<div class="campaign-conversion-prompt" style="
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||||
|
max-width: 400px;
|
||||||
|
z-index: 10000;
|
||||||
|
animation: slideIn 0.3s ease-out;
|
||||||
|
">
|
||||||
|
<button class="close-prompt" style="
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #999;
|
||||||
|
">×</button>
|
||||||
|
|
||||||
|
<h3 style="margin-top: 0; color: #d73027;">🚀 Turn Your Message Into a Campaign!</h3>
|
||||||
|
<p style="margin: 15px 0; color: #666;">
|
||||||
|
Want others to join your cause? Create a campaign so more people can send similar messages.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 10px; margin-top: 20px;">
|
||||||
|
<button id="convert-to-campaign" class="btn btn-primary" style="flex: 1;">
|
||||||
|
Create Campaign
|
||||||
|
</button>
|
||||||
|
<button id="skip-conversion" class="btn btn-secondary" style="flex: 1;">
|
||||||
|
No Thanks
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 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
|
// Initialize when DOM is loaded
|
||||||
|
|||||||
89
influence/app/public/js/verify-email.js
Normal file
89
influence/app/public/js/verify-email.js
Normal file
@ -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 = `
|
||||||
|
<div class="success-icon">✓</div>
|
||||||
|
<h2 style="color: #28a745;">Verification Successful!</h2>
|
||||||
|
<p class="message">${message}</p>
|
||||||
|
<a href="${redirectUrl}" class="btn">${buttonText}</a>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Auto-redirect after delay
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = redirectUrl;
|
||||||
|
}, autoRedirectDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(errorMessage) {
|
||||||
|
const statusDiv = document.getElementById('verification-status');
|
||||||
|
|
||||||
|
statusDiv.innerHTML = `
|
||||||
|
<div class="error-icon">✗</div>
|
||||||
|
<h2 style="color: #dc3545;">Verification Failed</h2>
|
||||||
|
<p class="message">${errorMessage}</p>
|
||||||
|
<a href="/" class="btn" style="background-color: #666;">Return to Home</a>
|
||||||
|
`;
|
||||||
|
}
|
||||||
102
influence/app/public/verify-email.html
Normal file
102
influence/app/public/verify-email.html
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Email Verification - BNKops Influence</title>
|
||||||
|
<link rel="icon" href="data:,">
|
||||||
|
<link rel="stylesheet" href="/css/styles.css">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.verification-container {
|
||||||
|
background: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||||
|
max-width: 500px;
|
||||||
|
width: 90%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-top: 4px solid #d73027;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-icon {
|
||||||
|
font-size: 64px;
|
||||||
|
color: #28a745;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-icon {
|
||||||
|
font-size: 64px;
|
||||||
|
color: #dc3545;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
margin: 20px 0;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 12px 24px;
|
||||||
|
background-color: #d73027;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background-color: #c02822;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
color: #d73027;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="verification-container">
|
||||||
|
<div class="logo">BNKops Influence</div>
|
||||||
|
|
||||||
|
<div id="verification-status">
|
||||||
|
<div class="loading">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p class="message">Verifying your email...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/js/api-client.js"></script>
|
||||||
|
<script src="/js/verify-email.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -90,6 +90,26 @@ router.post(
|
|||||||
emailsController.sendTestEmail
|
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(
|
router.get(
|
||||||
'/emails/logs',
|
'/emails/logs',
|
||||||
requireAdmin,
|
requireAdmin,
|
||||||
|
|||||||
@ -386,6 +386,29 @@ class EmailService {
|
|||||||
throw error;
|
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();
|
module.exports = new EmailService();
|
||||||
@ -28,7 +28,8 @@ class NocoDBService {
|
|||||||
users: process.env.NOCODB_TABLE_USERS,
|
users: process.env.NOCODB_TABLE_USERS,
|
||||||
calls: process.env.NOCODB_TABLE_CALLS,
|
calls: process.env.NOCODB_TABLE_CALLS,
|
||||||
representativeResponses: process.env.NOCODB_TABLE_REPRESENTATIVE_RESPONSES,
|
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
|
// Validate that all table IDs are set
|
||||||
@ -877,6 +878,83 @@ class NocoDBService {
|
|||||||
created_at: data.CreatedAt || data.created_at
|
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();
|
module.exports = new NocoDBService();
|
||||||
|
|||||||
113
influence/app/templates/email/email-verification.html
Normal file
113
influence/app/templates/email/email-verification.html
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Verify Your Email to Create a Campaign</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 30px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
color: #d73027;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
background-color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.cta-button {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #d73027;
|
||||||
|
color: white !important;
|
||||||
|
padding: 14px 28px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 20px 0;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
color: #3498db;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #e8f4f8;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.highlight {
|
||||||
|
background-color: #fff3cd;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 15px 0;
|
||||||
|
border-left: 4px solid #ffc107;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo">{{APP_NAME}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<h2>🚀 Verify Your Email to Create a Campaign</h2>
|
||||||
|
<p>Hi {{USER_NAME}},</p>
|
||||||
|
<p>You're one step away from turning your message into a powerful campaign!</p>
|
||||||
|
|
||||||
|
<div class="highlight">
|
||||||
|
<strong>What happens next?</strong>
|
||||||
|
<ul style="margin: 10px 0;">
|
||||||
|
<li>Click the verification button below</li>
|
||||||
|
<li>Create your account (if you don't have one)</li>
|
||||||
|
<li>Your email content will be pre-filled</li>
|
||||||
|
<li>Add campaign details and publish!</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<a href="{{VERIFICATION_URL}}" class="cta-button">Verify Email & Create Campaign</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<strong>⏰ Important:</strong> This link expires in 24 hours.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p style="margin-top: 20px; font-size: 14px; color: #666;">
|
||||||
|
If you didn't request this, you can safely ignore this email.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p style="margin-top: 20px; font-size: 12px; color: #999;">
|
||||||
|
If the button doesn't work, copy and paste this link into your browser:<br>
|
||||||
|
<span style="word-break: break-all;">{{VERIFICATION_URL}}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<p>{{APP_NAME}} - Empowering civic engagement</p>
|
||||||
|
<p style="margin-top: 10px;">{{TIMESTAMP}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
24
influence/app/templates/email/email-verification.txt
Normal file
24
influence/app/templates/email/email-verification.txt
Normal file
@ -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}}
|
||||||
@ -39,6 +39,11 @@ NODE_ENV=development
|
|||||||
EMAIL_TEST_MODE=true
|
EMAIL_TEST_MODE=true
|
||||||
TEST_EMAIL_RECIPIENT=your-test-email@domain.com
|
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
|
# NocoDB Table IDs
|
||||||
# These will be auto-generated when you run build-nocodb.sh
|
# These will be auto-generated when you run build-nocodb.sh
|
||||||
# DO NOT modify these manually - they are set by the setup script
|
# 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_CAMPAIGN_EMAILS=
|
||||||
NOCODB_TABLE_CAMPAIGNS=
|
NOCODB_TABLE_CAMPAIGNS=
|
||||||
NOCODB_TABLE_USERS=
|
NOCODB_TABLE_USERS=
|
||||||
|
NOCODB_TABLE_CALLS=
|
||||||
|
NOCODB_TABLE_REPRESENTATIVE_RESPONSES=
|
||||||
|
NOCODB_TABLE_RESPONSE_UPVOTES=
|
||||||
|
NOCODB_TABLE_EMAIL_VERIFICATIONS=
|
||||||
|
|
||||||
# Optional: Development Mode Settings
|
# Optional: Development Mode Settings
|
||||||
# Uncomment and modify these for local development with MailHog
|
# Uncomment and modify these for local development with MailHog
|
||||||
|
|||||||
@ -1615,6 +1615,65 @@ create_users_table() {
|
|||||||
create_table "$base_id" "influence_users" "$table_data" "User authentication and management"
|
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
|
# Function to create a new base
|
||||||
create_base() {
|
create_base() {
|
||||||
local base_data='{
|
local base_data='{
|
||||||
@ -1654,6 +1713,7 @@ update_env_with_table_ids() {
|
|||||||
local call_logs_table_id=$8
|
local call_logs_table_id=$8
|
||||||
local representative_responses_table_id=$9
|
local representative_responses_table_id=$9
|
||||||
local response_upvotes_table_id=${10}
|
local response_upvotes_table_id=${10}
|
||||||
|
local email_verifications_table_id=${11}
|
||||||
|
|
||||||
print_status "Updating .env file with NocoDB project and table IDs..."
|
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_CALLS" "$call_logs_table_id"
|
||||||
update_env_var "NOCODB_TABLE_REPRESENTATIVE_RESPONSES" "$representative_responses_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_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"
|
print_success "Successfully updated .env file with all table IDs"
|
||||||
|
|
||||||
@ -1828,8 +1889,15 @@ main() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
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
|
# 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"
|
print_error "One or more table IDs are invalid"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@ -1853,6 +1921,7 @@ main() {
|
|||||||
table_mapping["influence_call_logs"]="$CALL_LOGS_TABLE_ID"
|
table_mapping["influence_call_logs"]="$CALL_LOGS_TABLE_ID"
|
||||||
table_mapping["influence_representative_responses"]="$REPRESENTATIVE_RESPONSES_TABLE_ID"
|
table_mapping["influence_representative_responses"]="$REPRESENTATIVE_RESPONSES_TABLE_ID"
|
||||||
table_mapping["influence_response_upvotes"]="$RESPONSE_UPVOTES_TABLE_ID"
|
table_mapping["influence_response_upvotes"]="$RESPONSE_UPVOTES_TABLE_ID"
|
||||||
|
table_mapping["influence_email_verifications"]="$EMAIL_VERIFICATIONS_TABLE_ID"
|
||||||
|
|
||||||
# Get source table information
|
# Get source table information
|
||||||
local source_tables_response
|
local source_tables_response
|
||||||
@ -1907,6 +1976,7 @@ main() {
|
|||||||
print_status " - influence_call_logs (ID: $CALL_LOGS_TABLE_ID)"
|
print_status " - influence_call_logs (ID: $CALL_LOGS_TABLE_ID)"
|
||||||
print_status " - influence_representative_responses (ID: $REPRESENTATIVE_RESPONSES_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_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
|
# Automatically update .env file with new project ID
|
||||||
print_status ""
|
print_status ""
|
||||||
@ -1929,7 +1999,7 @@ main() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Update .env file with table IDs
|
# 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 ""
|
||||||
print_status "============================================================"
|
print_status "============================================================"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user