/*********************************************************************************************************************************************************************** * File Name: planAccess.service.js * Type of Program: Service * Description: Force-revokes access for every currently active subscriber of * a Tier Plan (no refund). Always fired automatically as part of * archivePlan/bulkArchivePlans (archiving a plan revokes its * subscribers' access, full stop — no opt-in) and again as part * of permanentlyDeletePlan/bulkPermanentlyDeletePlans, in case a * plan was archived before this existed and still has active * subscribers when it's finally deleted for good. * Author: Kenneth Obsequio (@lash0000) * Date Created: Aug. 4, 2026 ***********************************************************************************************************************************************************************/ 'use strict'; const mdl_UserTiers = require('../models/tiers/user_tiers.mdl'); const mdl_Users = require('../models/users/users.mdl'); const UserNotification = require('../models/notifications/user_notification.mdl'); const { NOTIFICATION_REGISTRY } = require('../data/notifications.data'); const { sendEmail } = require('./email.service'); const { fmtDate } = require('../utils/datetime.util'); // Revokes every active user_tiers row tied to `plan`, replicating revokeTier's // per-user "auto-downgrade to Free if no other active tier remains" rule // (controllers/admin/tiers.controller.js) — a user can hold more than one // concurrently-active plan, so this can't be a blanket status update. async function revokePlanSubscriberAccess(plan, revokedByUserId) { const activeRows = await mdl_UserTiers.findAll({ where: { plan_id: plan.plan_id, status: 'active' }, attributes: ['tier_id', 'user_id'], }); if (!activeRows.length) return { revoked_user_count: 0 }; const tierIds = activeRows.map((r) => r.tier_id); const userIds = [...new Set(activeRows.map((r) => String(r.user_id)))]; const now = new Date(); await mdl_UserTiers.update( { status: 'revoked', revoked_by: revokedByUserId, revoked_at: now }, { where: { tier_id: tierIds } }, ); for (const user_id of userIds) { const remainingActive = await mdl_UserTiers.count({ where: { user_id, status: 'active' } }); if (remainingActive === 0) { await mdl_UserTiers.create({ user_id, tier: 'free', status: 'active', starts_at: now, expires_at: null, granted_by: revokedByUserId, notes: 'Auto-downgrade after plan access was force-revoked.', }); } } try { const notify = NOTIFICATION_REGISTRY.tier_plan_access_revoked.build({ label: plan.label, planId: plan.plan_id }); await UserNotification.bulkCreate( userIds.map((user_id) => ({ user_id, ...notify, seen: false, createdAt: now, updatedAt: now })), { validate: false }, ); } catch (notifyErr) { console.error('[PLAN ACCESS REVOKE][NOTIFY]', notifyErr); } try { const users = await mdl_Users.findAll({ where: { user_id: userIds }, attributes: ['user_id', 'email', 'personal_info'], }); const dateStr = fmtDate(now); for (const u of users) { const name = u.personal_info?.name?.full_name ?? 'there'; sendEmail({ to: u.email, type: 'TIER_ACCESS_REVOKED', data: { name, label: plan.label, date: dateStr }, }).catch((emailErr) => console.error('[PLAN ACCESS REVOKE][EMAIL]', emailErr)); } } catch (emailBatchErr) { console.error('[PLAN ACCESS REVOKE][EMAIL BATCH]', emailBatchErr); } return { revoked_user_count: userIds.length }; } module.exports = { revokePlanSubscriberAccess };