Temp user updates and several bug fixes

This commit is contained in:
2025-08-03 16:08:11 -06:00
parent 6d12d1aef1
commit 567917c013
19 changed files with 843 additions and 35 deletions

View File

@@ -50,7 +50,25 @@ class AuthController {
error: 'Invalid email or password'
});
}
// Check if temp user has expired
const userType = 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) {
logger.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 {
const userId = extractId(user);
@@ -73,6 +91,7 @@ class AuthController {
req.session.userEmail = user.email || user.Email; // Make sure this is set
req.session.userName = user.name || user.Name;
req.session.isAdmin = user.admin || user.Admin || false;
req.session.userType = user.UserType || user.userType || (req.session.isAdmin ? 'admin' : 'user');
logger.info('User logged in:', {
email: req.session.userEmail,
@@ -97,7 +116,8 @@ class AuthController {
user: {
email: email,
name: req.session.userName,
isAdmin: req.session.isAdmin
isAdmin: req.session.isAdmin,
userType: req.session.userType
}
});
});
@@ -128,12 +148,48 @@ class AuthController {
}
async check(req, res) {
// If user is authenticated, check for temp user expiration
if (req.session?.authenticated && req.session?.userType === 'temp' && req.session?.userEmail) {
try {
const user = await nocodbService.getUserByEmail(req.session.userEmail);
if (user) {
const expiration = user.ExpiresAt || user.ExpiresAt || user.Expiration || user.expiration;
if (expiration) {
const expirationDate = new Date(expiration);
const now = new Date();
if (now > expirationDate) {
logger.warn(`Expired temp user session detected in check: ${req.session.userEmail}, expired: ${expiration}`);
// Destroy the session
req.session.destroy((err) => {
if (err) {
logger.error('Session destroy error:', err);
}
});
return res.json({
authenticated: false,
user: null,
expired: true,
message: 'Account has expired. Please contact an administrator.'
});
}
}
}
} catch (error) {
logger.error('Error checking temp user expiration in check:', error.message);
// Don't fail the check on database errors, just log it
}
}
res.json({
authenticated: req.session?.authenticated || false,
user: req.session?.authenticated ? {
email: req.session.userEmail,
name: req.session.userName,
isAdmin: req.session.isAdmin || false
isAdmin: req.session.isAdmin || false,
userType: req.session.userType || 'user'
} : null
});
}

View File

@@ -55,7 +55,7 @@ class UsersController {
async create(req, res) {
try {
const { email, password, name, admin } = req.body;
const { email, password, name, isAdmin, userType, expireDays } = req.body;
if (!email || !password) {
return res.status(400).json({
@@ -81,6 +81,14 @@ class UsersController {
});
}
// 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,
@@ -89,9 +97,13 @@ class UsersController {
password: password,
Name: name || '',
name: name || '',
Admin: admin === true,
admin: admin === true
// Removed created_at fields as they might not exist
Admin: isAdmin === true,
admin: isAdmin === true,
UserType: userType || 'user',
userType: userType || 'user',
CreatedAt: new Date().toISOString(),
ExpiresAt: expiresAt,
ExpireDays: userType === 'temp' ? expireDays : null
};
const response = await nocodbService.create(
@@ -106,7 +118,9 @@ class UsersController {
id: extractId(response),
email: email,
name: name,
admin: admin
admin: isAdmin,
userType: userType,
expiresAt: expiresAt
}
});

View File

@@ -1,11 +1,59 @@
const logger = require('../utils/logger');
const nocodbService = require('../services/nocodb');
const requireAuth = (req, res, next) => {
// Helper function to check if a temp user has expired
const checkTempUserExpiration = async (req, res) => {
if (req.session?.userType === 'temp' && req.session?.userEmail) {
try {
const user = await nocodbService.getUserByEmail(req.session.userEmail);
if (user) {
const expiration = user.ExpiresAt || user.expiresAt || user.Expiration || user.expiration;
if (expiration) {
const expirationDate = new Date(expiration);
const now = new Date();
if (now > expirationDate) {
logger.warn(`Expired temp user session detected: ${req.session.userEmail}, expired: ${expiration}`);
// Destroy the session
req.session.destroy((err) => {
if (err) {
logger.error('Session destroy error:', err);
}
});
if (req.xhr || req.headers.accept?.indexOf('json') > -1) {
return res.status(401).json({
success: false,
error: 'Account has expired. Please contact an administrator.',
expired: true
});
} else {
return res.redirect('/login.html?expired=true');
}
}
}
}
} catch (error) {
logger.error('Error checking temp user expiration:', error.message);
// Don't fail the request on database errors, just log it
}
}
return null; // No expiration issue
};
const requireAuth = async (req, res, next) => {
// Check for both authentication patterns used in your app
const isAuthenticated = (req.session && req.session.authenticated) ||
(req.session && req.session.userId && req.session.userEmail);
if (isAuthenticated) {
// Check if temp user has expired
const expirationResponse = await checkTempUserExpiration(req, res);
if (expirationResponse) {
return; // Response already sent by checkTempUserExpiration
}
next();
} else {
logger.warn('Unauthorized access attempt', {
@@ -25,12 +73,18 @@ const requireAuth = (req, res, next) => {
}
};
const requireAdmin = (req, res, next) => {
const requireAdmin = async (req, res, next) => {
// Check for both authentication patterns used in your app
const isAuthenticated = (req.session && req.session.authenticated) ||
(req.session && req.session.userId && req.session.userEmail);
if (isAuthenticated && req.session.isAdmin) {
// Check if temp user has expired
const expirationResponse = await checkTempUserExpiration(req, res);
if (expirationResponse) {
return; // Response already sent by checkTempUserExpiration
}
next();
} else {
logger.warn('Unauthorized admin access attempt', {
@@ -51,7 +105,68 @@ const requireAdmin = (req, res, next) => {
}
};
const requireNonTemp = async (req, res, next) => {
const isAuthenticated = (req.session && req.session.authenticated) ||
(req.session && req.session.userId && req.session.userEmail);
if (isAuthenticated && req.session.userType !== 'temp') {
// Check if temp user has expired (shouldn't happen here, but for safety)
const expirationResponse = await checkTempUserExpiration(req, res);
if (expirationResponse) {
return; // Response already sent by checkTempUserExpiration
}
next();
} else {
logger.warn('Temp user access denied', {
ip: req.ip,
path: req.path,
user: req.session?.userEmail || 'anonymous',
userType: req.session?.userType || 'unknown'
});
if (req.xhr || req.headers.accept?.indexOf('json') > -1) {
res.status(403).json({
success: false,
error: 'Access denied for temporary users'
});
} else {
res.redirect('/');
}
}
};
const requireDeletePermission = async (req, res, next) => {
const isAuthenticated = (req.session && req.session.authenticated) ||
(req.session && req.session.userId && req.session.userEmail);
// Only admins and regular users can delete, not temps
if (isAuthenticated && req.session.userType !== 'temp') {
// Check if temp user has expired (shouldn't happen here, but for safety)
const expirationResponse = await checkTempUserExpiration(req, res);
if (expirationResponse) {
return; // Response already sent by checkTempUserExpiration
}
next();
} else {
logger.warn('Delete permission denied', {
ip: req.ip,
path: req.path,
user: req.session?.userEmail || 'anonymous',
userType: req.session?.userType || 'unknown'
});
res.status(403).json({
success: false,
error: 'Delete permission denied'
});
}
};
module.exports = {
requireAuth,
requireAdmin
requireAdmin,
requireNonTemp,
requireDeletePermission
};

View File

@@ -422,10 +422,23 @@
<label for="user-password">Password</label>
<input type="password" id="user-password" required>
</div>
<div class="form-group">
<label for="user-type">User Type</label>
<select id="user-type" required>
<option value="user">Regular User</option>
<option value="temp">Temporary User</option>
<option value="admin">Admin</option>
</select>
</div>
<div class="form-group" id="expiration-group" style="display: none;">
<label for="user-expire-days">Expires After (days)</label>
<input type="number" id="user-expire-days" min="1" max="365" value="30">
<small class="help-text">Account will auto-delete after this many days</small>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="user-is-admin">
Is Admin
Is Admin (Legacy - use User Type instead)
</label>
</div>
<div class="form-actions">

View File

@@ -815,6 +815,44 @@
color: white;
}
.user-role.temp {
background-color: #ff9800;
color: white;
}
.expiration-info {
display: inline-block;
margin-left: 8px;
font-size: 0.8em;
color: #666;
background: #f5f5f5;
padding: 2px 6px;
border-radius: 3px;
}
.expiration-warning {
color: #ff5722;
font-weight: bold;
}
.expires-soon {
background-color: #fff3cd;
border-left: 4px solid #ffc107;
}
.expired {
background-color: #f8d7da;
border-left: 4px solid #dc3545;
opacity: 0.7;
}
.help-text {
font-size: 0.85em;
color: #666;
margin-top: 4px;
display: block;
}
.user-actions {
display: flex;
gap: 8px;

View File

@@ -54,3 +54,22 @@ body {
width: 100%;
overflow-x: hidden; /* Prevent horizontal scrolling */
}
/* Badge styles */
.badge {
display: inline-block;
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
font-weight: 500;
line-height: 1;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: 0.375rem;
margin-left: 0.5rem;
}
.temp-badge {
background-color: var(--warning-color);
color: white;
}

View File

@@ -0,0 +1,31 @@
/* Temp User Restrictions */
.temp-badge {
display: inline-block;
padding: 2px 8px;
background-color: #ff9800;
color: white;
font-size: 11px;
font-weight: bold;
border-radius: 3px;
margin-left: 5px;
text-transform: uppercase;
}
/* Hide restricted elements for temp users */
.temp-restricted {
display: none !important;
}
/* More specific selectors to ensure hiding */
a.temp-restricted,
.btn.temp-restricted,
.header-actions .temp-restricted,
.mobile-dropdown-item.temp-restricted {
display: none !important;
}
/* Hide Shift links/buttons whenever body has .temp-user */
body.temp-user a[href="/shifts.html"],
body.temp-user .mobile-dropdown-item a[href="/shifts.html"] {
display: none !important;
}

View File

@@ -16,3 +16,4 @@
@import url("modules/cache-busting.css");
@import url("modules/apartment-popup.css");
@import url("modules/apartment-marker.css");
@import url("modules/temp-user.css")

View File

@@ -54,7 +54,7 @@
<span class="btn-text">Homepage</span>
</a>
<a href="/shifts.html" class="btn btn-secondary">
<a href="/shifts.html" class="btn btn-secondary">
<span class="btn-icon">📅</span>
<span class="btn-text">View Shifts</span>
</a>

View File

@@ -358,6 +358,30 @@ function setupEventListeners() {
if (clearUserBtn) {
clearUserBtn.addEventListener('click', clearUserForm);
}
// User type change listener
const userTypeSelect = document.getElementById('user-type');
if (userTypeSelect) {
userTypeSelect.addEventListener('change', (e) => {
const expirationGroup = document.getElementById('expiration-group');
const isAdminCheckbox = document.getElementById('user-is-admin');
if (e.target.value === 'temp') {
expirationGroup.style.display = 'block';
isAdminCheckbox.checked = false;
isAdminCheckbox.disabled = true;
} else {
expirationGroup.style.display = 'none';
isAdminCheckbox.disabled = false;
if (e.target.value === 'admin') {
isAdminCheckbox.checked = true;
} else {
isAdminCheckbox.checked = false;
}
}
});
}
}
// Setup navigation between admin sections
@@ -1282,16 +1306,34 @@ function displayUsers(users) {
const createdDate = user.created_at || user['Created At'] || user.createdAt;
const formattedDate = createdDate ? new Date(createdDate).toLocaleDateString() : 'N/A';
const isAdmin = user.admin || user.Admin || false;
const userType = user.UserType || user.userType || (isAdmin ? 'admin' : 'user');
const userId = user.Id || user.id || user.ID;
// Handle expiration info
let expirationInfo = '';
if (user.ExpiresAt) {
const expirationDate = new Date(user.ExpiresAt);
const now = new Date();
const daysUntilExpiration = Math.floor((expirationDate - now) / (1000 * 60 * 60 * 24));
if (daysUntilExpiration < 0) {
expirationInfo = `<span class="expiration-info expiration-warning">Expired ${Math.abs(daysUntilExpiration)} days ago</span>`;
} else if (daysUntilExpiration <= 3) {
expirationInfo = `<span class="expiration-info expiration-warning">Expires in ${daysUntilExpiration} day${daysUntilExpiration !== 1 ? 's' : ''}</span>`;
} else {
expirationInfo = `<span class="expiration-info">Expires: ${expirationDate.toLocaleDateString()}</span>`;
}
}
return `
<tr>
<tr ${user.ExpiresAt && new Date(user.ExpiresAt) < new Date() ? 'class="expired"' : (user.ExpiresAt && new Date(user.ExpiresAt) - new Date() < 3 * 24 * 60 * 60 * 1000 ? 'class="expires-soon"' : '')}>
<td data-label="Email">${escapeHtml(user.email || user.Email || 'N/A')}</td>
<td data-label="Name">${escapeHtml(user.name || user.Name || 'N/A')}</td>
<td data-label="Role">
<span class="user-role ${isAdmin ? 'admin' : 'user'}">
${isAdmin ? 'Admin' : 'User'}
<span class="user-role ${userType}">
${userType.charAt(0).toUpperCase() + userType.slice(1)}
</span>
${expirationInfo}
</td>
<td data-label="Created">${formattedDate}</td>
<td data-label="Actions">
@@ -1401,6 +1443,9 @@ async function createUser(e) {
const email = document.getElementById('user-email').value.trim();
const password = document.getElementById('user-password').value;
const name = document.getElementById('user-name').value.trim();
const userType = document.getElementById('user-type').value;
const expireDays = userType === 'temp' ?
parseInt(document.getElementById('user-expire-days').value) : null;
const admin = document.getElementById('user-is-admin').checked;
if (!email || !password) {
@@ -1413,18 +1458,27 @@ async function createUser(e) {
return;
}
if (userType === 'temp' && (!expireDays || expireDays < 1 || expireDays > 365)) {
showStatus('Expiration days must be between 1 and 365 for temporary users', 'error');
return;
}
try {
const userData = {
email,
password,
name: name || '',
isAdmin: userType === 'admin' || admin,
userType,
expireDays
};
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password,
name: name || '',
admin
})
body: JSON.stringify(userData)
});
const data = await response.json();
@@ -1447,6 +1501,25 @@ function clearUserForm() {
const form = document.getElementById('create-user-form');
if (form) {
form.reset();
// Reset user type to default
const userTypeSelect = document.getElementById('user-type');
if (userTypeSelect) {
userTypeSelect.value = 'user';
}
// Hide expiration group
const expirationGroup = document.getElementById('expiration-group');
if (expirationGroup) {
expirationGroup.style.display = 'none';
}
// Re-enable admin checkbox
const isAdminCheckbox = document.getElementById('user-is-admin');
if (isAdminCheckbox) {
isAdminCheckbox.disabled = false;
}
showStatus('User form cleared', 'info');
}
}

View File

@@ -8,24 +8,41 @@ export async function checkAuth() {
const response = await fetch('/api/auth/check');
const data = await response.json();
// Check if user has expired
if (data.expired) {
showStatus('Account has expired. Please contact an administrator.', 'error');
setTimeout(() => {
window.location.href = '/login.html?expired=true';
}, 2000);
throw new Error('Account expired');
}
if (!data.authenticated) {
window.location.href = '/login.html';
throw new Error('Not authenticated');
}
currentUser = data.user;
currentUser.userType = data.user.userType || 'user'; // Ensure userType is set
updateUserInterface();
} catch (error) {
console.error('Auth check failed:', error);
window.location.href = '/login.html';
if (error.message !== 'Account expired') {
window.location.href = '/login.html';
}
throw error;
}
}
export function updateUserInterface() {
if (!currentUser) return;
/* NEW add a body class we can target with CSS */
document.body.classList.toggle('temp-user', currentUser.userType === 'temp');
document.body.classList.toggle('admin-user', currentUser.isAdmin === true);
// ----- existing code that manipulates DOM -----
// Update user email in both desktop and mobile
const userEmailElement = document.getElementById('user-email');
const mobileUserEmailElement = document.getElementById('mobile-user-email');
@@ -47,6 +64,52 @@ export function updateUserInterface() {
}
}
// Get all shifts links/buttons
const shiftsLinks = document.querySelectorAll('a[href="/shifts.html"]');
if (currentUser.userType === 'temp') {
// If user is temp, hide all shifts-related elements
shiftsLinks.forEach(link => {
const desktopButton = link.closest('.btn');
const mobileItem = link.closest('.mobile-dropdown-item');
if (desktopButton) {
desktopButton.classList.add('temp-restricted');
}
if (mobileItem) {
mobileItem.classList.add('temp-restricted');
}
});
} else {
// If user is NOT temp, ensure all shifts-related elements are visible
shiftsLinks.forEach(link => {
const desktopButton = link.closest('.btn');
const mobileItem = link.closest('.mobile-dropdown-item');
if (desktopButton) {
desktopButton.classList.remove('temp-restricted');
}
if (mobileItem) {
mobileItem.classList.remove('temp-restricted');
}
});
}
// Add temp user indicator for temp users
if (currentUser.userType === 'temp') {
// Hide user profile links
const userLinks = document.querySelectorAll('a[href="/user.html"]');
userLinks.forEach(link => link.style.display = 'none');
// Add temp user indicator
if (userEmailElement) {
userEmailElement.innerHTML = `${currentUser.email} <span class="badge temp-badge">Temp</span>`;
}
if (mobileUserEmailElement) {
mobileUserEmailElement.innerHTML = `${currentUser.email} <span class="badge temp-badge">Temp</span>`;
}
}
// Add admin link if user is admin
if (currentUser.isAdmin) {
addAdminLinks();

View File

@@ -244,10 +244,12 @@ function createPopupContent(location) {
data-location='${escapeHtml(JSON.stringify(location))}'>
✏️ Edit
</button>
<button class="btn btn-primary btn-sm move-location-popup-btn"
data-location='${escapeHtml(JSON.stringify(location))}'>
📍 Move
</button>
${currentUser.userType !== 'temp' ? `
<button class="btn btn-primary btn-sm move-location-popup-btn"
data-location='${escapeHtml(JSON.stringify(location))}'>
📍 Move
</button>
` : ''}
</div>
` : ''}
</div>
@@ -345,6 +347,16 @@ export function openEditForm(location) {
document.getElementById('edit-location-lng').value = location.longitude || '';
document.getElementById('edit-geo-location').value = location['Geo-Location'] || '';
// Show/hide delete button based on user type
const deleteBtn = document.getElementById('delete-location-btn');
if (deleteBtn) {
if (currentUser?.userType === 'temp') {
deleteBtn.style.display = 'none';
} else {
deleteBtn.style.display = '';
}
}
// Show edit footer
document.getElementById('edit-footer').classList.remove('hidden');
}

View File

@@ -6,6 +6,7 @@
import { MkDocsSearch } from './mkdocs-search.js';
import mapSearch from './map-search.js';
import databaseSearch from './database-search.js';
import { currentUser } from './auth.js';
export class UnifiedSearchManager {
constructor(config = {}) {
@@ -72,6 +73,14 @@ export class UnifiedSearchManager {
return false;
}
// Hide database search option for temp users
if (currentUser?.userType === 'temp') {
const databaseModeBtn = container.querySelector('[data-mode="database"]');
if (databaseModeBtn) {
databaseModeBtn.style.display = 'none';
}
}
this.setupEventListeners();
this.updatePlaceholder();
return true;
@@ -187,6 +196,13 @@ export class UnifiedSearchManager {
return;
}
// Prevent database search for temp users
if (currentUser?.userType === 'temp' && mode === 'database') {
console.log('Database search not available for temporary users');
this.showError('Database search is not available for temporary users');
return;
}
this.mode = mode;
// Update button states

View File

@@ -434,6 +434,14 @@
})
.catch(console.error);
// Check for expired parameter and show message
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('expired') === 'true') {
const errorMessage = document.getElementById('error-message');
errorMessage.textContent = 'Your account has expired. Please contact an administrator.';
errorMessage.classList.add('show');
}
// Modal elements
const modal = document.getElementById('password-recovery-modal');
const forgotPasswordLink = document.getElementById('forgot-password-link');

View File

@@ -1,6 +1,6 @@
const express = require('express');
const path = require('path');
const { requireAuth, requireAdmin } = require('../middleware/auth');
const { requireAuth, requireAdmin, requireNonTemp } = require('../middleware/auth');
// Import route modules
const authRoutes = require('./auth');
@@ -53,7 +53,7 @@ module.exports = (app) => {
app.use('/api/locations', requireAuth, locationRoutes);
app.use('/api/geocode', requireAuth, geocodingRoutes);
app.use('/api/settings', requireAuth, settingsRoutes);
app.use('/api/shifts', shiftsRoutes);
app.use('/api/shifts', requireNonTemp, shiftsRoutes);
app.use('/api/external', externalDataRoutes);
// Admin routes
@@ -164,12 +164,12 @@ module.exports = (app) => {
});
// Protected page route
app.get('/shifts.html', requireAuth, (req, res) => {
app.get('/shifts.html', requireNonTemp, (req, res) => {
res.sendFile(path.join(__dirname, '../public', 'shifts.html'));
});
// User profile page route
app.get('/user.html', requireAuth, (req, res) => {
app.get('/user.html', requireNonTemp, (req, res) => {
res.sendFile(path.join(__dirname, '../public', 'user.html'));
});

View File

@@ -2,7 +2,7 @@ const express = require('express');
const router = express.Router();
const locationsController = require('../controllers/locationsController');
const { strictLimiter } = require('../middleware/rateLimiter');
const { requireAuth } = require('../middleware/auth');
const { requireAuth, requireDeletePermission } = require('../middleware/auth');
// Get all locations (public)
router.get('/', locationsController.getAll);
@@ -16,7 +16,7 @@ router.post('/', requireAuth, strictLimiter, locationsController.create);
// Update location (requires authentication)
router.put('/:id', requireAuth, strictLimiter, locationsController.update);
// Delete location (requires authentication)
router.delete('/:id', requireAuth, strictLimiter, locationsController.delete);
// Delete location (requires authentication and delete permission)
router.delete('/:id', requireAuth, requireDeletePermission, strictLimiter, locationsController.delete);
module.exports = router;

View File

@@ -0,0 +1,88 @@
const nocodbService = require('./nocodb');
const logger = require('../utils/logger');
const config = require('../config');
class AccountExpirationService {
constructor() {
this.checkInterval = null;
}
// Start the expiration check service
start() {
// Run check every hour
this.checkInterval = setInterval(() => {
this.checkExpiredAccounts();
}, 60 * 60 * 1000); // 1 hour
// Also run immediately on start
this.checkExpiredAccounts();
logger.info('Account expiration service started');
}
// Stop the service
stop() {
if (this.checkInterval) {
clearInterval(this.checkInterval);
this.checkInterval = null;
}
}
// Check and handle expired accounts
async checkExpiredAccounts() {
try {
// Get all users
const users = await nocodbService.getLoginRecords();
const now = new Date();
const expiredUsers = [];
for (const user of users) {
if (user.ExpiresAt && new Date(user.ExpiresAt) < now) {
expiredUsers.push(user);
}
}
// Delete expired accounts
for (const user of expiredUsers) {
await this.deleteExpiredAccount(user);
}
if (expiredUsers.length > 0) {
logger.info(`Deleted ${expiredUsers.length} expired temp accounts`);
}
} catch (error) {
logger.error('Error checking expired accounts:', error);
}
}
// Delete a single expired account
async deleteExpiredAccount(user) {
try {
await nocodbService.deleteRecord(
config.nocodb.loginSheetId,
user.Id
);
logger.info(`Deleted expired temp account: ${user.Email}`);
// Optional: Send notification email before deletion
// await emailService.sendAccountExpirationNotice(user.Email);
} catch (error) {
logger.error(`Failed to delete expired account ${user.Email}:`, error);
}
}
// Calculate expiration date based on admin settings
static calculateExpirationDate(expireDays) {
if (!expireDays || expireDays <= 0) return null;
const expirationDate = new Date();
expirationDate.setDate(expirationDate.getDate() + expireDays);
return expirationDate;
}
}
module.exports = new AccountExpirationService();