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();