api: rework mood and user routes to match improvements on model design

Pedro Lucas Porcellis porcellis@eletrotupi.com 6 months ago ba9ebeebe13950cdc2b168d63fe2ec5b3be67d11
Parents: 06314a5
2 file(s) changed
  • api/src/routes/mood.js +488 -18
  • api/src/routes/users.js +199 -13
api/src/routes/mood.js
@@ -1,33 +1,503 @@
1 1 import express from "express";
2 + import {
3 + validateMoodComponents,
4 + isValidLevel,
5 + isValidRating,
6 + calculateEmotionStats
7 + } from "../utils/emotions.js";
8 + import {
9 + validateRequiredFields,
10 + isValidDate,
11 + isNotFutureDate,
12 + isValidDateRange,
13 + sanitizeString,
14 + validatePagination
15 + } from "../utils/validators.js";
2 16
3 17 const router = express.Router();
4 18
19 + // Create new mood entry with components
5 20 router.post("/", (req, res) => {
6 - const { user_id, stress_level, anxiety_level, title, description } = req.body;
21 + const {
22 + user_id,
23 + rating,
24 + stress_level,
25 + anxiety_level,
26 + energy_level,
27 + title,
28 + description,
29 + recorded_at,
30 + mood_components
31 + } = req.body;
32 +
33 + // Validate required fields
34 + const requiredValidation = validateRequiredFields(req.body, [
35 + 'user_id', 'stress_level', 'anxiety_level', 'energy_level', 'recorded_at'
36 + ]);
37 +
38 + if (!requiredValidation.valid) {
39 + return res.status(400).json({
40 + error: "Campos faltantes",
41 + missing: requiredValidation.missing
42 + });
43 + }
44 +
45 + // Validate levels
46 + if (!isValidLevel(stress_level)) {
47 + return res.status(400).json({ error: "stress_level deve ser entre 1 e 10" });
48 + }
49 + if (!isValidLevel(anxiety_level)) {
50 + return res.status(400).json({ error: "anxiety_level deve ser entre 1 e 10" });
51 + }
52 + if (!isValidLevel(energy_level)) {
53 + return res.status(400).json({ error: "energy_level deve ser entre 1 e 10" });
54 + }
55 +
56 + // Validate optional rating
57 + if (rating && !isValidRating(rating)) {
58 + return res.status(400).json({ error: "rating deve ser entre 1 e 10" });
59 + }
60 +
61 + // Validate date
62 + if (!isValidDate(recorded_at)) {
63 + return res.status(400).json({ error: "recorded_at deve ser uma data válida" });
64 + }
65 +
66 + if (!isNotFutureDate(recorded_at)) {
67 + return res.status(400).json({ error: "recorded_at não pode ser no futuro" });
68 + }
69 +
70 + // Validate mood components if provided
71 + if (mood_components) {
72 + const componentValidation = validateMoodComponents(mood_components);
73 + if (!componentValidation.valid) {
74 + return res.status(400).json({ error: componentValidation.error });
75 + }
76 + }
77 +
78 + const db = req.db;
79 +
80 + try {
81 + // Start transaction
82 + db.prepare("BEGIN").run();
83 +
84 + // Insert mood entry
85 + const moodStmt = db.prepare(`
86 + INSERT INTO mood (
87 + user_id, rating, stress_level, anxiety_level, energy_level,
88 + title, description, recorded_at
89 + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
90 + `);
91 +
92 + const moodResult = moodStmt.run(
93 + user_id,
94 + rating || null,
95 + stress_level,
96 + anxiety_level,
97 + energy_level,
98 + sanitizeString(title) || null,
99 + sanitizeString(description) || null,
100 + recorded_at
101 + );
7 102
8 - if (!user_id || !stress_level || !anxiety_level)
9 - return res.status(400).json({ error: "Campos faltantes" });
103 + const moodId = moodResult.lastInsertRowid;
10 104
11 - const stmt = req.db.prepare(
12 - `
13 - INSERT INTO mood (user_id, stress_level, anxiety_level, title, description)
14 - VALUES (?, ?, ?, ?, ?)
15 - `
16 - );
105 + // Insert mood components if provided
106 + if (mood_components && mood_components.length > 0) {
107 + const componentStmt = db.prepare(`
108 + INSERT INTO mood_components (mood_id, emotion, intensity)
109 + VALUES (?, ?, ?)
110 + `);
111 +
112 + for (const component of mood_components) {
113 + componentStmt.run(
114 + moodId,
115 + component.emotion.toLowerCase(),
116 + component.intensity
117 + );
118 + }
119 + }
17 120
18 - const result = stmt.run(
19 - user_id, stress_level, anxiety_level, title || null, description || null
20 - );
121 + // Commit transaction
122 + db.prepare("COMMIT").run();
21 123
22 - res.status(201).json({ id: result.lastInsertRowid });
124 + res.status(201).json({ id: moodId });
125 + } catch (err) {
126 + // Rollback on error
127 + db.prepare("ROLLBACK").run();
128 + console.error('Error creating mood:', err);
129 + res.status(500).json({ error: "Erro ao criar registro de humor" });
130 + }
23 131 });
24 132
25 - router.get("/:user_id", (req, res) => {
26 - const stmt = req.db.prepare("SELECT * FROM mood WHERE user_id = ?");
27 - const rows = stmt.all(req.params.user_id);
133 + // Get single mood entry with components
134 + router.get("/:mood_id", (req, res) => {
135 + const moodId = req.params.mood_id;
136 + const db = req.db;
137 +
138 + const moodStmt = db.prepare(`
139 + SELECT m.*, u.first_name, u.last_name
140 + FROM mood m
141 + JOIN users u ON m.user_id = u.id
142 + WHERE m.id = ?
143 + `);
144 +
145 + const mood = moodStmt.get(moodId);
146 +
147 + if (!mood) {
148 + return res.status(404).json({ error: "Humor não encontrado" });
149 + }
150 +
151 + // Get mood components
152 + const componentsStmt = db.prepare(`
153 + SELECT emotion, intensity
154 + FROM mood_components
155 + WHERE mood_id = ?
156 + `);
157 +
158 + const components = componentsStmt.all(moodId);
159 + mood.mood_components = components;
28 160
29 - res.json(rows);
161 + // Calculate emotion stats
162 + if (components.length > 0) {
163 + mood.emotion_stats = calculateEmotionStats(components);
164 + }
165 +
166 + res.json(mood);
30 167 });
31 168
32 - export default router;
169 + // Update mood entry
170 + router.put("/:mood_id", (req, res) => {
171 + const moodId = req.params.mood_id;
172 + const {
173 + rating,
174 + stress_level,
175 + anxiety_level,
176 + energy_level,
177 + title,
178 + description,
179 + recorded_at,
180 + mood_components
181 + } = req.body;
182 +
183 + const db = req.db;
33 184
185 + // Check if mood exists
186 + const existingMood = db.prepare("SELECT id FROM mood WHERE id = ?").get(moodId);
187 + if (!existingMood) {
188 + return res.status(404).json({ error: "Humor não encontrado" });
189 + }
190 +
191 + // Build update query dynamically
192 + const updates = [];
193 + const values = [];
194 +
195 + if (rating !== undefined) {
196 + if (!isValidRating(rating)) {
197 + return res.status(400).json({ error: "rating deve ser entre 1 e 10" });
198 + }
199 + updates.push("rating = ?");
200 + values.push(rating);
201 + }
202 +
203 + if (stress_level !== undefined) {
204 + if (!isValidLevel(stress_level)) {
205 + return res.status(400).json({ error: "stress_level deve ser entre 1 e 10" });
206 + }
207 + updates.push("stress_level = ?");
208 + values.push(stress_level);
209 + }
210 +
211 + if (anxiety_level !== undefined) {
212 + if (!isValidLevel(anxiety_level)) {
213 + return res.status(400).json({ error: "anxiety_level deve ser entre 1 e 10" });
214 + }
215 + updates.push("anxiety_level = ?");
216 + values.push(anxiety_level);
217 + }
218 +
219 + if (energy_level !== undefined) {
220 + if (!isValidLevel(energy_level)) {
221 + return res.status(400).json({ error: "energy_level deve ser entre 1 e 10" });
222 + }
223 + updates.push("energy_level = ?");
224 + values.push(energy_level);
225 + }
226 +
227 + if (title !== undefined) {
228 + updates.push("title = ?");
229 + values.push(sanitizeString(title) || null);
230 + }
231 +
232 + if (description !== undefined) {
233 + updates.push("description = ?");
234 + values.push(sanitizeString(description) || null);
235 + }
236 +
237 + if (recorded_at !== undefined) {
238 + if (!isValidDate(recorded_at)) {
239 + return res.status(400).json({ error: "recorded_at deve ser uma data válida" });
240 + }
241 + if (!isNotFutureDate(recorded_at)) {
242 + return res.status(400).json({ error: "recorded_at não pode ser no futuro" });
243 + }
244 + updates.push("recorded_at = ?");
245 + values.push(recorded_at);
246 + }
247 +
248 + updates.push("updated_at = CURRENT_TIMESTAMP");
249 +
250 + try {
251 + db.prepare("BEGIN").run();
252 +
253 + // Update mood if there are changes
254 + if (updates.length > 0) {
255 + const updateQuery = `UPDATE mood SET ${updates.join(', ')} WHERE id = ?`;
256 + values.push(moodId);
257 + db.prepare(updateQuery).run(...values);
258 + }
259 +
260 + // Update mood components if provided
261 + if (mood_components !== undefined) {
262 + const componentValidation = validateMoodComponents(mood_components);
263 + if (!componentValidation.valid) {
264 + db.prepare("ROLLBACK").run();
265 + return res.status(400).json({ error: componentValidation.error });
266 + }
267 +
268 + // Delete existing components
269 + db.prepare("DELETE FROM mood_components WHERE mood_id = ?").run(moodId);
270 +
271 + // Insert new components
272 + if (mood_components.length > 0) {
273 + const componentStmt = db.prepare(`
274 + INSERT INTO mood_components (mood_id, emotion, intensity)
275 + VALUES (?, ?, ?)
276 + `);
277 +
278 + for (const component of mood_components) {
279 + componentStmt.run(
280 + moodId,
281 + component.emotion.toLowerCase(),
282 + component.intensity
283 + );
284 + }
285 + }
286 + }
287 +
288 + db.prepare("COMMIT").run();
289 + res.json({ success: true, id: moodId });
290 + } catch (err) {
291 + db.prepare("ROLLBACK").run();
292 + console.error('Error updating mood:', err);
293 + res.status(500).json({ error: "Erro ao atualizar humor" });
294 + }
295 + });
296 +
297 + // Delete mood entry
298 + router.delete("/:mood_id", (req, res) => {
299 + const moodId = req.params.mood_id;
300 + const db = req.db;
301 +
302 + const result = db.prepare("DELETE FROM mood WHERE id = ?").run(moodId);
303 +
304 + if (result.changes === 0) {
305 + return res.status(404).json({ error: "Humor não encontrado" });
306 + }
307 +
308 + res.json({ success: true, deleted: moodId });
309 + });
310 +
311 + // Get user's moods with optional date range
312 + router.get("/user/:user_id", (req, res) => {
313 + const userId = req.params.user_id;
314 + const { start_date, end_date, page, limit } = req.query;
315 + const db = req.db;
316 +
317 + // Validate pagination
318 + const { page: validPage, limit: validLimit, offset } = validatePagination(page, limit);
319 +
320 + let query = `
321 + SELECT m.*,
322 + COUNT(mc.id) as component_count
323 + FROM mood m
324 + LEFT JOIN mood_components mc ON m.id = mc.mood_id
325 + WHERE m.user_id = ?
326 + `;
327 + const params = [userId];
328 +
329 + // Add date range filtering
330 + if (start_date && end_date) {
331 + if (!isValidDateRange(start_date, end_date)) {
332 + return res.status(400).json({ error: "Intervalo de datas inválido" });
333 + }
334 + query += " AND m.recorded_at BETWEEN ? AND ?";
335 + params.push(start_date, end_date);
336 + } else if (start_date) {
337 + if (!isValidDate(start_date)) {
338 + return res.status(400).json({ error: "Data de início inválida" });
339 + }
340 + query += " AND m.recorded_at >= ?";
341 + params.push(start_date);
342 + } else if (end_date) {
343 + if (!isValidDate(end_date)) {
344 + return res.status(400).json({ error: "Data de fim inválida" });
345 + }
346 + query += " AND m.recorded_at <= ?";
347 + params.push(end_date);
348 + }
349 +
350 + query += " GROUP BY m.id ORDER BY m.recorded_at DESC LIMIT ? OFFSET ?";
351 + params.push(validLimit, offset);
352 +
353 + const moods = db.prepare(query).all(...params);
354 +
355 + // Get components for each mood
356 + const moodIds = moods.map(m => m.id);
357 + if (moodIds.length > 0) {
358 + const componentsQuery = `
359 + SELECT mood_id, emotion, intensity
360 + FROM mood_components
361 + WHERE mood_id IN (${moodIds.map(() => '?').join(',')})
362 + `;
363 +
364 + const components = db.prepare(componentsQuery).all(...moodIds);
365 +
366 + // Group components by mood_id
367 + const componentsByMood = {};
368 + components.forEach(c => {
369 + if (!componentsByMood[c.mood_id]) {
370 + componentsByMood[c.mood_id] = [];
371 + }
372 + componentsByMood[c.mood_id].push({
373 + emotion: c.emotion,
374 + intensity: c.intensity
375 + });
376 + });
377 +
378 + // Attach components to moods
379 + moods.forEach(mood => {
380 + mood.mood_components = componentsByMood[mood.id] || [];
381 + if (mood.mood_components.length > 0) {
382 + mood.emotion_stats = calculateEmotionStats(mood.mood_components);
383 + }
384 + });
385 + }
386 +
387 + // Get total count for pagination
388 + let countQuery = "SELECT COUNT(*) as total FROM mood WHERE user_id = ?";
389 + const countParams = [userId];
390 +
391 + if (start_date && end_date) {
392 + countQuery += " AND recorded_at BETWEEN ? AND ?";
393 + countParams.push(start_date, end_date);
394 + } else if (start_date) {
395 + countQuery += " AND recorded_at >= ?";
396 + countParams.push(start_date);
397 + } else if (end_date) {
398 + countQuery += " AND recorded_at <= ?";
399 + countParams.push(end_date);
400 + }
401 +
402 + const { total } = db.prepare(countQuery).get(...countParams);
403 +
404 + res.json({
405 + moods,
406 + pagination: {
407 + page: validPage,
408 + limit: validLimit,
409 + total,
410 + total_pages: Math.ceil(total / validLimit)
411 + }
412 + });
413 + });
414 +
415 + // Get mood statistics for a user
416 + router.get("/user/:user_id/stats", (req, res) => {
417 + const userId = req.params.user_id;
418 + const { start_date, end_date } = req.query;
419 + const db = req.db;
420 +
421 + let baseQuery = "FROM mood WHERE user_id = ?";
422 + const params = [userId];
423 +
424 + if (start_date && end_date) {
425 + if (!isValidDateRange(start_date, end_date)) {
426 + return res.status(400).json({ error: "Intervalo de datas inválido" });
427 + }
428 + baseQuery += " AND recorded_at BETWEEN ? AND ?";
429 + params.push(start_date, end_date);
430 + }
431 +
432 + // Get aggregated stats
433 + const statsQuery = `
434 + SELECT
435 + COUNT(*) as total_entries,
436 + AVG(rating) as avg_rating,
437 + AVG(stress_level) as avg_stress,
438 + AVG(anxiety_level) as avg_anxiety,
439 + AVG(energy_level) as avg_energy,
440 + MIN(stress_level) as min_stress,
441 + MAX(stress_level) as max_stress,
442 + MIN(anxiety_level) as min_anxiety,
443 + MAX(anxiety_level) as max_anxiety,
444 + MIN(energy_level) as min_energy,
445 + MAX(energy_level) as max_energy
446 + ${baseQuery}
447 + `;
448 +
449 + const stats = db.prepare(statsQuery).get(...params);
450 +
451 + // Get emotion frequency
452 + const emotionQuery = `
453 + SELECT
454 + mc.emotion,
455 + COUNT(*) as frequency,
456 + AVG(mc.intensity) as avg_intensity
457 + FROM mood_components mc
458 + JOIN mood m ON mc.mood_id = m.id
459 + WHERE m.user_id = ?
460 + ${start_date && end_date ? 'AND m.recorded_at BETWEEN ? AND ?' : ''}
461 + GROUP BY mc.emotion
462 + ORDER BY frequency DESC
463 + `;
464 +
465 + const emotionParams = [...params];
466 + const emotions = db.prepare(emotionQuery).all(...emotionParams);
467 +
468 + // Get mood trends by day of week
469 + const dayTrendsQuery = `
470 + SELECT
471 + CASE CAST(strftime('%w', recorded_at) AS INTEGER)
472 + WHEN 0 THEN 'Sunday'
473 + WHEN 1 THEN 'Monday'
474 + WHEN 2 THEN 'Tuesday'
475 + WHEN 3 THEN 'Wednesday'
476 + WHEN 4 THEN 'Thursday'
477 + WHEN 5 THEN 'Friday'
478 + WHEN 6 THEN 'Saturday'
479 + END as day_of_week,
480 + AVG(stress_level) as avg_stress,
481 + AVG(anxiety_level) as avg_anxiety,
482 + AVG(energy_level) as avg_energy,
483 + COUNT(*) as entries
484 + ${baseQuery}
485 + GROUP BY strftime('%w', recorded_at)
486 + `;
487 +
488 + const dayTrends = db.prepare(dayTrendsQuery).all(...params);
489 +
490 + res.json({
491 + overall: stats,
492 + emotions: emotions,
493 + trends: {
494 + by_day_of_week: dayTrends
495 + },
496 + date_range: {
497 + start: start_date || 'all time',
498 + end: end_date || 'all time'
499 + }
500 + });
501 + });
502 +
503 + export default router;
api/src/routes/users.js
@@ -1,31 +1,218 @@
1 1 import express from "express";
2 2 import bcrypt from "bcryptjs";
3 + import {
4 + isValidEmail,
5 + isValidPassword,
6 + sanitizeString,
7 + validateRequiredFields
8 + } from "../utils/validators.js";
3 9
4 10 const router = express.Router();
5 11
6 - // XXX: Needs translations
12 + // Create user
7 13 router.post("/", (req, res) => {
8 - const { email, password, first_name, last_name } = req.body;
9 - if (!email || !password || !first_name || !last_name)
10 - return res.status(400).json({ error: "Campos faltantes" });
14 + const { email, password, first_name, last_name, timezone } = req.body;
15 +
16 + // Validate required fields
17 + const requiredValidation = validateRequiredFields(req.body, [
18 + 'email', 'password', 'first_name', 'last_name'
19 + ]);
20 +
21 + if (!requiredValidation.valid) {
22 + return res.status(400).json({
23 + error: "Campos faltantes",
24 + missing: requiredValidation.missing
25 + });
26 + }
27 +
28 + // Validate email format
29 + if (!isValidEmail(email)) {
30 + return res.status(400).json({ error: "Email inválido" });
31 + }
32 +
33 + // Validate password strength
34 + if (!isValidPassword(password)) {
35 + return res.status(400).json({ error: "Senha deve ter pelo menos 6 caracteres" });
36 + }
11 37
12 38 const hashed = bcrypt.hashSync(password, 10);
39 + const db = req.db;
13 40
14 41 try {
15 - const stmt = req.db.prepare(
16 - `
17 - INSERT INTO users (email, password, first_name, last_name)
18 - VALUES (?, ?, ?, ?)
19 - `
42 + const stmt = db.prepare(`
43 + INSERT INTO users (email, password, first_name, last_name, timezone)
44 + VALUES (?, ?, ?, ?, ?)
45 + `);
46 +
47 + const result = stmt.run(
48 + email.toLowerCase(),
49 + hashed,
50 + sanitizeString(first_name),
51 + sanitizeString(last_name),
52 + timezone || 'UTC'
20 53 );
21 54
22 - const result = stmt.run(email, hashed, first_name, last_name);
55 + res.status(201).json({ id: result.lastInsertRowid });
56 + } catch (err) {
57 + if (err.message.includes('UNIQUE')) {
58 + res.status(400).json({ error: "Email já cadastrado" });
59 + } else {
60 + console.error('Error creating user:', err);
61 + res.status(500).json({ error: "Erro ao criar usuário" });
62 + }
63 + }
64 + });
65 +
66 + // User login
67 + router.post("/login", (req, res) => {
68 + const { email, password } = req.body;
69 +
70 + // Validate required fields
71 + if (!email || !password) {
72 + return res.status(400).json({ error: "Email e senha são obrigatórios" });
73 + }
74 +
75 + const db = req.db;
76 +
77 + const user = db.prepare(`
78 + SELECT id, email, password, first_name, last_name, timezone
79 + FROM users
80 + WHERE email = ?
81 + `).get(email.toLowerCase());
82 +
83 + if (!user) {
84 + return res.status(401).json({ error: "Credenciais inválidas" });
85 + }
86 +
87 + const passwordMatch = bcrypt.compareSync(password, user.password);
88 +
89 + if (!passwordMatch) {
90 + return res.status(401).json({ error: "Credenciais inválidas" });
91 + }
92 +
93 + // Don't send password in response
94 + delete user.password;
95 +
96 + res.json({
97 + success: true,
98 + user
99 + });
100 + });
101 +
102 + // Get user profile
103 + router.get("/:user_id", (req, res) => {
104 + const userId = req.params.user_id;
105 + const db = req.db;
106 +
107 + const user = db.prepare(`
108 + SELECT id, email, first_name, last_name, timezone, created_at, updated_at
109 + FROM users
110 + WHERE id = ?
111 + `).get(userId);
112 +
113 + if (!user) {
114 + return res.status(404).json({ error: "Usuário não encontrado" });
115 + }
116 +
117 + // Get user's mood count
118 + const moodStats = db.prepare(`
119 + SELECT COUNT(*) as total_moods
120 + FROM mood
121 + WHERE user_id = ?
122 + `).get(userId);
123 +
124 + user.total_moods = moodStats.total_moods;
125 +
126 + res.json(user);
127 + });
128 +
129 + // Update user
130 + router.put("/:user_id", (req, res) => {
131 + const userId = req.params.user_id;
132 + const { email, password, first_name, last_name, timezone } = req.body;
133 + const db = req.db;
134 +
135 + // Check if user exists
136 + const existingUser = db.prepare("SELECT id FROM users WHERE id = ?").get(userId);
137 + if (!existingUser) {
138 + return res.status(404).json({ error: "Usuário não encontrado" });
139 + }
140 +
141 + const updates = [];
142 + const values = [];
143 +
144 + if (email !== undefined) {
145 + if (!isValidEmail(email)) {
146 + return res.status(400).json({ error: "Email inválido" });
147 + }
23 148
24 - res.status(201).json({ id: result.lastInsertRowid });
149 + // Check if email is already taken by another user
150 + const emailCheck = db.prepare("SELECT id FROM users WHERE email = ? AND id != ?")
151 + .get(email.toLowerCase(), userId);
152 +
153 + if (emailCheck) {
154 + return res.status(400).json({ error: "Email já está em uso" });
155 + }
156 +
157 + updates.push("email = ?");
158 + values.push(email.toLowerCase());
159 + }
160 +
161 + if (password !== undefined) {
162 + if (!isValidPassword(password)) {
163 + return res.status(400).json({ error: "Senha deve ter pelo menos 6 caracteres" });
164 + }
165 + updates.push("password = ?");
166 + values.push(bcrypt.hashSync(password, 10));
167 + }
168 +
169 + if (first_name !== undefined) {
170 + updates.push("first_name = ?");
171 + values.push(sanitizeString(first_name));
172 + }
173 +
174 + if (last_name !== undefined) {
175 + updates.push("last_name = ?");
176 + values.push(sanitizeString(last_name));
177 + }
178 +
179 + if (timezone !== undefined) {
180 + updates.push("timezone = ?");
181 + values.push(timezone);
182 + }
183 +
184 + if (updates.length === 0) {
185 + return res.status(400).json({ error: "Nenhum campo para atualizar" });
186 + }
187 +
188 + updates.push("updated_at = CURRENT_TIMESTAMP");
189 +
190 + try {
191 + const updateQuery = `UPDATE users SET ${updates.join(', ')} WHERE id = ?`;
192 + values.push(userId);
193 +
194 + db.prepare(updateQuery).run(...values);
195 +
196 + res.json({ success: true, id: userId });
25 197 } catch (err) {
26 - res.status(400).json({ error: "Email já cadastrado" });
198 + console.error('Error updating user:', err);
199 + res.status(500).json({ error: "Erro ao atualizar usuário" });
27 200 }
28 201 });
29 202
30 - export default router;
203 + // Delete user
204 + router.delete("/:user_id", (req, res) => {
205 + const userId = req.params.user_id;
206 + const db = req.db;
31 207
208 + // This will cascade delete all moods and mood_components
209 + const result = db.prepare("DELETE FROM users WHERE id = ?").run(userId);
210 +
211 + if (result.changes === 0) {
212 + return res.status(404).json({ error: "Usuário não encontrado" });
213 + }
214 +
215 + res.json({ success: true, deleted: userId });
216 + });
217 +
218 + export default router;