eletrotupi / tcc/ commit / ba9ebee

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

Pedro Lucas Porcellis porcellis@eletrotupi.com · 10 months ago ba9ebeebe13950cdc2b168d63fe2ec5b3be67d11 Browse files
Parents 06314a5
Changed files
2
Additions
+687
Deletions
-31
api/src/routes/mood.js modified+488 -18
api/src/routes/mood.js
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 modified+199 -13
api/src/routes/users.js
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;