History

feat(cloud): add team management page (#157)

* feat(cloud): add team management page with WorkOS org members Custom team page using WorkOS API for listing members, inviting, removing, and changing roles. Admin-gated write operations with server-side role checks. Uses Effect API layer pattern with typed endpoints and effect-atom for client-side state. Also fixes Sonner toaster to use system theme detection instead of next-themes, removes next-themes dependency. * style: format * chore: remove unused widget token endpoint and getWidgetToken * fix: remove any types from team handler tests * style: format * fix: add @types/node to storage-postgres for tsgo compat * fix: resolve merge conflict in test file * style: format * fix: resolve tsgo typecheck errors in billing and test files * fix: add explicit Plan types for tsgo compatibility * style: format * fix: cast empty array for tsgo type inference

Rhys Sullivan committed 7 months ago

Commit ec8d8861 parent 261c7b219 files · +1079 −16Browse files

Showing the changes to apps/cloud/src/api.ts (1 of 19 files). Show all

Unified · Split

apps/cloud/src/api.ts+27−0
@@ -46,6 +46,8 @@ import { · 45 unmodified lines
46import { WorkOSAuth } from "./auth/workos";46import { WorkOSAuth } from "./auth/workos";
47import { DbService } from "./services/db";47import { DbService } from "./services/db";
48import { createOrgExecutor } from "./services/executor";48import { createOrgExecutor } from "./services/executor";
49import { TeamOrgApi } from "./team/compose";
50import { TeamHandlers } from "./team/handlers";
49import { server } from "./env";51import { server } from "./env";
50 52
51// ---------------------------------------------------------------------------53// ---------------------------------------------------------------------------
@@ -90,6 +92,11 @@ const NonProtectedApiLive = HttpApiBuilder.api(NonProtectedApi).pipe( · 38 unmodified lines
90 Layer.provideMerge(SessionAuthLive),92 Layer.provideMerge(SessionAuthLive),
91);93);
92 94
95const TeamApiLive = HttpApiBuilder.api(TeamOrgApi).pipe(
96 Layer.provide(TeamHandlers),
97 Layer.provideMerge(OrgAuthLive),
98);
99
93// ---------------------------------------------------------------------------100// ---------------------------------------------------------------------------
94// Public auth web handler101// Public auth web handler
95// ---------------------------------------------------------------------------102// ---------------------------------------------------------------------------
@@ -113,6 +120,12 @@ const createNonProtectedHandler = () => · 17 unmodified lines
113 { middleware: HttpMiddleware.logger },120 { middleware: HttpMiddleware.logger },
114 );121 );
115 122
123const createTeamHandler = () =>
124 HttpApiBuilder.toWebHandler(
125 TeamApiLive.pipe(Layer.provideMerge(SharedServices), Layer.provideMerge(RouterConfig)),
126 { middleware: HttpMiddleware.logger },
127 );
128
116// ---------------------------------------------------------------------------129// ---------------------------------------------------------------------------
117// Protected handler — must be built per-request because the executor varies130// Protected handler — must be built per-request because the executor varies
118// ---------------------------------------------------------------------------131// ---------------------------------------------------------------------------
@@ -158,6 +171,7 @@ const buildProtectedHandler = ( · 39 unmodified lines
158 171
159const isAuthPath = (pathname: string): boolean => pathname.startsWith("/auth/");172const isAuthPath = (pathname: string): boolean => pathname.startsWith("/auth/");
160const isAutumnPath = (pathname: string): boolean => pathname.startsWith("/autumn/");173const isAutumnPath = (pathname: string): boolean => pathname.startsWith("/autumn/");
174const isTeamPath = (pathname: string): boolean => pathname.startsWith("/team/");
161const isExecutionPath = (pathname: string): boolean =>175const isExecutionPath = (pathname: string): boolean =>
162 pathname === "/executions" || /^\/executions\/[^/]+\/resume$/.test(pathname);176 pathname === "/executions" || /^\/executions\/[^/]+\/resume$/.test(pathname);
163 177
@@ -241,9 +255,22 @@ const handleAutumnRequest = async (request: Request): Promise<Response> => { · 77 unmodified lines
241 );255 );
242};256};
243 257
258// ---------------------------------------------------------------------------
259// Widget token endpoint — returns a WorkOS widget token for the session user
260// ---------------------------------------------------------------------------
261
244export const handleApiRequest = async (request: Request): Promise<Response> => {262export const handleApiRequest = async (request: Request): Promise<Response> => {
245 const pathname = new URL(request.url).pathname;263 const pathname = new URL(request.url).pathname;
246 264
265 if (isTeamPath(pathname)) {
266 const handler = createTeamHandler();
267 try {
268 return await handler.handler(request);
269 } finally {
270 await handler.dispose();
271 }
272 }
273
247 if (isAutumnPath(pathname)) {274 if (isAutumnPath(pathname)) {
248 return handleAutumnRequest(request);275 return handleAutumnRequest(request);
249 }276 }