mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
121 lines
5.2 KiB
JavaScript
121 lines
5.2 KiB
JavaScript
/***********************************************************************************************************************************************************************
|
|
* 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 any of `plans`, 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. Batched into flat, count-independent queries (no per-user or
|
|
// per-plan loop hitting the DB) so this scales to any number of affected
|
|
// plans/subscribers in a fixed number of round trips.
|
|
async function revokePlanSubscriberAccessBulk(plans, revokedByUserId) {
|
|
if (!plans.length) return { revoked_user_count: 0 };
|
|
|
|
const planIds = plans.map((p) => p.plan_id);
|
|
const labelByPlanId = new Map(plans.map((p) => [String(p.plan_id), p.label]));
|
|
|
|
const activeRows = await mdl_UserTiers.findAll({
|
|
where: { plan_id: planIds, status: 'active' },
|
|
attributes: ['tier_id', 'user_id', 'plan_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 } },
|
|
);
|
|
|
|
// One grouped query replaces a per-user COUNT: finds everyone who still
|
|
// holds another active tier after the revoke above.
|
|
const stillActiveRows = await mdl_UserTiers.findAll({
|
|
where: { user_id: userIds, status: 'active' },
|
|
attributes: ['user_id'],
|
|
group: ['user_id'],
|
|
});
|
|
const stillActiveUserIds = new Set(stillActiveRows.map((r) => String(r.user_id)));
|
|
const usersToDowngrade = userIds.filter((user_id) => !stillActiveUserIds.has(user_id));
|
|
|
|
if (usersToDowngrade.length) {
|
|
await mdl_UserTiers.bulkCreate(
|
|
usersToDowngrade.map((user_id) => ({
|
|
user_id,
|
|
tier: 'free',
|
|
status: 'active',
|
|
starts_at: now,
|
|
expires_at: null,
|
|
granted_by: revokedByUserId,
|
|
notes: 'Auto-downgrade after plan access was force-revoked.',
|
|
})),
|
|
);
|
|
}
|
|
|
|
// One row per (user, plan) relationship revoked — a user in two of the
|
|
// selected plans gets two notices/emails, one per plan label.
|
|
const revokedPairs = [...new Map(activeRows.map((r) => [`${r.user_id}:${r.plan_id}`, r])).values()];
|
|
|
|
try {
|
|
const notifications = revokedPairs.map((r) => {
|
|
const notify = NOTIFICATION_REGISTRY.tier_plan_access_revoked.build({
|
|
label: labelByPlanId.get(String(r.plan_id)),
|
|
planId: r.plan_id,
|
|
});
|
|
return { user_id: String(r.user_id), ...notify, seen: false, createdAt: now, updatedAt: now };
|
|
});
|
|
await UserNotification.bulkCreate(notifications, { 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 usersById = new Map(users.map((u) => [String(u.user_id), u]));
|
|
const dateStr = fmtDate(now);
|
|
for (const r of revokedPairs) {
|
|
const u = usersById.get(String(r.user_id));
|
|
if (!u) continue;
|
|
const name = u.personal_info?.name?.full_name ?? 'there';
|
|
sendEmail({
|
|
to: u.email,
|
|
type: 'TIER_ACCESS_REVOKED',
|
|
data: { name, label: labelByPlanId.get(String(r.plan_id)), 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 };
|
|
}
|
|
|
|
async function revokePlanSubscriberAccess(plan, revokedByUserId) {
|
|
return revokePlanSubscriberAccessBulk([plan], revokedByUserId);
|
|
}
|
|
|
|
module.exports = { revokePlanSubscriberAccess, revokePlanSubscriberAccessBulk };
|