Skip to content

Commit 7f6ac40

Browse files
committed
add: label crud with project
1 parent 37b53dc commit 7f6ac40

File tree

16 files changed

+1424
-30
lines changed

16 files changed

+1424
-30
lines changed

apps/api/src/modules/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import utility from "./utility";
44
import auth from "./auth";
55
import userRoutes from "./user";
66
import project from "./project";
7+
import labels from "./labels";
78

89
const getOptionsWithPrefix = (
910
options: FastifyPluginOptions,
@@ -28,6 +29,10 @@ export default fastifyPlugin(
2829
getOptionsWithPrefix(options, "/edge/users")
2930
),
3031
fastify.register(project, getOptionsWithPrefix(options, "/edge/project")),
32+
fastify.register(
33+
labels,
34+
getOptionsWithPrefix(options, "/edge/projects/:projectId/labels")
35+
),
3136
]);
3237
}
3338
);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { FastifyInstance, FastifyPluginOptions } from "fastify";
2+
import { fastifyPlugin } from "fastify-plugin";
3+
import LabelRoute from "./label.route";
4+
5+
export default fastifyPlugin(
6+
async (fastify: FastifyInstance, options: FastifyPluginOptions) => {
7+
await fastify.register(LabelRoute, options);
8+
}
9+
);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { FastifyReply, FastifyRequest } from "fastify";
2+
import type { DbNewLabel } from "@taskcord/database";
3+
import type LabelService from "./label.service";
4+
5+
export default class LabelController {
6+
private labelService: LabelService;
7+
8+
constructor(labelService: LabelService) {
9+
this.labelService = labelService;
10+
}
11+
12+
// Create a new label
13+
public async createLabel(
14+
request: FastifyRequest<{ Body: DbNewLabel }>,
15+
reply: FastifyReply
16+
) {
17+
const labelData = request.body;
18+
19+
const label = await this.labelService.createLabel(labelData);
20+
21+
return reply.code(201).send({ taskLabel: label });
22+
}
23+
24+
public async getAllProjectLabels(
25+
request: FastifyRequest<{ Params: { projectId: string } }>,
26+
reply: FastifyReply
27+
) {
28+
const labels = await this.labelService.getAllLabelsByProjectId(
29+
request.params.projectId
30+
);
31+
return reply.send({ taskLabels: labels });
32+
}
33+
34+
public async updateLabel(
35+
request: FastifyRequest<{
36+
Params: { labelId: string };
37+
Body: DbNewLabel;
38+
}>,
39+
reply: FastifyReply
40+
) {
41+
const labelData = request.body;
42+
const label = await this.labelService.updateLabel(
43+
request.params.labelId,
44+
labelData
45+
);
46+
return reply.send({ taskLabel: label });
47+
}
48+
49+
public async deleteLabel(
50+
request: FastifyRequest<{ Params: { labelId: string } }>,
51+
reply: FastifyReply
52+
) {
53+
const label = await this.labelService.deleteLabel(request.params.labelId);
54+
return reply.send({ taskLabel: label });
55+
}
56+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import type { FastifyInstance } from "fastify";
2+
import LabelController from "./label.controller";
3+
import LabelService from "./label.service";
4+
import { labelSchemas, labelSchemaRef } from "./label.schema";
5+
6+
export default function LabelsRoute(fastify: FastifyInstance) {
7+
// Register schemas
8+
for (const schema of labelSchemas) {
9+
fastify.addSchema(schema);
10+
}
11+
12+
const labelController = new LabelController(new LabelService());
13+
14+
// Create a new task label
15+
fastify.post(
16+
"/",
17+
{
18+
onRequest: [fastify.jwtAuth],
19+
schema: {
20+
tags: ["Task Labels"],
21+
description: "Create a new task label",
22+
body: labelSchemaRef("createLabelSchema"),
23+
response: {
24+
201: labelSchemaRef("labelResponse"),
25+
400: labelSchemaRef("errorResponse"),
26+
},
27+
},
28+
},
29+
// @ts-expect-error - this is a bug in fastify-zod
30+
labelController.createLabel.bind(labelController)
31+
);
32+
33+
// Get all task labels of a project
34+
fastify.get(
35+
"/",
36+
{
37+
onRequest: [fastify.jwtAuth],
38+
schema: {
39+
tags: ["Task Labels"],
40+
description: "Get all task labels of a project",
41+
response: {
42+
200: labelSchemaRef("labelsResponse"),
43+
},
44+
},
45+
},
46+
// @ts-expect-error - this is a bug in fastify-zod
47+
labelController.getAllProjectLabels.bind(labelController)
48+
);
49+
50+
// Update a task label
51+
fastify.put(
52+
"/:labelId",
53+
{
54+
onRequest: [fastify.jwtAuth],
55+
schema: {
56+
tags: ["Task Labels"],
57+
description: "Update a task label",
58+
body: labelSchemaRef("updateLabelSchema"),
59+
response: {
60+
200: labelSchemaRef("labelResponse"),
61+
400: labelSchemaRef("errorResponse"),
62+
},
63+
},
64+
},
65+
// @ts-expect-error - this is a bug in fastify-zod
66+
labelController.updateLabel.bind(labelController)
67+
);
68+
69+
// Delete a task label
70+
fastify.delete(
71+
"/:labelId",
72+
{
73+
onRequest: [fastify.jwtAuth],
74+
schema: {
75+
tags: ["Task Labels"],
76+
description: "Delete a task label",
77+
response: {
78+
200: labelSchemaRef("labelResponse"),
79+
400: labelSchemaRef("errorResponse"),
80+
},
81+
},
82+
},
83+
// @ts-expect-error - this is a bug in fastify-zod
84+
labelController.deleteLabel.bind(labelController)
85+
);
86+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { z } from "zod";
2+
import { buildJsonSchemas } from "fastify-zod";
3+
4+
const ProjectTaskLabel = {
5+
id: z.string().uuid(),
6+
label: z.string(),
7+
color: z.string(),
8+
description: z.string(),
9+
projectId: z.string().uuid(),
10+
creatorId: z.string().uuid(),
11+
};
12+
13+
const createLabelSchema = z.object({
14+
label: z.string(),
15+
color: z.string(),
16+
description: z.string(),
17+
// projectId: z.string().uuid(),
18+
// creatorId: z.string().uuid(),
19+
});
20+
21+
const updateLabelSchema = z.object({
22+
label: z.string().optional(),
23+
color: z.string().optional(),
24+
description: z.string().optional(),
25+
// projectId: z.string().uuid().optional(),
26+
// creatorId: z.string().uuid().optional(),
27+
});
28+
29+
const taskLabelResponseSchema = z.object({
30+
taskLabel: z.object(ProjectTaskLabel),
31+
});
32+
33+
const taskLabelsResponseSchema = z.object({
34+
taskLabels: z.array(z.object(ProjectTaskLabel)),
35+
});
36+
37+
const errorResponseSchema = z.object({
38+
statusCode: z.number(),
39+
error: z.string(),
40+
message: z.string(),
41+
});
42+
43+
const labelSchemaWithId = z.object({
44+
id: z.string().uuid(),
45+
});
46+
export const { schemas: labelSchemas, $ref: labelSchemaRef } = buildJsonSchemas(
47+
{
48+
createLabelSchema,
49+
updateLabelSchema,
50+
labelResponse: taskLabelResponseSchema,
51+
labelsResponse: taskLabelsResponseSchema,
52+
errorResponse: errorResponseSchema,
53+
labelSchemaWithId,
54+
} as const,
55+
{ $id: "LabelSchema" }
56+
);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { LabelDal, type DbLabel, type DbNewLabel } from "@taskcord/database";
2+
3+
export default class LabelService {
4+
public async createLabel(labelData: DbNewLabel): Promise<DbLabel> {
5+
const label = await LabelDal.createLabel(labelData);
6+
7+
return label;
8+
}
9+
10+
public async getAllLabelsByProjectId(projectId: string): Promise<DbLabel[]> {
11+
return LabelDal.getLabelsByProjectId(projectId);
12+
}
13+
14+
public async updateLabel(
15+
id: string,
16+
labelData: Partial<DbNewLabel>
17+
): Promise<DbLabel | null> {
18+
return LabelDal.updateLabel(id, labelData);
19+
}
20+
21+
public async deleteLabel(id: string): Promise<DbLabel | null> {
22+
return LabelDal.deleteLabel(id);
23+
}
24+
}

apps/api/src/modules/project/project.controller.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ export default class ProjectController {
4545
}
4646

4747
public async getAllProjects(request: FastifyRequest, reply: FastifyReply) {
48-
const projects = await this.projectService.getAllProjects();
48+
const projects = await this.projectService.getAllProjects(
49+
request.jwtUser.id
50+
);
4951
return reply.send({ projects });
5052
}
5153

apps/api/src/modules/project/project.service.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ export default class ProjectService {
3030
}
3131

3232
public async getProject(id: string): Promise<DbProject | null> {
33-
return await ProjectDal.getProjectById(id);
33+
return ProjectDal.getProjectById(id);
3434
}
3535

36-
public async getAllProjects(): Promise<DbProject[]> {
37-
return await ProjectDal.getAllProjects();
36+
public async getAllProjects(creatorId: string): Promise<DbProject[]> {
37+
return ProjectDal.getProjectsByCreatorId(creatorId);
3838
}
3939

4040
public async getUserProjects(userDiscordId: string): Promise<DbProject[]> {
@@ -45,11 +45,11 @@ export default class ProjectService {
4545
}
4646

4747
// Get projects where the user is the creator
48-
return await ProjectDal.getProjectsByCreatorId(user.id);
48+
return ProjectDal.getProjectsByCreatorId(user.id);
4949
}
5050

5151
public async getProjectsByManager(managerId: string): Promise<DbProject[]> {
52-
return await ProjectDal.getProjectsByManagerId(managerId);
52+
return ProjectDal.getProjectsByManagerId(managerId);
5353
}
5454

5555
public async updateProject(

apps/api/src/plugins/swagger.ts

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,53 @@ import type { FastifyInstance } from "fastify";
99
* @see https://github.com/fastify/fastify-swagger
1010
*/
1111
export default fastifyPlugin(
12-
async (fastify: FastifyInstance) => {
13-
await fastify.register(fastifySwagger, {
14-
mode: "dynamic",
15-
openapi: {
16-
info: {
17-
title: "Taskcord API",
18-
version: "0.0.5",
19-
},
12+
async (fastify: FastifyInstance) => {
13+
await fastify.register(fastifySwagger, {
14+
mode: "dynamic",
15+
openapi: {
16+
info: {
17+
title: "Taskcord API",
18+
version: "0.0.5",
19+
},
20+
},
21+
});
22+
await fastify.register(fastifySwaggerUI, {
23+
routePrefix: "/api/docs",
24+
initOAuth: {},
25+
logLevel: "error",
26+
uiConfig: {
27+
docExpansion: "full",
28+
deepLinking: false,
29+
persistAuthorization: true,
30+
},
31+
staticCSP: true,
32+
transformSpecification: (swaggerObject: any) => {
33+
swaggerObject.components = {
34+
...swaggerObject.components,
35+
securitySchemes: {
36+
bearerAuth: {
37+
type: "http",
38+
scheme: "bearer",
39+
bearerFormat: "JWT",
40+
description: "Enter your JWT token in the format: Bearer <token>",
2041
},
21-
});
42+
},
43+
};
2244

23-
await fastify.register(fastifySwaggerUI, {
24-
routePrefix: "/api/docs",
25-
initOAuth: {},
26-
logLevel: "error",
27-
uiConfig: {
28-
docExpansion: "full",
29-
deepLinking: false,
30-
},
31-
staticCSP: true,
32-
});
33-
},
34-
{
35-
dependencies: ["env-config"],
36-
fastify: "5.x",
37-
}
45+
// Add global security requirement
46+
swaggerObject.security = [
47+
{
48+
bearerAuth: [],
49+
},
50+
];
51+
52+
return swaggerObject;
53+
},
54+
transformSpecificationClone: true,
55+
});
56+
},
57+
{
58+
dependencies: ["env-config"],
59+
fastify: "5.x",
60+
}
3861
);

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
"run:api": "node ./apps/api/dist/index.js",
77
"clean": "turbo run clean",
88
"dev": "turbo run dev",
9+
"dev:api": "turbo run dev --filter=api",
10+
"dev:bot": "turbo run dev --filter=bot",
911
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
1012
"lint": "turbo run lint",
1113
"test": "turbo run test",

0 commit comments

Comments
 (0)