listmonk sync
This commit is contained in:
252
map/app/controllers/listmonkController.js
Normal file
252
map/app/controllers/listmonkController.js
Normal file
@@ -0,0 +1,252 @@
|
||||
const listmonkService = require('../services/listmonk');
|
||||
const nocodbService = require('../services/nocodb');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
// Get Listmonk sync status
|
||||
exports.getSyncStatus = async (req, res) => {
|
||||
try {
|
||||
const status = listmonkService.getSyncStatus();
|
||||
|
||||
// Also check connection if it's enabled
|
||||
if (status.enabled && !status.connected) {
|
||||
// Try to reconnect
|
||||
const reconnected = await listmonkService.checkConnection();
|
||||
status.connected = reconnected;
|
||||
}
|
||||
|
||||
res.json(status);
|
||||
} catch (error) {
|
||||
logger.error('Failed to get Listmonk status', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to get sync status'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Bulk sync all locations to Listmonk
|
||||
exports.syncAllLocations = async (req, res) => {
|
||||
try {
|
||||
if (!listmonkService.syncEnabled) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Listmonk sync is disabled'
|
||||
});
|
||||
}
|
||||
|
||||
const locationData = await nocodbService.getLocations();
|
||||
const locations = locationData?.list || [];
|
||||
|
||||
if (!locations || locations.length === 0) {
|
||||
return res.json({
|
||||
success: true,
|
||||
message: 'No locations to sync',
|
||||
results: { total: 0, success: 0, failed: 0, errors: [] }
|
||||
});
|
||||
}
|
||||
|
||||
const results = await listmonkService.bulkSync(locations, 'location');
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Bulk location sync completed: ${results.success} succeeded, ${results.failed} failed`,
|
||||
results
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Bulk location sync failed', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to sync locations to Listmonk'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Bulk sync all users to Listmonk
|
||||
exports.syncAllUsers = async (req, res) => {
|
||||
try {
|
||||
if (!listmonkService.syncEnabled) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Listmonk sync is disabled'
|
||||
});
|
||||
}
|
||||
|
||||
const config = require('../config');
|
||||
const userData = await nocodbService.getAllPaginated(config.nocodb.loginSheetId);
|
||||
const users = userData?.list || [];
|
||||
|
||||
if (!users || users.length === 0) {
|
||||
return res.json({
|
||||
success: true,
|
||||
message: 'No users to sync',
|
||||
results: { total: 0, success: 0, failed: 0, errors: [] }
|
||||
});
|
||||
}
|
||||
|
||||
const results = await listmonkService.bulkSync(users, 'user');
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Bulk user sync completed: ${results.success} succeeded, ${results.failed} failed`,
|
||||
results
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Bulk user sync failed', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to sync users to Listmonk'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Sync both locations and users
|
||||
exports.syncAll = async (req, res) => {
|
||||
try {
|
||||
if (!listmonkService.syncEnabled) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Listmonk sync is disabled'
|
||||
});
|
||||
}
|
||||
|
||||
let results = {
|
||||
locations: { total: 0, success: 0, failed: 0, errors: [] },
|
||||
users: { total: 0, success: 0, failed: 0, errors: [] }
|
||||
};
|
||||
|
||||
// Sync locations
|
||||
try {
|
||||
const locationData = await nocodbService.getLocations();
|
||||
const locations = locationData?.list || [];
|
||||
if (locations && locations.length > 0) {
|
||||
results.locations = await listmonkService.bulkSync(locations, 'location');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync locations during full sync', error);
|
||||
results.locations.errors.push({ error: error.message });
|
||||
}
|
||||
|
||||
// Sync users
|
||||
try {
|
||||
const userData = await nocodbService.getAllPaginated(config.nocodb.loginSheetId);
|
||||
const users = userData?.list || [];
|
||||
if (users && users.length > 0) {
|
||||
results.users = await listmonkService.bulkSync(users, 'user');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync users during full sync', error);
|
||||
results.users.errors.push({ error: error.message });
|
||||
}
|
||||
|
||||
const totalSuccess = results.locations.success + results.users.success;
|
||||
const totalFailed = results.locations.failed + results.users.failed;
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Complete sync finished: ${totalSuccess} succeeded, ${totalFailed} failed`,
|
||||
results
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Complete sync failed', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to perform complete sync'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Get Listmonk list statistics
|
||||
exports.getListStats = async (req, res) => {
|
||||
try {
|
||||
if (!listmonkService.syncEnabled) {
|
||||
return res.json({
|
||||
success: false,
|
||||
error: 'Listmonk sync is disabled',
|
||||
stats: null
|
||||
});
|
||||
}
|
||||
|
||||
const stats = await listmonkService.getListStats();
|
||||
|
||||
// Convert stats object to array format for frontend
|
||||
let statsArray = [];
|
||||
if (stats && typeof stats === 'object') {
|
||||
statsArray = Object.entries(stats).map(([key, list]) => ({
|
||||
id: key,
|
||||
name: list.name,
|
||||
subscriberCount: list.subscriber_count || 0,
|
||||
description: `Email list for ${key}`
|
||||
}));
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
stats: statsArray
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to get Listmonk list stats', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to get list statistics'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Test Listmonk connection
|
||||
exports.testConnection = async (req, res) => {
|
||||
try {
|
||||
const connected = await listmonkService.checkConnection();
|
||||
|
||||
if (connected) {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Listmonk connection successful',
|
||||
connected: true
|
||||
});
|
||||
} else {
|
||||
res.json({
|
||||
success: false,
|
||||
message: listmonkService.lastError || 'Connection failed',
|
||||
connected: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to test Listmonk connection', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to test connection'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Reinitialize Listmonk lists
|
||||
exports.reinitializeLists = async (req, res) => {
|
||||
try {
|
||||
if (!listmonkService.syncEnabled) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Listmonk sync is disabled'
|
||||
});
|
||||
}
|
||||
|
||||
const initialized = await listmonkService.initializeLists();
|
||||
|
||||
if (initialized) {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Listmonk lists reinitialized successfully'
|
||||
});
|
||||
} else {
|
||||
res.json({
|
||||
success: false,
|
||||
message: listmonkService.lastError || 'Failed to initialize lists'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to reinitialize Listmonk lists', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to reinitialize lists'
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
const nocodbService = require('../services/nocodb');
|
||||
const logger = require('../utils/logger');
|
||||
const config = require('../config');
|
||||
const listmonkService = require('../services/listmonk');
|
||||
const {
|
||||
syncGeoFields,
|
||||
validateCoordinates,
|
||||
@@ -196,6 +197,32 @@ class LocationsController {
|
||||
|
||||
logger.info('Location created successfully:', extractId(response));
|
||||
|
||||
// Real-time sync to Listmonk (async, don't block response)
|
||||
if (listmonkService.syncEnabled && response.Email) {
|
||||
setImmediate(async () => {
|
||||
try {
|
||||
const syncResult = await listmonkService.syncLocation(response);
|
||||
if (!syncResult.success) {
|
||||
logger.warn('Listmonk sync failed for new location', {
|
||||
locationId: extractId(response),
|
||||
email: response.Email,
|
||||
error: syncResult.error
|
||||
});
|
||||
} else {
|
||||
logger.debug('Location synced to Listmonk', {
|
||||
locationId: extractId(response),
|
||||
email: response.Email
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Listmonk sync error for new location', {
|
||||
locationId: extractId(response),
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
location: response
|
||||
@@ -257,6 +284,32 @@ class LocationsController {
|
||||
|
||||
logger.info('Location updated successfully:', locationId);
|
||||
|
||||
// Real-time sync to Listmonk (async, don't block response)
|
||||
if (listmonkService.syncEnabled && response.Email) {
|
||||
setImmediate(async () => {
|
||||
try {
|
||||
const syncResult = await listmonkService.syncLocation(response);
|
||||
if (!syncResult.success) {
|
||||
logger.warn('Listmonk sync failed for updated location', {
|
||||
locationId: locationId,
|
||||
email: response.Email,
|
||||
error: syncResult.error
|
||||
});
|
||||
} else {
|
||||
logger.debug('Updated location synced to Listmonk', {
|
||||
locationId: locationId,
|
||||
email: response.Email
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Listmonk sync error for updated location', {
|
||||
locationId: locationId,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
location: response
|
||||
@@ -285,6 +338,17 @@ class LocationsController {
|
||||
});
|
||||
}
|
||||
|
||||
// Get location data before deletion (for Listmonk cleanup)
|
||||
let locationData = null;
|
||||
if (listmonkService.syncEnabled) {
|
||||
try {
|
||||
const getResponse = await nocodbService.getById(config.nocodb.tableId, locationId);
|
||||
locationData = getResponse;
|
||||
} catch (error) {
|
||||
logger.warn('Could not fetch location data before deletion', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
await nocodbService.delete(
|
||||
config.nocodb.tableId,
|
||||
locationId
|
||||
@@ -292,6 +356,32 @@ class LocationsController {
|
||||
|
||||
logger.info(`Location ${locationId} deleted by ${req.session.userEmail}`);
|
||||
|
||||
// Remove from Listmonk (async, don't block response)
|
||||
if (listmonkService.syncEnabled && locationData && locationData.Email) {
|
||||
setImmediate(async () => {
|
||||
try {
|
||||
const syncResult = await listmonkService.removeSubscriber(locationData.Email);
|
||||
if (!syncResult.success) {
|
||||
logger.warn('Failed to remove deleted location from Listmonk', {
|
||||
locationId: locationId,
|
||||
email: locationData.Email,
|
||||
error: syncResult.error
|
||||
});
|
||||
} else {
|
||||
logger.debug('Deleted location removed from Listmonk', {
|
||||
locationId: locationId,
|
||||
email: locationData.Email
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Listmonk cleanup error for deleted location', {
|
||||
locationId: locationId,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Location deleted successfully'
|
||||
|
||||
@@ -3,6 +3,7 @@ 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) {
|
||||
@@ -111,6 +112,42 @@ class UsersController {
|
||||
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,
|
||||
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',
|
||||
@@ -152,11 +189,48 @@ class UsersController {
|
||||
});
|
||||
}
|
||||
|
||||
// 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'
|
||||
|
||||
Reference in New Issue
Block a user