Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,10 @@ export async function action({ request }: ActionFunctionArgs) {
}

const upsertBranchService = new UpsertBranchService();
const result = await upsertBranchService.call(userId, submission.value);
const result = await upsertBranchService.call(
{ type: "userMembership", userId },
submission.value
);

if (result.success) {
if (result.alreadyExisted) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { type ActionFunctionArgs, json } from "@remix-run/server-runtime";
import { tryCatch } from "@trigger.dev/core";
import { z } from "zod";
import { prisma } from "~/db.server";
import { authenticateRequest } from "~/services/apiAuth.server";
import { ArchiveBranchService } from "~/services/archiveBranch.server";
import { logger } from "~/services/logger.server";
import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server";

const ParamsSchema = z.object({
projectRef: z.string(),
Expand All @@ -21,7 +21,12 @@ export async function action({ request, params }: ActionFunctionArgs) {

logger.info("Archive branch", { url: request.url, params });

const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);
const authenticationResult = await authenticateRequest(request, {
personalAccessToken: true,
organizationAccessToken: true,
apiKey: false,
});

if (!authenticationResult) {
return json({ error: "Invalid or Missing Access Token" }, { status: 401 });
}
Expand Down Expand Up @@ -50,13 +55,16 @@ export async function action({ request, params }: ActionFunctionArgs) {
archivedAt: true,
},
where: {
organization: {
members: {
some: {
userId: authenticationResult.userId,
},
},
},
organization:
authenticationResult.type === "organizationAccessToken"
? { id: authenticationResult.result.organizationId }
: {
members: {
some: {
userId: authenticationResult.result.userId,
},
},
},
project: {
externalRef: projectRef,
},
Expand All @@ -74,9 +82,14 @@ export async function action({ request, params }: ActionFunctionArgs) {
}

const service = new ArchiveBranchService();
const result = await service.call(authenticationResult.userId, {
environmentId: environment.id,
});
const result = await service.call(
authenticationResult.type === "organizationAccessToken"
? { type: "orgId", organizationId: authenticationResult.result.organizationId }
: { type: "userMembership", userId: authenticationResult.result.userId },
{
environmentId: environment.id,
}
);

if (result.success) {
return json(result);
Expand Down
41 changes: 27 additions & 14 deletions apps/webapp/app/routes/api.v1.projects.$projectRef.branches.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { json, LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/server-runtime";
import { json, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/server-runtime";
import { tryCatch, UpsertBranchRequestBody } from "@trigger.dev/core/v3";
import { z } from "zod";
import { prisma } from "~/db.server";
import { authenticateRequest } from "~/services/apiAuth.server";
import { logger } from "~/services/logger.server";
import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server";
import { UpsertBranchService } from "~/services/upsertBranch.server";
Expand All @@ -19,7 +20,11 @@ export async function action({ request, params }: ActionFunctionArgs) {

logger.info("project upsert branch", { url: request.url });

const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);
const authenticationResult = await authenticateRequest(request, {
personalAccessToken: true,
organizationAccessToken: true,
apiKey: false,
});
if (!authenticationResult) {
return json({ error: "Invalid or Missing Access Token" }, { status: 401 });
}
Expand All @@ -38,13 +43,16 @@ export async function action({ request, params }: ActionFunctionArgs) {
},
where: {
externalRef: projectRef,
organization: {
members: {
some: {
userId: authenticationResult.userId,
},
},
},
organization:
authenticationResult.type === "organizationAccessToken"
? { id: authenticationResult.result.organizationId }
: {
members: {
some: {
userId: authenticationResult.result.userId,
},
},
},
},
});
if (!project) {
Expand Down Expand Up @@ -81,11 +89,16 @@ export async function action({ request, params }: ActionFunctionArgs) {
const { branch, env, git } = parsed.data;

const service = new UpsertBranchService();
const result = await service.call(authenticationResult.userId, {
branchName: branch,
parentEnvironmentId: previewEnvironment.id,
git,
});
const result = await service.call(
authenticationResult.type === "organizationAccessToken"
? { type: "orgId", organizationId: authenticationResult.result.organizationId }
: { type: "userMembership", userId: authenticationResult.result.userId },
{
branchName: branch,
parentEnvironmentId: previewEnvironment.id,
git,
}
);

if (!result.success) {
return json({ error: result.error }, { status: 400 });
Expand Down
7 changes: 6 additions & 1 deletion apps/webapp/app/routes/resources.branches.archive.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ export async function action({ request }: ActionFunctionArgs) {

const archiveBranchService = new ArchiveBranchService();

const result = await archiveBranchService.call(userId, submission.value);
const result = await archiveBranchService.call(
{ type: "userMembership", userId },
{
environmentId: submission.value.environmentId,
}
);

if (result.success) {
return redirectWithSuccessMessage(
Expand Down
32 changes: 24 additions & 8 deletions apps/webapp/app/services/archiveBranch.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,34 @@ export class ArchiveBranchService {
this.#prismaClient = prismaClient;
}

public async call(userId: string, { environmentId }: { environmentId: string }) {
public async call(
// The orgFilter approach is not ideal but we need to keep it this way for now because of how the service is used in routes and api endpoints.
// Currently authorization checks are spread across the controller/route layer and the service layer. Often we check in multiple places for org/project membership.
// Ideally we would take care of both the authentication and authorization checks in the controllers and routes.
// That would unify how we handle authorization and org/project membership checks. Also it would make the service layer queries simpler.
orgFilter:
| { type: "userMembership"; userId: string }
| { type: "orgId"; organizationId: string },
{
environmentId,
}: {
environmentId: string;
}
) {
try {
const environment = await this.#prismaClient.runtimeEnvironment.findFirstOrThrow({
where: {
id: environmentId,
organization: {
members: {
some: {
userId: userId,
},
},
},
organization:
orgFilter.type === "userMembership"
? {
members: {
some: {
userId: orgFilter.userId,
},
},
}
: { id: orgFilter.organizationId },
},
include: {
organization: {
Expand Down
28 changes: 20 additions & 8 deletions apps/webapp/app/services/upsertBranch.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,16 @@ export class UpsertBranchService {
this.#prismaClient = prismaClient;
}

public async call(userId: string, { parentEnvironmentId, branchName, git }: CreateBranchOptions) {
public async call(
// The orgFilter approach is not ideal but we need to keep it this way for now because of how the service is used in routes and api endpoints.
// Currently authorization checks are spread across the controller/route layer and the service layer. Often we check in multiple places for org/project membership.
// Ideally we would take care of both the authentication and authorization checks in the controllers and routes.
// That would unify how we handle authorization and org/project membership checks. Also it would make the service layer queries simpler.
orgFilter:
| { type: "userMembership"; userId: string }
| { type: "orgId"; organizationId: string },
{ parentEnvironmentId, branchName, git }: CreateBranchOptions
) {
const sanitizedBranchName = sanitizeBranchName(branchName);
if (!sanitizedBranchName) {
return {
Expand All @@ -34,13 +43,16 @@ export class UpsertBranchService {
const parentEnvironment = await this.#prismaClient.runtimeEnvironment.findFirst({
where: {
id: parentEnvironmentId,
organization: {
members: {
some: {
userId: userId,
},
},
},
organization:
orgFilter.type === "userMembership"
? {
members: {
some: {
userId: orgFilter.userId,
},
},
}
: { id: orgFilter.organizationId },
},
include: {
organization: {
Expand Down
Loading