feature: project templates with docs

This commit is contained in:
Scott Wilson
2024-11-06 23:12:32 -08:00
parent c6fa647825
commit 9b246166a1
72 changed files with 2847 additions and 104 deletions
@@ -0,0 +1,25 @@
import { TUnpackedPermission } from "@app/ee/services/project-template/project-template-types";
import { getPredefinedRoles } from "@app/services/project-role/project-role-fns";
export const ProjectTemplateDefaultEnvironments = [
{ name: "Development", slug: "dev", position: 1 },
{ name: "Staging", slug: "staging", position: 2 },
{ name: "Production", slug: "prod", position: 3 }
];
export const DefaultProjectTemplateIdentifier = "default";
export const getDefaultProjectTemplate = (orgId: string) => ({
id: "b11b49a9-09a9-4443-916a-4246f9ff2c69",
name: DefaultProjectTemplateIdentifier,
createdAt: new Date(),
updatedAt: new Date(),
description: "Infisical's default project template",
environments: ProjectTemplateDefaultEnvironments,
roles: [...getPredefinedRoles("project-template")].map(({ name, slug, permissions }) => ({
name,
slug,
permissions: permissions as TUnpackedPermission[]
})),
orgId
});
@@ -0,0 +1,7 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TProjectTemplateDALFactory = ReturnType<typeof projectTemplateDALFactory>;
export const projectTemplateDALFactory = (db: TDbClient) => ormify(db, TableName.ProjectTemplates);
@@ -0,0 +1,205 @@
import { ForbiddenError } from "@casl/ability";
import { packRules } from "@casl/ability/extra";
import { TProjectTemplates } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { getDefaultProjectTemplate } from "@app/ee/services/project-template/project-template-constants";
import {
TCreateProjectTemplateDTO,
TProjectTemplateEnvironment,
TProjectTemplateRole,
TUnpackedPermission,
TUpdateProjectTemplateDTO
} from "@app/ee/services/project-template/project-template-types";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { OrgServiceActor } from "@app/lib/types";
import { unpackPermissions } from "@app/server/routes/santizedSchemas/permission";
import { getPredefinedRoles } from "@app/services/project-role/project-role-fns";
import { TProjectTemplateDALFactory } from "./project-template-dal";
type TProjectTemplatesServiceFactoryDep = {
licenseService: TLicenseServiceFactory;
permissionService: TPermissionServiceFactory;
projectTemplateDAL: TProjectTemplateDALFactory;
};
export type TProjectTemplateServiceFactory = ReturnType<typeof projectTemplateServiceFactory>;
const $unpackProjectTemplate = ({ roles, environments, ...rest }: TProjectTemplates) => ({
...rest,
environments: environments as TProjectTemplateEnvironment[],
roles: [
...getPredefinedRoles("project-template").map(({ name, slug, permissions }) => ({
name,
slug,
permissions: permissions as TUnpackedPermission[]
})),
...(roles as TProjectTemplateRole[]).map((role) => ({
...role,
permissions: unpackPermissions(role.permissions)
}))
]
});
export const projectTemplateServiceFactory = ({
licenseService,
permissionService,
projectTemplateDAL
}: TProjectTemplatesServiceFactoryDep) => {
const listProjectTemplatesByOrg = async (actor: OrgServiceActor) => {
const { permission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
const plan = await licenseService.getPlan(actor.orgId);
if (!plan.projectTemplates)
throw new BadRequestError({
message: "Failed to access project templates due to plan restriction. Upgrade plan to access project templates."
});
const projectTemplates = await projectTemplateDAL.find({
orgId: actor.orgId
});
return [
getDefaultProjectTemplate(actor.orgId),
...projectTemplates.map((template) => $unpackProjectTemplate(template))
];
};
const findProjectTemplatesById = async (id: string, actor: OrgServiceActor) => {
const { permission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
const plan = await licenseService.getPlan(actor.orgId);
if (!plan.projectTemplates)
throw new BadRequestError({
message: "Failed to access project template due to plan restriction. Upgrade plan to access project templates."
});
const [projectTemplate] = await projectTemplateDAL.find({
id,
orgId: actor.orgId // ensure from user org
});
if (!projectTemplate) throw new NotFoundError({ message: `Could not find project template with ID ${id}` });
return {
...$unpackProjectTemplate(projectTemplate),
packedRoles: projectTemplate.roles as TProjectTemplateRole[] // preserve packed for when applying template
};
};
const createProjectTemplate = async (
{ roles, environments, ...params }: TCreateProjectTemplateDTO,
actor: OrgServiceActor
) => {
const { permission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.ProjectTemplates);
const plan = await licenseService.getPlan(actor.orgId);
if (!plan.projectTemplates)
throw new BadRequestError({
message: "Failed to create project template due to plan restriction. Upgrade plan to access project templates."
});
const projectTemplate = await projectTemplateDAL.create({
...params,
roles: JSON.stringify(roles.map((role) => ({ ...role, permissions: packRules(role.permissions) }))),
environments: JSON.stringify(environments),
orgId: actor.orgId
});
return $unpackProjectTemplate(projectTemplate);
};
const updateProjectTemplateById = async (
id: string,
{ roles, environments, ...params }: TUpdateProjectTemplateDTO,
actor: OrgServiceActor
) => {
const { permission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
const plan = await licenseService.getPlan(actor.orgId);
if (!plan.projectTemplates)
throw new BadRequestError({
message: "Failed to update project template due to plan restriction. Upgrade plan to access project templates."
});
const projectTemplate = await projectTemplateDAL.updateById(id, {
...params,
roles: roles
? JSON.stringify(roles.map((role) => ({ ...role, permissions: packRules(role.permissions) })))
: undefined,
environments: environments ? JSON.stringify(environments) : undefined
});
return $unpackProjectTemplate(projectTemplate);
};
const deleteProjectTemplateById = async (id: string, actor: OrgServiceActor) => {
const { permission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.ProjectTemplates);
const plan = await licenseService.getPlan(actor.orgId);
if (!plan.projectTemplates)
throw new BadRequestError({
message: "Failed to delete project template due to plan restriction. Upgrade plan to access project templates."
});
const projectTemplate = await projectTemplateDAL.deleteById(id);
return $unpackProjectTemplate(projectTemplate);
};
return {
listProjectTemplatesByOrg,
createProjectTemplate,
updateProjectTemplateById,
deleteProjectTemplateById,
findProjectTemplatesById
};
};
@@ -0,0 +1,24 @@
import { z } from "zod";
import { TProjectEnvironments } from "@app/db/schemas";
import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
export type TProjectTemplateEnvironment = Pick<TProjectEnvironments, "name" | "slug" | "position">;
export type TProjectTemplateRole = {
slug: string;
name: string;
permissions: TProjectPermissionV2Schema[];
};
export type TCreateProjectTemplateDTO = {
name: string;
description?: string;
roles: TProjectTemplateRole[];
environments: TProjectTemplateEnvironment[];
};
export type TUpdateProjectTemplateDTO = Partial<TCreateProjectTemplateDTO>;
export type TUnpackedPermission = z.infer<typeof UnpackedPermissionSchema>;