Whole new user interface and user system
This commit is contained in:
@@ -48,6 +48,24 @@ class AuthController {
|
||||
});
|
||||
}
|
||||
|
||||
// Check if temp user has expired
|
||||
const userType = user['User Type'] || user.UserType || user.userType || 'user';
|
||||
if (userType === 'temp') {
|
||||
const expiration = user.ExpiresAt || user.expiresAt || user.Expiration || user.expiration;
|
||||
if (expiration) {
|
||||
const expirationDate = new Date(expiration);
|
||||
const now = new Date();
|
||||
|
||||
if (now > expirationDate) {
|
||||
console.warn(`Expired temp user attempted login: ${email}, expired: ${expiration}`);
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Account has expired. Please contact an administrator.'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update last login time
|
||||
try {
|
||||
// Debug: Log user object structure
|
||||
@@ -78,6 +96,7 @@ class AuthController {
|
||||
req.session.userEmail = user.Email || user.email;
|
||||
req.session.userName = user.Name || user.name;
|
||||
req.session.isAdmin = user.Admin || user.admin || false;
|
||||
req.session.userType = userType;
|
||||
|
||||
console.log('User logged in successfully:', {
|
||||
email: req.session.userEmail,
|
||||
@@ -100,7 +119,8 @@ class AuthController {
|
||||
id: req.session.userId,
|
||||
email: req.session.userEmail,
|
||||
name: req.session.userName,
|
||||
isAdmin: req.session.isAdmin
|
||||
isAdmin: req.session.isAdmin,
|
||||
userType: req.session.userType
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -151,7 +171,8 @@ class AuthController {
|
||||
id: req.session.userId,
|
||||
email: req.session.userEmail,
|
||||
name: req.session.userName,
|
||||
isAdmin: req.session.isAdmin
|
||||
isAdmin: req.session.isAdmin,
|
||||
userType: req.session.userType || 'user'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -3,6 +3,29 @@ const emailService = require('../services/email');
|
||||
const representAPI = require('../services/represent-api');
|
||||
const { generateSlug, validateSlug } = require('../utils/validators');
|
||||
|
||||
const VALID_CAMPAIGN_STATUSES = ['draft', 'active', 'paused', 'archived'];
|
||||
|
||||
const normalizeTargetLevels = (rawLevels) => {
|
||||
if (Array.isArray(rawLevels)) {
|
||||
return rawLevels;
|
||||
}
|
||||
|
||||
if (typeof rawLevels === 'string' && rawLevels.length > 0) {
|
||||
return rawLevels.split(',').map(level => level.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const normalizeStatus = (rawStatus, fallback = 'draft') => {
|
||||
if (!rawStatus) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const status = String(rawStatus).toLowerCase();
|
||||
return VALID_CAMPAIGN_STATUSES.includes(status) ? status : fallback;
|
||||
};
|
||||
|
||||
// Helper function to cache representatives
|
||||
async function cacheRepresentatives(postalCode, representatives, representData) {
|
||||
try {
|
||||
@@ -40,6 +63,9 @@ class CampaignsController {
|
||||
if (id != null) {
|
||||
emailCount = await nocoDB.getCampaignEmailCount(id);
|
||||
}
|
||||
|
||||
const rawTargetLevels = campaign['Target Government Levels'] || campaign.target_government_levels;
|
||||
const normalizedTargetLevels = normalizeTargetLevels(rawTargetLevels);
|
||||
|
||||
// Normalize campaign data structure for frontend
|
||||
return {
|
||||
@@ -50,22 +76,42 @@ class CampaignsController {
|
||||
email_subject: campaign['Email Subject'] || campaign.email_subject,
|
||||
email_body: campaign['Email Body'] || campaign.email_body,
|
||||
call_to_action: campaign['Call to Action'] || campaign.call_to_action,
|
||||
status: campaign['Status'] || campaign.status,
|
||||
status: normalizeStatus(campaign['Status'] || campaign.status),
|
||||
allow_smtp_email: campaign['Allow SMTP Email'] || campaign.allow_smtp_email,
|
||||
allow_mailto_link: campaign['Allow Mailto Link'] || campaign.allow_mailto_link,
|
||||
collect_user_info: campaign['Collect User Info'] || campaign.collect_user_info,
|
||||
show_email_count: campaign['Show Email Count'] || campaign.show_email_count,
|
||||
allow_email_editing: campaign['Allow Email Editing'] || campaign.allow_email_editing,
|
||||
target_government_levels: campaign['Target Government Levels'] || campaign.target_government_levels,
|
||||
target_government_levels: normalizedTargetLevels,
|
||||
created_at: campaign.CreatedAt || campaign.created_at,
|
||||
updated_at: campaign.UpdatedAt || campaign.updated_at,
|
||||
created_by_user_id: campaign['Created By User ID'] || campaign.created_by_user_id || null,
|
||||
created_by_user_email: campaign['Created By User Email'] || campaign.created_by_user_email || null,
|
||||
created_by_user_name: campaign['Created By User Name'] || campaign.created_by_user_name || null,
|
||||
emailCount
|
||||
};
|
||||
}));
|
||||
|
||||
let filteredCampaigns = campaignsWithCounts;
|
||||
|
||||
if (req.user && !req.user.isAdmin) {
|
||||
const sessionUserId = req.user.id != null ? String(req.user.id) : (req.session?.userId != null ? String(req.session.userId) : null);
|
||||
const sessionUserEmail = req.user.email ? String(req.user.email).toLowerCase() : (req.session?.userEmail ? String(req.session.userEmail).toLowerCase() : null);
|
||||
|
||||
filteredCampaigns = campaignsWithCounts.filter((campaign) => {
|
||||
const createdById = campaign.created_by_user_id != null ? String(campaign.created_by_user_id) : null;
|
||||
const createdByEmail = campaign.created_by_user_email ? String(campaign.created_by_user_email).toLowerCase() : null;
|
||||
|
||||
return (
|
||||
(sessionUserId && createdById && createdById === sessionUserId) ||
|
||||
(sessionUserEmail && createdByEmail && createdByEmail === sessionUserEmail)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
campaigns: campaignsWithCounts
|
||||
campaigns: filteredCampaigns
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Get campaigns error:', error);
|
||||
@@ -111,15 +157,18 @@ class CampaignsController {
|
||||
email_subject: campaign['Email Subject'] || campaign.email_subject,
|
||||
email_body: campaign['Email Body'] || campaign.email_body,
|
||||
call_to_action: campaign['Call to Action'] || campaign.call_to_action,
|
||||
status: campaign['Status'] || campaign.status,
|
||||
status: normalizeStatus(campaign['Status'] || campaign.status),
|
||||
allow_smtp_email: campaign['Allow SMTP Email'] || campaign.allow_smtp_email,
|
||||
allow_mailto_link: campaign['Allow Mailto Link'] || campaign.allow_mailto_link,
|
||||
collect_user_info: campaign['Collect User Info'] || campaign.collect_user_info,
|
||||
show_email_count: campaign['Show Email Count'] || campaign.show_email_count,
|
||||
allow_email_editing: campaign['Allow Email Editing'] || campaign.allow_email_editing,
|
||||
target_government_levels: campaign['Target Government Levels'] || campaign.target_government_levels,
|
||||
target_government_levels: normalizeTargetLevels(campaign['Target Government Levels'] || campaign.target_government_levels),
|
||||
created_at: campaign.CreatedAt || campaign.created_at,
|
||||
updated_at: campaign.UpdatedAt || campaign.updated_at,
|
||||
created_by_user_id: campaign['Created By User ID'] || campaign.created_by_user_id || null,
|
||||
created_by_user_email: campaign['Created By User Email'] || campaign.created_by_user_email || null,
|
||||
created_by_user_name: campaign['Created By User Name'] || campaign.created_by_user_name || null,
|
||||
emailCount
|
||||
}
|
||||
});
|
||||
@@ -147,8 +196,8 @@ class CampaignsController {
|
||||
});
|
||||
}
|
||||
|
||||
const campaignStatus = campaign['Status'] || campaign.status;
|
||||
if (campaignStatus !== 'active') {
|
||||
const campaignStatus = normalizeStatus(campaign['Status'] || campaign.status);
|
||||
if (campaignStatus !== 'active') {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Campaign is not currently active'
|
||||
@@ -180,11 +229,7 @@ class CampaignsController {
|
||||
collect_user_info: campaign['Collect User Info'] || campaign.collect_user_info,
|
||||
show_email_count: campaign['Show Email Count'] || campaign.show_email_count,
|
||||
allow_email_editing: campaign['Allow Email Editing'] || campaign.allow_email_editing,
|
||||
target_government_levels: Array.isArray(campaign['Target Government Levels'] || campaign.target_government_levels)
|
||||
? (campaign['Target Government Levels'] || campaign.target_government_levels)
|
||||
: (typeof (campaign['Target Government Levels'] || campaign.target_government_levels) === 'string' && (campaign['Target Government Levels'] || campaign.target_government_levels).length > 0
|
||||
? (campaign['Target Government Levels'] || campaign.target_government_levels).split(',').map(s => s.trim())
|
||||
: []),
|
||||
target_government_levels: normalizeTargetLevels(campaign['Target Government Levels'] || campaign.target_government_levels),
|
||||
emailCount
|
||||
}
|
||||
});
|
||||
@@ -217,7 +262,10 @@ class CampaignsController {
|
||||
target_government_levels = ['Federal', 'Provincial', 'Municipal']
|
||||
} = req.body;
|
||||
|
||||
// Generate slug from title
|
||||
const ownerUserId = req.user?.id ?? req.session?.userId ?? null;
|
||||
const ownerEmail = req.user?.email ?? req.session?.userEmail ?? null;
|
||||
const ownerName = req.user?.name ?? req.session?.userName ?? null;
|
||||
const normalizedStatus = normalizeStatus(status, 'draft');
|
||||
let slug = generateSlug(title);
|
||||
|
||||
// Ensure slug is unique
|
||||
@@ -235,18 +283,18 @@ class CampaignsController {
|
||||
email_subject,
|
||||
email_body,
|
||||
call_to_action,
|
||||
status,
|
||||
status: normalizedStatus,
|
||||
allow_smtp_email,
|
||||
allow_mailto_link,
|
||||
collect_user_info,
|
||||
show_email_count,
|
||||
allow_email_editing,
|
||||
// NocoDB MultiSelect expects an array of values
|
||||
target_government_levels: Array.isArray(target_government_levels)
|
||||
? target_government_levels
|
||||
: (typeof target_government_levels === 'string' && target_government_levels.length > 0
|
||||
? target_government_levels.split(',').map(s => s.trim())
|
||||
: [])
|
||||
target_government_levels: normalizeTargetLevels(target_government_levels),
|
||||
// Add user ownership data
|
||||
created_by_user_id: ownerUserId,
|
||||
created_by_user_email: ownerEmail,
|
||||
created_by_user_name: ownerName
|
||||
};
|
||||
|
||||
const campaign = await nocoDB.createCampaign(campaignData);
|
||||
@@ -262,15 +310,18 @@ class CampaignsController {
|
||||
email_subject: campaign['Email Subject'] || campaign.email_subject,
|
||||
email_body: campaign['Email Body'] || campaign.email_body,
|
||||
call_to_action: campaign['Call to Action'] || campaign.call_to_action,
|
||||
status: campaign['Status'] || campaign.status,
|
||||
status: normalizeStatus(campaign['Status'] || campaign.status),
|
||||
allow_smtp_email: campaign['Allow SMTP Email'] || campaign.allow_smtp_email,
|
||||
allow_mailto_link: campaign['Allow Mailto Link'] || campaign.allow_mailto_link,
|
||||
collect_user_info: campaign['Collect User Info'] || campaign.collect_user_info,
|
||||
show_email_count: campaign['Show Email Count'] || campaign.show_email_count,
|
||||
allow_email_editing: campaign['Allow Email Editing'] || campaign.allow_email_editing,
|
||||
target_government_levels: campaign['Target Government Levels'] || campaign.target_government_levels,
|
||||
target_government_levels: normalizeTargetLevels(campaign['Target Government Levels'] || campaign.target_government_levels),
|
||||
created_at: campaign.CreatedAt || campaign.created_at,
|
||||
updated_at: campaign.UpdatedAt || campaign.updated_at
|
||||
updated_at: campaign.UpdatedAt || campaign.updated_at,
|
||||
created_by_user_id: campaign['Created By User ID'] || campaign.created_by_user_id || ownerUserId,
|
||||
created_by_user_email: campaign['Created By User Email'] || campaign.created_by_user_email || ownerEmail,
|
||||
created_by_user_name: campaign['Created By User Name'] || campaign.created_by_user_name || ownerName
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -288,18 +339,45 @@ class CampaignsController {
|
||||
async updateCampaign(req, res, next) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const updates = req.body;
|
||||
const updates = { ...req.body };
|
||||
|
||||
const existingCampaign = await nocoDB.getCampaignById(id);
|
||||
if (!existingCampaign) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Campaign not found'
|
||||
});
|
||||
}
|
||||
|
||||
const isAdmin = (req.user && req.user.isAdmin) || req.session?.isAdmin || false;
|
||||
const sessionUserId = req.user?.id ?? req.session?.userId ?? null;
|
||||
const sessionUserEmail = req.user?.email ?? req.session?.userEmail ?? null;
|
||||
|
||||
if (!isAdmin) {
|
||||
const createdById = existingCampaign['Created By User ID'] ?? existingCampaign.created_by_user_id ?? null;
|
||||
const createdByEmail = existingCampaign['Created By User Email'] ?? existingCampaign.created_by_user_email ?? null;
|
||||
|
||||
const ownsCampaign = (
|
||||
(createdById != null && sessionUserId != null && String(createdById) === String(sessionUserId)) ||
|
||||
(createdByEmail && sessionUserEmail && String(createdByEmail).toLowerCase() === String(sessionUserEmail).toLowerCase())
|
||||
);
|
||||
|
||||
if (!ownsCampaign) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Access denied'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// If title is being updated, regenerate slug
|
||||
if (updates.title) {
|
||||
let slug = generateSlug(updates.title);
|
||||
|
||||
// Ensure slug is unique (but allow current campaign to keep its slug)
|
||||
const existingCampaign = await nocoDB.getCampaignBySlug(slug);
|
||||
const existingId = existingCampaign ? (existingCampaign.ID || existingCampaign.Id || existingCampaign.id) : null;
|
||||
if (existingCampaign && String(existingId) !== String(id)) {
|
||||
|
||||
const campaignWithSlug = await nocoDB.getCampaignBySlug(slug);
|
||||
const existingId = campaignWithSlug ? (campaignWithSlug.ID || campaignWithSlug.Id || campaignWithSlug.id) : null;
|
||||
if (campaignWithSlug && String(existingId) !== String(id)) {
|
||||
let counter = 1;
|
||||
let originalSlug = slug;
|
||||
const originalSlug = slug;
|
||||
while (await nocoDB.getCampaignBySlug(slug)) {
|
||||
slug = `${originalSlug}-${counter}`;
|
||||
counter++;
|
||||
@@ -308,20 +386,33 @@ class CampaignsController {
|
||||
updates.slug = slug;
|
||||
}
|
||||
|
||||
// Ensure target_government_levels remains an array for MultiSelect
|
||||
if (updates.target_government_levels) {
|
||||
updates.target_government_levels = Array.isArray(updates.target_government_levels)
|
||||
? updates.target_government_levels
|
||||
: (typeof updates.target_government_levels === 'string'
|
||||
? updates.target_government_levels.split(',').map(s => s.trim())
|
||||
: []);
|
||||
if (updates.target_government_levels !== undefined) {
|
||||
updates.target_government_levels = normalizeTargetLevels(updates.target_government_levels);
|
||||
}
|
||||
|
||||
updates.updated_at = new Date().toISOString();
|
||||
if (updates.status !== undefined) {
|
||||
const sanitizedStatus = normalizeStatus(updates.status, null);
|
||||
if (!sanitizedStatus) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Invalid campaign status'
|
||||
});
|
||||
}
|
||||
updates.status = sanitizedStatus;
|
||||
}
|
||||
|
||||
// Remove user ownership fields from updates to prevent unauthorized changes
|
||||
delete updates.created_by_user_id;
|
||||
delete updates.created_by_user_email;
|
||||
delete updates.created_by_user_name;
|
||||
// Remove auto-generated fields that NocoDB handles
|
||||
delete updates.updated_at;
|
||||
delete updates.UpdatedAt;
|
||||
delete updates.created_at;
|
||||
delete updates.CreatedAt;
|
||||
|
||||
const campaign = await nocoDB.updateCampaign(id, updates);
|
||||
|
||||
// Normalize the updated campaign data
|
||||
res.json({
|
||||
success: true,
|
||||
campaign: {
|
||||
@@ -332,14 +423,18 @@ class CampaignsController {
|
||||
email_subject: campaign['Email Subject'] || campaign.email_subject,
|
||||
email_body: campaign['Email Body'] || campaign.email_body,
|
||||
call_to_action: campaign['Call to Action'] || campaign.call_to_action,
|
||||
status: campaign['Status'] || campaign.status,
|
||||
status: normalizeStatus(campaign['Status'] || campaign.status),
|
||||
allow_smtp_email: campaign['Allow SMTP Email'] || campaign.allow_smtp_email,
|
||||
allow_mailto_link: campaign['Allow Mailto Link'] || campaign.allow_mailto_link,
|
||||
collect_user_info: campaign['Collect User Info'] || campaign.collect_user_info,
|
||||
show_email_count: campaign['Show Email Count'] || campaign.show_email_count,
|
||||
target_government_levels: campaign['Target Government Levels'] || campaign.target_government_levels,
|
||||
allow_email_editing: campaign['Allow Email Editing'] || campaign.allow_email_editing,
|
||||
target_government_levels: normalizeTargetLevels(campaign['Target Government Levels'] || campaign.target_government_levels),
|
||||
created_at: campaign.CreatedAt || campaign.created_at,
|
||||
updated_at: campaign.UpdatedAt || campaign.updated_at
|
||||
updated_at: campaign.UpdatedAt || campaign.updated_at,
|
||||
created_by_user_id: campaign['Created By User ID'] || campaign.created_by_user_id || null,
|
||||
created_by_user_email: campaign['Created By User Email'] || campaign.created_by_user_email || null,
|
||||
created_by_user_name: campaign['Created By User Name'] || campaign.created_by_user_name || null
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -393,17 +488,16 @@ class CampaignsController {
|
||||
} = req.body;
|
||||
|
||||
// Get campaign
|
||||
const campaign = await nocoDB.getCampaignBySlug(slug);
|
||||
const campaign = await nocoDB.getCampaignBySlug(slug);
|
||||
if (!campaign) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Campaign not found'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
if ((campaign['Status'] || campaign.status) !== 'active') {
|
||||
const campaignStatus = normalizeStatus(campaign['Status'] || campaign.status);
|
||||
if (campaignStatus !== 'active') {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Campaign is not currently active'
|
||||
@@ -425,14 +519,14 @@ class CampaignsController {
|
||||
});
|
||||
}
|
||||
|
||||
// Use custom email content if provided (when email editing is enabled), otherwise use campaign defaults
|
||||
const allowEmailEditing = campaign['Allow Email Editing'] || campaign.allow_email_editing;
|
||||
const subject = (allowEmailEditing && customEmailSubject)
|
||||
? customEmailSubject
|
||||
: (campaign['Email Subject'] || campaign.email_subject);
|
||||
const message = (allowEmailEditing && customEmailBody)
|
||||
? customEmailBody
|
||||
: (campaign['Email Body'] || campaign.email_body);
|
||||
// Use custom email content if provided (when email editing is enabled), otherwise use campaign defaults
|
||||
const allowEmailEditing = campaign['Allow Email Editing'] || campaign.allow_email_editing;
|
||||
const subject = (allowEmailEditing && customEmailSubject)
|
||||
? customEmailSubject
|
||||
: (campaign['Email Subject'] || campaign.email_subject);
|
||||
const message = (allowEmailEditing && customEmailBody)
|
||||
? customEmailBody
|
||||
: (campaign['Email Body'] || campaign.email_body);
|
||||
|
||||
let emailResult = { success: true };
|
||||
|
||||
@@ -515,7 +609,7 @@ class CampaignsController {
|
||||
});
|
||||
}
|
||||
|
||||
const campaignStatus = campaign['Status'] || campaign.status;
|
||||
const campaignStatus = normalizeStatus(campaign['Status'] || campaign.status);
|
||||
if (campaignStatus !== 'active') {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
@@ -570,7 +664,7 @@ class CampaignsController {
|
||||
});
|
||||
}
|
||||
|
||||
const campaignStatus = campaign['Status'] || campaign.status;
|
||||
const campaignStatus = normalizeStatus(campaign['Status'] || campaign.status);
|
||||
if (campaignStatus !== 'active') {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
@@ -687,6 +781,36 @@ class CampaignsController {
|
||||
async getCampaignAnalytics(req, res, next) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const campaign = await nocoDB.getCampaignById(id);
|
||||
|
||||
if (!campaign) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Campaign not found'
|
||||
});
|
||||
}
|
||||
|
||||
const sessionUserId = req.user?.id ?? req.session?.userId ?? null;
|
||||
const sessionUserEmail = req.user?.email ?? req.session?.userEmail ?? null;
|
||||
const isAdmin = (req.user && req.user.isAdmin) || req.session?.isAdmin || false;
|
||||
|
||||
if (!isAdmin) {
|
||||
const createdById = campaign['Created By User ID'] ?? campaign.created_by_user_id ?? null;
|
||||
const createdByEmail = campaign['Created By User Email'] ?? campaign.created_by_user_email ?? null;
|
||||
|
||||
const ownsCampaign = (
|
||||
(createdById != null && sessionUserId != null && String(createdById) === String(sessionUserId)) ||
|
||||
(createdByEmail && sessionUserEmail && String(createdByEmail).toLowerCase() === String(sessionUserEmail).toLowerCase())
|
||||
);
|
||||
|
||||
if (!ownsCampaign) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Access denied'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const analytics = await nocoDB.getCampaignAnalytics(id);
|
||||
|
||||
|
||||
338
influence/app/controllers/usersController.js
Normal file
338
influence/app/controllers/usersController.js
Normal file
@@ -0,0 +1,338 @@
|
||||
const nocodbService = require('../services/nocodb');
|
||||
const { sendLoginDetails } = require('../services/email');
|
||||
const { sanitizeUser, extractId } = require('../utils/helpers');
|
||||
|
||||
class UsersController {
|
||||
async getAll(req, res) {
|
||||
try {
|
||||
console.log('UsersController.getAll called');
|
||||
console.log('Users table ID:', nocodbService.tableIds.users);
|
||||
|
||||
if (!nocodbService.tableIds.users) {
|
||||
console.error('Users table not configured in environment');
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: 'Users table not configured. Please set NOCODB_TABLE_USERS in your environment variables.'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Fetching users from NocoDB...');
|
||||
const response = await nocodbService.getAll(nocodbService.tableIds.users, {
|
||||
limit: 100
|
||||
});
|
||||
|
||||
const users = response.list || [];
|
||||
console.log(`Retrieved ${users.length} users from database`);
|
||||
|
||||
// Remove password field from response for security
|
||||
const safeUsers = users.map(sanitizeUser);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
users: safeUsers
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching users:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to fetch users: ' + error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async create(req, res) {
|
||||
try {
|
||||
const { email, password, name, phone, isAdmin, userType, expireDays } = req.body;
|
||||
|
||||
if (!email || !password) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Email and password are required'
|
||||
});
|
||||
}
|
||||
|
||||
if (!nocodbService.tableIds.users) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: 'Users table not configured'
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user already exists
|
||||
console.log(`Checking if user exists with email: ${email}`);
|
||||
let existingUser = null;
|
||||
try {
|
||||
existingUser = await nocodbService.getUserByEmail(email);
|
||||
console.log('Existing user check result:', existingUser ? 'User exists' : 'User does not exist');
|
||||
} catch (error) {
|
||||
console.error('Error checking for existing user:', error.message);
|
||||
// Continue with creation if check fails - NocoDB will handle the unique constraint
|
||||
}
|
||||
|
||||
if (existingUser) {
|
||||
console.log('Existing user found:', { id: existingUser.ID || existingUser.Id || existingUser.id, email: existingUser.Email || existingUser.email });
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'User with this email already exists'
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate expiration date for temp users
|
||||
let expiresAt = null;
|
||||
if (userType === 'temp' && expireDays) {
|
||||
const expirationDate = new Date();
|
||||
expirationDate.setDate(expirationDate.getDate() + expireDays);
|
||||
expiresAt = expirationDate.toISOString();
|
||||
}
|
||||
|
||||
// Create new user - use the exact column titles from NocoDB schema
|
||||
const userData = {
|
||||
Email: email,
|
||||
Name: name || '',
|
||||
Password: password,
|
||||
Phone: phone || '',
|
||||
Admin: isAdmin === true,
|
||||
'User Type': userType || 'user',
|
||||
ExpiresAt: expiresAt,
|
||||
ExpireDays: userType === 'temp' ? expireDays : null,
|
||||
'Last Login': null
|
||||
};
|
||||
|
||||
const response = await nocodbService.create(
|
||||
nocodbService.tableIds.users,
|
||||
userData
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: 'User created successfully',
|
||||
user: {
|
||||
id: extractId(response),
|
||||
email: email,
|
||||
name: name,
|
||||
phone: phone,
|
||||
admin: isAdmin,
|
||||
userType: userType,
|
||||
expiresAt: expiresAt
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error creating user:', error);
|
||||
|
||||
// Check if it's a unique constraint violation (email already exists)
|
||||
if (error.response?.data?.code === '23505' ||
|
||||
error.response?.data?.message?.includes('already exists') ||
|
||||
error.message?.includes('already exists')) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'A user with this email address already exists'
|
||||
});
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to create user: ' + (error.message || 'Unknown error')
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async delete(req, res) {
|
||||
try {
|
||||
const userId = req.params.id;
|
||||
|
||||
if (!nocodbService.tableIds.users) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: 'Users table not configured'
|
||||
});
|
||||
}
|
||||
|
||||
// Don't allow admins to delete themselves
|
||||
if (userId === req.session.userId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Cannot delete your own account'
|
||||
});
|
||||
}
|
||||
|
||||
await nocodbService.deleteUser(userId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'User deleted successfully'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error deleting user:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to delete user'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async sendLoginDetails(req, res) {
|
||||
try {
|
||||
const userId = req.params.id;
|
||||
|
||||
if (!nocodbService.tableIds.users) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: 'Users table not configured'
|
||||
});
|
||||
}
|
||||
|
||||
// Get user data from database
|
||||
const user = await nocodbService.getById(
|
||||
nocodbService.tableIds.users,
|
||||
userId
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'User not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Send login details email
|
||||
await sendLoginDetails(user);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Login details sent successfully'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error sending login details:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to send login details'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async emailAllUsers(req, res) {
|
||||
try {
|
||||
const { subject, content } = req.body;
|
||||
|
||||
if (!subject || !content) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Subject and content are required'
|
||||
});
|
||||
}
|
||||
|
||||
if (!nocodbService.tableIds.users) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: 'Users table not configured'
|
||||
});
|
||||
}
|
||||
|
||||
// Get all users
|
||||
const response = await nocodbService.getAll(nocodbService.tableIds.users, {
|
||||
limit: 1000
|
||||
});
|
||||
|
||||
const users = response.list || [];
|
||||
|
||||
if (users.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'No users found to email'
|
||||
});
|
||||
}
|
||||
|
||||
// Import email service
|
||||
const { sendEmail } = require('../services/email');
|
||||
const emailTemplates = require('../services/emailTemplates');
|
||||
|
||||
// Convert rich text content to plain text for the text version
|
||||
const stripHtmlTags = (html) => {
|
||||
return html.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim();
|
||||
};
|
||||
|
||||
// Prepare base template variables
|
||||
const baseTemplateVariables = {
|
||||
APP_NAME: 'BNKops Influence - User Broadcast',
|
||||
EMAIL_SUBJECT: subject,
|
||||
EMAIL_CONTENT: content,
|
||||
EMAIL_CONTENT_TEXT: stripHtmlTags(content),
|
||||
SENDER_NAME: req.session.userName || req.session.userEmail || 'Administrator',
|
||||
TIMESTAMP: new Date().toLocaleString()
|
||||
};
|
||||
|
||||
// Send emails to all users
|
||||
const emailResults = [];
|
||||
const failedEmails = [];
|
||||
|
||||
for (const user of users) {
|
||||
try {
|
||||
const userVariables = {
|
||||
...baseTemplateVariables,
|
||||
USER_NAME: user.Name || user.name || user.Email || user.email || 'User',
|
||||
USER_EMAIL: user.Email || user.email
|
||||
};
|
||||
|
||||
const emailContent = await emailTemplates.render('user-broadcast', userVariables);
|
||||
|
||||
await sendEmail({
|
||||
to: user.Email || user.email,
|
||||
subject: subject,
|
||||
text: emailContent.text,
|
||||
html: emailContent.html
|
||||
});
|
||||
|
||||
emailResults.push({
|
||||
email: user.Email || user.email,
|
||||
name: user.Name || user.name || user.Email || user.email,
|
||||
success: true
|
||||
});
|
||||
|
||||
console.log(`Sent broadcast email to: ${user.Email || user.email}`);
|
||||
} catch (emailError) {
|
||||
console.error(`Failed to send broadcast email to ${user.Email || user.email}:`, emailError);
|
||||
failedEmails.push({
|
||||
email: user.Email || user.email,
|
||||
name: user.Name || user.name || user.Email || user.email,
|
||||
error: emailError.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const successCount = emailResults.length;
|
||||
const failCount = failedEmails.length;
|
||||
|
||||
if (successCount === 0) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to send any emails',
|
||||
details: failedEmails
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Sent email to ${successCount} user${successCount !== 1 ? 's' : ''}${failCount > 0 ? `, ${failCount} failed` : ''}`,
|
||||
results: {
|
||||
successful: emailResults,
|
||||
failed: failedEmails,
|
||||
total: users.length,
|
||||
subject: subject
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error sending broadcast email:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to send broadcast email'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new UsersController();
|
||||
Reference in New Issue
Block a user