Openstatus www.openstatus.dev

Improve backend code (#182)

* 🚀

* 🧹

* 🛂 pr check

* 🛂 pr check

authored by

Thibault Le Ouay and committed by
GitHub
5dcb1b2d 6b182a75

+186 -247
+4 -4
apps/web/package.json
··· 39 39 "@t3-oss/env-nextjs": "0.4.1", 40 40 "@tailwindcss/typography": "0.5.9", 41 41 "@tanstack/react-table": "8.9.3", 42 - "@trpc/client": "10.35.0", 43 - "@trpc/next": "10.35.0", 44 - "@trpc/react-query": "10.35.0", 45 - "@trpc/server": "10.35.0", 42 + "@trpc/client": "10.37.1", 43 + "@trpc/next": "10.37.1", 44 + "@trpc/react-query": "10.37.1", 45 + "@trpc/server": "10.37.1", 46 46 "@upstash/qstash": "0.3.6", 47 47 "@upstash/redis": "1.21.0", 48 48 "@vercel/analytics": "1.0.1",
-2
apps/web/src/app/app/(dashboard)/[workspaceSlug]/monitors/page.tsx
··· 13 13 import { ActionButton } from "./_components/action-button"; 14 14 import { EmptyState } from "./_components/empty-state"; 15 15 16 - const limit = allPlans.free.limits.monitors; 17 - 18 16 export default async function MonitorPage({ 19 17 params, 20 18 }: {
+1 -1
apps/web/src/app/app/(dashboard)/[workspaceSlug]/status-pages/page.tsx
··· 75 75 <dt>Monitors</dt> 76 76 <dd className="flex flex-wrap justify-end gap-2"> 77 77 {page.monitorsToPages.map( 78 - ({ monitor: { id, name, active } }, i) => ( 78 + ({ monitor: { id, name, active } }) => ( 79 79 <Badge key={id} variant={active ? "default" : "outline"}> 80 80 {name} 81 81 <span
+7 -5
apps/web/src/middleware.ts
··· 89 89 .from(workspace) 90 90 .where(eq(workspace.id, result[0].users_to_workspaces.workspaceId)) 91 91 .get(); 92 - const orgSelection = new URL( 93 - `/app/${currentWorkspace.slug}/monitors`, 94 - req.url, 95 - ); 96 - return NextResponse.redirect(orgSelection); 92 + if (currentWorkspace) { 93 + const orgSelection = new URL( 94 + `/app/${currentWorkspace.slug}/monitors`, 95 + req.url, 96 + ); 97 + return NextResponse.redirect(orgSelection); 98 + } 97 99 } else { 98 100 // return NextResponse.redirect(new URL("/app/onboarding", req.url)); 99 101 // probably redirect to onboarding
+2 -2
apps/web/src/trpc/shared.ts
··· 1 - import type { HttpBatchLinkOptions, HTTPHeaders, TRPCLink } from "@trpc/client"; 1 + import type { HTTPBatchLinkOptions, HTTPHeaders, TRPCLink } from "@trpc/client"; 2 2 import { httpBatchLink } from "@trpc/client"; 3 3 4 4 import type { AppRouter } from "@openstatus/api"; ··· 16 16 ((runtime) => { 17 17 const sharedOpts = { 18 18 headers: opts?.headers, 19 - } satisfies Partial<HttpBatchLinkOptions>; 19 + } satisfies Partial<HTTPBatchLinkOptions>; 20 20 21 21 const edgeLink = httpBatchLink({ 22 22 ...sharedOpts,
+2 -2
packages/api/package.json
··· 13 13 "@openstatus/emails": "workspace:^", 14 14 "@openstatus/plans": "workspace:^", 15 15 "@t3-oss/env-core": "0.6.0", 16 - "@trpc/client": "10.35.0", 17 - "@trpc/server": "10.35.0", 16 + "@trpc/client": "10.37.1", 17 + "@trpc/server": "10.37.1", 18 18 "random-word-slugs": "0.1.7", 19 19 "stripe": "12.17.0", 20 20 "superjson": "1.13.1",
+29 -81
packages/api/src/router/incident.ts
··· 18 18 } from "@openstatus/db/src/schema"; 19 19 20 20 import { createTRPCRouter, protectedProcedure } from "../trpc"; 21 + import { hasUserAccessToWorkspace } from "./utils"; 21 22 22 23 export const incidentRouter = createTRPCRouter({ 23 24 createIncident: protectedProcedure 24 25 .input(insertIncidentSchema) 25 26 .mutation(async (opts) => { 26 - const currentWorkspace = await opts.ctx.db 27 - .select() 28 - .from(workspace) 29 - .where(eq(workspace.slug, opts.input.workspaceSlug)) 30 - .get(); 31 - const currentUser = opts.ctx.db 32 - .select() 33 - .from(user) 34 - .where(eq(user.tenantId, opts.ctx.auth.userId)) 35 - .as("currentUser"); 36 - const result = await opts.ctx.db 37 - .select() 38 - .from(usersToWorkspaces) 39 - .where(eq(usersToWorkspaces.workspaceId, currentWorkspace.id)) 40 - .innerJoin(currentUser, eq(usersToWorkspaces.userId, currentUser.id)) 41 - .get(); 27 + const result = await hasUserAccessToWorkspace({ 28 + workspaceSlug: opts.input.workspaceSlug, 29 + ctx: opts.ctx, 30 + }); 31 + if (!result) return; 42 32 43 - if (!result) return; 44 33 const { id, workspaceSlug, monitors, date, ...incidentInput } = 45 34 opts.input; 46 35 47 36 const newIncident = await opts.ctx.db 48 37 .insert(incident) 49 38 .values({ 50 - workspaceId: currentWorkspace.id, 39 + workspaceId: result.workspace.id, 51 40 ...incidentInput, 52 41 }) 53 42 .returning() ··· 82 71 createIncidentUpdate: protectedProcedure 83 72 .input(insertIncidentUpdateSchema) 84 73 .mutation(async (opts) => { 85 - const currentWorkspace = await opts.ctx.db 86 - .select() 87 - .from(workspace) 88 - .where(eq(workspace.slug, opts.input.workspaceSlug)) 89 - .get(); 90 - const currentUser = opts.ctx.db 91 - .select() 92 - .from(user) 93 - .where(eq(user.tenantId, opts.ctx.auth.userId)) 94 - .as("currentUser"); 95 - const result = await opts.ctx.db 96 - .select() 97 - .from(usersToWorkspaces) 98 - .where(eq(usersToWorkspaces.workspaceId, currentWorkspace.id)) 99 - .innerJoin(currentUser, eq(usersToWorkspaces.userId, currentUser.id)) 100 - .get(); 101 - 102 - if (!result) return; 74 + // Check if user has access to workspace 75 + const data = await hasUserAccessToWorkspace({ 76 + workspaceSlug: opts.input.workspaceSlug, 77 + ctx: opts.ctx, 78 + }); 79 + if (!data) return; 103 80 104 81 // update parent incident with latest status 105 82 await opts.ctx.db ··· 120 97 updateIncident: protectedProcedure 121 98 .input(insertIncidentSchemaWithMonitors) 122 99 .mutation(async (opts) => { 123 - const currentWorkspace = await opts.ctx.db 124 - .select() 125 - .from(workspace) 126 - .where(eq(workspace.slug, opts.input.workspaceSlug)) 127 - .get(); 128 - const currentUser = opts.ctx.db 129 - .select() 130 - .from(user) 131 - .where(eq(user.tenantId, opts.ctx.auth.userId)) 132 - .as("currentUser"); 133 - const result = await opts.ctx.db 134 - .select() 135 - .from(usersToWorkspaces) 136 - .where(eq(usersToWorkspaces.workspaceId, currentWorkspace.id)) 137 - .innerJoin(currentUser, eq(usersToWorkspaces.userId, currentUser.id)) 138 - .get(); 139 - 140 - if (!result) return; 100 + const data = await hasUserAccessToWorkspace({ 101 + workspaceSlug: opts.input.workspaceSlug, 102 + ctx: opts.ctx, 103 + }); 104 + if (!data) return; 141 105 142 - console.log(opts.input); 143 106 const { monitors, workspaceSlug, ...incidentInput } = opts.input; 144 - console.log(incidentInput); 145 107 146 108 if (!incidentInput.id) return; 147 109 ··· 204 166 .from(workspace) 205 167 .where(eq(workspace.slug, opts.input.workspaceSlug)) 206 168 .get(); 169 + if (!currentWorkspace) return; 207 170 const currentUser = opts.ctx.db 208 171 .select() 209 172 .from(user) ··· 241 204 .from(user) 242 205 .where(eq(user.tenantId, opts.ctx.auth.userId)) 243 206 .get(); 244 - 207 + if (!currentUser) return; 245 208 const result = await opts.ctx.db 246 209 .select() 247 210 .from(usersToWorkspaces) ··· 276 239 .from(user) 277 240 .where(eq(user.tenantId, opts.ctx.auth.userId)) 278 241 .get(); 279 - 242 + if (!currentUser) return; 280 243 const result = await opts.ctx.db 281 244 .select() 282 245 .from(usersToWorkspaces) ··· 340 303 getIncidentByWorkspace: protectedProcedure 341 304 .input(z.object({ workspaceSlug: z.string() })) 342 305 .query(async (opts) => { 343 - const currentWorkspace = await opts.ctx.db 344 - .select() 345 - .from(workspace) 346 - .where(eq(workspace.slug, opts.input.workspaceSlug)) 347 - .get(); 348 - 349 - const currentUser = opts.ctx.db 350 - .select() 351 - .from(user) 352 - .where(eq(user.tenantId, opts.ctx.auth.userId)) 353 - .as("currentUser"); 354 - 355 - const result = await opts.ctx.db 356 - .select() 357 - .from(usersToWorkspaces) 358 - .where(eq(usersToWorkspaces.workspaceId, currentWorkspace.id)) 359 - .innerJoin(currentUser, eq(usersToWorkspaces.userId, currentUser.id)) 360 - .get(); 361 - 362 - if (!result) return; 306 + const data = await hasUserAccessToWorkspace({ 307 + workspaceSlug: opts.input.workspaceSlug, 308 + ctx: opts.ctx, 309 + }); 310 + if (!data) return; 363 311 364 312 const selectIncidentSchemaWithRelation = selectIncidentSchema.extend({ 365 313 status: StatusEnum.default("investigating"), // TODO: remove! ··· 375 323 incidentUpdates: z.array(selectIncidentUpdateSchema), 376 324 }); 377 325 378 - const data = await opts.ctx.db.query.incident.findMany({ 379 - where: eq(incident.workspaceId, currentWorkspace.id), 326 + const result = await opts.ctx.db.query.incident.findMany({ 327 + where: eq(incident.workspaceId, data.workspace.id), 380 328 with: { 381 329 monitorsToIncidents: { with: { monitor: true } }, 382 330 incidentUpdates: { ··· 388 336 orderBy: (incident, { desc }) => [desc(incident.createdAt)], 389 337 }); 390 338 391 - return z.array(selectIncidentSchemaWithRelation).parse(data); 339 + return z.array(selectIncidentSchemaWithRelation).parse(result); 392 340 }), 393 341 });
+18 -45
packages/api/src/router/monitor.ts
··· 10 10 selectMonitorSchema, 11 11 user, 12 12 usersToWorkspaces, 13 - workspace, 14 13 } from "@openstatus/db/src/schema"; 15 14 import { allPlans } from "@openstatus/plans"; 16 15 17 16 import { createTRPCRouter, protectedProcedure } from "../trpc"; 17 + import { hasUserAccessToWorkspace } from "./utils"; 18 18 19 19 const monitorLimit = allPlans.free.limits.monitors; 20 20 const periodicityLimit = allPlans.free.limits.periodicity; ··· 23 23 createMonitor: protectedProcedure 24 24 .input(z.object({ data: insertMonitorSchema, workspaceSlug: z.string() })) 25 25 .mutation(async (opts) => { 26 - const currentWorkspace = await opts.ctx.db 27 - .select() 28 - .from(workspace) 29 - .where(eq(workspace.slug, opts.input.workspaceSlug)) 30 - .get(); 31 - const currentUser = opts.ctx.db 32 - .select() 33 - .from(user) 34 - .where(eq(user.tenantId, opts.ctx.auth.userId)) 35 - .as("currentUser"); 36 - const result = await opts.ctx.db 37 - .select() 38 - .from(usersToWorkspaces) 39 - .where(eq(usersToWorkspaces.workspaceId, currentWorkspace.id)) 40 - .innerJoin(currentUser, eq(usersToWorkspaces.userId, currentUser.id)) 41 - .get(); 42 - 43 - // the user don't have access to this workspace 44 - if (!result || !result.users_to_workspaces) return; 26 + const result = await hasUserAccessToWorkspace({ 27 + workspaceSlug: opts.input.workspaceSlug, 28 + ctx: opts.ctx, 29 + }); 30 + if (!result) return; 45 31 46 32 const monitorNumbers = ( 47 33 await opts.ctx.db.query.monitor.findMany({ 48 - where: eq(monitor.workspaceId, currentWorkspace.id), 34 + where: eq(monitor.workspaceId, result.workspace.id), 49 35 }) 50 36 ).length; 51 37 ··· 73 59 .insert(monitor) 74 60 .values({ 75 61 ...data, 76 - workspaceId: currentWorkspace.id, 62 + workspaceId: result.workspace.id, 77 63 regions: regions?.join(","), 78 64 }) 79 65 .returning() 80 66 .get(); 81 67 82 - await analytics.identify(result.users_to_workspaces.userId, { 83 - userId: result.users_to_workspaces.userId, 68 + await analytics.identify(result.user.id, { 69 + userId: result.user.id, 84 70 }); 85 71 await trackAnalytics({ 86 72 event: "Monitor Created", ··· 223 209 .innerJoin(currentUser, eq(usersToWorkspaces.userId, currentUser.id)) 224 210 .get(); 225 211 226 - if (!result.users_to_workspaces) return; 212 + if (!result?.users_to_workspaces) return; 227 213 228 214 await opts.ctx.db 229 215 .update(monitor) ··· 267 253 getMonitorsByWorkspace: protectedProcedure 268 254 .input(z.object({ workspaceSlug: z.string() })) 269 255 .query(async (opts) => { 270 - const currentUser = opts.ctx.db 271 - .select() 272 - .from(user) 273 - .where(eq(user.tenantId, opts.ctx.auth.userId)) 274 - .as("currentUser"); 275 - 276 - const currentWorkspace = await opts.ctx.db 277 - .select() 278 - .from(workspace) 279 - .where(eq(workspace.slug, opts.input.workspaceSlug)) 280 - .get(); 281 - const result = await opts.ctx.db 282 - .select() 283 - .from(usersToWorkspaces) 284 - .where(eq(usersToWorkspaces.workspaceId, currentWorkspace.id)) 285 - .innerJoin(currentUser, eq(usersToWorkspaces.userId, currentUser.id)) 286 - .get(); 287 - // the user don't have access to this workspace 288 - if (!result || !result.users_to_workspaces) return; 256 + // Check if user has access to workspace 257 + const data = await hasUserAccessToWorkspace({ 258 + workspaceSlug: opts.input.workspaceSlug, 259 + ctx: opts.ctx, 260 + }); 261 + if (!data) return; 289 262 290 263 const monitors = await opts.ctx.db 291 264 .select() 292 265 .from(monitor) 293 - .where(eq(monitor.workspaceId, currentWorkspace.id)) 266 + .where(eq(monitor.workspaceId, data.workspace.id)) 294 267 .all(); 295 268 // const selectMonitorsArray = selectMonitorSchema.array(); 296 269
+15 -31
packages/api/src/router/page.ts
··· 5 5 import { and, eq, inArray, not, sql } from "@openstatus/db"; 6 6 import { 7 7 incident, 8 - incidentUpdate, 9 8 insertPageSchemaWithMonitors, 10 9 monitor, 11 10 monitorsToIncidents, 12 11 monitorsToPages, 13 12 page, 14 - selectIncidentSchema, 15 - selectIncidentUpdateSchema, 16 - selectMonitorSchema, 17 - selectPageSchema, 18 13 selectPageSchemaWithRelation, 19 14 user, 20 15 usersToWorkspaces, ··· 23 18 import { allPlans } from "@openstatus/plans"; 24 19 25 20 import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; 21 + import { hasUserAccessToWorkspace } from "./utils"; 26 22 27 23 const limit = allPlans.free.limits["status-pages"]; 28 24 ··· 32 28 .input(insertPageSchemaWithMonitors) 33 29 .mutation(async (opts) => { 34 30 if (!opts.input.workspaceSlug) return; 35 - const currentWorkspace = await opts.ctx.db 36 - .select() 37 - .from(workspace) 38 - .where(eq(workspace.slug, opts.input.workspaceSlug)) 39 - .get(); 40 - const currentUser = opts.ctx.db 41 - .select() 42 - .from(user) 43 - .where(eq(user.tenantId, opts.ctx.auth.userId)) 44 - .as("currentUser"); 45 - const result = await opts.ctx.db 46 - .select() 47 - .from(usersToWorkspaces) 48 - .where(eq(usersToWorkspaces.workspaceId, currentWorkspace.id)) 49 - .innerJoin(currentUser, eq(usersToWorkspaces.userId, currentUser.id)) 50 - .get(); 31 + const data = await hasUserAccessToWorkspace({ 32 + workspaceSlug: opts.input.workspaceSlug, 33 + ctx: opts.ctx, 34 + }); 35 + if (!data) return; 51 36 52 - if (!result) return; 53 37 const { monitors, workspaceId, workspaceSlug, id, ...pageInput } = 54 38 opts.input; 55 39 56 40 const pageNumbers = ( 57 41 await opts.ctx.db.query.page.findMany({ 58 - where: eq(page.workspaceId, currentWorkspace.id), 42 + where: eq(page.workspaceId, data.workspace.id), 59 43 }) 60 44 ).length; 61 45 ··· 69 53 70 54 const newPage = await opts.ctx.db 71 55 .insert(page) 72 - .values({ workspaceId: currentWorkspace.id, ...pageInput }) 56 + .values({ workspaceId: data.workspace.id, ...pageInput }) 73 57 .returning() 74 58 .get(); 75 59 if (monitors && monitors.length > 0) { ··· 84 68 await opts.ctx.db.insert(monitorsToPages).values(values).run(); 85 69 } 86 70 87 - await analytics.identify(result.users_to_workspaces.userId, { 88 - userId: result.users_to_workspaces.userId, 71 + await analytics.identify(data.user.id, { 72 + userId: data.user.id, 89 73 }); 90 74 await trackAnalytics({ 91 75 event: "Page Created", ··· 101 85 .from(user) 102 86 .where(eq(user.tenantId, opts.ctx.auth.userId)) 103 87 .get(); 104 - 88 + if (!currentUser) return; 105 89 const result = await opts.ctx.db 106 90 .select() 107 91 .from(usersToWorkspaces) ··· 130 114 .from(user) 131 115 .where(eq(user.tenantId, opts.ctx.auth.userId)) 132 116 .get(); 133 - 117 + if (!currentUser) return; 134 118 const result = await opts.ctx.db 135 119 .select() 136 120 .from(usersToWorkspaces) ··· 208 192 .from(user) 209 193 .where(eq(user.tenantId, opts.ctx.auth.userId)) 210 194 .get(); 211 - 195 + if (!currentUser) return; 212 196 const result = await opts.ctx.db 213 197 .select() 214 198 .from(usersToWorkspaces) ··· 239 223 .from(user) 240 224 .where(eq(user.tenantId, opts.ctx.auth.userId)) 241 225 .get(); 242 - 226 + if (!currentUser) return; 243 227 const currentWorkspace = await opts.ctx.db 244 228 .select() 245 229 .from(workspace) 246 230 .where(eq(workspace.slug, opts.input.workspaceSlug)) 247 231 .get(); 232 + if (!currentWorkspace) return; 248 233 const result = await opts.ctx.db 249 234 .select() 250 235 .from(usersToWorkspaces) ··· 271 256 .query(async (opts) => { 272 257 const result = await opts.ctx.db.query.page.findFirst({ 273 258 where: sql`lower(${page.slug}) = ${opts.input.slug}`, 274 - // with: { incidents: true }, 275 259 }); 276 260 277 261 if (!result) {
+1 -1
packages/api/src/router/stripe/index.ts
··· 109 109 metadata: { 110 110 workspaceId: String(workspace.id), 111 111 }, 112 - email: currentUser.email || "", 112 + email: currentUser?.email || "", 113 113 }; 114 114 const stripeUser = await stripe.customers.create(customerData); 115 115
+3 -1
packages/api/src/router/stripe/webhook.ts
··· 1 1 import { TRPCError } from "@trpc/server"; 2 2 import type Stripe from "stripe"; 3 - import { custom, z } from "zod"; 3 + import { z } from "zod"; 4 4 5 5 import { analytics, trackAnalytics } from "@openstatus/analytics"; 6 6 import { eq } from "@openstatus/db"; ··· 68 68 .from(user) 69 69 .where(eq(user.email, customer.email)) 70 70 .get(); 71 + if (!userResult) return; 72 + 71 73 await analytics.identify(String(userResult.id), { 72 74 email: customer.email, 73 75 userId: userResult.id,
+43
packages/api/src/router/utils.ts
··· 1 + import { eq } from "@openstatus/db"; 2 + import { user, usersToWorkspaces, workspace } from "@openstatus/db/src/schema"; 3 + 4 + import { Context } from "../trpc"; 5 + 6 + /** 7 + * Check if the user has access to the workspace, and return the workspace and user otherwise undefined 8 + * @param workspaceSlug 9 + * @param tenantId 10 + * @param ctx - trpc context 11 + * @returns {Promise<{ workspaceId: string; userId: string }> | undefined} - workspaceId and userId 12 + */ 13 + export const hasUserAccessToWorkspace = async ({ 14 + workspaceSlug, 15 + ctx, 16 + }: { 17 + workspaceSlug: string; 18 + ctx: Context; 19 + }) => { 20 + if (!ctx.auth?.userId) return; 21 + const currentUser = ctx.db 22 + .select() 23 + .from(user) 24 + .where(eq(user.tenantId, ctx.auth?.userId)) 25 + .as("currentUser"); 26 + 27 + const currentWorkspace = await ctx.db 28 + .select() 29 + .from(workspace) 30 + .where(eq(workspace.slug, workspaceSlug)) 31 + .get(); 32 + if (!currentWorkspace) return; 33 + const result = await ctx.db 34 + .select() 35 + .from(usersToWorkspaces) 36 + .where(eq(usersToWorkspaces.workspaceId, currentWorkspace.id)) 37 + .innerJoin(currentUser, eq(usersToWorkspaces.userId, currentUser.id)) 38 + .get(); 39 + // the user doesn't have access to this workspace 40 + if (!result || !result.users_to_workspaces) return; 41 + 42 + return { workspace: currentWorkspace, user: result.currentUser }; 43 + };
+10 -23
packages/api/src/router/workspace.ts
··· 3 3 4 4 import { eq } from "@openstatus/db"; 5 5 import { 6 - selectPageSchema, 7 6 selectWorkspaceSchema, 8 7 user, 9 - usersToWorkspaces, 10 8 workspace, 11 9 } from "@openstatus/db/src/schema"; 12 10 13 11 import { createTRPCRouter, protectedProcedure } from "../trpc"; 12 + import { hasUserAccessToWorkspace } from "./utils"; 14 13 15 14 export const workspaceRouter = createTRPCRouter({ 16 15 getUserWithWorkspace: protectedProcedure.query(async (opts) => { ··· 25 24 where: eq(user.tenantId, opts.ctx.auth.userId), 26 25 }); 27 26 }), 27 + 28 28 getWorkspace: protectedProcedure 29 29 .input(z.object({ slug: z.string() })) 30 30 .query(async (opts) => { 31 - const currentUser = opts.ctx.db 32 - .select() 33 - .from(user) 34 - .where(eq(user.tenantId, opts.ctx.auth.userId)) 35 - .as("currentUser"); 36 - const currentWorkspace = await opts.ctx.db 37 - .select() 38 - .from(workspace) 39 - .where(eq(workspace.slug, opts.input.slug)) 40 - .get(); 41 - const result = await opts.ctx.db 42 - .select() 43 - .from(usersToWorkspaces) 44 - .where(eq(usersToWorkspaces.workspaceId, currentWorkspace.id)) 45 - .innerJoin(currentUser, eq(usersToWorkspaces.userId, currentUser.id)) 46 - .get(); 31 + const data = await hasUserAccessToWorkspace({ 32 + workspaceSlug: opts.input.slug, 33 + ctx: opts.ctx, 34 + }); 35 + if (!data) return; 47 36 48 - if (!result.users_to_workspaces) return; 49 - 50 - const data = await opts.ctx.db.query.workspace.findFirst({ 51 - where: eq(workspace.id, currentWorkspace.id), 37 + const result = await opts.ctx.db.query.workspace.findFirst({ 38 + where: eq(workspace.id, data.workspace.id), 52 39 }); 53 40 54 - return selectWorkspaceSchema.parse(data); 41 + return selectWorkspaceSchema.parse(result); 55 42 }), 56 43 57 44 createWorkspace: protectedProcedure
+3 -1
packages/api/src/trpc.ts
··· 5 5 SignedOutAuthObject, 6 6 } from "@clerk/nextjs/api"; 7 7 import { getAuth } from "@clerk/nextjs/server"; 8 - import { initTRPC, TRPCError } from "@trpc/server"; 8 + import { inferAsyncReturnType, initTRPC, TRPCError } from "@trpc/server"; 9 9 import superjson from "superjson"; 10 10 import { ZodError } from "zod"; 11 11 ··· 54 54 req: opts.req, 55 55 }); 56 56 }; 57 + 58 + export type Context = inferAsyncReturnType<typeof createTRPCContext>; 57 59 58 60 /** 59 61 * 2. INITIALIZATION
+3 -3
packages/db/package.json
··· 13 13 "@libsql/client": "0.3.1", 14 14 "@t3-oss/env-core": "0.6.0", 15 15 "dotenv": "16.3.1", 16 - "drizzle-orm": "0.27.2", 17 - "drizzle-zod": "0.4.4", 16 + "drizzle-orm": "0.28.2", 17 + "drizzle-zod": "0.5.0", 18 18 "zod": "3.21.4" 19 19 }, 20 20 "devDependencies": { 21 21 "@types/node": "20.3.1", 22 - "drizzle-kit": "0.19.10", 22 + "drizzle-kit": "0.19.12", 23 23 "tsconfig": "workspace:^", 24 24 "typescript": "5.1.6", 25 25 "bufferutil": "4.0.7",
+45 -45
pnpm-lock.yaml
··· 163 163 specifier: 8.9.3 164 164 version: 8.9.3(react-dom@18.2.0)(react@18.2.0) 165 165 '@trpc/client': 166 - specifier: 10.35.0 167 - version: 10.35.0(@trpc/server@10.35.0) 166 + specifier: 10.37.1 167 + version: 10.37.1(@trpc/server@10.37.1) 168 168 '@trpc/next': 169 - specifier: 10.35.0 170 - version: 10.35.0(@tanstack/react-query@4.32.1)(@trpc/client@10.35.0)(@trpc/react-query@10.35.0)(@trpc/server@10.35.0)(next@13.4.12)(react-dom@18.2.0)(react@18.2.0) 169 + specifier: 10.37.1 170 + version: 10.37.1(@tanstack/react-query@4.32.1)(@trpc/client@10.37.1)(@trpc/react-query@10.37.1)(@trpc/server@10.37.1)(next@13.4.12)(react-dom@18.2.0)(react@18.2.0) 171 171 '@trpc/react-query': 172 - specifier: 10.35.0 173 - version: 10.35.0(@tanstack/react-query@4.32.1)(@trpc/client@10.35.0)(@trpc/server@10.35.0)(react-dom@18.2.0)(react@18.2.0) 172 + specifier: 10.37.1 173 + version: 10.37.1(@tanstack/react-query@4.32.1)(@trpc/client@10.37.1)(@trpc/server@10.37.1)(react-dom@18.2.0)(react@18.2.0) 174 174 '@trpc/server': 175 - specifier: 10.35.0 176 - version: 10.35.0 175 + specifier: 10.37.1 176 + version: 10.37.1 177 177 '@upstash/qstash': 178 178 specifier: 0.3.6 179 179 version: 0.3.6 ··· 360 360 specifier: 0.6.0 361 361 version: 0.6.0(typescript@5.1.6)(zod@3.21.4) 362 362 '@trpc/client': 363 - specifier: 10.35.0 364 - version: 10.35.0(@trpc/server@10.35.0) 363 + specifier: 10.37.1 364 + version: 10.37.1(@trpc/server@10.37.1) 365 365 '@trpc/server': 366 - specifier: 10.35.0 367 - version: 10.35.0 366 + specifier: 10.37.1 367 + version: 10.37.1 368 368 random-word-slugs: 369 369 specifier: 0.1.7 370 370 version: 0.1.7 ··· 431 431 specifier: 16.3.1 432 432 version: 16.3.1 433 433 drizzle-orm: 434 - specifier: 0.27.2 435 - version: 0.27.2(@libsql/client@0.3.1) 434 + specifier: 0.28.2 435 + version: 0.28.2(@libsql/client@0.3.1) 436 436 drizzle-zod: 437 - specifier: 0.4.4 438 - version: 0.4.4(drizzle-orm@0.27.2)(zod@3.21.4) 437 + specifier: 0.5.0 438 + version: 0.5.0(drizzle-orm@0.28.2)(zod@3.21.4) 439 439 zod: 440 440 specifier: 3.21.4 441 441 version: 3.21.4 ··· 447 447 specifier: 4.0.7 448 448 version: 4.0.7 449 449 drizzle-kit: 450 - specifier: 0.19.10 451 - version: 0.19.10 450 + specifier: 0.19.12 451 + version: 0.19.12 452 452 encoding: 453 453 specifier: 0.1.13 454 454 version: 0.1.13 ··· 5054 5054 engines: {node: '>= 10'} 5055 5055 dev: false 5056 5056 5057 - /@trpc/client@10.35.0(@trpc/server@10.35.0): 5058 - resolution: {integrity: sha512-FyVNQGLLsNourpYq7bojvvvpM63woCPL9c+lJo9x1Yheop5fng2GEyiVcVjCmBNk5tVARHjG9bTG1UeNF5t8OQ==} 5057 + /@trpc/client@10.37.1(@trpc/server@10.37.1): 5058 + resolution: {integrity: sha512-OSblNfeI0Z9ERn3usgLV2x63CwwPoNOHf1FQK85cOT7F8MNaWyEHoEv7tHUwNGJwyzKXmpU+ockZ0movzX3D0g==} 5059 5059 peerDependencies: 5060 - '@trpc/server': 10.35.0 5060 + '@trpc/server': 10.37.1 5061 5061 dependencies: 5062 - '@trpc/server': 10.35.0 5062 + '@trpc/server': 10.37.1 5063 5063 dev: false 5064 5064 5065 - /@trpc/next@10.35.0(@tanstack/react-query@4.32.1)(@trpc/client@10.35.0)(@trpc/react-query@10.35.0)(@trpc/server@10.35.0)(next@13.4.12)(react-dom@18.2.0)(react@18.2.0): 5066 - resolution: {integrity: sha512-mUp7OrsF/vwxPOjZ6lP4X0XVxXA8N7PaWys6NgigUaKsGZ9pht5QyQNfprVpIv7PnSknLuICY/fKEBL9+zfH3A==} 5065 + /@trpc/next@10.37.1(@tanstack/react-query@4.32.1)(@trpc/client@10.37.1)(@trpc/react-query@10.37.1)(@trpc/server@10.37.1)(next@13.4.12)(react-dom@18.2.0)(react@18.2.0): 5066 + resolution: {integrity: sha512-0KEgr09mBfao56lkj7ZBfVOY86d3+bDH1o0zJkDHSH60Dp/hIJ7wLCnZJIhePlZxEwknCQjVeLsTy4Pqlu8NyQ==} 5067 5067 peerDependencies: 5068 5068 '@tanstack/react-query': ^4.18.0 5069 - '@trpc/client': 10.35.0 5070 - '@trpc/react-query': 10.35.0 5071 - '@trpc/server': 10.35.0 5069 + '@trpc/client': 10.37.1 5070 + '@trpc/react-query': 10.37.1 5071 + '@trpc/server': 10.37.1 5072 5072 next: '*' 5073 5073 react: '>=16.8.0' 5074 5074 react-dom: '>=16.8.0' 5075 5075 dependencies: 5076 5076 '@tanstack/react-query': 4.32.1(react-dom@18.2.0)(react@18.2.0) 5077 - '@trpc/client': 10.35.0(@trpc/server@10.35.0) 5078 - '@trpc/react-query': 10.35.0(@tanstack/react-query@4.32.1)(@trpc/client@10.35.0)(@trpc/server@10.35.0)(react-dom@18.2.0)(react@18.2.0) 5079 - '@trpc/server': 10.35.0 5077 + '@trpc/client': 10.37.1(@trpc/server@10.37.1) 5078 + '@trpc/react-query': 10.37.1(@tanstack/react-query@4.32.1)(@trpc/client@10.37.1)(@trpc/server@10.37.1)(react-dom@18.2.0)(react@18.2.0) 5079 + '@trpc/server': 10.37.1 5080 5080 next: 13.4.12(@babel/core@7.22.9)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) 5081 5081 react: 18.2.0 5082 5082 react-dom: 18.2.0(react@18.2.0) 5083 5083 react-ssr-prepass: 1.5.0(react@18.2.0) 5084 5084 dev: false 5085 5085 5086 - /@trpc/react-query@10.35.0(@tanstack/react-query@4.32.1)(@trpc/client@10.35.0)(@trpc/server@10.35.0)(react-dom@18.2.0)(react@18.2.0): 5087 - resolution: {integrity: sha512-tPC14UAIlrMlPZgN5TGOzcgqI4dEBUaJSmc+PjLmLfP+7vcNwOweCSdvq9gsDBCZf9aYq3Bf6KFi90ej+0ZUjQ==} 5086 + /@trpc/react-query@10.37.1(@tanstack/react-query@4.32.1)(@trpc/client@10.37.1)(@trpc/server@10.37.1)(react-dom@18.2.0)(react@18.2.0): 5087 + resolution: {integrity: sha512-TbOOPp0fZVaKfaeEyDoV8QeTHW1vgPTbfOs0uSQ4AzBXqXPu+9v1B44z8GGRJSdUxuOX9pG/6Ap5Kx8PQ3eF+Q==} 5088 5088 peerDependencies: 5089 5089 '@tanstack/react-query': ^4.18.0 5090 - '@trpc/client': 10.35.0 5091 - '@trpc/server': 10.35.0 5090 + '@trpc/client': 10.37.1 5091 + '@trpc/server': 10.37.1 5092 5092 react: '>=16.8.0' 5093 5093 react-dom: '>=16.8.0' 5094 5094 dependencies: 5095 5095 '@tanstack/react-query': 4.32.1(react-dom@18.2.0)(react@18.2.0) 5096 - '@trpc/client': 10.35.0(@trpc/server@10.35.0) 5097 - '@trpc/server': 10.35.0 5096 + '@trpc/client': 10.37.1(@trpc/server@10.37.1) 5097 + '@trpc/server': 10.37.1 5098 5098 react: 18.2.0 5099 5099 react-dom: 18.2.0(react@18.2.0) 5100 5100 dev: false 5101 5101 5102 - /@trpc/server@10.35.0: 5103 - resolution: {integrity: sha512-UntbE9gG+kB2nEwuJ0hw7AmQuRWw67voKaVBPBA7Ow6+aGZc00gva7015Ey5BT7Qf1wq7xJMLUdgw4QiVU+VpQ==} 5102 + /@trpc/server@10.37.1: 5103 + resolution: {integrity: sha512-r3VeA319/braYMBIzj+XLgLKQ9lJSVglvPvP9HUv4kr5w6Y5grQMxMcExhTiZWltE9bnSJHKtBBzHafOo7KC8A==} 5104 5104 dev: false 5105 5105 5106 5106 /@tsconfig/node10@1.0.9: ··· 7244 7244 wordwrap: 1.0.0 7245 7245 dev: true 7246 7246 7247 - /drizzle-kit@0.19.10: 7248 - resolution: {integrity: sha512-v7bd+7YUO3NJJTY7hwt9k+DPJVw+31iejG/ztkvNS0hK8zWjJvA6NAECYZVswKQ+efCjzLgPCx+I9X105Zqt6Q==} 7247 + /drizzle-kit@0.19.12: 7248 + resolution: {integrity: sha512-rcsmh5gUIkvuD0WrbEc+aLpqY2q2J8ltynRcJiJo2l01hhsYvPnX0sgxWlFXlfAIa5ZXNw2nJZhYlslI6tG3MA==} 7249 7249 hasBin: true 7250 7250 dependencies: 7251 7251 '@drizzle-team/studio': 0.0.5 ··· 7264 7264 - supports-color 7265 7265 dev: true 7266 7266 7267 - /drizzle-orm@0.27.2(@libsql/client@0.3.1): 7268 - resolution: {integrity: sha512-ZvBvceff+JlgP7FxHKe0zOU9CkZ4RcOtibumIrqfYzDGuOeF0YUY0F9iMqYpRM7pxnLRfC+oO7rWOUH3T5oFQA==} 7267 + /drizzle-orm@0.28.2(@libsql/client@0.3.1): 7268 + resolution: {integrity: sha512-QRyuzvpJr7GE6LpvZ/sg2nAKNg2if1uGGkgFTiXn4auuYId//vVJe6HBsDTktfKfcaDGzIYos+/f+PS5EkBmrg==} 7269 7269 peerDependencies: 7270 7270 '@aws-sdk/client-rds-data': '>=3' 7271 7271 '@cloudflare/workers-types': '>=3' ··· 7329 7329 '@libsql/client': 0.3.1(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@6.0.3) 7330 7330 dev: false 7331 7331 7332 - /drizzle-zod@0.4.4(drizzle-orm@0.27.2)(zod@3.21.4): 7333 - resolution: {integrity: sha512-XNn0mjQ306Hvg+CuyKV98wnsWBWeTqv+kgE/N79Zj0EdCvjD2kaoN5sFDGFkMVqPQuP7IB1IPs0lEcOij1RvBA==} 7332 + /drizzle-zod@0.5.0(drizzle-orm@0.28.2)(zod@3.21.4): 7333 + resolution: {integrity: sha512-gIOXclphhaleYFEGZFxSyJBoiPRyksGyIPgmgWz6a+j5JeaOrOf1QOPjf1p5AHYA+1O3Q01ondGOZfgObxBTsg==} 7334 7334 peerDependencies: 7335 7335 drizzle-orm: '>=0.23.13' 7336 7336 zod: '*' 7337 7337 dependencies: 7338 - drizzle-orm: 0.27.2(@libsql/client@0.3.1) 7338 + drizzle-orm: 0.28.2(@libsql/client@0.3.1) 7339 7339 zod: 3.21.4 7340 7340 dev: false 7341 7341