Repository: lesada/cocriar-api
Pull request #3: feat: delete event
Author: login: ericardmatosz
Created: 2025-05-01T23:08:50Z | Merged: 2025-05-05T01:01:00Z



Diff:
diff --git a/src/http/controllers/events/create-event.ts b/src/http/controllers/events/create-event.ts
index 97dc0c3..95307c2 100644
--- a/src/http/controllers/events/create-event.ts
+++ b/src/http/controllers/events/create-event.ts
@@ -1,4 +1,4 @@
-import { createEventsService } from "@/services/events/create-event";
+import { createEventService } from "@/services/events/create-event";
 import type { FastifyReply, FastifyRequest } from "fastify";
 import { z } from "zod";
 
@@ -17,6 +17,6 @@ export const createEventBodySchema = z.object({
 
 export async function createEvent(req: FastifyRequest, rep: FastifyReply) {
 	const parsed = createEventBodySchema.parse(req.body);
-	const event = await createEventsService(parsed);
+	const event = await createEventService(parsed);
 	return rep.status(201).send({ event });
 }
diff --git a/src/http/controllers/events/delete-event.ts b/src/http/controllers/events/delete-event.ts
new file mode 100644
index 0000000..ac954e6
--- /dev/null
+++ b/src/http/controllers/events/delete-event.ts
@@ -0,0 +1,32 @@
+import { EventNotFoundError } from "@/errors/event-not-found";
+import { deleteEventService } from "@/services/events/delete-event";
+import type { FastifyReply, FastifyRequest } from "fastify";
+import { z } from "zod";
+
+export const deleteEventParamsSchema = z.object({
+	event_id: z.string().uuid(),
+});
+
+export async function deleteEvent(req: FastifyRequest, rep: FastifyReply) {
+	const parsedParams = deleteEventParamsSchema.safeParse(req.params);
+
+	if (!parsedParams.success) {
+		return rep.status(400).send({
+			error: "Invalid params",
+			issues: parsedParams.error.format(),
+		});
+	}
+
+	try {
+		await deleteEventService({ event_id: parsedParams.data.event_id });
+		return rep.status(204).send();
+	} catch (err) {
+		if (err instanceof EventNotFoundError) {
+			return rep
+				.status(404)
+				.send({ error: err.message, event_id: parsedParams.data.event_id });
+		}
+
+		return rep.status(500).send({ error: "Internal server error" });
+	}
+}
diff --git a/src/http/routes/event-subscriptions.ts b/src/http/routes/event-subscriptions.ts
index 1a5f1f5..3f93de4 100644
--- a/src/http/routes/event-subscriptions.ts
+++ b/src/http/routes/event-subscriptions.ts
@@ -17,8 +17,6 @@ export async function eventSubscriptions(app: FastifyInstance) {
 		{
 			schema: {
 				summary: "Subscribe to an event",
-				description:
-					"Subscribe a user to an event by providing event_id in the URL and user details in the body.",
 				tags: ["Subscriptions"],
 				params: subscribeParamsSchema,
 				body: subscribeBodySchema,
@@ -37,8 +35,6 @@ export async function eventSubscriptions(app: FastifyInstance) {
 		{
 			schema: {
 				summary: "Unsubscribe to an event",
-				description:
-					"Unubscribe a user to an event by providing event_id and the email in the body",
 				tags: ["Subscriptions"],
 				params: UnsubscribeParamsSchema,
 				body: UnsubscribeBodySchema,
diff --git a/src/http/routes/events.ts b/src/http/routes/events.ts
index cd3c5cf..2383bcd 100644
--- a/src/http/routes/events.ts
+++ b/src/http/routes/events.ts
@@ -3,6 +3,10 @@ import {
 	createEvent,
 	createEventBodySchema,
 } from "../controllers/events/create-event";
+import {
+	deleteEvent,
+	deleteEventParamsSchema,
+} from "../controllers/events/delete-event";
 
 export async function eventsRoutes(app: FastifyInstance) {
 	app.post(
@@ -10,8 +14,20 @@ export async function eventsRoutes(app: FastifyInstance) {
 		{
 			schema: {
 				body: createEventBodySchema,
+				tags: ["Events"],
 			},
 		},
 		createEvent,
 	);
+
+	app.delete(
+		"/:event_id",
+		{
+			schema: {
+				params: deleteEventParamsSchema,
+				tags: ["Events"],
+			},
+		},
+		deleteEvent,
+	);
 }
diff --git a/src/http/routes/health.ts b/src/http/routes/health.ts
index 45ca0f6..c739548 100644
--- a/src/http/routes/health.ts
+++ b/src/http/routes/health.ts
@@ -1,7 +1,15 @@
 import type { FastifyInstance } from "fastify";
 
 export async function healthRoutes(app: FastifyInstance) {
-	app.get("/", (req, reply) => {
-		reply.send({ status: "ok" });
-	});
+	app.get(
+		"/",
+		{
+			schema: {
+				tags: ["Health"],
+			},
+		},
+		(req, reply) => {
+			reply.send({ status: "ok" });
+		},
+	);
 }
diff --git a/src/services/events/create-event.ts b/src/services/events/create-event.ts
index 4abb2f6..398867a 100644
--- a/src/services/events/create-event.ts
+++ b/src/services/events/create-event.ts
@@ -1,6 +1,6 @@
 import { prisma } from "@/lib/prisma";
 
-interface CreateEventsService {
+interface CreateEventService {
 	title: string;
 	image_url: string;
 	tag: string;
@@ -10,7 +10,7 @@ interface CreateEventsService {
 	max_participants?: number;
 }
 
-export async function createEventsService({
+export async function createEventService({
 	address,
 	description,
 	event_date,
@@ -18,7 +18,7 @@ export async function createEventsService({
 	max_participants,
 	tag,
 	title,
-}: CreateEventsService) {
+}: CreateEventService) {
 	const event = await prisma.event.create({
 		data: {
 			address,
diff --git a/src/services/events/delete-event.ts b/src/services/events/delete-event.ts
new file mode 100644
index 0000000..b59c0b4
--- /dev/null
+++ b/src/services/events/delete-event.ts
@@ -0,0 +1,20 @@
+import { EventNotFoundError } from "@/errors/event-not-found";
+import { prisma } from "@/lib/prisma";
+
+interface DeleteEventService {
+	event_id: string;
+}
+
+export async function deleteEventService({ event_id }: DeleteEventService) {
+	await prisma.event
+		.findUniqueOrThrow({
+			where: { id: event_id },
+		})
+		.catch(() => {
+			throw new EventNotFoundError(event_id);
+		});
+
+	await prisma.event.delete({
+		where: { id: event_id },
+	});
+}
diff --git a/src/tests/controllers/events/delete-event.test.ts b/src/tests/controllers/events/delete-event.test.ts
new file mode 100644
index 0000000..b479af1
--- /dev/null
+++ b/src/tests/controllers/events/delete-event.test.ts
@@ -0,0 +1,64 @@
+import {
+	deleteEvent,
+	deleteEventParamsSchema,
+} from "@/http/controllers/events/delete-event";
+import type { FastifyReply, FastifyRequest } from "fastify";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+describe("http > controllers > event-subscription > delete-subscription", () => {
+	let mockReply: FastifyReply;
+	let mockRequest: FastifyRequest;
+
+	beforeEach(() => {
+		mockReply = {
+			status: vi.fn().mockReturnThis(),
+			send: vi.fn().mockReturnThis(),
+		} as unknown as FastifyReply;
+
+		mockRequest = {
+			params: { event_id: "invalid-event-id" },
+		} as FastifyRequest;
+	});
+
+	it("should return 400 if params are invalid", async () => {
+		const mockFormat = vi.fn().mockReturnValueOnce([
+			{
+				message: "Invalid event_id",
+				path: ["event_id"],
+				code: "invalid_type",
+			},
+		]);
+
+		const mockSafeParseParams = vi.fn().mockReturnValueOnce({
+			success: false,
+			error: {
+				format: mockFormat,
+				issues: [
+					{
+						message: "Invalid event_id",
+						path: ["event_id"],
+						code: "invalid_type",
+					},
+				],
+				message: "Invalid event_id",
+				isEmpty: false,
+			},
+		});
+
+		deleteEventParamsSchema.safeParse = mockSafeParseParams;
+
+		await deleteEvent(mockRequest, mockReply);
+
+		expect(mockReply.status).toHaveBeenCalledWith(400);
+		expect(mockReply.send).toHaveBeenCalledWith({
+			error: "Invalid params",
+			issues: [
+				{
+					message: "Invalid event_id",
+					path: ["event_id"],
+					code: "invalid_type",
+				},
+			],
+		});
+	});
+});
diff --git a/src/tests/services/events/create-event.test.ts b/src/tests/services/events/create-event.test.ts
index a255ff0..7480d6d 100644
--- a/src/tests/services/events/create-event.test.ts
+++ b/src/tests/services/events/create-event.test.ts
@@ -1,5 +1,5 @@
 import { prisma } from "@/lib/prisma";
-import { createEventsService } from "@/services/events/create-event";
+import { createEventService } from "@/services/events/create-event";
 import { type Mock, beforeEach, describe, expect, test } from "vitest";
 
 describe("services > create-events", () => {
@@ -25,7 +25,7 @@ describe("services > create-events", () => {
 	});
 
 	test("should create an event with the correct data", async () => {
-		const result = await createEventsService(mockEventInput);
+		const result = await createEventService(mockEventInput);
 
 		expect(prisma.event.create).toHaveBeenCalledOnce();
 		expect(prisma.event.create).toHaveBeenCalledWith({
diff --git a/src/tests/services/events/delete-event.test.ts b/src/tests/services/events/delete-event.test.ts
new file mode 100644
index 0000000..4540885
--- /dev/null
+++ b/src/tests/services/events/delete-event.test.ts
@@ -0,0 +1,47 @@
+import { EventNotFoundError } from "@/errors/event-not-found";
+import { prisma } from "@/lib/prisma";
+import { deleteEventService } from "@/services/events/delete-event";
+import { type Mock, beforeEach, describe, expect, test, vi } from "vitest";
+
+describe("services > delete-events", () => {
+	const mockEventId = "event-123";
+
+	beforeEach(() => {
+		vi.clearAllMocks();
+
+		(prisma.event.findUnique as Mock).mockReset();
+		(prisma.event.delete as Mock).mockReset();
+	});
+
+	test("should delete an event if it exists", async () => {
+		(prisma.event.findUniqueOrThrow as Mock).mockResolvedValue({
+			id: mockEventId,
+		});
+		(prisma.event.delete as Mock).mockResolvedValue({ id: mockEventId });
+
+		await deleteEventService({ event_id: mockEventId });
+
+		expect(prisma.event.findUniqueOrThrow).toHaveBeenCalledOnce();
+		expect(prisma.event.findUniqueOrThrow).toHaveBeenCalledWith({
+			where: { id: mockEventId },
+		});
+
+		expect(prisma.event.delete).toHaveBeenCalledOnce();
+		expect(prisma.event.delete).toHaveBeenCalledWith({
+			where: { id: mockEventId },
+		});
+	});
+
+	test("should throw EventNotFoundError if event does not exist", async () => {
+		(prisma.event.findUniqueOrThrow as Mock).mockRejectedValue(
+			new Error("Not found"),
+		);
+
+		await expect(deleteEventService({ event_id: mockEventId })).rejects.toThrow(
+			EventNotFoundError,
+		);
+
+		expect(prisma.event.findUniqueOrThrow).toHaveBeenCalledOnce();
+		expect(prisma.event.delete).not.toHaveBeenCalled();
+	});
+});
diff --git a/src/tests/setup.ts b/src/tests/setup.ts
index 6611307..b73c8c3 100644
--- a/src/tests/setup.ts
+++ b/src/tests/setup.ts
@@ -11,6 +11,8 @@ beforeAll(async () => {
 				create: vi.fn(),
 				findUnique: vi.fn(),
 				findMany: vi.fn(),
+				delete: vi.fn(),
+				findUniqueOrThrow: vi.fn(),
 			},
 			subscribedUser: {
 				create: vi.fn(),


Reviews:
user: login: lesada
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-02T11:40:26Z
user: login: lesada
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-04T23:18:56Z
user: login: lesada
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-04T23:19:37Z
user: login: lesada
state: APPROVED
author_association: OWNER
submitted_at: 2025-05-04T23:20:13Z

Review comments:
diff_hunk: @@ -0,0 +1,22 @@
+import { deleteEventsService } from "@/services/events/delete-event";
+import { FastifyReply, FastifyRequest } from "fastify";
+import { z } from "zod";
+
+export async function deleteEvent(req: FastifyRequest, rep: FastifyReply) {
+  const deleteEventParamsSchema = z.object({
+    id: z.string().uuid()
+  })
+
+  const parsed = deleteEventParamsSchema.safeParse(req.params)
+
+  if (!parsed.success)  { 
+    return rep.status(400).send(parsed.error.format())
+  }
+
+  try {
+		await deleteEventsService({ id: parsed.data.id });
+		return rep.status(204).send(); 
+	} catch (error) {
path: src/http/controllers/delete-event.ts
user: login: lesada
body: Usa só catch {} sem (error)
created_at: 2025-05-02T11:40:26Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,20 @@
+import { EventNotFoundError } from "@/errors/event-not-found";
+import { prisma } from "@/lib/prisma";
+
+interface DeleteEventService {
+	event_id: string;
+}
+
+export async function deleteEventService({ event_id }: DeleteEventService) {
+	const event = await prisma.event.findUnique({
path: src/services/events/delete-event.ts
user: login: lesada
body: acho que aqui é legal usar o find unique or throw
created_at: 2025-05-04T23:18:55Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,41 @@
+import { EventNotFoundError } from "@/errors/event-not-found";
+import { prisma } from "@/lib/prisma";
+import { deleteEventService } from "@/services/events/delete-event";
+import { type Mock, beforeEach, describe, expect, test } from "vitest";
+
+describe("services > delete-events", () => {
+	const mockEventId = "event-123";
+
+	beforeEach(() => {
+		(prisma.event.findUnique as Mock).mockReset();
+		(prisma.event.delete as Mock).mockReset();
+	});
+
+	test("should delete an event if it exists", async () => {
+		(prisma.event.findUnique as Mock).mockResolvedValue({ id: mockEventId });
+		(prisma.event.delete as Mock).mockResolvedValue({ id: mockEventId });
+
+		await deleteEventService({ event_id: mockEventId });
+
+		expect(prisma.event.findUnique).toHaveBeenCalledOnce();
+		expect(prisma.event.findUnique).toHaveBeenCalledWith({
+			where: { id: mockEventId },
+		});
+
+		expect(prisma.event.delete).toHaveBeenCalledOnce();
+		expect(prisma.event.delete).toHaveBeenCalledWith({
+			where: { id: mockEventId },
+		});
+	});
+
+	test("should throw EventNotFoundError if event does not exist", async () => {
+		(prisma.event.findUnique as Mock).mockResolvedValue(null);
path: src/tests/services/events/delete-event.test.ts
user: login: lesada
body: mocka globalmente
created_at: 2025-05-04T23:19:37Z
author_association: OWNER

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-05-04T23:39:48Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=lesada_cocriar-api&pullRequest=3) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=lesada_cocriar-api&pullRequest=3&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=lesada_cocriar-api&pullRequest=3&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=lesada_cocriar-api&pullRequest=3&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [83.6% Coverage on New Code](https://sonarcloud.io/component_measures?id=lesada_cocriar-api&pullRequest=3&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=lesada_cocriar-api&pullRequest=3&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=lesada_cocriar-api&pullRequest=3)


author_association: NONE