Skip to content

Commit 1d5e8d4

Browse files
bchapuisclaude
andcommitted
Fold userPlan into getOrganizationBillingInfo as a single query
Instead of a separate resolveOrganizationPlan call, embed a scalar subquery in getOrganizationBillingInfo that checks if any org member has plan "pro". All trigger paths now read billingInfo.userPlan with no extra round trip. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 593719c commit 1d5e8d4

7 files changed

Lines changed: 18 additions & 37 deletions

File tree

apps/api/src/db/queries.ts

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as crypto from "crypto";
2-
import { and, eq } from "drizzle-orm";
2+
import { and, eq, sql } from "drizzle-orm";
33
import { v7 as uuidv7 } from "uuid";
44

55
import { TRIAL_CREDITS } from "../constants/billing";
@@ -2128,7 +2128,8 @@ export async function getOrganizationComputeCredits(
21282128
}
21292129

21302130
/**
2131-
* Get organization billing info for workflow execution
2131+
* Get organization billing info for workflow execution.
2132+
* Includes the best user plan in the org (pro > trial) via a single query.
21322133
*/
21332134
export async function getOrganizationBillingInfo(
21342135
db: ReturnType<typeof createDatabase>,
@@ -2139,6 +2140,7 @@ export async function getOrganizationBillingInfo(
21392140
subscriptionStatus: string | null;
21402141
currentPeriodEnd: Date | null;
21412142
overageLimit: number | null;
2143+
userPlan: string;
21422144
}
21432145
| undefined
21442146
> {
@@ -2148,32 +2150,20 @@ export async function getOrganizationBillingInfo(
21482150
subscriptionStatus: organizations.subscriptionStatus,
21492151
currentPeriodEnd: organizations.currentPeriodEnd,
21502152
overageLimit: organizations.overageLimit,
2153+
userPlan: sql<string>`coalesce(
2154+
(select ${users.plan} from ${users}
2155+
where ${users.organizationId} = ${organizations.id}
2156+
and ${users.plan} = ${Plan.PRO}
2157+
limit 1),
2158+
${Plan.TRIAL}
2159+
)`.as("user_plan"),
21512160
})
21522161
.from(organizations)
21532162
.where(eq(organizations.id, organizationId))
21542163
.limit(1);
21552164
return organization;
21562165
}
21572166

