Skip to content

Commit 7533941

Browse files
committed
fix: add createMultiMethodApiRoute helper, rewrite override API with proper authorization
1 parent 8cd61cf commit 7533941

2 files changed

Lines changed: 367 additions & 96 deletions

File tree

Lines changed: 84 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/server-runtime";
21
import { json } from "@remix-run/server-runtime";
32
import { z } from "zod";
43
import { prisma } from "~/db.server";
5-
import { authenticateApiRequestWithFailure } from "~/services/apiAuth.server";
6-
import { apiCors } from "~/utils/apiCors";
4+
import { createMultiMethodApiRoute } from "~/services/routeBuilders/apiBuilder.server";
75
import { ServiceValidationError } from "~/v3/services/baseService.server";
86
import { PromptService } from "~/v3/services/promptService.server";
97

@@ -24,104 +22,94 @@ const UpdateBody = z.object({
2422
commitMessage: z.string().optional(),
2523
});
2624

27-
export async function loader({ request }: LoaderFunctionArgs) {
28-
if (request.method.toUpperCase() === "OPTIONS") {
29-
return apiCors(request, json({}));
30-
}
31-
return apiCors(request, json({ error: "Method not allowed" }, { status: 405 }));
32-
}
33-
34-
export async function action({ request, params }: ActionFunctionArgs) {
35-
const authResult = await authenticateApiRequestWithFailure(request, { allowJWT: true });
36-
if (!authResult) {
37-
return apiCors(request, json({ error: "Invalid or Missing API Key" }, { status: 401 }));
38-
}
39-
40-
if (!authResult.ok) {
41-
return apiCors(request, json({ error: authResult.error }, { status: 401 }));
42-
}
43-
44-
const { slug } = ParamsSchema.parse(params);
45-
46-
const prompt = await prisma.prompt.findUnique({
25+
async function findPrompt(slug: string, authentication: { environment: { projectId: string; id: string } }) {
26+
return prisma.prompt.findUnique({
4727
where: {
4828
projectId_runtimeEnvironmentId_slug: {
49-
projectId: authResult.environment.projectId,
50-
runtimeEnvironmentId: authResult.environment.id,
29+
projectId: authentication.environment.projectId,
30+
runtimeEnvironmentId: authentication.environment.id,
5131
slug,
5232
},
5333
},
5434
});
35+
}
5536

56-
if (!prompt) {
57-
return apiCors(request, json({ error: "Prompt not found" }, { status: 404 }));
58-
}
59-
60-
const method = request.method.toUpperCase();
61-
const service = new PromptService();
62-
63-
if (method === "POST") {
64-
let rawBody: unknown;
65-
try {
66-
rawBody = await request.json();
67-
} catch {
68-
return apiCors(request, json({ error: "Invalid JSON" }, { status: 400 }));
69-
}
70-
const parsed = CreateBody.safeParse(rawBody);
71-
if (!parsed.success) {
72-
return apiCors(
73-
request,
74-
json({ error: "Invalid request body", issues: parsed.error.issues }, { status: 400 })
75-
);
76-
}
77-
78-
const result = await service.createOverride(prompt.id, {
79-
textContent: parsed.data.textContent,
80-
model: parsed.data.model,
81-
commitMessage: parsed.data.commitMessage,
82-
source: parsed.data.source ?? "api",
83-
});
84-
85-
return apiCors(request, json({ ok: true, version: result.version }));
86-
}
87-
88-
if (method === "PUT" || method === "PATCH") {
89-
let rawBody: unknown;
90-
try {
91-
rawBody = await request.json();
92-
} catch {
93-
return apiCors(request, json({ error: "Invalid JSON" }, { status: 400 }));
94-
}
95-
const parsed = UpdateBody.safeParse(rawBody);
96-
if (!parsed.success) {
97-
return apiCors(
98-
request,
99-
json({ error: "Invalid request body", issues: parsed.error.issues }, { status: 400 })
100-
);
101-
}
102-
103-
try {
104-
await service.updateOverride(prompt.id, parsed.data);
105-
} catch (e) {
106-
if (e instanceof ServiceValidationError) {
107-
return apiCors(
108-
request,
109-
json({ error: e.message }, { status: e.status ?? 400 })
110-
);
111-
}
112-
throw e;
113-
}
114-
115-
return apiCors(request, json({ ok: true }));
116-
}
117-
118-
if (method === "DELETE") {
119-
await service.removeOverride(prompt.id);
120-
return apiCors(request, json({ ok: true }));
121-
}
37+
const { action, loader } = createMultiMethodApiRoute({
38+
params: ParamsSchema,
39+
allowJWT: true,
40+
corsStrategy: "all",
41+
authorization: {
42+
action: "update",
43+
resource: (params) => ({ prompts: params.slug }),
44+
superScopes: ["admin"],
45+
},
46+
methods: {
47+
POST: {
48+
body: CreateBody,
49+
handler: async ({ params, body, authentication }) => {
50+
const prompt = await findPrompt(params.slug, authentication);
51+
if (!prompt) return json({ error: "Prompt not found" }, { status: 404 });
52+
53+
const service = new PromptService();
54+
const result = await service.createOverride(prompt.id, {
55+
textContent: body.textContent,
56+
model: body.model,
57+
commitMessage: body.commitMessage,
58+
source: body.source ?? "api",
59+
});
60+
61+
return json({ ok: true, version: result.version });
62+
},
63+
},
64+
PUT: {
65+
body: UpdateBody,
66+
handler: async ({ params, body, authentication }) => {
67+
const prompt = await findPrompt(params.slug, authentication);
68+
if (!prompt) return json({ error: "Prompt not found" }, { status: 404 });
69+
70+
try {
71+
const service = new PromptService();
72+
await service.updateOverride(prompt.id, body);
73+
} catch (e) {
74+
if (e instanceof ServiceValidationError) {
75+
return json({ error: e.message }, { status: e.status ?? 400 });
76+
}
77+
throw e;
78+
}
79+
80+
return json({ ok: true });
81+
},
82+
},
83+
PATCH: {
84+
body: UpdateBody,
85+
handler: async ({ params, body, authentication }) => {
86+
const prompt = await findPrompt(params.slug, authentication);
87+
if (!prompt) return json({ error: "Prompt not found" }, { status: 404 });
88+
89+
try {
90+
const service = new PromptService();
91+
await service.updateOverride(prompt.id, body);
92+
} catch (e) {
93+
if (e instanceof ServiceValidationError) {
94+
return json({ error: e.message }, { status: e.status ?? 400 });
95+
}
96+
throw e;
97+
}
98+
99+
return json({ ok: true });
100+
},
101+
},
102+
DELETE: {
103+
handler: async ({ params, authentication }) => {
104+
const prompt = await findPrompt(params.slug, authentication);
105+
if (!prompt) return json({ error: "Prompt not found" }, { status: 404 });
106+
107+
const service = new PromptService();
108+
await service.removeOverride(prompt.id);
109+
return json({ ok: true });
110+
},
111+
},
112+
},
113+
});
122114

123-
return apiCors(
124-
request,
125-
json({ error: "Method not allowed" }, { status: 405, headers: { Allow: "POST, PUT, PATCH, DELETE" } })
126-
);
127-
}
115+
export { action, loader };

0 commit comments

Comments
 (0)