bibest-code 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "todo-api",
3
+ "version": "1.0.0",
4
+ "description": "API REST complète pour une Todo List - by BIBEST CODE",
5
+ "main": "src/server.js",
6
+ "scripts": {
7
+ "start": "node src/server.js",
8
+ "dev": "nodemon src/server.js"
9
+ },
10
+ "keywords": ["todo", "rest", "api", "express"],
11
+ "author": "BIBI ONDOUA / BIBEST CODE",
12
+ "license": "MIT",
13
+ "dependencies": {
14
+ "express": "^4.18.2",
15
+ "uuid": "^9.0.0",
16
+ "cors": "^2.8.5",
17
+ "morgan": "^1.10.0"
18
+ },
19
+ "devDependencies": {
20
+ "nodemon": "^3.0.1"
21
+ }
22
+ }
@@ -0,0 +1,285 @@
1
+ // ============================================================
2
+ // Contrôleur Todo — Logique métier CRUD
3
+ // BIBEST CODE - by BIBI ONDOUA
4
+ // ============================================================
5
+
6
+ const { v4: uuidv4 } = require("uuid");
7
+ const { todos } = require("../data/todos");
8
+
9
+ // ─────────────────────────────────────────────────────────────
10
+ // GET /api/todos
11
+ // Récupère tous les todos avec filtres, tri et pagination
12
+ // ─────────────────────────────────────────────────────────────
13
+ const getAllTodos = (req, res) => {
14
+ let result = [...todos];
15
+
16
+ const { completed, priority, search, sort, order, page, limit } = req.query;
17
+
18
+ // --- Filtres ---
19
+ if (completed !== undefined) {
20
+ const isCompleted = completed === "true";
21
+ result = result.filter((t) => t.completed === isCompleted);
22
+ }
23
+
24
+ if (priority) {
25
+ result = result.filter((t) => t.priority === priority);
26
+ }
27
+
28
+ if (search) {
29
+ const term = search.toLowerCase();
30
+ result = result.filter(
31
+ (t) =>
32
+ t.title.toLowerCase().includes(term) ||
33
+ (t.description && t.description.toLowerCase().includes(term))
34
+ );
35
+ }
36
+
37
+ // --- Tri ---
38
+ const validSortFields = ["createdAt", "updatedAt", "title", "priority"];
39
+ const sortField = validSortFields.includes(sort) ? sort : "createdAt";
40
+ const sortOrder = order === "asc" ? 1 : -1;
41
+
42
+ result.sort((a, b) => {
43
+ if (a[sortField] < b[sortField]) return -1 * sortOrder;
44
+ if (a[sortField] > b[sortField]) return 1 * sortOrder;
45
+ return 0;
46
+ });
47
+
48
+ // --- Statistiques avant pagination ---
49
+ const total = result.length;
50
+ const totalCompleted = result.filter((t) => t.completed).length;
51
+ const totalPending = total - totalCompleted;
52
+
53
+ // --- Pagination ---
54
+ const pageNum = parseInt(page) || 1;
55
+ const limitNum = parseInt(limit) || 10;
56
+ const startIndex = (pageNum - 1) * limitNum;
57
+ const endIndex = startIndex + limitNum;
58
+ const paginated = result.slice(startIndex, endIndex);
59
+
60
+ res.status(200).json({
61
+ success: true,
62
+ message: "Todos récupérés avec succès.",
63
+ stats: {
64
+ total,
65
+ completed: totalCompleted,
66
+ pending: totalPending,
67
+ },
68
+ pagination: {
69
+ currentPage: pageNum,
70
+ totalPages: Math.ceil(total / limitNum),
71
+ limit: limitNum,
72
+ hasNextPage: endIndex < total,
73
+ hasPrevPage: pageNum > 1,
74
+ },
75
+ data: paginated,
76
+ });
77
+ };
78
+
79
+ // ─────────────────────────────────────────────────────────────
80
+ // GET /api/todos/:id
81
+ // Récupère un todo par son ID
82
+ // ─────────────────────────────────────────────────────────────
83
+ const getTodoById = (req, res) => {
84
+ const { id } = req.params;
85
+ const todo = todos.find((t) => t.id === id);
86
+
87
+ if (!todo) {
88
+ return res.status(404).json({
89
+ success: false,
90
+ message: `Aucun todo trouvé avec l'ID : ${id}`,
91
+ });
92
+ }
93
+
94
+ res.status(200).json({
95
+ success: true,
96
+ message: "Todo récupéré avec succès.",
97
+ data: todo,
98
+ });
99
+ };
100
+
101
+ // ─────────────────────────────────────────────────────────────
102
+ // POST /api/todos
103
+ // Crée un nouveau todo
104
+ // ─────────────────────────────────────────────────────────────
105
+ const createTodo = (req, res) => {
106
+ const { title, description = "", priority = "medium" } = req.body;
107
+
108
+ const newTodo = {
109
+ id: uuidv4(),
110
+ title: title.trim(),
111
+ description: description.trim(),
112
+ completed: false,
113
+ priority,
114
+ createdAt: new Date(),
115
+ updatedAt: new Date(),
116
+ };
117
+
118
+ todos.push(newTodo);
119
+
120
+ res.status(201).json({
121
+ success: true,
122
+ message: "Todo créé avec succès.",
123
+ data: newTodo,
124
+ });
125
+ };
126
+
127
+ // ─────────────────────────────────────────────────────────────
128
+ // PUT /api/todos/:id
129
+ // Remplace complètement un todo (tous les champs)
130
+ // ─────────────────────────────────────────────────────────────
131
+ const replaceTodo = (req, res) => {
132
+ const { id } = req.params;
133
+ const index = todos.findIndex((t) => t.id === id);
134
+
135
+ if (index === -1) {
136
+ return res.status(404).json({
137
+ success: false,
138
+ message: `Aucun todo trouvé avec l'ID : ${id}`,
139
+ });
140
+ }
141
+
142
+ const { title, description = "", completed = false, priority = "medium" } = req.body;
143
+
144
+ if (!title || title.trim() === "") {
145
+ return res.status(400).json({
146
+ success: false,
147
+ message: "Le champ 'title' est requis pour un remplacement complet (PUT).",
148
+ });
149
+ }
150
+
151
+ const replaced = {
152
+ id,
153
+ title: title.trim(),
154
+ description: description.trim(),
155
+ completed: Boolean(completed),
156
+ priority,
157
+ createdAt: todos[index].createdAt,
158
+ updatedAt: new Date(),
159
+ };
160
+
161
+ todos[index] = replaced;
162
+
163
+ res.status(200).json({
164
+ success: true,
165
+ message: "Todo remplacé avec succès.",
166
+ data: replaced,
167
+ });
168
+ };
169
+
170
+ // ─────────────────────────────────────────────────────────────
171
+ // PATCH /api/todos/:id
172
+ // Met à jour partiellement un todo
173
+ // ─────────────────────────────────────────────────────────────
174
+ const updateTodo = (req, res) => {
175
+ const { id } = req.params;
176
+ const index = todos.findIndex((t) => t.id === id);
177
+
178
+ if (index === -1) {
179
+ return res.status(404).json({
180
+ success: false,
181
+ message: `Aucun todo trouvé avec l'ID : ${id}`,
182
+ });
183
+ }
184
+
185
+ const { title, description, completed, priority } = req.body;
186
+ const current = todos[index];
187
+
188
+ const updated = {
189
+ ...current,
190
+ ...(title !== undefined && { title: title.trim() }),
191
+ ...(description !== undefined && { description: description.trim() }),
192
+ ...(completed !== undefined && { completed }),
193
+ ...(priority !== undefined && { priority }),
194
+ updatedAt: new Date(),
195
+ };
196
+
197
+ todos[index] = updated;
198
+
199
+ res.status(200).json({
200
+ success: true,
201
+ message: "Todo mis à jour avec succès.",
202
+ data: updated,
203
+ });
204
+ };
205
+
206
+ // ─────────────────────────────────────────────────────────────
207
+ // PATCH /api/todos/:id/toggle
208
+ // Bascule le statut completed d'un todo
209
+ // ─────────────────────────────────────────────────────────────
210
+ const toggleTodo = (req, res) => {
211
+ const { id } = req.params;
212
+ const index = todos.findIndex((t) => t.id === id);
213
+
214
+ if (index === -1) {
215
+ return res.status(404).json({
216
+ success: false,
217
+ message: `Aucun todo trouvé avec l'ID : ${id}`,
218
+ });
219
+ }
220
+
221
+ todos[index].completed = !todos[index].completed;
222
+ todos[index].updatedAt = new Date();
223
+
224
+ res.status(200).json({
225
+ success: true,
226
+ message: `Todo marqué comme ${todos[index].completed ? "✅ complété" : "⏳ en attente"}.`,
227
+ data: todos[index],
228
+ });
229
+ };
230
+
231
+ // ─────────────────────────────────────────────────────────────
232
+ // DELETE /api/todos/:id
233
+ // Supprime un todo par son ID
234
+ // ─────────────────────────────────────────────────────────────
235
+ const deleteTodo = (req, res) => {
236
+ const { id } = req.params;
237
+ const index = todos.findIndex((t) => t.id === id);
238
+
239
+ if (index === -1) {
240
+ return res.status(404).json({
241
+ success: false,
242
+ message: `Aucun todo trouvé avec l'ID : ${id}`,
243
+ });
244
+ }
245
+
246
+ const deleted = todos.splice(index, 1)[0];
247
+
248
+ res.status(200).json({
249
+ success: true,
250
+ message: "Todo supprimé avec succès.",
251
+ data: deleted,
252
+ });
253
+ };
254
+
255
+ // ─────────────────────────────────────────────────────────────
256
+ // DELETE /api/todos
257
+ // Supprime tous les todos complétés
258
+ // ─────────────────────────────────────────────────────────────
259
+ const deleteCompletedTodos = (req, res) => {
260
+ const before = todos.length;
261
+ const removed = todos.filter((t) => t.completed);
262
+
263
+ // Mutation du tableau partagé
264
+ todos.splice(0, todos.length, ...todos.filter((t) => !t.completed));
265
+
266
+ const after = todos.length;
267
+
268
+ res.status(200).json({
269
+ success: true,
270
+ message: `${before - after} todo(s) complété(s) supprimé(s).`,
271
+ deletedCount: before - after,
272
+ deletedItems: removed,
273
+ });
274
+ };
275
+
276
+ module.exports = {
277
+ getAllTodos,
278
+ getTodoById,
279
+ createTodo,
280
+ replaceTodo,
281
+ updateTodo,
282
+ toggleTodo,
283
+ deleteTodo,
284
+ deleteCompletedTodos,
285
+ };
@@ -0,0 +1,39 @@
1
+ // ============================================================
2
+ // Stockage en mémoire (remplaçable par une vraie BDD)
3
+ // BIBEST CODE - by BIBI ONDOUA
4
+ // ============================================================
5
+
6
+ const { v4: uuidv4 } = require("uuid");
7
+
8
+ // Données initiales de démonstration
9
+ let todos = [
10
+ {
11
+ id: uuidv4(),
12
+ title: "Apprendre Express.js",
13
+ description: "Maîtriser la création d'API REST avec Express",
14
+ completed: true,
15
+ priority: "high",
16
+ createdAt: new Date("2024-01-10T08:00:00.000Z"),
17
+ updatedAt: new Date("2024-01-12T10:30:00.000Z"),
18
+ },
19
+ {
20
+ id: uuidv4(),
21
+ title: "Créer une API Todo List",
22
+ description: "Implémenter toutes les routes CRUD",
23
+ completed: false,
24
+ priority: "high",
25
+ createdAt: new Date("2024-01-15T09:00:00.000Z"),
26
+ updatedAt: new Date("2024-01-15T09:00:00.000Z"),
27
+ },
28
+ {
29
+ id: uuidv4(),
30
+ title: "Écrire la documentation",
31
+ description: "Documenter toutes les routes de l'API",
32
+ completed: false,
33
+ priority: "medium",
34
+ createdAt: new Date("2024-01-16T11:00:00.000Z"),
35
+ updatedAt: new Date("2024-01-16T11:00:00.000Z"),
36
+ },
37
+ ];
38
+
39
+ module.exports = { todos };
@@ -0,0 +1,27 @@
1
+ // ============================================================
2
+ // Middleware de gestion globale des erreurs
3
+ // BIBEST CODE - by BIBI ONDOUA
4
+ // ============================================================
5
+
6
+ // Gestion des routes inexistantes (404)
7
+ const notFound = (req, res, next) => {
8
+ res.status(404).json({
9
+ success: false,
10
+ message: `Route introuvable : ${req.method} ${req.originalUrl}`,
11
+ });
12
+ };
13
+
14
+ // Gestionnaire d'erreurs global (500)
15
+ const errorHandler = (err, req, res, next) => {
16
+ console.error("❌ Erreur serveur :", err.stack);
17
+
18
+ const statusCode = err.statusCode || 500;
19
+
20
+ res.status(statusCode).json({
21
+ success: false,
22
+ message: err.message || "Erreur interne du serveur.",
23
+ ...(process.env.NODE_ENV === "development" && { stack: err.stack }),
24
+ });
25
+ };
26
+
27
+ module.exports = { notFound, errorHandler };
@@ -0,0 +1,92 @@
1
+ // ============================================================
2
+ // Middlewares de validation
3
+ // BIBEST CODE - by BIBI ONDOUA
4
+ // ============================================================
5
+
6
+ const VALID_PRIORITIES = ["low", "medium", "high"];
7
+
8
+ // Valide le body lors de la création d'un todo
9
+ const validateCreate = (req, res, next) => {
10
+ const { title, description, priority } = req.body;
11
+
12
+ const errors = [];
13
+
14
+ if (!title || typeof title !== "string" || title.trim() === "") {
15
+ errors.push("Le champ 'title' est requis et doit être une chaîne non vide.");
16
+ } else if (title.trim().length > 100) {
17
+ errors.push("Le champ 'title' ne doit pas dépasser 100 caractères.");
18
+ }
19
+
20
+ if (description !== undefined) {
21
+ if (typeof description !== "string") {
22
+ errors.push("Le champ 'description' doit être une chaîne de caractères.");
23
+ } else if (description.length > 500) {
24
+ errors.push("Le champ 'description' ne doit pas dépasser 500 caractères.");
25
+ }
26
+ }
27
+
28
+ if (priority !== undefined && !VALID_PRIORITIES.includes(priority)) {
29
+ errors.push(`Le champ 'priority' doit être l'une des valeurs : ${VALID_PRIORITIES.join(", ")}.`);
30
+ }
31
+
32
+ if (errors.length > 0) {
33
+ return res.status(400).json({
34
+ success: false,
35
+ message: "Données invalides.",
36
+ errors,
37
+ });
38
+ }
39
+
40
+ next();
41
+ };
42
+
43
+ // Valide le body lors de la mise à jour d'un todo
44
+ const validateUpdate = (req, res, next) => {
45
+ const { title, description, completed, priority } = req.body;
46
+
47
+ const errors = [];
48
+
49
+ if (Object.keys(req.body).length === 0) {
50
+ return res.status(400).json({
51
+ success: false,
52
+ message: "Le corps de la requête ne peut pas être vide.",
53
+ errors: ["Fournissez au moins un champ à mettre à jour : title, description, completed, priority."],
54
+ });
55
+ }
56
+
57
+ if (title !== undefined) {
58
+ if (typeof title !== "string" || title.trim() === "") {
59
+ errors.push("Le champ 'title' doit être une chaîne non vide.");
60
+ } else if (title.trim().length > 100) {
61
+ errors.push("Le champ 'title' ne doit pas dépasser 100 caractères.");
62
+ }
63
+ }
64
+
65
+ if (description !== undefined) {
66
+ if (typeof description !== "string") {
67
+ errors.push("Le champ 'description' doit être une chaîne de caractères.");
68
+ } else if (description.length > 500) {
69
+ errors.push("Le champ 'description' ne doit pas dépasser 500 caractères.");
70
+ }
71
+ }
72
+
73
+ if (completed !== undefined && typeof completed !== "boolean") {
74
+ errors.push("Le champ 'completed' doit être un booléen (true ou false).");
75
+ }
76
+
77
+ if (priority !== undefined && !VALID_PRIORITIES.includes(priority)) {
78
+ errors.push(`Le champ 'priority' doit être l'une des valeurs : ${VALID_PRIORITIES.join(", ")}.`);
79
+ }
80
+
81
+ if (errors.length > 0) {
82
+ return res.status(400).json({
83
+ success: false,
84
+ message: "Données invalides.",
85
+ errors,
86
+ });
87
+ }
88
+
89
+ next();
90
+ };
91
+
92
+ module.exports = { validateCreate, validateUpdate };
@@ -0,0 +1,53 @@
1
+ // ============================================================
2
+ // Routes Todo — API REST
3
+ // BIBEST CODE - by BIBI ONDOUA
4
+ // ============================================================
5
+
6
+ const express = require("express");
7
+ const router = express.Router();
8
+
9
+ const {
10
+ getAllTodos,
11
+ getTodoById,
12
+ createTodo,
13
+ replaceTodo,
14
+ updateTodo,
15
+ toggleTodo,
16
+ deleteTodo,
17
+ deleteCompletedTodos,
18
+ } = require("../controllers/todoController");
19
+
20
+ const { validateCreate, validateUpdate } = require("../middlewares/validate");
21
+
22
+ // ─────────────────────────────────────────────────────────────
23
+ // Routes de collection → /api/todos
24
+ // ─────────────────────────────────────────────────────────────
25
+
26
+ // GET /api/todos → Lister tous les todos (filtres + pagination)
27
+ // POST /api/todos → Créer un nouveau todo
28
+ // DELETE /api/todos → Supprimer tous les todos complétés
29
+ router.get("/", getAllTodos);
30
+ router.post("/", validateCreate, createTodo);
31
+ router.delete("/", deleteCompletedTodos);
32
+
33
+ // ─────────────────────────────────────────────────────────────
34
+ // Routes de ressource individuelle → /api/todos/:id
35
+ // ─────────────────────────────────────────────────────────────
36
+
37
+ // GET /api/todos/:id → Récupérer un todo
38
+ // PUT /api/todos/:id → Remplacer complètement un todo
39
+ // PATCH /api/todos/:id → Mettre à jour partiellement un todo
40
+ // DELETE /api/todos/:id → Supprimer un todo
41
+ router.get("/:id", getTodoById);
42
+ router.put("/:id", validateCreate, replaceTodo);
43
+ router.patch("/:id", validateUpdate, updateTodo);
44
+ router.delete("/:id", deleteTodo);
45
+
46
+ // ─────────────────────────────────────────────────────────────
47
+ // Route spéciale → Basculer le statut completed
48
+ // ─────────────────────────────────────────────────────────────
49
+
50
+ // PATCH /api/todos/:id/toggle → Toggle completed
51
+ router.patch("/:id/toggle", toggleTodo);
52
+
53
+ module.exports = router;
@@ -0,0 +1,86 @@
1
+ // ============================================================
2
+ // Serveur principal — Todo API REST
3
+ // BIBEST CODE - by BIBI ONDOUA
4
+ // ============================================================
5
+
6
+ const express = require("express");
7
+ const cors = require("cors");
8
+ const morgan = require("morgan");
9
+
10
+ const todoRoutes = require("./routes/todoRoutes");
11
+ const { notFound, errorHandler } = require("./middlewares/errorHandler");
12
+
13
+ const app = express();
14
+ const PORT = process.env.PORT || 3000;
15
+
16
+ // ─────────────────────────────────────────────────────────────
17
+ // Middlewares globaux
18
+ // ─────────────────────────────────────────────────────────────
19
+ app.use(cors()); // Autorise les requêtes cross-origin
20
+ app.use(express.json()); // Parse le body JSON
21
+ app.use(express.urlencoded({ extended: true })); // Parse les form-data
22
+ app.use(morgan("dev")); // Logs HTTP colorés
23
+
24
+ // ─────────────────────────────────────────────────────────────
25
+ // Route d'accueil / health-check
26
+ // ─────────────────────────────────────────────────────────────
27
+ app.get("/", (req, res) => {
28
+ res.status(200).json({
29
+ success: true,
30
+ name: "Todo List REST API",
31
+ version: "1.0.0",
32
+ author: "BIBI ONDOUA — BIBEST CODE",
33
+ description: "API REST complète avec routes CRUD pour une Todo List",
34
+ endpoints: {
35
+ base: "/api/todos",
36
+ routes: [
37
+ { method: "GET", path: "/api/todos", description: "Lister tous les todos (filtres + pagination)" },
38
+ { method: "GET", path: "/api/todos/:id", description: "Récupérer un todo par ID" },
39
+ { method: "POST", path: "/api/todos", description: "Créer un nouveau todo" },
40
+ { method: "PUT", path: "/api/todos/:id", description: "Remplacer complètement un todo" },
41
+ { method: "PATCH", path: "/api/todos/:id", description: "Mettre à jour partiellement un todo" },
42
+ { method: "PATCH", path: "/api/todos/:id/toggle", description: "Basculer le statut completed" },
43
+ { method: "DELETE", path: "/api/todos/:id", description: "Supprimer un todo" },
44
+ { method: "DELETE", path: "/api/todos", description: "Supprimer tous les todos complétés" },
45
+ ],
46
+ queryParams: {
47
+ completed: "true | false",
48
+ priority: "low | medium | high",
49
+ search: "texte libre (titre/description)",
50
+ sort: "createdAt | updatedAt | title | priority",
51
+ order: "asc | desc (défaut: desc)",
52
+ page: "numéro de page (défaut: 1)",
53
+ limit: "items par page (défaut: 10)",
54
+ },
55
+ },
56
+ });
57
+ });
58
+
59
+ // ─────────────────────────────────────────────────────────────
60
+ // Montage des routes
61
+ // ─────────────────────────────────────────────────────────────
62
+ app.use("/api/todos", todoRoutes);
63
+
64
+ // ─────────────────────────────────────────────────────────────
65
+ // Gestion des erreurs (toujours en dernier)
66
+ // ─────────────────────────────────────────────────────────────
67
+ app.use(notFound);
68
+ app.use(errorHandler);
69
+
70
+ // ─────────────────────────────────────────────────────────────
71
+ // Démarrage du serveur
72
+ // ─────────────────────────────────────────────────────────────
73
+ app.listen(PORT, () => {
74
+ console.log("");
75
+ console.log("╔══════════════════════════════════════════════╗");
76
+ console.log("║ 🚀 Todo List REST API 🚀 ║");
77
+ console.log("║ BIBEST CODE — BIBI ONDOUA ║");
78
+ console.log("╠══════════════════════════════════════════════╣");
79
+ console.log(`║ ✅ Serveur démarré sur le port ${PORT} ║`);
80
+ console.log(`║ 🌐 http://localhost:${PORT} ║`);
81
+ console.log(`║ 📦 Base URL : http://localhost:${PORT}/api/todos ║`);
82
+ console.log("╚══════════════════════════════════════════════╝");
83
+ console.log("");
84
+ });
85
+
86
+ module.exports = app;