2158-
/**
2159-
* Resolve user plan for an organization by checking its members.
2160-
* Used by automated triggers where no specific user context is available.
2161-
* Returns "pro" if any member has a pro plan, "trial" otherwise.
2162-
*/
2163-
export async function resolveOrganizationPlan(
2164-
db: ReturnType<typeof createDatabase>,
2165-
organizationId: string
2166-
): Promise<string> {
2167-
const [user] = await db
2168-
.select({ plan: users.plan })
2169-
.from(users)
2170-
.where(
2171-
and(eq(users.organizationId, organizationId), eq(users.plan, Plan.PRO))
2172-
)
2173-
.limit(1);
2174-
return user?.plan ?? Plan.TRIAL;
2175-
}
2176-
21772167
/**
21782168
* Create a new secret for an organization
21792169
*

apps/api/src/email.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import type { Workflow } from "@dafthunk/types";
22

33
import type { Bindings } from "./context";
4-
import {
5-
createDatabase,
6-
getOrganizationBillingInfo,
7-
resolveOrganizationPlan,
8-
} from "./db";
4+
import { createDatabase, getOrganizationBillingInfo } from "./db";
95
import { getAgentByName } from "./durable-objects/agent-utils";
106
import { createWorkerRuntime } from "./runtime/cloudflare-worker-runtime";
117
import { WorkflowStore } from "./stores/workflow-store";
@@ -179,7 +175,7 @@ async function triggerWorkflowForEmail({
179175
userId: "email_trigger",
180176
organizationId,
181177
computeCredits: billingInfo.computeCredits,
182-
userPlan: await resolveOrganizationPlan(db, organizationId),
178+
userPlan: billingInfo.userPlan,
183179
workflow: {
184180
id: workflow.id,
185181
name: workflow.name,

apps/api/src/queue.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { createDatabase } from "./db";
44
import {
55
getOrganizationBillingInfo,
66
getQueueTriggersByQueue,
7-
resolveOrganizationPlan,
87
} from "./db/queries";
98
import { getAgentByName } from "./durable-objects/agent-utils";
109
import { createWorkerRuntime } from "./runtime/cloudflare-worker-runtime";
@@ -42,7 +41,7 @@ async function executeWorkflow(
4241
userId: "queue_trigger",
4342
organizationId: workflowInfo.organizationId,
4443
computeCredits: billingInfo.computeCredits,
45-
userPlan: await resolveOrganizationPlan(db, workflowInfo.organizationId),
44+
userPlan: billingInfo.userPlan,
4645
workflow: {
4746
id: workflowInfo.id,
4847
name: workflowData.name,

apps/api/src/routes/discord-webhook.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
getDiscordBotById,
99
getDiscordTriggersByBot,
1010
getOrganizationBillingInfo,
11-
resolveOrganizationPlan,
1211
} from "../db";
1312
import { getAgentByName } from "../durable-objects/agent-utils";
1413
import { createWorkerRuntime } from "../runtime/cloudflare-worker-runtime";
@@ -299,7 +298,7 @@ async function executeWorkflow(
299298
userId: "discord_trigger",
300299
organizationId,
301300
computeCredits: billingInfo.computeCredits,
302-
userPlan: await resolveOrganizationPlan(db, organizationId),
301+
userPlan: billingInfo.userPlan,
303302
workflow: {
304303
id: workflow.id,
305304
name: workflow.name,

apps/api/src/routes/telegram-webhook.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
getTelegramBot,
99
getTelegramSecretTokenByBot,
1010
getTelegramTriggersByBot,
11-
resolveOrganizationPlan,
1211
} from "../db";
1312
import { getAgentByName } from "../durable-objects/agent-utils";
1413
import { createWorkerRuntime } from "../runtime/cloudflare-worker-runtime";
@@ -216,7 +215,7 @@ async function executeWorkflow(
216215
userId: "telegram_trigger",
217216
organizationId,
218217
computeCredits: billingInfo.computeCredits,
219-
userPlan: await resolveOrganizationPlan(db, organizationId),
218+
userPlan: billingInfo.userPlan,
220219
workflow: {
221220
id: workflow.id,
222221
name: workflow.name,

apps/api/src/routes/whatsapp-webhook.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
getWhatsAppAccount,
99
getWhatsAppTriggersByAccount,
1010
getWhatsAppVerifyTokenByAccount,
11-
resolveOrganizationPlan,
1211
} from "../db";
1312
import { getAgentByName } from "../durable-objects/agent-utils";
1413
import { createWorkerRuntime } from "../runtime/cloudflare-worker-runtime";
@@ -322,7 +321,7 @@ async function executeWorkflow(
322321
userId: "whatsapp_trigger",
323322
organizationId,
324323
computeCredits: billingInfo.computeCredits,
325-
userPlan: await resolveOrganizationPlan(db, organizationId),
324+
userPlan: billingInfo.userPlan,
326325
workflow: {
327326
id: workflow.id,
328327
name: workflow.name,

apps/api/src/scheduled.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
createDatabase,
66
getActiveScheduledTriggers,
77
getOrganizationBillingInfo,
8-
resolveOrganizationPlan,
98
} from "./db";
109
import { getAgentByName } from "./durable-objects/agent-utils";
1110
import { createWorkerRuntime } from "./runtime/cloudflare-worker-runtime";
@@ -74,7 +73,7 @@ export async function handleScheduledEvent(
7473
userId: "scheduled_trigger",
7574
organizationId: workflow.organizationId,
7675
computeCredits: billingInfo.computeCredits,
77-
userPlan: await resolveOrganizationPlan(db, workflow.organizationId),
76+
userPlan: billingInfo.userPlan,
7877
workflow: {
7978
id: workflow.id,
8079
name: workflow.name,

0 commit comments

Comments
 (0)