feat: added support for version limit update

This commit is contained in:
Sheen Capadngan
2024-05-31 23:18:02 +08:00
parent 9117067ab5
commit abd8d6aa8a
8 changed files with 185 additions and 2 deletions
@@ -334,6 +334,44 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
} }
}); });
server.route({
method: "PUT",
url: "/:workspaceSlug/version-limit",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
workspaceSlug: z.string().trim()
}),
body: z.object({
pitVersionLimit: z.number().min(1).max(100)
}),
response: {
200: z.object({
message: z.string(),
workspace: ProjectsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspace = await server.services.project.updateVersionLimit({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
pitVersionLimit: req.body.pitVersionLimit,
workspaceSlug: req.params.workspaceSlug
});
return {
message: "Successfully changed workspace version limit",
workspace
};
}
});
server.route({ server.route({
method: "GET", method: "GET",
url: "/:workspaceId/integrations", url: "/:workspaceId/integrations",
@@ -39,6 +39,7 @@ import {
TToggleProjectAutoCapitalizationDTO, TToggleProjectAutoCapitalizationDTO,
TUpdateProjectDTO, TUpdateProjectDTO,
TUpdateProjectNameDTO, TUpdateProjectNameDTO,
TUpdateProjectVersionLimitDTO,
TUpgradeProjectDTO TUpgradeProjectDTO
} from "./project-types"; } from "./project-types";
@@ -407,6 +408,35 @@ export const projectServiceFactory = ({
return updatedProject; return updatedProject;
}; };
const updateVersionLimit = async ({
actor,
actorId,
actorOrgId,
actorAuthMethod,
pitVersionLimit,
workspaceSlug
}: TUpdateProjectVersionLimitDTO) => {
const project = await projectDAL.findProjectBySlug(workspaceSlug, actorOrgId);
if (!project) {
throw new BadRequestError({
message: "Project not found"
});
}
const { hasRole } = await permissionService.getProjectPermission(
actor,
actorId,
project.id,
actorAuthMethod,
actorOrgId
);
if (!hasRole(ProjectMembershipRole.Admin))
throw new BadRequestError({ message: "Only admins are allowed to take this action" });
return projectDAL.updateById(project.id, { pitVersionLimit });
};
const updateName = async ({ const updateName = async ({
projectId, projectId,
actor, actor,
@@ -502,6 +532,7 @@ export const projectServiceFactory = ({
getAProject, getAProject,
toggleAutoCapitalization, toggleAutoCapitalization,
updateName, updateName,
upgradeProject upgradeProject,
updateVersionLimit
}; };
}; };
@@ -43,6 +43,11 @@ export type TToggleProjectAutoCapitalizationDTO = {
autoCapitalization: boolean; autoCapitalization: boolean;
} & TProjectPermission; } & TProjectPermission;
export type TUpdateProjectVersionLimitDTO = {
pitVersionLimit: number;
workspaceSlug: string;
} & Omit<TProjectPermission, "projectId">;
export type TUpdateProjectNameDTO = { export type TUpdateProjectNameDTO = {
name: string; name: string;
} & TProjectPermission; } & TProjectPermission;
@@ -20,6 +20,7 @@ import {
TUpdateWorkspaceIdentityRoleDTO, TUpdateWorkspaceIdentityRoleDTO,
TUpdateWorkspaceUserRoleDTO, TUpdateWorkspaceUserRoleDTO,
UpdateEnvironmentDTO, UpdateEnvironmentDTO,
UpdatePitVersionLimitDTO,
Workspace Workspace
} from "./types"; } from "./types";
@@ -249,6 +250,21 @@ export const useToggleAutoCapitalization = () => {
}); });
}; };
export const useUpdateWorkspaceVersionLimit = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, UpdatePitVersionLimitDTO>({
mutationFn: ({ projectSlug, pitVersionLimit }) => {
return apiRequest.put(`/api/v1/workspace/${projectSlug}/version-limit`, {
pitVersionLimit
});
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
}
});
};
export const useDeleteWorkspace = () => { export const useDeleteWorkspace = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
+3 -1
View File
@@ -16,6 +16,7 @@ export type Workspace = {
upgradeStatus: string | null; upgradeStatus: string | null;
autoCapitalization: boolean; autoCapitalization: boolean;
environments: WorkspaceEnv[]; environments: WorkspaceEnv[];
pitVersionLimit: number;
slug: string; slug: string;
}; };
@@ -48,6 +49,7 @@ export type CreateWorkspaceDTO = {
}; };
export type RenameWorkspaceDTO = { workspaceID: string; newWorkspaceName: string }; export type RenameWorkspaceDTO = { workspaceID: string; newWorkspaceName: string };
export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number };
export type ToggleAutoCapitalizationDTO = { workspaceID: string; state: boolean }; export type ToggleAutoCapitalizationDTO = { workspaceID: string; state: boolean };
export type DeleteWorkspaceDTO = { workspaceID: string }; export type DeleteWorkspaceDTO = { workspaceID: string };
@@ -128,4 +130,4 @@ export type TUpdateWorkspaceGroupRoleDTO = {
temporaryAccessStartTime: string; temporaryAccessStartTime: string;
} }
)[]; )[];
}; };
@@ -0,0 +1,88 @@
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input } from "@app/components/v2";
import { useProjectPermission, useWorkspace } from "@app/context";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { useUpdateWorkspaceVersionLimit } from "@app/hooks/api/workspace/queries";
const formSchema = z.object({
pitVersionLimit: z.coerce.number().min(1).max(100)
});
type TForm = z.infer<typeof formSchema>;
export const PointInTimeVersionLimitSection = () => {
const { mutateAsync: updatePitVersion } = useUpdateWorkspaceVersionLimit();
const { currentWorkspace } = useWorkspace();
const { membership } = useProjectPermission();
const {
control,
formState: { isSubmitting },
handleSubmit
} = useForm<TForm>({
resolver: zodResolver(formSchema),
values: {
pitVersionLimit: currentWorkspace?.pitVersionLimit || 10
}
});
if (!currentWorkspace) return null;
const handleVersionLimitSubmit = async ({ pitVersionLimit }: TForm) => {
try {
await updatePitVersion({
pitVersionLimit,
projectSlug: currentWorkspace.slug
});
createNotification({
text: "Successfully updated version limit",
type: "success"
});
} catch (err) {
createNotification({
text: "Failed updating project's version limit",
type: "error"
});
}
};
const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin);
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex w-full items-center justify-between">
<p className="text-xl font-semibold">Point in Time Recovery</p>
</div>
<p className="mb-4 mt-2 max-w-2xl text-sm text-gray-400">
This defines the maximum number of folder snapshots, secret versions, and folder versions
that are retained by the system
</p>
<form onSubmit={handleSubmit(handleVersionLimitSubmit)} autoComplete="off">
<div className="max-w-xs">
<Controller
control={control}
defaultValue={0}
name="pitVersionLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="Version Limit"
>
<Input {...field} type="number" min={1} step={1} isDisabled={!isAdmin} />
</FormControl>
)}
/>
</div>
<Button colorSchema="secondary" type="submit" isLoading={isSubmitting} disabled={!isAdmin}>
Save
</Button>
</form>
</div>
);
};
@@ -0,0 +1 @@
export { PointInTimeVersionLimitSection } from "./PointInTimeVersionLimitSection";
@@ -3,6 +3,7 @@ import { BackfillSecretReferenceSecretion } from "../BackfillSecretReferenceSect
import { DeleteProjectSection } from "../DeleteProjectSection"; import { DeleteProjectSection } from "../DeleteProjectSection";
import { E2EESection } from "../E2EESection"; import { E2EESection } from "../E2EESection";
import { EnvironmentSection } from "../EnvironmentSection"; import { EnvironmentSection } from "../EnvironmentSection";
import { PointInTimeVersionLimitSection } from "../PointInTimeVersionLimitSection";
import { ProjectNameChangeSection } from "../ProjectNameChangeSection"; import { ProjectNameChangeSection } from "../ProjectNameChangeSection";
import { SecretTagsSection } from "../SecretTagsSection"; import { SecretTagsSection } from "../SecretTagsSection";
@@ -14,6 +15,7 @@ export const ProjectGeneralTab = () => {
<SecretTagsSection /> <SecretTagsSection />
<AutoCapitalizationSection /> <AutoCapitalizationSection />
<E2EESection /> <E2EESection />
<PointInTimeVersionLimitSection />
<BackfillSecretReferenceSecretion /> <BackfillSecretReferenceSecretion />
<DeleteProjectSection /> <DeleteProjectSection />
</div> </div>