1- import type { ActionFunctionArgs } from "@remix-run/server-runtime" ;
21import { json } from "@remix-run/server-runtime" ;
32import { tryCatch } from "@trigger.dev/core/utils" ;
43import { z } from "zod" ;
54import { prisma } from "~/db.server" ;
65import { RegionsPresenter } from "~/presenters/v3/RegionsPresenter.server" ;
7- import { logger } from "~/services/logger.server" ;
8- import { authorizePatOrganizationAccess } from "~/services/organizationApiAccess.server" ;
9- import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server" ;
10- import { ServiceValidationError } from "~/v3/services/baseService.server" ;
6+ import { createActionPATApiRoute } from "~/services/routeBuilders/apiBuilder.server" ;
117import { SetDefaultRegionService } from "~/v3/services/setDefaultRegion.server" ;
128
139const ParamsSchema = z . object ( {
@@ -19,106 +15,75 @@ const SetDefaultRegionRequestBody = z.object({
1915 region : z . string ( ) . min ( 1 ) ,
2016} ) ;
2117
22- export async function action ( { request, params } : ActionFunctionArgs ) {
23- // Clearing the default is unsupported: SetDefaultRegionService has no path to
24- // unset defaultWorkerGroupId, so DELETE is intentionally not implemented.
25- if ( request . method . toUpperCase ( ) !== "PUT" ) {
26- return json ( { error : "Method Not Allowed" } , { status : 405 } ) ;
27- }
28-
29- const parsedParams = ParamsSchema . safeParse ( params ) ;
30-
31- if ( ! parsedParams . success ) {
32- return json ( { error : "Invalid Params" } , { status : 400 } ) ;
33- }
34-
35- try {
36- const authenticationResult = await authenticateApiRequestWithPersonalAccessToken ( request ) ;
37-
38- if ( ! authenticationResult ) {
39- return json ( { error : "Invalid or Missing Access Token" } , { status : 401 } ) ;
40- }
41-
18+ // Clearing the default is unsupported: SetDefaultRegionService has no path to
19+ // unset defaultWorkerGroupId, so only PUT is implemented (other methods 405).
20+ export const action = createActionPATApiRoute (
21+ {
22+ method : "PUT" ,
23+ params : ParamsSchema ,
24+ body : SetDefaultRegionRequestBody ,
25+ // Resolve the org (id only, no membership) so the plugin can compute the
26+ // caller's role floor for the manage:project gate below.
27+ context : async ( { projectRef } ) => {
28+ const project = await prisma . project . findFirst ( {
29+ where : { externalRef : projectRef , deletedAt : null } ,
30+ select : { organizationId : true } ,
31+ } ) ;
32+ return project ? { organizationId : project . organizationId } : { } ;
33+ } ,
34+ authorization : { action : "manage" , resource : ( ) => ( { type : "project" } ) } ,
35+ } ,
36+ async ( { params, body, authentication } ) => {
37+ // Membership floor: resolve scoped to the caller so a non-member gets a 404
38+ // (the authorization block enforces the role; this enforces membership).
4239 const project = await prisma . project . findFirst ( {
4340 where : {
44- externalRef : parsedParams . data . projectRef ,
41+ externalRef : params . projectRef ,
4542 organization : {
4643 deletedAt : null ,
47- members : { some : { userId : authenticationResult . userId } } ,
44+ members : { some : { userId : authentication . userId } } ,
4845 } ,
4946 deletedAt : null ,
5047 } ,
51- select : { id : true , slug : true , organizationId : true } ,
48+ select : { id : true , slug : true } ,
5249 } ) ;
5350
5451 if ( ! project ) {
5552 return json ( { error : "Project not found" } , { status : 404 } ) ;
5653 }
5754
58- const denied = await authorizePatOrganizationAccess ( {
59- request,
60- organizationId : project . organizationId ,
61- resource : "project" ,
62- action : "manage" ,
63- } ) ;
64- if ( denied ) {
65- return denied ;
66- }
67-
68- let rawBody : unknown ;
69- try {
70- rawBody = await request . json ( ) ;
71- } catch {
72- return json ( { error : "Invalid request body" } , { status : 400 } ) ;
73- }
74-
75- const body = SetDefaultRegionRequestBody . safeParse ( rawBody ) ;
76-
77- if ( ! body . success ) {
78- return json ( { error : "Invalid request body" } , { status : 400 } ) ;
79- }
80-
8155 // Resolve the region name to a worker group id the same way the dashboard
8256 // does — through the presenter, which filters to regions this project can
8357 // actually use (allowed queues / hidden / compute access). PAT users are
8458 // never admins here.
8559 const presenter = new RegionsPresenter ( ) ;
8660 const [ presenterError , result ] = await tryCatch (
87- presenter . call ( { userId : authenticationResult . userId , projectSlug : project . slug } )
61+ presenter . call ( { userId : authentication . userId , projectSlug : project . slug } )
8862 ) ;
8963
9064 if ( presenterError ) {
9165 return json ( { error : presenterError . message } , { status : 400 } ) ;
9266 }
9367
94- const region = result . regions . find ( ( r ) => r . name === body . data . region ) ;
68+ const region = result . regions . find ( ( r ) => r . name === body . region ) ;
9569
9670 if ( ! region ) {
9771 return json (
9872 {
99- error : `Region '${ body . data . region } ' not found` ,
73+ error : `Region '${ body . region } ' not found` ,
10074 availableRegions : result . regions . map ( ( r ) => r . name ) ,
10175 } ,
10276 { status : 400 }
10377 ) ;
10478 }
10579
106- try {
107- const updated = await new SetDefaultRegionService ( ) . call ( {
108- projectId : project . id ,
109- regionId : region . id ,
110- } ) ;
80+ // SetDefaultRegionService throws ServiceValidationError; the builder maps it
81+ // to its status (default 400).
82+ const updated = await new SetDefaultRegionService ( ) . call ( {
83+ projectId : project . id ,
84+ regionId : region . id ,
85+ } ) ;
11186
112- return json ( { id : updated . id , name : updated . name } ) ;
113- } catch ( error ) {
114- if ( error instanceof ServiceValidationError ) {
115- return json ( { error : error . message } , { status : 400 } ) ;
116- }
117- throw error ;
118- }
119- } catch ( error ) {
120- if ( error instanceof Response ) throw error ;
121- logger . error ( "Failed to set default region" , { error } ) ;
122- return json ( { error : "Internal Server Error" } , { status : 500 } ) ;
87+ return json ( { id : updated . id , name : updated . name } ) ;
12388 }
124- }
89+ ) ;
0 commit comments