fix: Clear notification cache keys when changing billing limits
This commit is contained in:
@@ -368,11 +368,14 @@ export class Users {
|
|||||||
throw new NotFound('Project not found or you do not have permission to update billing limits');
|
throw new NotFound('Project not found or you do not have permission to update billing limits');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the project
|
// Get the project with current limits
|
||||||
const project = await prisma.project.findUnique({
|
const project = await prisma.project.findUnique({
|
||||||
where: {id},
|
where: {id},
|
||||||
select: {
|
select: {
|
||||||
subscription: true,
|
subscription: true,
|
||||||
|
billingLimitWorkflows: true,
|
||||||
|
billingLimitCampaigns: true,
|
||||||
|
billingLimitTransactional: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -395,8 +398,17 @@ export class Users {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Invalidate cache so new limits take effect immediately
|
// Clear notification cache keys for limits that changed
|
||||||
await BillingLimitService.invalidateCache(id);
|
// This allows new warning/limit emails to be sent when the new limits are reached
|
||||||
|
await BillingLimitService.clearNotificationCacheForChangedLimits(
|
||||||
|
id,
|
||||||
|
{
|
||||||
|
workflows: project.billingLimitWorkflows,
|
||||||
|
campaigns: project.billingLimitCampaigns,
|
||||||
|
transactional: project.billingLimitTransactional,
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
|
||||||
const limitsAndUsage = await BillingLimitService.getLimitsAndUsage(id);
|
const limitsAndUsage = await BillingLimitService.getLimitsAndUsage(id);
|
||||||
|
|
||||||
|
|||||||
@@ -487,7 +487,11 @@ export class BillingLimitService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidate usage cache for a project
|
* Invalidate usage cache for a project
|
||||||
* Call this when billing period resets or limits are changed
|
* Primarily used in tests to reset cache state between scenarios.
|
||||||
|
* In production, the cache naturally expires after 5 minutes.
|
||||||
|
*
|
||||||
|
* NOTE: Updating billing limits does NOT require clearing usage cache
|
||||||
|
* (use clearNotificationCacheForChangedLimits instead)
|
||||||
*
|
*
|
||||||
* @param projectId - Project ID
|
* @param projectId - Project ID
|
||||||
*/
|
*/
|
||||||
@@ -506,6 +510,69 @@ export class BillingLimitService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear notification cache keys for billing limits that have changed
|
||||||
|
* This allows new warning/limit emails to be sent when updated limits are reached
|
||||||
|
*
|
||||||
|
* @param projectId - Project ID
|
||||||
|
* @param oldLimits - Previous billing limits
|
||||||
|
* @param newLimits - New billing limits
|
||||||
|
*/
|
||||||
|
public static async clearNotificationCacheForChangedLimits(
|
||||||
|
projectId: string,
|
||||||
|
oldLimits: {
|
||||||
|
workflows: number | null;
|
||||||
|
campaigns: number | null;
|
||||||
|
transactional: number | null;
|
||||||
|
},
|
||||||
|
newLimits: {
|
||||||
|
workflows: number | null;
|
||||||
|
campaigns: number | null;
|
||||||
|
transactional: number | null;
|
||||||
|
},
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||||
|
|
||||||
|
const keysToDelete: string[] = [];
|
||||||
|
|
||||||
|
// Check workflows limit
|
||||||
|
if (oldLimits.workflows !== newLimits.workflows) {
|
||||||
|
keysToDelete.push(
|
||||||
|
Keys.Billing.warningEmail(projectId, EmailSourceType.WORKFLOW, year, month),
|
||||||
|
Keys.Billing.limitEmail(projectId, EmailSourceType.WORKFLOW, year, month),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check campaigns limit
|
||||||
|
if (oldLimits.campaigns !== newLimits.campaigns) {
|
||||||
|
keysToDelete.push(
|
||||||
|
Keys.Billing.warningEmail(projectId, EmailSourceType.CAMPAIGN, year, month),
|
||||||
|
Keys.Billing.limitEmail(projectId, EmailSourceType.CAMPAIGN, year, month),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check transactional limit
|
||||||
|
if (oldLimits.transactional !== newLimits.transactional) {
|
||||||
|
keysToDelete.push(
|
||||||
|
Keys.Billing.warningEmail(projectId, EmailSourceType.TRANSACTIONAL, year, month),
|
||||||
|
Keys.Billing.limitEmail(projectId, EmailSourceType.TRANSACTIONAL, year, month),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keysToDelete.length > 0) {
|
||||||
|
await Promise.all(keysToDelete.map(key => redis.del(key)));
|
||||||
|
signale.debug(
|
||||||
|
`[BILLING_LIMIT] Cleared notification cache for changed limits in project ${projectId} (${keysToDelete.length} keys)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
signale.warn(`[BILLING_LIMIT] Failed to clear notification cache for ${projectId}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Redis cache key for usage count
|
* Get Redis cache key for usage count
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user