Skip to content

Commit b738d06

Browse files
committed
Add import env var API endpoint
1 parent fdb4e69 commit b738d06

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { ActionFunctionArgs, json } from "@remix-run/server-runtime";
2+
import { ImportEnvironmentVariablesRequestBody } from "@trigger.dev/core/v3";
3+
import { parse } from "dotenv";
4+
import { z } from "zod";
5+
import { prisma } from "~/db.server";
6+
import { findProjectByRef } from "~/models/project.server";
7+
import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server";
8+
import { EnvironmentVariablesRepository } from "~/v3/environmentVariables/environmentVariablesRepository.server";
9+
10+
const ParamsSchema = z.object({
11+
projectRef: z.string(),
12+
slug: z.string(),
13+
});
14+
15+
export async function action({ params, request }: ActionFunctionArgs) {
16+
const parsedParams = ParamsSchema.safeParse(params);
17+
18+
if (!parsedParams.success) {
19+
return json({ error: "Invalid params" }, { status: 400 });
20+
}
21+
22+
const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);
23+
24+
if (!authenticationResult) {
25+
return json({ error: "Invalid or Missing API key" }, { status: 401 });
26+
}
27+
28+
const user = await prisma.user.findUnique({
29+
where: {
30+
id: authenticationResult.userId,
31+
},
32+
});
33+
34+
if (!user) {
35+
return json({ error: "Invalid or Missing API key" }, { status: 401 });
36+
}
37+
38+
const project = await findProjectByRef(parsedParams.data.projectRef, user.id);
39+
40+
if (!project) {
41+
return json({ error: "Project not found" }, { status: 404 });
42+
}
43+
44+
const environment = await prisma.runtimeEnvironment.findFirst({
45+
where: {
46+
projectId: project.id,
47+
slug: parsedParams.data.slug,
48+
},
49+
});
50+
51+
if (!environment) {
52+
return json({ error: "Environment not found" }, { status: 404 });
53+
}
54+
55+
const repository = new EnvironmentVariablesRepository();
56+
57+
const body = await parseImportBody(request);
58+
59+
const result = await repository.create(project.id, user.id, {
60+
overwrite: body.overwrite === true ? true : false,
61+
environmentIds: [environment.id],
62+
variables: Object.entries(body.variables).map(([key, value]) => ({
63+
key,
64+
value,
65+
})),
66+
});
67+
68+
if (result.success) {
69+
return json({ success: true });
70+
} else {
71+
return json({ error: result.error, variableErrors: result.variableErrors }, { status: 400 });
72+
}
73+
}
74+
75+
async function parseImportBody(request: Request): Promise<ImportEnvironmentVariablesRequestBody> {
76+
const contentType = request.headers.get("content-type") ?? "application/json";
77+
78+
if (contentType.includes("application/octet-stream")) {
79+
// We have a "dotenv" formatted file uploaded
80+
const buffer = await request.arrayBuffer();
81+
82+
const variables = parse(Buffer.from(buffer));
83+
84+
const overwrite = request.headers.get("x-overwrite") === "true";
85+
86+
return { variables, overwrite };
87+
} else {
88+
const rawBody = await request.json();
89+
90+
const body = ImportEnvironmentVariablesRequestBody.safeParse(rawBody);
91+
92+
if (!body.success) {
93+
throw json({ error: "Invalid body" }, { status: 400 });
94+
}
95+
96+
return body.data;
97+
}
98+
}

apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ export class EnvironmentVariablesRepository implements Repository {
9494
return { success: false as const, error: `Environment not found` };
9595
}
9696

97+
// Check to see if any of the variables are `TRIGGER_SECRET_KEY` or `TRIGGER_API_URL`
98+
const triggerKeys = options.variables.map((v) => v.key);
99+
if (triggerKeys.includes("TRIGGER_SECRET_KEY") || triggerKeys.includes("TRIGGER_API_URL")) {
100+
return {
101+
success: false as const,
102+
error: `You cannot set the variables TRIGGER_SECRET_KEY or TRIGGER_API_URL as they will be set automatically`,
103+
};
104+
}
105+
97106
//get rid of empty variables
98107
const values = options.variables.filter((v) => v.key.trim() !== "" && v.value.trim() !== "");
99108
if (values.length === 0) {

packages/core/src/v3/schemas/api.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,3 +380,12 @@ export const UpdateEnvironmentVariableRequestBody = z.object({
380380
export type UpdateEnvironmentVariableRequestBody = z.infer<
381381
typeof UpdateEnvironmentVariableRequestBody
382382
>;
383+
384+
export const ImportEnvironmentVariablesRequestBody = z.object({
385+
variables: z.record(z.string()),
386+
overwrite: z.boolean().optional(),
387+
});
388+
389+
export type ImportEnvironmentVariablesRequestBody = z.infer<
390+
typeof ImportEnvironmentVariablesRequestBody
391+
>;

0 commit comments

Comments
 (0)