feat: added update, get by id and get by slug as tag api methods

This commit is contained in:
=
2024-06-17 15:13:11 +05:30
parent 2780414fcb
commit 65a2b0116b
7 changed files with 216 additions and 13 deletions
+10 -10
View File
@@ -2459,9 +2459,9 @@
} }
}, },
"node_modules/@fastify/session": { "node_modules/@fastify/session": {
"version": "10.7.0", "version": "10.9.0",
"resolved": "https://registry.npmjs.org/@fastify/session/-/session-10.7.0.tgz", "resolved": "https://registry.npmjs.org/@fastify/session/-/session-10.9.0.tgz",
"integrity": "sha512-ECA75gnyaxcyIukgyO2NGT3XdbLReNl/pTKrrkRfDc6pVqNtdptwwfx9KXrIMOfsO4B3m84eF3wZ9GgnebiZ4w==", "integrity": "sha512-u/c42RuAaxCeEuRCAwK2+/SfGqKOd0NSyRzEvDwFBWySQoKUZQyb9OmmJSWJBbOP1OfaU2OsDrjbPbghE1l/YQ==",
"dependencies": { "dependencies": {
"fastify-plugin": "^4.0.0", "fastify-plugin": "^4.0.0",
"safe-stable-stringify": "^2.3.1" "safe-stable-stringify": "^2.3.1"
@@ -6301,12 +6301,12 @@
} }
}, },
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.2", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"fill-range": "^7.0.1" "fill-range": "^7.1.1"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -7957,9 +7957,9 @@
} }
}, },
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.0.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
+15
View File
@@ -508,12 +508,27 @@ export const SECRET_TAGS = {
LIST: { LIST: {
projectId: "The ID of the project to list tags from." projectId: "The ID of the project to list tags from."
}, },
GET_TAG_BY_ID: {
projectId: "The ID of the project to get tags from.",
tagId: "The ID of the tag to get details"
},
GET_TAG_BY_SLUG: {
projectId: "The ID of the project to get tags from.",
tagSlug: "The slug of the tag to get details"
},
CREATE: { CREATE: {
projectId: "The ID of the project to create the tag in.", projectId: "The ID of the project to create the tag in.",
name: "The name of the tag to create.", name: "The name of the tag to create.",
slug: "The slug of the tag to create.", slug: "The slug of the tag to create.",
color: "The color of the tag to create." color: "The color of the tag to create."
}, },
UPDATE: {
projectId: "The ID of the project to update the tag in.",
tagId: "The ID of the tag to get details",
name: "The name of the tag to update.",
slug: "The slug of the tag to update.",
color: "The color of the tag to update."
},
DELETE: { DELETE: {
tagId: "The ID of the tag to delete.", tagId: "The ID of the tag to delete.",
projectId: "The ID of the project to delete the tag from." projectId: "The ID of the project to delete the tag from."
+12
View File
@@ -59,6 +59,18 @@ export class BadRequestError extends Error {
} }
} }
export class NotFoundError extends Error {
name: string;
error: unknown;
constructor({ name, error, message }: { message?: string; name?: string; error?: unknown }) {
super(message ?? "The requested entity is not found");
this.name = name || "NotFound";
this.error = error;
}
}
export class DisableRotationErrors extends Error { export class DisableRotationErrors extends Error {
name: string; name: string;
@@ -6,6 +6,7 @@ import {
BadRequestError, BadRequestError,
DatabaseError, DatabaseError,
InternalServerError, InternalServerError,
NotFoundError,
ScimRequestError, ScimRequestError,
UnauthorizedError UnauthorizedError
} from "@app/lib/errors"; } from "@app/lib/errors";
@@ -15,6 +16,8 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
req.log.error(error); req.log.error(error);
if (error instanceof BadRequestError) { if (error instanceof BadRequestError) {
void res.status(400).send({ statusCode: 400, message: error.message, error: error.name }); void res.status(400).send({ statusCode: 400, message: error.message, error: error.name });
} else if (error instanceof NotFoundError) {
void res.status(404).send({ statusCode: 404, message: error.message, error: error.name });
} else if (error instanceof UnauthorizedError) { } else if (error instanceof UnauthorizedError) {
void res.status(403).send({ statusCode: 403, message: error.message, error: error.name }); void res.status(403).send({ statusCode: 403, message: error.message, error: error.name });
} else if (error instanceof DatabaseError || error instanceof InternalServerError) { } else if (error instanceof DatabaseError || error instanceof InternalServerError) {
@@ -36,6 +36,67 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
} }
}); });
server.route({
method: "GET",
url: "/:projectId/tags/:tagId",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim().describe(SECRET_TAGS.GET_TAG_BY_ID.projectId),
tagId: z.string().trim().describe(SECRET_TAGS.GET_TAG_BY_ID.tagId)
}),
response: {
200: z.object({
workspaceTag: SecretTagsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.getTagById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.tagId
});
return { workspaceTag };
}
});
server.route({
method: "GET",
url: "/:projectId/tags/slug/:tagSlug",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim().describe(SECRET_TAGS.GET_TAG_BY_SLUG.projectId),
tagSlug: z.string().trim().describe(SECRET_TAGS.GET_TAG_BY_SLUG.tagSlug)
}),
response: {
200: z.object({
workspaceTag: SecretTagsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.getTagBySlug({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
slug: req.params.tagSlug,
projectId: req.params.projectId
});
return { workspaceTag };
}
});
server.route({ server.route({
method: "POST", method: "POST",
url: "/:projectId/tags", url: "/:projectId/tags",
@@ -71,6 +132,42 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
} }
}); });
server.route({
method: "PATCH",
url: "/:projectId/tags/:tagId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
projectId: z.string().trim().describe(SECRET_TAGS.UPDATE.projectId),
tagId: z.string().trim().describe(SECRET_TAGS.UPDATE.tagId)
}),
body: z.object({
name: z.string().trim().describe(SECRET_TAGS.UPDATE.name),
slug: z.string().trim().describe(SECRET_TAGS.UPDATE.slug),
color: z.string().trim().describe(SECRET_TAGS.UPDATE.color)
}),
response: {
200: z.object({
workspaceTag: SecretTagsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.updateTag({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
id: req.params.tagId
});
return { workspaceTag };
}
});
server.route({ server.route({
method: "DELETE", method: "DELETE",
url: "/:projectId/tags/:tagId", url: "/:projectId/tags/:tagId",
@@ -2,10 +2,17 @@ import { ForbiddenError } from "@casl/ability";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TSecretTagDALFactory } from "./secret-tag-dal"; import { TSecretTagDALFactory } from "./secret-tag-dal";
import { TCreateTagDTO, TDeleteTagDTO, TListProjectTagsDTO } from "./secret-tag-types"; import {
TCreateTagDTO,
TDeleteTagDTO,
TGetTagByIdDTO,
TGetTagBySlugDTO,
TListProjectTagsDTO,
TUpdateTagDTO
} from "./secret-tag-types";
type TSecretTagServiceFactoryDep = { type TSecretTagServiceFactoryDep = {
secretTagDAL: TSecretTagDALFactory; secretTagDAL: TSecretTagDALFactory;
@@ -48,6 +55,28 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
return newTag; return newTag;
}; };
const updateTag = async ({ actorId, actor, actorOrgId, actorAuthMethod, id, name, color, slug }: TUpdateTagDTO) => {
const tag = await secretTagDAL.findById(id);
if (!tag) throw new BadRequestError({ message: "Tag doesn't exist" });
if (slug) {
const existingTag = await secretTagDAL.findOne({ slug, projectId: tag.projectId });
if (existingTag && existingTag.id !== tag.id) throw new BadRequestError({ message: "Tag already exist" });
}
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
tag.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags);
const updatedTag = await secretTagDAL.updateById(tag.id, { name, color, slug });
return updatedTag;
};
const deleteTag = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TDeleteTagDTO) => { const deleteTag = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TDeleteTagDTO) => {
const tag = await secretTagDAL.findById(id); const tag = await secretTagDAL.findById(id);
if (!tag) throw new BadRequestError({ message: "Tag doesn't exist" }); if (!tag) throw new BadRequestError({ message: "Tag doesn't exist" });
@@ -65,6 +94,38 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
return deletedTag; return deletedTag;
}; };
const getTagById = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TGetTagByIdDTO) => {
const tag = await secretTagDAL.findById(id);
if (!tag) throw new NotFoundError({ message: "Tag doesn't exist" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
tag.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
return tag;
};
const getTagBySlug = async ({ actorId, actor, actorOrgId, actorAuthMethod, slug, projectId }: TGetTagBySlugDTO) => {
const tag = await secretTagDAL.findOne({ projectId, slug });
if (!tag) throw new NotFoundError({ message: "Tag doesn't exist" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
tag.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
return tag;
};
const getProjectTags = async ({ actor, actorId, actorOrgId, actorAuthMethod, projectId }: TListProjectTagsDTO) => { const getProjectTags = async ({ actor, actorId, actorOrgId, actorAuthMethod, projectId }: TListProjectTagsDTO) => {
const { permission } = await permissionService.getProjectPermission( const { permission } = await permissionService.getProjectPermission(
actor, actor,
@@ -79,5 +140,5 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
return tags; return tags;
}; };
return { createTag, deleteTag, getProjectTags }; return { createTag, deleteTag, getProjectTags, getTagById, getTagBySlug, updateTag };
}; };
@@ -6,6 +6,21 @@ export type TCreateTagDTO = {
slug: string; slug: string;
} & TProjectPermission; } & TProjectPermission;
export type TUpdateTagDTO = {
id: string;
name?: string;
slug?: string;
color?: string;
} & Omit<TProjectPermission, "projectId">;
export type TGetTagByIdDTO = {
id: string;
} & Omit<TProjectPermission, "projectId">;
export type TGetTagBySlugDTO = {
slug: string;
} & TProjectPermission;
export type TDeleteTagDTO = { export type TDeleteTagDTO = {
id: string; id: string;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;