413 lines
15 KiB
JavaScript
413 lines
15 KiB
JavaScript
const nocodbService = require('../services/nocodb');
|
|
const logger = require('../utils/logger');
|
|
const config = require('../config');
|
|
const { sanitizeUser, extractId } = require('../utils/helpers');
|
|
const { sendLoginDetails } = require('../services/email');
|
|
const listmonkService = require('../services/listmonk');
|
|
|
|
class UsersController {
|
|
async getAll(req, res) {
|
|
try {
|
|
// Debug logging
|
|
logger.info('UsersController.getAll called');
|
|
logger.info('loginSheetId from config:', config.nocodb.loginSheetId);
|
|
logger.info('NocoDB config:', {
|
|
apiUrl: config.nocodb.apiUrl,
|
|
hasToken: !!config.nocodb.apiToken,
|
|
projectId: config.nocodb.projectId,
|
|
tableId: config.nocodb.tableId,
|
|
loginSheetId: config.nocodb.loginSheetId
|
|
});
|
|
|
|
if (!config.nocodb.loginSheetId) {
|
|
logger.error('Login sheet not configured in environment');
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'Login sheet not configured. Please set NOCODB_LOGIN_SHEET in your environment variables.'
|
|
});
|
|
}
|
|
|
|
logger.info('Fetching users from NocoDB...');
|
|
// Remove the sort parameter that's causing the error
|
|
const response = await nocodbService.getAll(config.nocodb.loginSheetId, {
|
|
limit: 100
|
|
// Removed: sort: '-created_at'
|
|
});
|
|
|
|
const users = response.list || [];
|
|
logger.info(`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) {
|
|
logger.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, isAdmin, userType, expireDays } = req.body;
|
|
|
|
if (!email || !password) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Email and password are required'
|
|
});
|
|
}
|
|
|
|
if (!config.nocodb.loginSheetId) {
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'Login sheet not configured'
|
|
});
|
|
}
|
|
|
|
// Check if user already exists
|
|
const existingUser = await nocodbService.getUserByEmail(email);
|
|
|
|
if (existingUser) {
|
|
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 actual column names from your table
|
|
const userData = {
|
|
Email: email,
|
|
email: email,
|
|
Password: password,
|
|
password: password,
|
|
Name: name || '',
|
|
name: name || '',
|
|
Admin: isAdmin === true,
|
|
admin: isAdmin === true,
|
|
'User Type': userType || 'user', // Handle space in field name
|
|
UserType: userType || 'user',
|
|
userType: userType || 'user',
|
|
CreatedAt: new Date().toISOString(),
|
|
ExpiresAt: expiresAt,
|
|
ExpireDays: userType === 'temp' ? expireDays : null
|
|
};
|
|
|
|
const response = await nocodbService.create(
|
|
config.nocodb.loginSheetId,
|
|
userData
|
|
);
|
|
|
|
// Real-time sync to Listmonk (async, don't block response)
|
|
if (listmonkService.syncEnabled && email) {
|
|
setImmediate(async () => {
|
|
try {
|
|
const userForSync = {
|
|
ID: extractId(response),
|
|
Email: email,
|
|
Name: name,
|
|
Admin: isAdmin,
|
|
'User Type': userType, // Handle space in field name
|
|
UserType: userType,
|
|
'Created At': new Date().toISOString(),
|
|
ExpiresAt: expiresAt
|
|
};
|
|
|
|
const syncResult = await listmonkService.syncUser(userForSync);
|
|
if (!syncResult.success) {
|
|
logger.warn('Listmonk sync failed for new user', {
|
|
userId: extractId(response),
|
|
email: email,
|
|
error: syncResult.error
|
|
});
|
|
} else {
|
|
logger.debug('User synced to Listmonk', {
|
|
userId: extractId(response),
|
|
email: email
|
|
});
|
|
}
|
|
} catch (error) {
|
|
logger.error('Listmonk sync error for new user', {
|
|
email: email,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'User created successfully',
|
|
user: {
|
|
id: extractId(response),
|
|
email: email,
|
|
name: name,
|
|
admin: isAdmin,
|
|
userType: userType,
|
|
expiresAt: expiresAt
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
logger.error('Error creating user:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to create user'
|
|
});
|
|
}
|
|
}
|
|
|
|
async delete(req, res) {
|
|
try {
|
|
const userId = req.params.id;
|
|
|
|
if (!config.nocodb.loginSheetId) {
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'Login sheet 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'
|
|
});
|
|
}
|
|
|
|
// Get user data before deletion (for Listmonk cleanup)
|
|
let userData = null;
|
|
if (listmonkService.syncEnabled) {
|
|
try {
|
|
const getResponse = await nocodbService.getById(config.nocodb.loginSheetId, userId);
|
|
userData = getResponse;
|
|
} catch (error) {
|
|
logger.warn('Could not fetch user data before deletion', error.message);
|
|
}
|
|
}
|
|
|
|
await nocodbService.delete(
|
|
config.nocodb.loginSheetId,
|
|
userId
|
|
);
|
|
|
|
// Remove from Listmonk (async, don't block response)
|
|
if (listmonkService.syncEnabled && userData && userData.Email) {
|
|
setImmediate(async () => {
|
|
try {
|
|
const syncResult = await listmonkService.syncUser(userData, 'delete');
|
|
if (!syncResult.success) {
|
|
logger.warn('Failed to remove deleted user from Listmonk', {
|
|
userId: userId,
|
|
email: userData.Email,
|
|
error: syncResult.error
|
|
});
|
|
} else {
|
|
logger.debug('Deleted user removed from Listmonk', {
|
|
userId: userId,
|
|
email: userData.Email
|
|
});
|
|
}
|
|
} catch (error) {
|
|
logger.error('Listmonk cleanup error for deleted user', {
|
|
userId: userId,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'User deleted successfully'
|
|
});
|
|
|
|
} catch (error) {
|
|
logger.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 (!config.nocodb.loginSheetId) {
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'Login sheet not configured'
|
|
});
|
|
}
|
|
|
|
// Get user data from database
|
|
const user = await nocodbService.getById(
|
|
config.nocodb.loginSheetId,
|
|
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) {
|
|
logger.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 (!config.nocodb.loginSheetId) {
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'Login sheet not configured'
|
|
});
|
|
}
|
|
|
|
// Get all users
|
|
const response = await nocodbService.getAll(config.nocodb.loginSheetId, {
|
|
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');
|
|
const config_app = require('../config');
|
|
|
|
// 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: 'CMlite Map - 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
|
|
});
|
|
|
|
logger.info(`Sent broadcast email to: ${user.Email || user.email}`);
|
|
} catch (emailError) {
|
|
logger.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) {
|
|
logger.error('Error sending broadcast email:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to send broadcast email'
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = new UsersController(); |