mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
101 lines
4.3 KiB
JavaScript
101 lines
4.3 KiB
JavaScript
/***********************************************************************************************************************************************************************
|
|
* File Name : expire_user_tiers.cron.js
|
|
* Type : Cron Job
|
|
* Description : Marks active user_tiers rows as 'expired' when their expires_at
|
|
* has passed. Runs every minute to support short-duration plans
|
|
* (minute- and hour-level plans in addition to day/month/year).
|
|
*
|
|
* For each expired batch it:
|
|
* 1. Bulk-updates matching rows to status = 'expired'.
|
|
* 2. Sends an in-app UserNotification to each affected user.
|
|
*
|
|
* Safety:
|
|
* - Only touches rows with expires_at IS NOT NULL so
|
|
* manually-granted unlimited tiers (expires_at = NULL) are
|
|
* never touched.
|
|
* - Bulk update happens before notifications so a restart
|
|
* mid-run never re-expires already-expired rows.
|
|
*
|
|
* Schedule : Every minute ("* * * * *"). Registered by cron/client.cron.js.
|
|
*
|
|
* Author: Kenneth Obsequio (@lash0000)
|
|
* Date Created: Jun. 29, 2026
|
|
***********************************************************************************************************************************************************************/
|
|
'use strict';
|
|
|
|
const { Op } = require('sequelize');
|
|
const mdl_UserTiers = require('../../models/tiers/user_tiers.mdl');
|
|
const mdl_TierPlans = require('../../models/tiers/tier_plans.mdl');
|
|
const UserNotification = require('../../models/notifications/user_notification.mdl');
|
|
const CronNotificationSetting = require('../../models/notifications/cron_notification_setting.mdl');
|
|
const { getNotificationTemplate, renderNotificationContent } = require('../../services/notificationTemplate.service');
|
|
|
|
require('../../models/tiers/tier.associations');
|
|
|
|
async function run() {
|
|
// ── 1. Find all active tiers whose expires_at has passed ──────────────────
|
|
let expired;
|
|
try {
|
|
expired = await mdl_UserTiers.findAll({
|
|
where: {
|
|
status: 'active',
|
|
expires_at: { [Op.ne]: null, [Op.lte]: new Date() },
|
|
},
|
|
include: [{
|
|
model: mdl_TierPlans,
|
|
as: 'plan',
|
|
attributes: ['plan_id', 'label', 'tier'],
|
|
required: false,
|
|
}],
|
|
attributes: ['tier_id', 'user_id', 'tier'],
|
|
});
|
|
} catch (err) {
|
|
console.error('[CRON][EXPIRE TIERS] Failed to query user_tiers:', err);
|
|
return;
|
|
}
|
|
|
|
if (!expired.length) return;
|
|
|
|
const tierIds = expired.map((t) => t.tier_id);
|
|
|
|
// ── 2. Bulk-update to expired ──────────────────────────────────────────────
|
|
try {
|
|
await mdl_UserTiers.update(
|
|
{ status: 'expired' },
|
|
{ where: { tier_id: tierIds } }
|
|
);
|
|
} catch (err) {
|
|
console.error('[CRON][EXPIRE TIERS] Bulk update failed:', err);
|
|
return;
|
|
}
|
|
|
|
// ── 3. Send in-app notifications (one per affected user) ──────────────────
|
|
// Status flip above always happens — only this step is skippable via settings.
|
|
const settings = await CronNotificationSetting.findOne({ where: { job_name: 'expireUserTiers' } });
|
|
if (!settings || settings.enabled) {
|
|
try {
|
|
const template = await getNotificationTemplate('tier_expired');
|
|
const notifications = expired.map((t) => ({
|
|
user_id: t.user_id,
|
|
...renderNotificationContent(template, {
|
|
tier: t.tier,
|
|
label: t.plan?.label ?? null,
|
|
planId: t.plan?.plan_id ?? null,
|
|
}),
|
|
}));
|
|
|
|
await UserNotification.bulkCreate(notifications, { ignoreDuplicates: true });
|
|
} catch (err) {
|
|
console.error('[CRON][EXPIRE TIERS] Notification bulkCreate failed:', err);
|
|
}
|
|
}
|
|
|
|
console.log(`[CRON][EXPIRE TIERS] Expired ${expired.length} tier(s) for ${new Set(expired.map((t) => t.user_id)).size} user(s).`);
|
|
}
|
|
|
|
module.exports = {
|
|
name: 'expireUserTiers',
|
|
schedule: '* * * * *',
|
|
run,
|
|
};
|