Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v3: env var management API #1088

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion apps/webapp/app/models/project.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ export async function findProjectBySlug(orgSlug: string, projectSlug: string, us
}

export async function findProjectByRef(externalRef: string, userId: string) {
// Find the project scoped to the organization, making sure the user belongs to that org
return await prisma.project.findFirst({
where: {
externalRef,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ function EditEnvironmentVariablePanel({
name={`values[${index}].value`}
placeholder="Not set"
defaultValue={value}
type="password"
/>
</Fragment>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { ActionFunctionArgs, LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { UpdateEnvironmentVariableRequestBody } from "@trigger.dev/core/v3";
import { z } from "zod";
import { prisma } from "~/db.server";
import { findProjectByRef } from "~/models/project.server";
import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server";
import { EnvironmentVariablesRepository } from "~/v3/environmentVariables/environmentVariablesRepository.server";

const ParamsSchema = z.object({
projectRef: z.string(),
slug: z.string(),
name: z.string(),
});

export async function action({ params, request }: ActionFunctionArgs) {
const parsedParams = ParamsSchema.safeParse(params);

if (!parsedParams.success) {
return json({ error: "Invalid params" }, { status: 400 });
}

const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);

if (!authenticationResult) {
return json({ error: "Invalid or Missing API key" }, { status: 401 });
}

const user = await prisma.user.findUnique({
where: {
id: authenticationResult.userId,
},
});

if (!user) {
return json({ error: "Invalid or Missing API key" }, { status: 401 });
}

const project = await findProjectByRef(parsedParams.data.projectRef, user.id);

if (!project) {
return json({ error: "Project not found" }, { status: 404 });
}

const environment = await prisma.runtimeEnvironment.findFirst({
where: {
projectId: project.id,
slug: parsedParams.data.slug,
},
});

if (!environment) {
return json({ error: "Environment not found" }, { status: 404 });
}

// Find the environment variable
const variable = await prisma.environmentVariable.findFirst({
where: {
key: parsedParams.data.name,
projectId: project.id,
},
});

if (!variable) {
return json({ error: "Environment variable not found" }, { status: 404 });
}

const repository = new EnvironmentVariablesRepository();

switch (request.method.toUpperCase()) {
case "DELETE": {
const result = await repository.deleteValue(project.id, user.id, {
id: variable.id,
environmentId: environment.id,
});

if (result.success) {
return json({ success: true });
} else {
return json({ error: result.error }, { status: 400 });
}
}
case "PUT":
case "POST": {
const jsonBody = await request.json();

const body = UpdateEnvironmentVariableRequestBody.safeParse(jsonBody);

if (!body.success) {
return json({ error: "Invalid request body", issues: body.error.issues }, { status: 400 });
}

const result = await repository.edit(project.id, user.id, {
values: [
{
value: body.data.value,
environmentId: environment.id,
},
],
id: variable.id,
keepEmptyValues: true,
});

if (result.success) {
return json({ success: true });
} else {
return json({ error: result.error }, { status: 400 });
}
}
}
}

export async function loader({ params, request }: LoaderFunctionArgs) {
const parsedParams = ParamsSchema.safeParse(params);

if (!parsedParams.success) {
return json({ error: "Invalid params" }, { status: 400 });
}

const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);

if (!authenticationResult) {
return json({ error: "Invalid or Missing API key" }, { status: 401 });
}

const user = await prisma.user.findUnique({
where: {
id: authenticationResult.userId,
},
});

if (!user) {
return json({ error: "Invalid or Missing API key" }, { status: 401 });
}

const project = await findProjectByRef(parsedParams.data.projectRef, user.id);

if (!project) {
return json({ error: "Project not found" }, { status: 404 });
}

const environment = await prisma.runtimeEnvironment.findFirst({
where: {
projectId: project.id,
slug: parsedParams.data.slug,
},
});

if (!environment) {
return json({ error: "Environment not found" }, { status: 404 });
}

// Find the environment variable
const variable = await prisma.environmentVariable.findFirst({
where: {
key: parsedParams.data.name,
projectId: project.id,
},
});

if (!variable) {
return json({ error: "Environment variable not found" }, { status: 404 });
}

const repository = new EnvironmentVariablesRepository();

const variables = await repository.getEnvironment(project.id, user.id, environment.id, true);

const environmentVariable = variables.find((v) => v.key === parsedParams.data.name);

if (!environmentVariable) {
return json({ error: "Environment variable not found" }, { status: 404 });
}

return json({
value: environmentVariable.value,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { ActionFunctionArgs, json } from "@remix-run/server-runtime";
import { ImportEnvironmentVariablesRequestBody } from "@trigger.dev/core/v3";
import { parse } from "dotenv";
import { z } from "zod";
import { prisma } from "~/db.server";
import { findProjectByRef } from "~/models/project.server";
import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server";
import { EnvironmentVariablesRepository } from "~/v3/environmentVariables/environmentVariablesRepository.server";

const ParamsSchema = z.object({
projectRef: z.string(),
slug: z.string(),
});

export async function action({ params, request }: ActionFunctionArgs) {
const parsedParams = ParamsSchema.safeParse(params);

if (!parsedParams.success) {
return json({ error: "Invalid params" }, { status: 400 });
}

const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);

if (!authenticationResult) {
return json({ error: "Invalid or Missing API key" }, { status: 401 });
}

const user = await prisma.user.findUnique({
where: {
id: authenticationResult.userId,
},
});

if (!user) {
return json({ error: "Invalid or Missing API key" }, { status: 401 });
}

const project = await findProjectByRef(parsedParams.data.projectRef, user.id);

if (!project) {
return json({ error: "Project not found" }, { status: 404 });
}

const environment = await prisma.runtimeEnvironment.findFirst({
where: {
projectId: project.id,
slug: parsedParams.data.slug,
},
});

if (!environment) {
return json({ error: "Environment not found" }, { status: 404 });
}

const repository = new EnvironmentVariablesRepository();

const body = await parseImportBody(request);

const result = await repository.create(project.id, user.id, {
overwrite: body.overwrite === true ? true : false,
environmentIds: [environment.id],
variables: Object.entries(body.variables).map(([key, value]) => ({
key,
value,
})),
});

if (result.success) {
return json({ success: true });
} else {
return json({ error: result.error, variableErrors: result.variableErrors }, { status: 400 });
}
}

async function parseImportBody(request: Request): Promise<ImportEnvironmentVariablesRequestBody> {
const contentType = request.headers.get("content-type") ?? "application/json";

if (contentType.includes("application/octet-stream")) {
// We have a "dotenv" formatted file uploaded
const buffer = await request.arrayBuffer();

const variables = parse(Buffer.from(buffer));

const overwrite = request.headers.get("x-overwrite") === "true";

return { variables, overwrite };
} else {
const rawBody = await request.json();

const body = ImportEnvironmentVariablesRequestBody.safeParse(rawBody);

if (!body.success) {
throw json({ error: "Invalid body" }, { status: 400 });
}

return body.data;
}
}
Loading
Loading