1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
/**
* categories.controller.ts
* Lógica de negócio do CRUD de categorias.
*/
import type { FastifyReply, FastifyRequest } from 'fastify';
import {
findAllCategories,
findCategoryById,
findCategoryBySlug,
createCategory,
updateCategory,
deleteCategory,
} from './categories.repository.js';
import type {
CategoryParams,
CreateCategoryBody,
UpdateCategoryBody,
} from './categories.schema.js';
import { eq, count } from 'drizzle-orm';
import { tickets } from '@/db/schema.js';
// ─── GET /categories ──────────────────────────────────────────────────────────
export async function listCategoriesHandler(
request: FastifyRequest,
reply: FastifyReply,
) {
const list = await findAllCategories(request.server.db);
return reply.send(list);
}
// ─── GET /categories/:id ──────────────────────────────────────────────────────
export async function getCategoryHandler(
request: FastifyRequest<{ Params: CategoryParams }>,
reply: FastifyReply,
) {
const category = await findCategoryById(request.server.db, request.params.id);
if (!category) {
return reply.notFound(`Categoria #${request.params.id} não encontrada.`);
}
return reply.send(category);
}
// ─── POST /categories ─────────────────────────────────────────────────────────
export async function createCategoryHandler(
request: FastifyRequest<{ Body: CreateCategoryBody }>,
reply: FastifyReply,
) {
const { name, slug, description } = request.body;
const db = request.server.db;
// Slug deve ser único
const existing = await findCategoryBySlug(db, slug);
if (existing) {
return reply.conflict(`Já existe uma categoria com o slug "${slug}".`);
}
const category = await createCategory(db, {
name,
slug,
description: description ?? null,
});
return reply.code(201).send(category);
}
// ─── PATCH /categories/:id ────────────────────────────────────────────────────
export async function updateCategoryHandler(
request: FastifyRequest<{ Params: CategoryParams; Body: UpdateCategoryBody }>,
reply: FastifyReply,
) {
const { id } = request.params;
const db = request.server.db;
const existing = await findCategoryById(db, id);
if (!existing) {
return reply.notFound(`Categoria #${id} não encontrada.`);
}
// Se o slug está sendo alterado, verifica conflito com outro registro
if (request.body.slug && request.body.slug !== existing.slug) {
const slugConflict = await findCategoryBySlug(db, request.body.slug);
if (slugConflict) {
return reply.conflict(`Já existe uma categoria com o slug "${request.body.slug}".`);
}
}
const updated = await updateCategory(db, id, request.body);
return reply.send(updated);
}
// ─── DELETE /categories/:id ───────────────────────────────────────────────────
export async function deleteCategoryHandler(
request: FastifyRequest<{ Params: CategoryParams }>,
reply: FastifyReply,
) {
const { id } = request.params;
const db = request.server.db;
const existing = await findCategoryById(db, id);
if (!existing) {
return reply.notFound(`Categoria #${id} não encontrada.`);
}
// Protege contra deleção de categoria em uso
const [{ total }] = await db
.select({ total: count() })
.from(tickets)
.where(eq(tickets.categoryId, id));
if (total > 0) {
return reply.conflict(
`Categoria em uso por ${total} chamado(s). Reclassifique-os antes de excluir.`,
);
}
await deleteCategory(db, id);
return reply.code(204).send();
}
|