From c377f7a9af46dc188a33a1439c9e64de90f1071d Mon Sep 17 00:00:00 2001 From: John Yeo Date: Wed, 23 Jul 2025 09:47:59 +0100 Subject: [PATCH] feat: adding upstash cache --- server/package.json | 1 + server/src/internal/api/apiRouter.ts | 3 + .../internal/api/entities/getEntityUtils.ts | 26 ++++-- .../src/internal/api/entitled/checkRouter.ts | 40 +-------- server/src/internal/customers/CusService.ts | 19 ++--- .../internal/customers/attach/handleAttach.ts | 3 +- .../customers/cusCache/cusCacheUtils.ts | 17 ++++ .../customers/cusCache/getCusWithCache.ts | 83 +++++++++++++++++++ .../customers/cusCache/updateCachedCus.ts | 43 ++++++++++ .../customers/cusCache/upstashUtils.ts | 13 +++ .../customers/handlers/cusDeleteHandlers.ts | 9 +- .../customers/handlers/handleGetCustomer.ts | 23 ++--- .../handlers/handleUpdateBalances.ts | 26 +++--- .../handlers/handleUpdateCustomer.ts | 9 +- .../handlers/handleUpdateEntitlement.ts | 1 + .../handleCreateEntity/handleCreateEntity.ts | 2 +- .../src/middleware/refreshCacheMiddleware.ts | 47 +++++++++++ server/src/trigger/updateBalanceTask.ts | 22 +++-- server/src/trigger/updateUsageTask.ts | 16 +++- 19 files changed, 308 insertions(+), 95 deletions(-) create mode 100644 server/src/internal/customers/cusCache/cusCacheUtils.ts create mode 100644 server/src/internal/customers/cusCache/getCusWithCache.ts create mode 100644 server/src/internal/customers/cusCache/updateCachedCus.ts create mode 100644 server/src/internal/customers/cusCache/upstashUtils.ts create mode 100644 server/src/middleware/refreshCacheMiddleware.ts diff --git a/server/package.json b/server/package.json index 01ccc293a..6d9c81a9b 100644 --- a/server/package.json +++ b/server/package.json @@ -46,6 +46,7 @@ "@react-email/components": "^0.0.42", "@sentry/node": "^9.30.0", "@supabase/supabase-js": "^2.46.2", + "@upstash/redis": "^1.35.1", "ai": "^4.3.10", "autumn-js": "^0.0.77", "axios": "^1.8.3", diff --git a/server/src/internal/api/apiRouter.ts b/server/src/internal/api/apiRouter.ts index 487ffcd62..443a7b403 100644 --- a/server/src/internal/api/apiRouter.ts +++ b/server/src/internal/api/apiRouter.ts @@ -25,12 +25,15 @@ import { internalFeatureRouter } from "../features/internalFeatureRouter.js"; import { analyticsRouter } from "../analytics/analyticsRouter.js"; import { handleConnectStripe } from "../orgs/handlers/handleConnectStripe.js"; import { handleDeleteStripe } from "../orgs/handlers/handleDeleteStripe.js"; +import { refreshCusCache } from "../customers/cusCache/updateCachedCus.js"; +import { refreshCacheMiddleware } from "@/middleware/refreshCacheMiddleware.js"; const apiRouter: Router = Router(); apiRouter.use(apiAuthMiddleware); apiRouter.use(pricingMiddleware); apiRouter.use(analyticsMiddleware); +apiRouter.use(refreshCacheMiddleware); apiRouter.use("/customers", cusRouter); apiRouter.use("/invoices", invoiceRouter); diff --git a/server/src/internal/api/entities/getEntityUtils.ts b/server/src/internal/api/entities/getEntityUtils.ts index 236fbe030..86c659c91 100644 --- a/server/src/internal/api/entities/getEntityUtils.ts +++ b/server/src/internal/api/entities/getEntityUtils.ts @@ -1,4 +1,5 @@ import type { DrizzleCli } from "@/db/initDrizzle.js"; +import { getCusWithCache } from "@/internal/customers/cusCache/getCusWithCache.js"; import { ACTIVE_STATUSES, RELEVANT_STATUSES, @@ -46,25 +47,32 @@ export const getEntityResponse = async ({ apiVersion: number; features: Feature[]; }) => { - let customer = await CusService.getFull({ - db, + // let customer = await CusService.getFull({ + // db, + // idOrInternalId: customerId, + // orgId: org.id, + // env, + // withEntities: true, + // withSubs: true, + // expand, + // entityId, + // }); + let customer = await getCusWithCache({ idOrInternalId: customerId, orgId: org.id, env, - withEntities: true, - withSubs: true, expand, entityId, }); let entities = customer.entities.filter((e: Entity) => - entityIds.includes(e.id), + entityIds.includes(e.id) ); let entityCusProducts = customer.customer_products.filter( (p: FullCusProduct) => entities.some((e: Entity) => e.internal_id == p.internal_entity_id) || - nullish(p.internal_entity_id), + nullish(p.internal_entity_id) ); let subs = customer.subscriptions || []; @@ -72,7 +80,7 @@ export const getEntityResponse = async ({ const entityResponses: EntityResponse[] = []; for (const entityId of entityIds) { let entity = customer.entities.find( - (e: Entity) => e.id == entityId || e.internal_id == entityId, + (e: Entity) => e.id == entityId || e.internal_id == entityId ); if (!entity) { throw new RecaseError({ @@ -84,8 +92,8 @@ export const getEntityResponse = async ({ let entitySubs = subs.filter((s: Subscription) => entityCusProducts.some((p: FullCusProduct) => - p.subscription_ids?.includes(s.stripe_id || ""), - ), + p.subscription_ids?.includes(s.stripe_id || "") + ) ); let { main, addOns } = await processFullCusProducts({ diff --git a/server/src/internal/api/entitled/checkRouter.ts b/server/src/internal/api/entitled/checkRouter.ts index d2b36f9c4..d617ea102 100644 --- a/server/src/internal/api/entitled/checkRouter.ts +++ b/server/src/internal/api/entitled/checkRouter.ts @@ -25,44 +25,6 @@ import { getV2CheckResponse } from "./checkUtils/getV2CheckResponse.js"; export const checkRouter: Router = Router(); -const getRequiredAndActualBalance = ({ - cusEnts, - feature, - originalFeatureId, - required, - entityId, -}: { - cusEnts: FullCustomerEntitlement[]; - feature: Feature; - originalFeatureId: string; - required: number; - entityId: string; -}) => { - let requiredBalance = required; - if ( - feature.type === FeatureType.CreditSystem && - feature.id !== originalFeatureId - ) { - requiredBalance = featureToCreditSystem({ - featureId: originalFeatureId, - creditSystem: feature, - amount: required, - }); - } - - const actualBalance = getFeatureBalance({ - cusEnts, - internalFeatureId: feature.internal_id!, - entityId, - }); - - return { - required: requiredBalance, - actual: actualBalance, - entityId, - }; -}; - checkRouter.post("", async (req: any, res: any) => { try { let { @@ -181,7 +143,7 @@ checkRouter.post("", async (req: any, res: any) => { const { allowed, balance } = v2Response; const featureToUse = allFeatures.find( - (f: Feature) => f.id === v2Response.feature_id, + (f: Feature) => f.id === v2Response.feature_id ); if (allowed && req.isPublic !== true) { diff --git a/server/src/internal/customers/CusService.ts b/server/src/internal/customers/CusService.ts index 049b2a7cf..5f01aa67b 100644 --- a/server/src/internal/customers/CusService.ts +++ b/server/src/internal/customers/CusService.ts @@ -17,6 +17,7 @@ import { DrizzleCli } from "@/db/initDrizzle.js"; import { getFullCusQuery } from "./getFullCusQuery.js"; import { trace } from "@opentelemetry/api"; import { withSpan } from "../analytics/tracer/spanUtils.js"; +import { RELEVANT_STATUSES } from "./cusProducts/CusProductService.js"; const tracer = trace.getTracer("express"); @@ -26,11 +27,7 @@ export class CusService { idOrInternalId, orgId, env, - inStatuses = [ - CusProductStatus.Active, - CusProductStatus.PastDue, - CusProductStatus.Scheduled, - ], + inStatuses = RELEVANT_STATUSES, withEntities = false, entityId, expand, @@ -72,7 +69,7 @@ export class CusService { withEntities, withTrialsUsed, withSubs, - entityId, + entityId ); let result = await db.execute(query); @@ -123,10 +120,10 @@ export class CusService { where: and( or( eq(customers.id, idOrInternalId), - eq(customers.internal_id, idOrInternalId), + eq(customers.internal_id, idOrInternalId) ), eq(customers.org_id, orgId), - eq(customers.env, env), + eq(customers.env, env) ), }); @@ -152,7 +149,7 @@ export class CusService { where: and( eq(customers.email, email), eq(customers.org_id, orgId), - eq(customers.env, env), + eq(customers.env, env) ), }); @@ -270,8 +267,8 @@ export class CusService { and( eq(customers.internal_id, internalId), eq(customers.org_id, orgId), - eq(customers.env, env), - ), + eq(customers.env, env) + ) ) .returning(); diff --git a/server/src/internal/customers/attach/handleAttach.ts b/server/src/internal/customers/attach/handleAttach.ts index 8e9d81546..9982f59ea 100644 --- a/server/src/internal/customers/attach/handleAttach.ts +++ b/server/src/internal/customers/attach/handleAttach.ts @@ -9,6 +9,7 @@ import { handleAttachErrors } from "./attachUtils/handleAttachErrors.js"; import { checkStripeConnections, createStripePrices } from "./attachRouter.js"; import { insertCustomItems } from "./attachUtils/insertCustomItems.js"; import { runAttachFunction } from "./attachUtils/getAttachFunction.js"; +import { refreshCusCache } from "../cusCache/updateCachedCus.js"; export const handleAttach = async (req: any, res: any) => routeHandler({ @@ -20,8 +21,6 @@ export const handleAttach = async (req: any, res: any) => const attachBody = AttachBodySchema.parse(req.body); - const logger = req.logtail; - const { attachParams, customPrices, customEnts } = await getAttachParams({ req, attachBody, diff --git a/server/src/internal/customers/cusCache/cusCacheUtils.ts b/server/src/internal/customers/cusCache/cusCacheUtils.ts new file mode 100644 index 000000000..c0fc22e7a --- /dev/null +++ b/server/src/internal/customers/cusCache/cusCacheUtils.ts @@ -0,0 +1,17 @@ +export const buildBaseCusCacheKey = ({ + idOrInternalId, + entityId, + orgId, + env, +}: { + idOrInternalId: string; + entityId?: string; + orgId: string; + env: string; +}) => { + if (entityId) { + return `customer:${idOrInternalId}_${orgId}_${env}:${entityId}`; + } else { + return `customer:${idOrInternalId}_${orgId}_${env}`; + } +}; diff --git a/server/src/internal/customers/cusCache/getCusWithCache.ts b/server/src/internal/customers/cusCache/getCusWithCache.ts new file mode 100644 index 000000000..33ef70027 --- /dev/null +++ b/server/src/internal/customers/cusCache/getCusWithCache.ts @@ -0,0 +1,83 @@ +import { AppEnv, CusExpand, EntityExpand, FullCustomer } from "@autumn/shared"; +import { ACTIVE_STATUSES } from "../cusProducts/CusProductService.js"; +import { db } from "@/db/initDrizzle.js"; +import { CusService } from "../CusService.js"; +import { buildBaseCusCacheKey } from "./cusCacheUtils.js"; +import { initUpstash } from "./upstashUtils.js"; + +export const getCusWithCache = async ({ + idOrInternalId, + orgId, + env, + entityId, + expand = [], + allowNotFound = true, + skipCache = false, + skipGet = false, +}: { + idOrInternalId: string; + orgId: string; + env: AppEnv; + entityId?: string; + + // Optional + expand?: (CusExpand | EntityExpand)[]; + allowNotFound?: boolean; + skipCache?: boolean; + skipGet?: boolean; +}): Promise => { + const statuses = ACTIVE_STATUSES; + const withEntities = true; + const withSubs = true; + + const upstash = await initUpstash(); + if (!upstash) skipCache = true; + + const baseKey = buildBaseCusCacheKey({ + idOrInternalId, + orgId, + env, + entityId, + }); + + const cacheKey = `${baseKey}:${expand.join(",")}`; + if (!skipCache && !skipGet) { + try { + const cached = await upstash!.get(cacheKey); + if (cached) { + console.log(`Cache hit: ${cacheKey}`); + return cached as FullCustomer; + } else { + console.log(`Cache miss: ${cacheKey}`); + } + } catch (error) { + console.error(error); + } + } + + const customer = await CusService.getFull({ + db, + idOrInternalId, + orgId, + env, + inStatuses: statuses, + withEntities, + withSubs, + allowNotFound, + expand, + entityId, + }); + + if (entityId && !customer.entity) skipCache = true; + + if (!skipCache) { + try { + await upstash!.set(cacheKey, customer); + await upstash!.expire(cacheKey, 1000); // Expire after 60 seconds + } catch (error) { + console.error(error); + } + } + + return customer; +}; diff --git a/server/src/internal/customers/cusCache/updateCachedCus.ts b/server/src/internal/customers/cusCache/updateCachedCus.ts new file mode 100644 index 000000000..11170eca9 --- /dev/null +++ b/server/src/internal/customers/cusCache/updateCachedCus.ts @@ -0,0 +1,43 @@ +import { CusExpand, FullCusEntWithFullCusProduct } from "@autumn/shared"; +import { AppEnv } from "autumn-js"; +import { buildBaseCusCacheKey } from "./cusCacheUtils.js"; +import { getCusWithCache } from "./getCusWithCache.js"; +import { initUpstash } from "./upstashUtils.js"; + +export const refreshCusCache = async ({ + customerId, + entityId, + orgId, + env, +}: { + customerId: string; + entityId?: string; + orgId: string; + env: AppEnv; +}) => { + const upstash = await initUpstash(); + if (!upstash) return; + + const baseKey = buildBaseCusCacheKey({ + idOrInternalId: customerId, + orgId, + env, + }); + + const list = await upstash.keys(`${baseKey}:*`); + + for (const key of list) { + const keyName = key; + let params = keyName.split(":"); + let expand = params ? params[params.length - 1].split(",") : []; + + await getCusWithCache({ + idOrInternalId: customerId, + orgId, + env, + expand: expand as CusExpand[], + skipGet: true, + }); + console.log(`updated cache key: ${keyName}`); + } +}; diff --git a/server/src/internal/customers/cusCache/upstashUtils.ts b/server/src/internal/customers/cusCache/upstashUtils.ts new file mode 100644 index 000000000..ca8f9e53e --- /dev/null +++ b/server/src/internal/customers/cusCache/upstashUtils.ts @@ -0,0 +1,13 @@ +import "dotenv/config"; +import { Redis } from "@upstash/redis"; + +export const initUpstash = async () => { + if (!process.env.UPSTASH_TOKEN) { + return null; + } + + return new Redis({ + url: "https://divine-sheepdog-46319.upstash.io", + token: process.env.UPSTASH_TOKEN, + }); +}; diff --git a/server/src/internal/customers/handlers/cusDeleteHandlers.ts b/server/src/internal/customers/handlers/cusDeleteHandlers.ts index 75a93b6bc..c74ed8a5c 100644 --- a/server/src/internal/customers/handlers/cusDeleteHandlers.ts +++ b/server/src/internal/customers/handlers/cusDeleteHandlers.ts @@ -7,6 +7,7 @@ import { ExtendedRequest, ExtendedResponse } from "@/utils/models/Request.js"; import { routeHandler } from "@/utils/routerUtils.js"; import { AppEnv, ErrCode, Organization } from "@autumn/shared"; import { StatusCodes } from "http-status-codes"; +import { refreshCusCache } from "../cusCache/updateCachedCus.js"; export const deleteCusById = async ({ db, @@ -55,7 +56,7 @@ export const deleteCusById = async ({ `Couldn't delete ${chalk.yellow("stripe customer")} ${ customer.processor.id }`, - error?.message || error, + error?.message || error ); } } @@ -90,6 +91,12 @@ export const handleDeleteCustomer = async (req: any, res: any) => deleteInStripe: req.query.delete_in_stripe === "true", }); + await refreshCusCache({ + customerId: req.params.customer_id, + orgId: org.id, + env, + }); + res.status(200).json(data); }, }); diff --git a/server/src/internal/customers/handlers/handleGetCustomer.ts b/server/src/internal/customers/handlers/handleGetCustomer.ts index 09a7e0f56..21a5d1c9b 100644 --- a/server/src/internal/customers/handlers/handleGetCustomer.ts +++ b/server/src/internal/customers/handlers/handleGetCustomer.ts @@ -10,6 +10,7 @@ import { StatusCodes } from "http-status-codes"; import { getCustomerDetails } from "../cusUtils/getCustomerDetails.js"; import { parseCusExpand } from "../cusUtils/cusUtils.js"; import { orgToVersion } from "@/utils/versionUtils.js"; +import { getCusWithCache } from "../cusCache/getCusWithCache.js"; export const handleGetCustomer = async (req: any, res: any) => routeHandler({ @@ -35,21 +36,23 @@ export const handleGetCustomer = async (req: any, res: any) => logger.info(`getting customer ${customerId} for org ${org.slug}`); const startTime = Date.now(); - const customer = await CusService.getFull({ - db, + const customer = await getCusWithCache({ idOrInternalId: customerId, orgId: org.id, - env: env, - inStatuses: [ - CusProductStatus.Active, - CusProductStatus.PastDue, - CusProductStatus.Scheduled, - ], - withEntities: true, + env, expand: expandArray, allowNotFound: true, - withSubs: true, }); + // const customer = await CusService.getFull({ + // db, + // idOrInternalId: customerId, + // orgId: org.id, + // env: env, + // withEntities: true, + // expand: expandArray, + // allowNotFound: true, + // withSubs: true, + // }); logger.info(`get customer took ${Date.now() - startTime}ms`); if (!customer) { diff --git a/server/src/internal/customers/handlers/handleUpdateBalances.ts b/server/src/internal/customers/handlers/handleUpdateBalances.ts index a5e630469..353ce7ce8 100644 --- a/server/src/internal/customers/handlers/handleUpdateBalances.ts +++ b/server/src/internal/customers/handlers/handleUpdateBalances.ts @@ -19,6 +19,7 @@ import { } from "@/internal/customers/cusProducts/cusEnts/cusEntUtils.js"; import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js"; import { notNullish } from "@/utils/genUtils.js"; +import { refreshCusCache } from "../cusCache/updateCachedCus.js"; const getCusFeaturesAndOrg = async (req: any, customerId: string) => { // 1. Get customer @@ -63,7 +64,7 @@ export const handleUpdateBalances = async (req: any, res: any) => { const { customer, org } = await getCusFeaturesAndOrg(req, cusId); const featuresToUpdate = features.filter((f: any) => - balances.map((b: any) => b.feature_id).includes(f.id), + balances.map((b: any) => b.feature_id).includes(f.id) ); if (featuresToUpdate.length === 0) { @@ -82,13 +83,12 @@ export const handleUpdateBalances = async (req: any, res: any) => { logger.info("--------------------------------"); logger.info( - `REQUEST: UPDATE BALANCES FOR CUSTOMER ${customer.id}, ORG: ${org.slug}`, + `REQUEST: UPDATE BALANCES FOR CUSTOMER ${customer.id}, ORG: ${org.slug}` ); logger.info( `Features to update: ${balances.map( - (b: any) => - `${b.feature_id} - ${b.unlimited ? "unlimited" : b.balance}`, - )}`, + (b: any) => `${b.feature_id} - ${b.unlimited ? "unlimited" : b.balance}` + )}` ); // Get deductions for each feature @@ -111,7 +111,7 @@ export const handleUpdateBalances = async (req: any, res: any) => { } const feature = featuresToUpdate.find( - (f: any) => f.id === balance.feature_id, + (f: any) => f.id === balance.feature_id ); if (balance.unlimited === true) { @@ -191,18 +191,17 @@ export const handleUpdateBalances = async (req: any, res: any) => { ? cusEnts.find( (cusEnt) => cusEnt.internal_feature_id === feature!.internal_id! && - cusEnt.entitlement.interval === interval, + cusEnt.entitlement.interval === interval ) : cusEnts.find( - (cusEnt) => - cusEnt.internal_feature_id === feature!.internal_id!, + (cusEnt) => cusEnt.internal_feature_id === feature!.internal_id! ); if (!cusEnt) { logger.warn( `No active cus ent to set unlimited balance for feature: ${ feature!.id - }`, + }` ); return; } @@ -265,6 +264,13 @@ export const handleUpdateBalances = async (req: any, res: any) => { batchDeduct.push(performDeduction()); } await Promise.all(batchDeduct); + + await refreshCusCache({ + customerId: cusId, + orgId: org.id, + env, + }); + logger.info(" ✅ Successfully updated balances"); res.status(200).json({ success: true }); diff --git a/server/src/internal/customers/handlers/handleUpdateCustomer.ts b/server/src/internal/customers/handlers/handleUpdateCustomer.ts index e0db74d43..fbe7f44dd 100644 --- a/server/src/internal/customers/handlers/handleUpdateCustomer.ts +++ b/server/src/internal/customers/handlers/handleUpdateCustomer.ts @@ -11,6 +11,7 @@ import { parseCusExpand } from "../cusUtils/cusUtils.js"; import { FeatureService } from "@/internal/features/FeatureService.js"; import { ExtendedResponse } from "@/utils/models/Request.js"; import { ExtendedRequest } from "@/utils/models/Request.js"; +import { refreshCusCache } from "../cusCache/updateCachedCus.js"; export const handleUpdateCustomer = async (req: any, res: any) => routeHandler({ @@ -97,7 +98,7 @@ export const handleUpdateCustomer = async (req: any, res: any) => const stripeCli = createStripeCli({ org, env: req.env }); await stripeCli.customers.update( originalCustomer.processor.id, - stripeUpdate as any, + stripeUpdate as any ); } @@ -134,6 +135,12 @@ export const handleUpdateCustomer = async (req: any, res: any) => reqApiVersion: req.apiVersion, }); + await refreshCusCache({ + customerId, + orgId, + env, + }); + res.status(200).json(customerDetails); }, }); diff --git a/server/src/internal/customers/handlers/handleUpdateEntitlement.ts b/server/src/internal/customers/handlers/handleUpdateEntitlement.ts index 09806c851..099f44c2b 100644 --- a/server/src/internal/customers/handlers/handleUpdateEntitlement.ts +++ b/server/src/internal/customers/handlers/handleUpdateEntitlement.ts @@ -14,6 +14,7 @@ import { performDeductionOnCusEnt } from "@/trigger/updateBalanceTask.js"; import { ExtendedRequest } from "@/utils/models/Request.js"; import { DrizzleCli } from "@/db/initDrizzle.js"; import { CusProductService } from "../cusProducts/CusProductService.js"; +import { refreshCusCache } from "../cusCache/updateCachedCus.js"; const getCusOrgAndCusPrice = async ({ db, diff --git a/server/src/internal/entities/handlers/handleCreateEntity/handleCreateEntity.ts b/server/src/internal/entities/handlers/handleCreateEntity/handleCreateEntity.ts index 35430c0aa..cdd72bc83 100644 --- a/server/src/internal/entities/handlers/handleCreateEntity/handleCreateEntity.ts +++ b/server/src/internal/entities/handlers/handleCreateEntity/handleCreateEntity.ts @@ -56,7 +56,7 @@ export const createEntities = async ({ internalCustomerId: customer.internal_id, orgId: org.id, env, - }), + }) ); let newEntities: Entity[] = []; diff --git a/server/src/middleware/refreshCacheMiddleware.ts b/server/src/middleware/refreshCacheMiddleware.ts new file mode 100644 index 000000000..254479fa9 --- /dev/null +++ b/server/src/middleware/refreshCacheMiddleware.ts @@ -0,0 +1,47 @@ +const urls = [ + { + method: "POST", + url: "/customers/:customer_id", + }, + { + method: "DELETE", + url: "/customers/:customer_id", + }, + { + method: "POST", + url: "/customers/:customer_id/balances", + }, + { + method: "POST", + url: "/customers/customer_entitlements/:customer_entitlement_id", + }, + { + method: "POST", + url: "/customers/:customer_id/balances", + }, + { + method: "POST", + url: "/customers/:customer_id/coupons/:coupon_id", + }, + { + method: "POST", + url: "/customers/:customer_id/entities", + }, + { + method: "POST", + url: "/customers/:customer_id/transfer_product", + }, + { + method: "POST", + url: "/attach", + }, +]; +export const refreshCacheMiddleware = async (req: any, res: any, next: any) => { + res.on("finish", async () => { + console.log("URL:", req.originalUrl); + console.log("METHOD:", req.method); + console.log("--------------------------------"); + }); + + next(); +}; diff --git a/server/src/trigger/updateBalanceTask.ts b/server/src/trigger/updateBalanceTask.ts index 56f03c9b6..c71a3177e 100644 --- a/server/src/trigger/updateBalanceTask.ts +++ b/server/src/trigger/updateBalanceTask.ts @@ -42,6 +42,7 @@ import { getBillingType, getEntOptions, } from "@/internal/products/prices/priceUtils.js"; +import { refreshCusCache } from "@/internal/customers/cusCache/updateCachedCus.js"; // Decimal.set({ precision: 12 }); // 12 DP precision @@ -67,7 +68,7 @@ const getFeatureDeductions = ({ features: Feature[]; }) => { const meteredFeatures = features.filter( - (feature) => feature.type === FeatureType.Metered, + (feature) => feature.type === FeatureType.Metered ); const featureDeductions = []; for (const feature of features) { @@ -86,7 +87,7 @@ const getFeatureDeductions = ({ let unlimitedExists = cusEnts.some( (cusEnt) => cusEnt.entitlement.allowance_type === AllowanceType.Unlimited && - cusEnt.entitlement.internal_feature_id == feature.internal_id, + cusEnt.entitlement.internal_feature_id == feature.internal_id ); if (unlimitedExists || !deduction) { @@ -142,7 +143,7 @@ export const logBalanceUpdate = ({ console.log( ` - Customer: ${customer.id} (${customer.env}) | Org: ${ org.slug - } | Features: ${features.map((f) => f.id).join(", ")}`, + } | Features: ${features.map((f) => f.id).join(", ")}` ); console.log(" - Properties:", properties); console.log( @@ -152,7 +153,7 @@ export const logBalanceUpdate = ({ if (notNullish(cusEnt.entitlement.entity_feature_id)) { console.log( - ` - Entity feature ID found for feature: ${cusEnt.feature_id}`, + ` - Entity feature ID found for feature: ${cusEnt.feature_id}` ); if (notNullish(entityId)) { @@ -179,7 +180,7 @@ export const logBalanceUpdate = ({ })`; }), "| Deductions:", - featureDeductions.map((f: any) => `${f.feature.id}: ${f.deduction}`), + featureDeductions.map((f: any) => `${f.feature.id}: ${f.deduction}`) ); }; @@ -462,7 +463,7 @@ export const deductFromUsageBasedCusEnt = async ({ if (!usageBasedEnt) { console.log( - ` - Feature ${feature.id}, To deduct: ${toDeduct} -> no usage-based entitlement found`, + ` - Feature ${feature.id}, To deduct: ${toDeduct} -> no usage-based entitlement found` ); return; } @@ -659,7 +660,7 @@ export const runUpdateBalanceTask = async ({ console.log("--------------------------------"); console.log( - `UPDATING BALANCE FOR CUSTOMER (${customerId}), ORG: ${org.slug}`, + `UPDATING BALANCE FOR CUSTOMER (${customerId}), ORG: ${org.slug}` ); const cusEnts: any = await updateCustomerBalance({ @@ -673,6 +674,13 @@ export const runUpdateBalanceTask = async ({ entityId, }); + await refreshCusCache({ + customerId, + orgId: org.id, + env, + entityId, + }); + if (!cusEnts || cusEnts.length === 0) { return; } diff --git a/server/src/trigger/updateUsageTask.ts b/server/src/trigger/updateUsageTask.ts index 0bbe634f3..d3cd4ce69 100644 --- a/server/src/trigger/updateUsageTask.ts +++ b/server/src/trigger/updateUsageTask.ts @@ -20,6 +20,7 @@ import { } from "./updateBalanceTask.js"; import { CusService } from "@/internal/customers/CusService.js"; import { DrizzleCli } from "@/db/initDrizzle.js"; +import { refreshCusCache } from "@/internal/customers/cusCache/updateCachedCus.js"; // 2. Get deductions for each feature const getFeatureDeductions = ({ @@ -42,7 +43,7 @@ const getFeatureDeductions = ({ let unlimitedExists = cusEnts.some( (cusEnt) => cusEnt.entitlement.allowance_type === AllowanceType.Unlimited && - cusEnt.entitlement.internal_feature_id == feature.internal_id, + cusEnt.entitlement.internal_feature_id == feature.internal_id ); if (unlimitedExists) { @@ -129,7 +130,7 @@ const logUsageUpdate = ({ org.slug } | Features: ${features.map((f) => f.id).join(", ")} | Set Usage: ${ setUsage ? "true" : "false" - }`, + }` ); console.log( @@ -153,7 +154,7 @@ const logUsageUpdate = ({ })`; }), "| Deductions:", - featureDeductions.map((f: any) => `${f.feature.id}: ${f.deduction}`), + featureDeductions.map((f: any) => `${f.feature.id}: ${f.deduction}`) ); }; @@ -300,7 +301,7 @@ export const runUpdateUsageTask = async ({ console.log("--------------------------------"); console.log( - `HANDLING USAGE TASK FOR CUSTOMER (${customerId}), ORG: ${org.slug}`, + `HANDLING USAGE TASK FOR CUSTOMER (${customerId}), ORG: ${org.slug}` ); const cusEnts: any = await updateUsage({ @@ -316,6 +317,13 @@ export const runUpdateUsageTask = async ({ entityId, }); + await refreshCusCache({ + customerId, + entityId, + orgId: org.id, + env, + }); + if (!cusEnts || cusEnts.length === 0) { return; }