const path = require('path'); const { models } = require(path.resolve(process.env.ROOT_PATH, './db/db.js')); const BaseController = require(path.resolve(process.env.ROOT_PATH, './controllers/BaseController.js')); const { subDays } = require('date-fns'); class StatsController extends BaseController { constructor() { super(); } /** * Get overall system statistics * * @swagger * /stats/system: * get: * summary: Get system statistics * description: Retrieves overall system statistics including mailbox and domain counts * tags: [Statistics] * security: * - bearerAuth: [] * responses: * 200: * description: System statistics * content: * application/json: * schema: * type: object * properties: * mailboxes: * type: object * properties: * total: * type: integer * description: Total number of mailboxes * active: * type: integer * description: Number of active mailboxes * expiringToday: * type: integer * description: Number of mailboxes expiring today * domains: * type: object * properties: * total: * type: integer * description: Total number of domains * active: * type: integer * description: Number of active domains * creationStats: * type: array * description: Mailbox creation statistics for the last 7 days * items: * type: object * properties: * date: * type: string * format: date * mailboxesCreated: * type: integer * 500: * description: Server error */ async getSystemStats(req, res) { try { // Get total counts const totalMailboxes = await models.Mailbox.query().count('* as count').first(); const activeMailboxes = await models.Mailbox.query() .where('active', 1) .count('* as count') .first(); const expiringToday = await models.Mailbox.query() .where('expires', '<', new Date(new Date().setHours(23, 59, 59, 999)).toISOString().slice(0, 19).replace('T', ' ')) .where('expires', '>', new Date().toISOString().slice(0, 19).replace('T', ' ')) .count('* as count') .first(); const totalDomains = await models.Domain.query().count('* as count').first(); const activeDomains = await models.Domain.query() .where('active', 1) .count('* as count') .first(); // Get creation stats for the last 7 days const last7Days = []; for (let i = 6; i >= 0; i--) { const date = subDays(new Date(), i); const formattedDate = date.toISOString().slice(0, 10); // Get mailboxes created on this date const mailboxesCreated = await models.Mailbox.query() .where('created', 'like', `${formattedDate}%`) .count('* as count') .first(); last7Days.push({ date: formattedDate, mailboxesCreated: parseInt(mailboxesCreated.count) || 0 }); } return res.json({ mailboxes: { total: parseInt(totalMailboxes.count) || 0, active: parseInt(activeMailboxes.count) || 0, expiringToday: parseInt(expiringToday.count) || 0 }, domains: { total: parseInt(totalDomains.count) || 0, active: parseInt(activeDomains.count) || 0 }, creationStats: last7Days }); } catch (error) { console.error('Error fetching system stats:', error); return res.status(500).json({ error: 'Failed to fetch system statistics' }); } } /** * Get mailbox statistics * * @swagger * /stats/mailboxes: * get: * summary: Get mailbox statistics * description: Retrieves detailed statistics about mailboxes * tags: [Statistics] * security: * - bearerAuth: [] * responses: * 200: * description: Mailbox statistics * content: * application/json: * schema: * type: object * properties: * expiryStats: * type: object * properties: * expired: * type: integer * description: Number of expired mailboxes * expiringSoon: * type: integer * description: Number of mailboxes expiring within 3 days * creationByDay: * type: array * description: Mailbox creation statistics for the last 30 days * items: * type: object * properties: * date: * type: string * format: date * count: * type: integer * topDomains: * type: array * description: Top 10 domains by mailbox count * items: * type: object * properties: * domain: * type: string * count: * type: integer * 500: * description: Server error */ async getMailboxStats(req, res) { try { // Get mailboxes by expiry status const expired = await models.Mailbox.query() .where('expires', '<', new Date().toISOString().slice(0, 19).replace('T', ' ')) .count('* as count') .first(); const expiringSoon = await models.Mailbox.query() .where('expires', '>', new Date().toISOString().slice(0, 19).replace('T', ' ')) .where('expires', '<', new Date(new Date().getTime() + 3 * 24 * 60 * 60 * 1000).toISOString().slice(0, 19).replace('T', ' ')) .count('* as count') .first(); // Get mailboxes by creation date (last 30 days) const creationByDay = []; for (let i = 29; i >= 0; i--) { const date = subDays(new Date(), i); const formattedDate = date.toISOString().slice(0, 10); const created = await models.Mailbox.query() .where('created', 'like', `${formattedDate}%`) .count('* as count') .first(); creationByDay.push({ date: formattedDate, count: parseInt(created.count) || 0 }); } // Get top domains by mailbox count const topDomains = await models.Mailbox.query() .select('domain') .count('* as count') .groupBy('domain') .orderBy('count', 'desc') .limit(10); return res.json({ expiryStats: { expired: parseInt(expired.count) || 0, expiringSoon: parseInt(expiringSoon.count) || 0 }, creationByDay, topDomains: topDomains.map(d => ({ domain: d.domain, count: parseInt(d.count) || 0 })) }); } catch (error) { console.error('Error fetching mailbox stats:', error); return res.status(500).json({ error: 'Failed to fetch mailbox statistics' }); } } /** * Get domain statistics * * @swagger * /stats/domains: * get: * summary: Get domain statistics * description: Retrieves detailed statistics about domains * tags: [Statistics] * security: * - bearerAuth: [] * responses: * 200: * description: Domain statistics * content: * application/json: * schema: * type: object * properties: * domains: * type: object * properties: * total: * type: integer * description: Total number of domains * active: * type: integer * description: Number of active domains * inactive: * type: integer * description: Number of inactive domains * topDomains: * type: array * description: Top 10 domains by mailbox count * items: * type: object * properties: * domain: * type: string * mailboxCount: * type: integer * domainDetails: * type: array * description: Detailed information about all domains * items: * type: object * properties: * domain: * type: string * description: * type: string * active: * type: boolean * mailboxCount: * type: integer * 500: * description: Server error */ async getDomainStats(req, res) { try { // Get all domains with mailbox counts const domains = await models.Domain.query() .select('domain.domain', 'domain.description', 'domain.active') .leftJoin('mailbox', 'domain.domain', 'mailbox.domain') .groupBy('domain.domain') .count('mailbox.username as mailboxCount'); // Get active vs inactive domains const activeDomains = domains.filter(d => d.active === 1).length; const inactiveDomains = domains.filter(d => d.active === 0).length; // Get domains with most mailboxes const topDomains = [...domains] .sort((a, b) => parseInt(b.mailboxCount) - parseInt(a.mailboxCount)) .slice(0, 10) .map(d => ({ domain: d.domain, mailboxCount: parseInt(d.mailboxCount) || 0 })); return res.json({ domains: { total: domains.length, active: activeDomains, inactive: inactiveDomains }, topDomains, domainDetails: domains.map(d => ({ domain: d.domain, description: d.description, active: d.active === 1, mailboxCount: parseInt(d.mailboxCount) || 0 })) }); } catch (error) { console.error('Error fetching domain stats:', error); return res.status(500).json({ error: 'Failed to fetch domain statistics' }); } } /** * Get user statistics * * @swagger * /stats/users: * get: * summary: Get user statistics * description: Retrieves detailed statistics about users * tags: [Statistics] * security: * - bearerAuth: [] * responses: * 200: * description: User statistics * content: * application/json: * schema: * type: object * properties: * users: * type: object * properties: * total: * type: integer * description: Total number of users * admin: * type: integer * description: Number of admin users * active: * type: integer * description: Number of active users * creationByDay: * type: array * description: User creation statistics for the last 30 days * items: * type: object * properties: * date: * type: string * format: date * count: * type: integer * invites: * type: object * properties: * active: * type: integer * description: Number of active invites * 500: * description: Server error */ async getUserStats(req, res) { try { // Get total users count const totalUsers = await models.User.query().count('* as count').first(); const adminUsers = await models.User.query() .where('is_admin', 1) .count('* as count') .first(); const activeUsers = await models.User.query() .where('is_active', 1) .count('* as count') .first(); // Get users created in the last 30 days const creationByDay = []; for (let i = 29; i >= 0; i--) { const date = subDays(new Date(), i); const formattedDate = date.toISOString().slice(0, 10); const created = await models.User.query() .where('created', 'like', `${formattedDate}%`) .count('* as count') .first(); creationByDay.push({ date: formattedDate, count: parseInt(created.count) || 0 }); } // Get active invites count const activeInvites = await models.Invite.query() .where('expires', '>', new Date().toISOString().slice(0, 19).replace('T', ' ')) .count('* as count') .first(); return res.json({ users: { total: parseInt(totalUsers.count) || 0, admin: parseInt(adminUsers.count) || 0, active: parseInt(activeUsers.count) || 0 }, creationByDay, invites: { active: parseInt(activeInvites.count) || 0 } }); } catch (error) { console.error('Error fetching user stats:', error); return res.status(500).json({ error: 'Failed to fetch user statistics' }); } } } module.exports = new StatsController();