From 0e3e2416461a0ce2c0ac445421e11d980239d552 Mon Sep 17 00:00:00 2001 From: amianthus <49116958+SirTenzin@users.noreply.github.com> Date: Mon, 2 Mar 2026 13:15:29 +0000 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=F0=9F=90=9B=20trials=5Fused=20not?= =?UTF-8?q?=20properly=20bypassing=20cache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apiCusUtils/getApiCustomerExpand.ts | 28 ++++---- .../cusResponseUtils/getCusTrialsUsed.ts | 65 +++++++++++++++++++ 2 files changed, 76 insertions(+), 17 deletions(-) create mode 100644 server/src/internal/customers/cusUtils/cusResponseUtils/getCusTrialsUsed.ts diff --git a/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerExpand.ts b/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerExpand.ts index 4c5cf1e54..352125613 100644 --- a/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerExpand.ts +++ b/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerExpand.ts @@ -11,6 +11,7 @@ import { CusService } from "../../CusService.js"; import { getCusPaymentMethodRes } from "../cusResponseUtils/getCusPaymentMethodRes.js"; import { getCusReferrals } from "../cusResponseUtils/getCusReferrals.js"; import { getCusRewards } from "../cusResponseUtils/getCusRewards.js"; +import { getCusTrialsUsed } from "../cusResponseUtils/getCusTrialsUsed.js"; export const getApiCustomerExpand = async ({ ctx, @@ -21,7 +22,7 @@ export const getApiCustomerExpand = async ({ customerId?: string; fullCus?: FullCustomer; }): Promise => { - const { org, env, db, logger, expand } = ctx; + const { org, env, db, expand } = ctx; // Filter out balances.feature and subscriptions.plan const filteredExpand = filterExpand({ @@ -45,19 +46,6 @@ export const getApiCustomerExpand = async ({ }); } - const getCusTrialsUsed = () => { - if (expand.includes(CustomerExpand.TrialsUsed)) { - return ( - fullCus.trials_used?.map((t) => ({ - plan_id: t.product_id, - customer_id: t.customer_id, - fingerprint: t.fingerprint, - })) ?? [] - ); - } - return undefined; - }; - const getApiCusEntities = () => { if (expand.includes(CustomerExpand.Entities)) { return fullCus.entities.map((e) => ApiBaseEntitySchema.parse(e)); @@ -67,7 +55,7 @@ export const getApiCustomerExpand = async ({ const cusExpand = expand as CustomerExpand[]; - const [rewards, referrals, paymentMethod] = await Promise.all([ + const [rewards, referrals, paymentMethod, trialsUsed] = await Promise.all([ getCusRewards({ org, env, @@ -77,7 +65,6 @@ export const getApiCustomerExpand = async ({ ), expand: cusExpand, }), - getCusReferrals({ db, fullCus, @@ -89,10 +76,17 @@ export const getApiCustomerExpand = async ({ fullCus, expand: cusExpand, }), + getCusTrialsUsed({ + db, + fullCus, + orgId: org.id, + env, + expand: cusExpand, + }), ]); return { - trials_used: getCusTrialsUsed() ?? undefined, + trials_used: trialsUsed ?? undefined, entities: getApiCusEntities() ?? undefined, rewards: rewards ?? undefined, // upcoming_invoice: upcomingInvoice, diff --git a/server/src/internal/customers/cusUtils/cusResponseUtils/getCusTrialsUsed.ts b/server/src/internal/customers/cusUtils/cusResponseUtils/getCusTrialsUsed.ts new file mode 100644 index 000000000..b6713cb13 --- /dev/null +++ b/server/src/internal/customers/cusUtils/cusResponseUtils/getCusTrialsUsed.ts @@ -0,0 +1,65 @@ +import { + type AppEnv, + CustomerExpand, + customerProducts, + customers, + type FullCustomer, + products, +} from "@autumn/shared"; +import { and, eq, isNotNull, or } from "drizzle-orm"; +import type { DrizzleCli } from "@/db/initDrizzle.js"; + +export const getCusTrialsUsed = async ({ + db, + fullCus, + orgId, + env, + expand, +}: { + db: DrizzleCli; + fullCus: FullCustomer; + orgId: string; + env: AppEnv; + expand?: CustomerExpand[]; +}) => { + if (!expand?.includes(CustomerExpand.TrialsUsed)) { + return undefined; + } + + const rows = await db + .select({ + plan_id: products.id, + customer_id: customers.id, + fingerprint: customers.fingerprint, + }) + .from(customerProducts) + .innerJoin( + products, + eq(customerProducts.internal_product_id, products.internal_id), + ) + .innerJoin( + customers, + eq(customerProducts.internal_customer_id, customers.internal_id), + ) + .where( + and( + or( + eq(customers.id, fullCus.id ?? ""), + fullCus.fingerprint + ? eq(customers.fingerprint, fullCus.fingerprint) + : undefined, + ), + eq(products.org_id, orgId), + eq(products.env, env), + isNotNull(customerProducts.free_trial_id), + ), + ); + + return rows + .filter((r) => r.customer_id !== null) + .map((r) => ({ + plan_id: r.plan_id, + customer_id: r.customer_id as string, + fingerprint: r.fingerprint, + })); +}; From e20043c6e7e1bfdfe833bee5ac1ce42bcdf0fcc4 Mon Sep 17 00:00:00 2001 From: amianthus <49116958+SirTenzin@users.noreply.github.com> Date: Mon, 2 Mar 2026 13:44:12 +0000 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20move=20db=20call=20t?= =?UTF-8?q?o=20repos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repos/fetchCustomerProductFreeTrials.ts | 56 ++++++++++++++++++ .../customers/cusProducts/repos/index.ts | 2 + .../apiCusUtils/getApiCustomerExpand.ts | 4 +- .../cusResponseUtils/getCusTrialsUsed.ts | 58 ++----------------- 4 files changed, 65 insertions(+), 55 deletions(-) create mode 100644 server/src/internal/customers/cusProducts/repos/fetchCustomerProductFreeTrials.ts diff --git a/server/src/internal/customers/cusProducts/repos/fetchCustomerProductFreeTrials.ts b/server/src/internal/customers/cusProducts/repos/fetchCustomerProductFreeTrials.ts new file mode 100644 index 000000000..a307f67d2 --- /dev/null +++ b/server/src/internal/customers/cusProducts/repos/fetchCustomerProductFreeTrials.ts @@ -0,0 +1,56 @@ +import { + customerProducts, + customers, + type FullCustomer, + products, +} from "@autumn/shared"; +import { and, eq, isNotNull, or } from "drizzle-orm"; +import type { RepoContext } from "@/db/repoContext.js"; + +/** Fetch all customer_products rows with a free trial for a given customer (or matching fingerprint). */ +export const fetchCustomerProductFreeTrials = async ({ + ctx, + fullCus, +}: { + ctx: RepoContext; + fullCus: FullCustomer; +}) => { + const { db, org, env } = ctx; + + const rows = await db + .select({ + plan_id: products.id, + customer_id: customers.id, + fingerprint: customers.fingerprint, + }) + .from(customerProducts) + .innerJoin( + products, + eq(customerProducts.internal_product_id, products.internal_id), + ) + .innerJoin( + customers, + eq(customerProducts.internal_customer_id, customers.internal_id), + ) + .where( + and( + or( + eq(customers.id, fullCus.id ?? ""), + fullCus.fingerprint + ? eq(customers.fingerprint, fullCus.fingerprint) + : undefined, + ), + eq(products.org_id, org.id), + eq(products.env, env), + isNotNull(customerProducts.trial_ends_at), + ), + ); + + return rows + .filter((r) => r.customer_id !== null) + .map((r) => ({ + plan_id: r.plan_id, + customer_id: r.customer_id as string, + fingerprint: r.fingerprint, + })); +}; diff --git a/server/src/internal/customers/cusProducts/repos/index.ts b/server/src/internal/customers/cusProducts/repos/index.ts index 338b6637b..967be5935 100644 --- a/server/src/internal/customers/cusProducts/repos/index.ts +++ b/server/src/internal/customers/cusProducts/repos/index.ts @@ -1,5 +1,7 @@ import { batchUpdateCustomerProducts } from "./batchUpdateCustomerProducts"; +import { fetchCustomerProductFreeTrials } from "./fetchCustomerProductFreeTrials"; export const customerProductRepo = { batchUpdate: batchUpdateCustomerProducts, + fetchFreeTrials: fetchCustomerProductFreeTrials, }; diff --git a/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerExpand.ts b/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerExpand.ts index 352125613..909f6a4cd 100644 --- a/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerExpand.ts +++ b/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerExpand.ts @@ -77,10 +77,8 @@ export const getApiCustomerExpand = async ({ expand: cusExpand, }), getCusTrialsUsed({ - db, + ctx, fullCus, - orgId: org.id, - env, expand: cusExpand, }), ]); diff --git a/server/src/internal/customers/cusUtils/cusResponseUtils/getCusTrialsUsed.ts b/server/src/internal/customers/cusUtils/cusResponseUtils/getCusTrialsUsed.ts index b6713cb13..383dc1f7e 100644 --- a/server/src/internal/customers/cusUtils/cusResponseUtils/getCusTrialsUsed.ts +++ b/server/src/internal/customers/cusUtils/cusResponseUtils/getCusTrialsUsed.ts @@ -1,65 +1,19 @@ -import { - type AppEnv, - CustomerExpand, - customerProducts, - customers, - type FullCustomer, - products, -} from "@autumn/shared"; -import { and, eq, isNotNull, or } from "drizzle-orm"; -import type { DrizzleCli } from "@/db/initDrizzle.js"; +import { CustomerExpand, type FullCustomer } from "@autumn/shared"; +import type { RepoContext } from "@/db/repoContext.js"; +import { customerProductRepo } from "../../cusProducts/repos/index.js"; export const getCusTrialsUsed = async ({ - db, + ctx, fullCus, - orgId, - env, expand, }: { - db: DrizzleCli; + ctx: RepoContext; fullCus: FullCustomer; - orgId: string; - env: AppEnv; expand?: CustomerExpand[]; }) => { if (!expand?.includes(CustomerExpand.TrialsUsed)) { return undefined; } - const rows = await db - .select({ - plan_id: products.id, - customer_id: customers.id, - fingerprint: customers.fingerprint, - }) - .from(customerProducts) - .innerJoin( - products, - eq(customerProducts.internal_product_id, products.internal_id), - ) - .innerJoin( - customers, - eq(customerProducts.internal_customer_id, customers.internal_id), - ) - .where( - and( - or( - eq(customers.id, fullCus.id ?? ""), - fullCus.fingerprint - ? eq(customers.fingerprint, fullCus.fingerprint) - : undefined, - ), - eq(products.org_id, orgId), - eq(products.env, env), - isNotNull(customerProducts.free_trial_id), - ), - ); - - return rows - .filter((r) => r.customer_id !== null) - .map((r) => ({ - plan_id: r.plan_id, - customer_id: r.customer_id as string, - fingerprint: r.fingerprint, - })); + return customerProductRepo.fetchFreeTrials({ ctx, fullCus }); }; From bba2b035c139ec266e8699db1751a176ca28db74 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Tue, 3 Mar 2026 10:14:50 +0000 Subject: [PATCH 3/3] updated free trial matching condition --- .../cusProducts/repos/fetchCustomerProductFreeTrials.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/internal/customers/cusProducts/repos/fetchCustomerProductFreeTrials.ts b/server/src/internal/customers/cusProducts/repos/fetchCustomerProductFreeTrials.ts index a307f67d2..1fa95ccac 100644 --- a/server/src/internal/customers/cusProducts/repos/fetchCustomerProductFreeTrials.ts +++ b/server/src/internal/customers/cusProducts/repos/fetchCustomerProductFreeTrials.ts @@ -35,7 +35,7 @@ export const fetchCustomerProductFreeTrials = async ({ .where( and( or( - eq(customers.id, fullCus.id ?? ""), + eq(customers.internal_id, fullCus.internal_id), fullCus.fingerprint ? eq(customers.fingerprint, fullCus.fingerprint) : undefined,