History

Remove unknown context casts from organization and billing handlers

Rhys Sullivan committed 9 months ago

Commit f46bd121 parent b5a42d64 files · +90 −101Browse files

Unified · Split

4 files
  1. executor/packages/database/src/billing/internal-handlers.ts +12 −13
  2. executor/packages/database/src/billing/public-handlers.ts +21 −23
  3. executor/packages/database/src/organizations/handlers.ts +32 −37
  4. executor/packages/database/src/workspaces/handlers.ts +25 −28
executor/packages/database/src/billing/internal-handlers.ts+12−13
@@ -2,6 +2,11 @@ import type { Doc, Id } from "../../convex/_generated/dataModel.d.ts"; · 1 unmodified line
22 import type { MutationCtx, QueryCtx } from "../../convex/_generated/server";
33
44 type DbCtx = Pick<QueryCtx, "db"> | Pick<MutationCtx, "db">;
5+type BillingAccessCtx = QueryCtx & {
6+ account: Doc<"accounts">;
7+ actorMembership: Doc<"organizationMembers">;
8+ organizationId: Id<"organizations">;
9+};
510
611 async function getBillableSeatCount(ctx: DbCtx, organizationId: Id<"organizations">): Promise<number> {
712 const members = await ctx.db
@@ -23,27 +28,21 @@ async function getSeatState( · 15 unmodified lines
2328 .unique();
2429 }
2530
26−export async function getBillingAccessForRequestHandler(ctx: unknown) {
27− const typedCtx = ctx as QueryCtx & {
28− account: Doc<"accounts">;
29− actorMembership: Doc<"organizationMembers">;
30− organizationId: Id<"organizations">;
31− };
32−
33− const organization = await typedCtx.db.get(typedCtx.organizationId);
31+export async function getBillingAccessForRequestHandler(ctx: BillingAccessCtx) {
32+ const organization = await ctx.db.get(ctx.organizationId);
3433 if (!organization) {
3534 return null;
3635 }
3736
38− const billableMembers = await getBillableSeatCount(typedCtx, typedCtx.organizationId);
39− const customer = await typedCtx.db
37+ const billableMembers = await getBillableSeatCount(ctx, ctx.organizationId);
38+ const customer = await ctx.db
4039 .query("billingCustomers")
41− .withIndex("by_org", (q) => q.eq("organizationId", typedCtx.organizationId))
40+ .withIndex("by_org", (q) => q.eq("organizationId", ctx.organizationId))
4241 .unique();
4342
4443 return {
45− role: typedCtx.actorMembership.role,
46− email: typedCtx.account.email,
44+ role: ctx.actorMembership.role,
45+ email: ctx.account.email,
4746 organizationName: organization.name,
4847 billableMembers,
4948 customerId: customer?.stripeCustomerId ?? null,
executor/packages/database/src/billing/public-handlers.ts+21−23
@@ -8,6 +8,14 @@ type Internal = typeof import("../../convex/_generated/api").internal; · 7 unmodified lines
88 type Components = typeof import("../../convex/_generated/api").components;
99
1010 type DbCtx = Pick<QueryCtx, "db"> | Pick<MutationCtx, "db">;
11+type BillingSummaryCtx = QueryCtx & {
12+ organizationId: Id<"organizations">;
13+ runQuery: QueryCtx["runQuery"];
14+};
15+type RetrySeatSyncCtx = MutationCtx & {
16+ organizationId: Id<"organizations">;
17+ scheduler: MutationCtx["scheduler"];
18+};
1119
1220 function canAccessBilling(role: string): boolean {
1321 return isAdminRole(role) || canManageBilling(role);
@@ -31,29 +39,24 @@ async function getSeatState(ctx: DbCtx, organizationId: Id<"organizations">) { · 17 unmodified lines
3139 }
3240
3341 export async function getBillingSummaryHandler(
34− ctx: unknown,
42+ ctx: BillingSummaryCtx,
3543 components: Components,
3644 ) {
37− const typedCtx = ctx as QueryCtx & {
38− organizationId: Id<"organizations">;
39− runQuery: QueryCtx["runQuery"];
40− };
41−
42− const billableMembers = await getBillableSeatCount(typedCtx, typedCtx.organizationId);
43− const seatState = await getSeatState(typedCtx, typedCtx.organizationId);
44− const customer = await typedCtx.db
45+ const billableMembers = await getBillableSeatCount(ctx, ctx.organizationId);
46+ const seatState = await getSeatState(ctx, ctx.organizationId);
47+ const customer = await ctx.db
4548 .query("billingCustomers")
46− .withIndex("by_org", (q) => q.eq("organizationId", typedCtx.organizationId))
49+ .withIndex("by_org", (q) => q.eq("organizationId", ctx.organizationId))
4750 .unique();
4851
49− const subscription = await typedCtx.runQuery(components.stripe.public.getSubscriptionByOrgId, {
50− orgId: String(typedCtx.organizationId),
52+ const subscription = await ctx.runQuery(components.stripe.public.getSubscriptionByOrgId, {
53+ orgId: String(ctx.organizationId),
5154 });
5255
5356 const syncStatus = seatState?.syncError ? "error" : seatState?.lastSyncAt ? "ok" : "pending";
5457
5558 return {
56− organizationId: String(typedCtx.organizationId),
59+ organizationId: String(ctx.organizationId),
5760 customer: customer
5861 ? {
5962 stripeCustomerId: customer.stripeCustomerId,
@@ -198,20 +201,15 @@ export async function createCustomerPortalHandler( · 138 unmodified lines
198201 }
199202
200203 export async function retrySeatSyncHandler(
201− ctx: unknown,
204+ ctx: RetrySeatSyncCtx,
202205 internal: Internal,
203206 ) {
204− const typedCtx = ctx as MutationCtx & {
205− organizationId: Id<"organizations">;
206− scheduler: MutationCtx["scheduler"];
207− };
208−
209− const nextVersion = await typedCtx.runMutation(internal.billingInternal.bumpSeatSyncVersion, {
210− organizationId: typedCtx.organizationId,
207+ const nextVersion = await ctx.runMutation(internal.billingInternal.bumpSeatSyncVersion, {
208+ organizationId: ctx.organizationId,
211209 });
212210
213− await safeRunAfter(typedCtx.scheduler, 0, internal.billingSync.syncSeatQuantity, {
214− organizationId: typedCtx.organizationId,
211+ await safeRunAfter(ctx.scheduler, 0, internal.billingSync.syncSeatQuantity, {
212+ organizationId: ctx.organizationId,
215213 expectedVersion: nextVersion,
216214 });
217215
executor/packages/database/src/organizations/handlers.ts+32−37
@@ -50,17 +50,16 @@ async function mapWorkspaceWithIcon( · 49 unmodified lines
5050 };
5151 }
5252
53−export async function createOrganizationHandler(ctx: unknown, args: { name: string }) {
54− const typedCtx = ctx as AuthedCtx;
55− const account = typedCtx.account;
53+export async function createOrganizationHandler(ctx: AuthedCtx, args: { name: string }) {
54+ const account = ctx.account;
5655 const name = args.name.trim();
5756 if (name.length < 2) {
5857 throw new Error("Organization name must be at least 2 characters");
5958 }
6059
6160 const now = Date.now();
62− const slug = await ensureUniqueOrganizationSlug(typedCtx, name);
63− const organizationId = await typedCtx.db.insert("organizations", {
61+ const slug = await ensureUniqueOrganizationSlug(ctx, name);
62+ const organizationId = await ctx.db.insert("organizations", {
6463 slug,
6564 name,
6665 status: "active",
@@ -69,7 +68,7 @@ export async function createOrganizationHandler(ctx: unknown, args: { name: stri · 2 unmodified lines
6968 updatedAt: now,
7069 });
7170
72− await upsertOrganizationMembership(typedCtx, {
71+ await upsertOrganizationMembership(ctx, {
7372 organizationId,
7473 accountId: account._id,
7574 role: "owner",
@@ -78,7 +77,7 @@ export async function createOrganizationHandler(ctx: unknown, args: { name: stri · 2 unmodified lines
7877 now,
7978 });
8079
81− const workspaceId = await typedCtx.db.insert("workspaces", {
80+ const workspaceId = await ctx.db.insert("workspaces", {
8281 organizationId,
8382 slug: "default",
8483 name: "Default Workspace",
@@ -87,15 +86,15 @@ export async function createOrganizationHandler(ctx: unknown, args: { name: stri · 2 unmodified lines
8786 updatedAt: now,
8887 });
8988
90− await seedWorkspaceMembersFromOrganization(typedCtx, {
89+ await seedWorkspaceMembersFromOrganization(ctx, {
9190 organizationId,
9291 workspaceId,
9392 now,
9493 mapRole: mapOrganizationRoleToWorkspaceRole,
9594 });
9695
97− const organization = await typedCtx.db.get(organizationId);
98− const workspace = await typedCtx.db.get(workspaceId);
96+ const organization = await ctx.db.get(organizationId);
97+ const workspace = await ctx.db.get(workspaceId);
9998 if (!organization || !workspace) {
10099 throw new Error("Failed to create organization");
101100 }
@@ -108,18 +107,17 @@ export async function createOrganizationHandler(ctx: unknown, args: { name: stri · 6 unmodified lines
108107 status: organization.status,
109108 createdAt: organization.createdAt,
110109 },
111− workspace: await mapWorkspaceWithIcon(typedCtx, workspace),
110+ workspace: await mapWorkspaceWithIcon(ctx, workspace),
112111 };
113112 }
114113
115−export async function listOrganizationsMineHandler(ctx: unknown) {
116− const typedCtx = ctx as OptionalAccountCtx;
117− const account = typedCtx.account;
114+export async function listOrganizationsMineHandler(ctx: OptionalAccountCtx) {
115+ const account = ctx.account;
118116 if (!account) {
119117 return [];
120118 }
121119
122− const memberships = await typedCtx.db
120+ const memberships = await ctx.db
123121 .query("organizationMembers")
124122 .withIndex("by_account", (q) => q.eq("accountId", account._id))
125123 .collect();
@@ -128,7 +126,7 @@ export async function listOrganizationsMineHandler(ctx: unknown) { · 2 unmodified lines
128126 memberships
129127 .filter((membership) => membership.status === "active")
130128 .map(async (membership) => {
131− const org = await typedCtx.db.get(membership.organizationId);
129+ const org = await ctx.db.get(membership.organizationId);
132130 if (!org) {
133131 return null;
134132 }
@@ -146,22 +144,21 @@ export async function listOrganizationsMineHandler(ctx: unknown) { · 11 unmodified lines
146144 return organizations.filter((org): org is NonNullable<typeof org> => org !== null);
147145 }
148146
149−export async function getNavigationStateHandler(ctx: unknown) {
150− const typedCtx = ctx as OptionalAccountCtx;
151− const account = typedCtx.account;
147+export async function getNavigationStateHandler(ctx: OptionalAccountCtx) {
148+ const account = ctx.account;
152149 const organizations: Array<{ id: Id<"organizations">; name: string; slug: string; status: string; role: string }> = [];
153150 const workspaces: WorkspaceSummary[] = [];
154151
155152 if (!account) {
156− if (typedCtx.sessionId) {
157− const anonymousSession = await typedCtx.db
153+ if (ctx.sessionId) {
154+ const anonymousSession = await ctx.db
158155 .query("anonymousSessions")
159− .withIndex("by_session_id", (q) => q.eq("sessionId", typedCtx.sessionId as string))
156+ .withIndex("by_session_id", (q) => q.eq("sessionId", ctx.sessionId))
160157 .unique();
161158 if (anonymousSession?.workspaceId) {
162− const workspace = await typedCtx.db.get(anonymousSession.workspaceId);
159+ const workspace = await ctx.db.get(anonymousSession.workspaceId);
163160 if (workspace) {
164− workspaces.push(await mapWorkspaceWithIcon(typedCtx, workspace));
161+ workspaces.push(await mapWorkspaceWithIcon(ctx, workspace));
165162 }
166163 }
167164 }
@@ -174,7 +171,7 @@ export async function getNavigationStateHandler(ctx: unknown) { · 6 unmodified lines
174171 };
175172 }
176173
177− const memberships = await typedCtx.db
174+ const memberships = await ctx.db
178175 .query("organizationMembers")
179176 .withIndex("by_account", (q) => q.eq("accountId", account._id))
180177 .collect();
@@ -182,7 +179,7 @@ export async function getNavigationStateHandler(ctx: unknown) { · 1 unmodified line
182179 const activeMemberships = memberships.filter((membership) => membership.status === "active");
183180
184181 for (const membership of activeMemberships) {
185− const org = await typedCtx.db.get(membership.organizationId);
182+ const org = await ctx.db.get(membership.organizationId);
186183 if (!org) {
187184 continue;
188185 }
@@ -195,12 +192,12 @@ export async function getNavigationStateHandler(ctx: unknown) { · 6 unmodified lines
195192 role: membership.role,
196193 });
197194
198− const orgWorkspaces = await typedCtx.db
195+ const orgWorkspaces = await ctx.db
199196 .query("workspaces")
200197 .withIndex("by_organization_created", (q) => q.eq("organizationId", org._id))
201198 .collect();
202199 for (const workspace of orgWorkspaces) {
203− workspaces.push(await mapWorkspaceWithIcon(typedCtx, workspace));
200+ workspaces.push(await mapWorkspaceWithIcon(ctx, workspace));
204201 }
205202 }
206203
@@ -217,16 +214,15 @@ export async function getNavigationStateHandler(ctx: unknown) { · 10 unmodified lines
217214 }
218215
219216 export async function getOrganizationAccessHandler(
220− ctx: unknown,
217+ ctx: OptionalAccountCtx,
221218 args: { organizationId: Id<"organizations"> },
222219 ) {
223− const typedCtx = ctx as OptionalAccountCtx;
224− const account = typedCtx.account;
220+ const account = ctx.account;
225221 if (!account) {
226222 return null;
227223 }
228224
229− const membership = await getOrganizationMembership(typedCtx, args.organizationId, account._id);
225+ const membership = await getOrganizationMembership(ctx, args.organizationId, account._id);
230226 if (!membership || membership.status !== "active") {
231227 return null;
232228 }
@@ -240,21 +236,20 @@ export async function getOrganizationAccessHandler( · 7 unmodified lines
240236 }
241237
242238 export async function resolveWorkosOrganizationIdHandler(
243− ctx: unknown,
239+ ctx: OptionalAccountCtx,
244240 args: { organizationId: Id<"organizations"> },
245241 ) {
246− const typedCtx = ctx as OptionalAccountCtx;
247− const account = typedCtx.account;
242+ const account = ctx.account;
248243 if (!account) {
249244 return null;
250245 }
251246
252− const membership = await getOrganizationMembership(typedCtx, args.organizationId, account._id);
247+ const membership = await getOrganizationMembership(ctx, args.organizationId, account._id);
253248 if (!membership || membership.status !== "active") {
254249 return null;
255250 }
256251
257− const organization = await typedCtx.db.get(args.organizationId);
252+ const organization = await ctx.db.get(args.organizationId);
258253 if (!organization || organization.status !== "active") {
259254 return null;
260255 }
executor/packages/database/src/workspaces/handlers.ts+25−28
@@ -152,11 +152,10 @@ async function toWorkspaceResult( · 151 unmodified lines
152152 }
153153
154154 export async function createWorkspaceHandler(
155− ctx: unknown,
155+ ctx: AuthedCtx,
156156 args: { name: string; organizationId?: Id<"organizations">; iconStorageId?: Id<"_storage"> },
157157 ) {
158− const typedCtx = ctx as AuthedCtx;
159− const account = typedCtx.account;
158+ const account = ctx.account;
160159 const name = args.name.trim();
161160 if (name.length < 2) {
162161 throw new Error("Workspace name must be at least 2 characters");
@@ -164,14 +163,14 @@ export async function createWorkspaceHandler( · 1 unmodified line
164163
165164 let organizationId = args.organizationId;
166165 if (organizationId) {
167− const membership = await getOrganizationMembership(typedCtx, organizationId, account._id);
166+ const membership = await getOrganizationMembership(ctx, organizationId, account._id);
168167 if (!membership || membership.status !== "active") {
169168 throw new Error("You are not a member of this organization");
170169 }
171170 } else {
172171 const now = Date.now();
173− const organizationSlug = await ensureUniqueOrganizationSlug(typedCtx, name);
174− organizationId = await typedCtx.db.insert("organizations", {
172+ const organizationSlug = await ensureUniqueOrganizationSlug(ctx, name);
173+ organizationId = await ctx.db.insert("organizations", {
175174 slug: organizationSlug,
176175 name,
177176 status: "active",
@@ -180,7 +179,7 @@ export async function createWorkspaceHandler( · 2 unmodified lines
180179 updatedAt: now,
181180 });
182181
183− await upsertOrganizationMembership(typedCtx, {
182+ await upsertOrganizationMembership(ctx, {
184183 organizationId,
185184 accountId: account._id,
186185 role: "owner",
@@ -195,9 +194,9 @@ export async function createWorkspaceHandler( · 8 unmodified lines
195194 }
196195
197196 const now = Date.now();
198− const slug = await ensureUniqueWorkspaceSlug(typedCtx, organizationId, name);
197+ const slug = await ensureUniqueWorkspaceSlug(ctx, organizationId, name);
199198
200− const workspaceId = await typedCtx.db.insert("workspaces", {
199+ const workspaceId = await ctx.db.insert("workspaces", {
201200 organizationId,
202201 slug,
203202 name,
@@ -207,46 +206,45 @@ export async function createWorkspaceHandler( · 3 unmodified lines
207206 updatedAt: now,
208207 });
209208
210− const workspace = await typedCtx.db.get(workspaceId);
209+ const workspace = await ctx.db.get(workspaceId);
211210 if (!workspace) {
212211 throw new Error("Failed to create workspace");
213212 }
214213
215− await seedWorkspaceMembersFromOrganization(typedCtx, {
214+ await seedWorkspaceMembersFromOrganization(ctx, {
216215 organizationId,
217216 workspaceId,
218217 now,
219218 mapRole: mapOrganizationRoleToWorkspaceRole,
220219 });
221220
222− await cleanupEmptyStarterWorkspace(typedCtx, organizationId, workspaceId);
221+ await cleanupEmptyStarterWorkspace(ctx, organizationId, workspaceId);
223222
224− return await toWorkspaceResult(typedCtx, workspace);
223+ return await toWorkspaceResult(ctx, workspace);
225224 }
226225
227−export async function listWorkspacesHandler(ctx: unknown, args: { organizationId?: Id<"organizations"> }) {
228− const typedCtx = ctx as OptionalAccountCtx;
229− const account = typedCtx.account;
226+export async function listWorkspacesHandler(ctx: OptionalAccountCtx, args: { organizationId?: Id<"organizations"> }) {
227+ const account = ctx.account;
230228 if (!account) {
231229 return [];
232230 }
233231
234232 const organizationId = args.organizationId;
235233 if (organizationId) {
236− const membership = await getOrganizationMembership(typedCtx, organizationId, account._id);
234+ const membership = await getOrganizationMembership(ctx, organizationId, account._id);
237235 if (!membership || membership.status !== "active") {
238236 return [];
239237 }
240238
241− const docs = await typedCtx.db
239+ const docs = await ctx.db
242240 .query("workspaces")
243241 .withIndex("by_organization_created", (q) => q.eq("organizationId", organizationId))
244242 .collect();
245− const filteredDocs = await filterDisplayWorkspaces(typedCtx, docs);
246− return await Promise.all(filteredDocs.map(async (workspace) => await toWorkspaceResult(typedCtx, workspace)));
243+ const filteredDocs = await filterDisplayWorkspaces(ctx, docs);
244+ return await Promise.all(filteredDocs.map(async (workspace) => await toWorkspaceResult(ctx, workspace)));
247245 }
248246
249− const memberships = await typedCtx.db
247+ const memberships = await ctx.db
250248 .query("organizationMembers")
251249 .withIndex("by_account", (q) => q.eq("accountId", account._id))
252250 .collect();
@@ -255,21 +253,20 @@ export async function listWorkspacesHandler(ctx: unknown, args: { organizationId · 2 unmodified lines
255253 const allWorkspaces: WorkspaceResult[] = [];
256254
257255 for (const membership of activeMemberships) {
258− const docs = await typedCtx.db
256+ const docs = await ctx.db
259257 .query("workspaces")
260258 .withIndex("by_organization_created", (q) => q.eq("organizationId", membership.organizationId))
261259 .collect();
262− const filteredDocs = await filterDisplayWorkspaces(typedCtx, docs);
260+ const filteredDocs = await filterDisplayWorkspaces(ctx, docs);
263261 for (const workspace of filteredDocs) {
264− allWorkspaces.push(await toWorkspaceResult(typedCtx, workspace));
262+ allWorkspaces.push(await toWorkspaceResult(ctx, workspace));
265263 }
266264 }
267265
268266 return Array.from(new Map(allWorkspaces.map((workspace) => [workspace.id, workspace])).values());
269267 }
270268
271−export async function generateWorkspaceIconUploadUrlHandler(ctx: unknown) {
272− const typedCtx = ctx as AuthedCtx;
273− void typedCtx.account;
274− return await typedCtx.storage.generateUploadUrl();
269+export async function generateWorkspaceIconUploadUrlHandler(ctx: AuthedCtx) {
270+ void ctx.account;
271+ return await ctx.storage.generateUploadUrl();
275272 }