chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,329 @@
|
||||
import { type PrismaClient } from "@trigger.dev/database";
|
||||
import { prisma } from "~/db.server";
|
||||
import { DeleteProjectService } from "~/services/deleteProject.server";
|
||||
import { BranchTrackingConfigSchema, type BranchTrackingConfig } from "~/v3/github";
|
||||
import { checkGitHubBranchExists } from "~/services/gitHub.server";
|
||||
import { errAsync, fromPromise, okAsync, ResultAsync } from "neverthrow";
|
||||
import { type BuildSettings } from "~/v3/buildSettings";
|
||||
|
||||
export class ProjectSettingsService {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
renameProject(projectId: string, newName: string) {
|
||||
return fromPromise(
|
||||
this.#prismaClient.project.update({
|
||||
where: {
|
||||
id: projectId,
|
||||
},
|
||||
data: {
|
||||
name: newName,
|
||||
},
|
||||
}),
|
||||
(error) => ({
|
||||
type: "other" as const,
|
||||
cause: error,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
deleteProject(projectId: string, userId: string) {
|
||||
const deleteProjectService = new DeleteProjectService(this.#prismaClient);
|
||||
|
||||
return fromPromise(deleteProjectService.call({ projectId, userId }), (error) => ({
|
||||
type: "other" as const,
|
||||
cause: error,
|
||||
}));
|
||||
}
|
||||
|
||||
connectGitHubRepo(
|
||||
projectId: string,
|
||||
organizationId: string,
|
||||
repositoryId: string,
|
||||
installationId: string
|
||||
) {
|
||||
const getRepository = () =>
|
||||
fromPromise(
|
||||
this.#prismaClient.githubRepository.findFirst({
|
||||
where: {
|
||||
id: repositoryId,
|
||||
installationId,
|
||||
installation: {
|
||||
organizationId: organizationId,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
defaultBranch: true,
|
||||
},
|
||||
}),
|
||||
(error) => ({
|
||||
type: "other" as const,
|
||||
cause: error,
|
||||
})
|
||||
).andThen((repository) => {
|
||||
if (!repository) {
|
||||
return errAsync({ type: "gh_repository_not_found" as const });
|
||||
}
|
||||
return okAsync(repository);
|
||||
});
|
||||
|
||||
const findExistingConnection = () =>
|
||||
fromPromise(
|
||||
this.#prismaClient.connectedGithubRepository.findFirst({
|
||||
where: {
|
||||
projectId: projectId,
|
||||
},
|
||||
}),
|
||||
(error) => ({ type: "other" as const, cause: error })
|
||||
);
|
||||
|
||||
const createConnectedRepo = (defaultBranch: string, previewDeploymentsEnabled: boolean) =>
|
||||
fromPromise(
|
||||
this.#prismaClient.connectedGithubRepository.create({
|
||||
data: {
|
||||
projectId: projectId,
|
||||
repositoryId: repositoryId,
|
||||
branchTracking: {
|
||||
prod: { branch: defaultBranch },
|
||||
staging: {},
|
||||
} satisfies BranchTrackingConfig,
|
||||
previewDeploymentsEnabled,
|
||||
},
|
||||
}),
|
||||
(error) => ({ type: "other" as const, cause: error })
|
||||
);
|
||||
|
||||
return ResultAsync.combine([
|
||||
getRepository(),
|
||||
findExistingConnection(),
|
||||
this.isPreviewEnvironmentEnabled(projectId),
|
||||
]).andThen(([repository, existingConnection, previewEnvironmentEnabled]) => {
|
||||
if (existingConnection) {
|
||||
return errAsync({ type: "project_already_has_connected_repository" as const });
|
||||
}
|
||||
|
||||
return createConnectedRepo(repository.defaultBranch, previewEnvironmentEnabled);
|
||||
});
|
||||
}
|
||||
|
||||
disconnectGitHubRepo(projectId: string) {
|
||||
return fromPromise(
|
||||
this.#prismaClient.connectedGithubRepository.delete({
|
||||
where: {
|
||||
projectId: projectId,
|
||||
},
|
||||
}),
|
||||
(error) => ({ type: "other" as const, cause: error })
|
||||
);
|
||||
}
|
||||
|
||||
updateGitSettings(
|
||||
projectId: string,
|
||||
productionBranch?: string,
|
||||
stagingBranch?: string,
|
||||
previewDeploymentsEnabled?: boolean
|
||||
) {
|
||||
const getExistingConnectedRepo = () =>
|
||||
fromPromise(
|
||||
this.#prismaClient.connectedGithubRepository.findFirst({
|
||||
where: {
|
||||
projectId: projectId,
|
||||
},
|
||||
include: {
|
||||
repository: {
|
||||
include: {
|
||||
installation: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
(error) => ({ type: "other" as const, cause: error })
|
||||
)
|
||||
.andThen((connectedRepo) => {
|
||||
if (!connectedRepo) {
|
||||
return errAsync({ type: "connected_gh_repository_not_found" as const });
|
||||
}
|
||||
return okAsync(connectedRepo);
|
||||
})
|
||||
.map((connectedRepo) => {
|
||||
const branchTrackingOrFailure = BranchTrackingConfigSchema.safeParse(
|
||||
connectedRepo.branchTracking
|
||||
);
|
||||
const branchTracking = branchTrackingOrFailure.success
|
||||
? branchTrackingOrFailure.data
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
...connectedRepo,
|
||||
branchTracking,
|
||||
};
|
||||
});
|
||||
|
||||
const validateProductionBranch = ({
|
||||
installationId,
|
||||
fullRepoName,
|
||||
oldProductionBranch,
|
||||
}: {
|
||||
installationId: number;
|
||||
fullRepoName: string;
|
||||
oldProductionBranch?: string;
|
||||
}) => {
|
||||
if (productionBranch && oldProductionBranch !== productionBranch) {
|
||||
return checkGitHubBranchExists(installationId, fullRepoName, productionBranch).andThen(
|
||||
(exists) => {
|
||||
if (!exists) {
|
||||
return errAsync({ type: "production_tracking_branch_not_found" as const });
|
||||
}
|
||||
return okAsync(productionBranch);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return okAsync(productionBranch);
|
||||
};
|
||||
|
||||
const validateStagingBranch = ({
|
||||
installationId,
|
||||
fullRepoName,
|
||||
oldStagingBranch,
|
||||
}: {
|
||||
installationId: number;
|
||||
fullRepoName: string;
|
||||
oldStagingBranch?: string;
|
||||
}) => {
|
||||
if (stagingBranch && oldStagingBranch !== stagingBranch) {
|
||||
return checkGitHubBranchExists(installationId, fullRepoName, stagingBranch).andThen(
|
||||
(exists) => {
|
||||
if (!exists) {
|
||||
return errAsync({ type: "staging_tracking_branch_not_found" as const });
|
||||
}
|
||||
return okAsync(stagingBranch);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return okAsync(stagingBranch);
|
||||
};
|
||||
|
||||
const updateConnectedRepo = (data: {
|
||||
productionBranch: string | undefined;
|
||||
stagingBranch: string | undefined;
|
||||
previewDeploymentsEnabled: boolean | undefined;
|
||||
}) =>
|
||||
fromPromise(
|
||||
this.#prismaClient.connectedGithubRepository.update({
|
||||
where: {
|
||||
projectId: projectId,
|
||||
},
|
||||
data: {
|
||||
branchTracking: {
|
||||
prod: data.productionBranch ? { branch: data.productionBranch } : {},
|
||||
staging: data.stagingBranch ? { branch: data.stagingBranch } : {},
|
||||
} satisfies BranchTrackingConfig,
|
||||
previewDeploymentsEnabled: data.previewDeploymentsEnabled,
|
||||
},
|
||||
}),
|
||||
(error) => ({ type: "other" as const, cause: error })
|
||||
);
|
||||
|
||||
return getExistingConnectedRepo()
|
||||
.andThen((connectedRepo) => {
|
||||
const installationId = Number(connectedRepo.repository.installation.appInstallationId);
|
||||
|
||||
return ResultAsync.combine([
|
||||
validateProductionBranch({
|
||||
installationId,
|
||||
fullRepoName: connectedRepo.repository.fullName,
|
||||
oldProductionBranch: connectedRepo.branchTracking?.prod?.branch,
|
||||
}),
|
||||
validateStagingBranch({
|
||||
installationId,
|
||||
fullRepoName: connectedRepo.repository.fullName,
|
||||
oldStagingBranch: connectedRepo.branchTracking?.staging?.branch,
|
||||
}),
|
||||
this.isPreviewEnvironmentEnabled(projectId),
|
||||
]);
|
||||
})
|
||||
.map(([productionBranch, stagingBranch, previewEnvironmentEnabled]) => ({
|
||||
productionBranch,
|
||||
stagingBranch,
|
||||
previewDeploymentsEnabled: previewDeploymentsEnabled && previewEnvironmentEnabled,
|
||||
}))
|
||||
.andThen(updateConnectedRepo);
|
||||
}
|
||||
|
||||
updateBuildSettings(projectId: string, buildSettings: BuildSettings) {
|
||||
return fromPromise(
|
||||
this.#prismaClient.project.update({
|
||||
where: {
|
||||
id: projectId,
|
||||
},
|
||||
data: {
|
||||
buildSettings: buildSettings,
|
||||
},
|
||||
}),
|
||||
(error) => ({
|
||||
type: "other" as const,
|
||||
cause: error,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
verifyProjectMembership(organizationSlug: string, projectSlug: string, userId: string) {
|
||||
const findProject = () =>
|
||||
fromPromise(
|
||||
this.#prismaClient.project.findFirst({
|
||||
where: {
|
||||
slug: projectSlug,
|
||||
organization: {
|
||||
slug: organizationSlug,
|
||||
members: {
|
||||
some: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
organizationId: true,
|
||||
},
|
||||
}),
|
||||
(error) => ({ type: "other" as const, cause: error })
|
||||
);
|
||||
|
||||
return findProject().andThen((project) => {
|
||||
if (!project) {
|
||||
return errAsync({ type: "user_not_in_project" as const });
|
||||
}
|
||||
|
||||
return okAsync({
|
||||
projectId: project.id,
|
||||
organizationId: project.organizationId,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private isPreviewEnvironmentEnabled(projectId: string) {
|
||||
return fromPromise(
|
||||
this.#prismaClient.runtimeEnvironment.findFirst({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
where: {
|
||||
projectId: projectId,
|
||||
slug: "preview",
|
||||
},
|
||||
}),
|
||||
(error) => ({
|
||||
type: "other" as const,
|
||||
cause: error,
|
||||
})
|
||||
).map((previewEnvironment) => previewEnvironment !== null);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user