eletrotupi / tcc/ commit / 634e68b

api: flesh initial version of the backend api (just a test)

Pedro Lucas Porcellis porcellis@eletrotupi.com · 10 months ago 634e68b8ab478e7ccb51cd03e1af8382cc520611 Browse files
Parents 36300f0
Changed files
12
Additions
+285
Deletions
-27
/dev/null → api/migrations/001_initial_schema.sql
1 +
-- Users table
2 +
CREATE TABLE IF NOT EXISTS users (
3 +
  id INTEGER PRIMARY KEY AUTOINCREMENT,
4 +
  email TEXT UNIQUE NOT NULL,
5 +
  password TEXT NOT NULL,
6 +
  first_name TEXT NOT NULL,
7 +
  last_name TEXT NOT NULL,
8 +
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
9 +
);
10 +

11 +
-- Mood tracking table
12 +
CREATE TABLE IF NOT EXISTS mood (
13 +
  id INTEGER PRIMARY KEY AUTOINCREMENT,
14 +
  user_id INTEGER NOT NULL,
15 +
  stress_level INTEGER NOT NULL CHECK (stress_level BETWEEN 1 AND 5),
16 +
  anxiety_level INTEGER NOT NULL CHECK (anxiety_level BETWEEN 1 AND 5),
17 +
  title TEXT,
18 +
  description TEXT,
19 +
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
20 +
  FOREIGN KEY (user_id) REFERENCES users(id)
21 +
);
22 +

23 +
-- Index for faster mood queries by user
24 +
CREATE INDEX IF NOT EXISTS idx_mood_user_id ON mood(user_id);
/dev/null → api/orbit.db
Binary file
api/package.json modified+3 -3
api/package.json
1 1
{
2 2
  "name": "api",
3 3
  "version": "1.0.0",
4 4
  "description": "",
5 5
  "license": "ISC",
6 6
  "author": "",
7 -
  "type": "commonjs",
8 -
  "main": "index.js",
7 +
  "type": "module",
8 +
  "main": "src/index.js",
9 9
  "directories": {
10 10
    "test": "tests"
11 11
  },
12 12
  "scripts": {
13 -
    "test": "echo \"Error: no test specified\" && exit 1"
13 +
    "test": "npx ava"
14 14
  },
15 15
  "dependencies": {
16 16
    "bcryptjs": "^3.0.3",
17 17
    "better-sqlite3": "^12.5.0",
18 18
    "express": "^5.2.1"
20 20
  "devDependencies": {
21 21
    "ava": "^6.4.1",
22 22
    "supertest": "^7.1.4"
23 23
  }
24 24
}
api/src/app.js modified+4 -10
api/src/app.js
1 -
const express = require("express");
2 -
const app = express();
3 -

4 -
const users = require("./routes/users");
5 -
const mood = require("./routes/mood");
6 -

7 -
app.use(express.json());
8 -
app.use("/users", users);
9 -
app.use("/mood", mood);
1 +
import { createApp } from "./create-app.js";
2 +
import db from "./db.js";
10 3

11 -
module.exports = app;
4 +
const app = createApp(db);
12 5

6 +
export default app;
/dev/null → api/src/create-app.js
1 +
import express from "express";
2 +
import users from "./routes/users.js";
3 +
import mood from "./routes/mood.js";
4 +

5 +
export function createApp(database) {
6 +
  const app = express();
7 +

8 +
  app.use(express.json());
9 +

10 +
  // Inject database into request
11 +
  app.use((req, res, next) => {
12 +
    req.db = database;
13 +
    next();
14 +
  });
15 +

16 +
  app.use("/users", users);
17 +
  app.use("/mood", mood);
18 +

19 +
  return app;
20 +
}
21 +

22 +
export default createApp;
api/src/db.js modified+4 -3
api/src/db.js
1 -
const Database = require('better-sqlite3');
2 -
const db = new Database('orbit.db');
1 +
import { setupDatabase } from './setup-db.js';
2 +

3 +
const db = setupDatabase();
3 4

4 -
module.exports = db;
5 +
export default db;
api/src/routes/mood.js
1 -
const express = require("express");
2 -
const db = require("../db");
1 +
import express from "express";
2 +

3 3
const router = express.Router();
4 4

5 5
router.post("/", (req, res) => {
6 6
  const { user_id, stress_level, anxiety_level, title, description } = req.body;
7 7

8 8
  if (!user_id || !stress_level || !anxiety_level)
9 -
    return res.status(400).json({ error: "Campos fatlantes" });
9 +
    return res.status(400).json({ error: "Campos faltantes" });
10 10

11 -
  const stmt = db.prepare(
11 +
  const stmt = req.db.prepare(
12 12
    `
13 13
      INSERT INTO mood (user_id, stress_level, anxiety_level, title, description)
14 14
      VALUES (?, ?, ?, ?, ?)
15 15
    `
16 16
  );
21 21

22 22
  res.status(201).json({ id: result.lastInsertRowid });
23 23
});
24 24

25 25
router.get("/:user_id", (req, res) => {
26 -
  const stmt = db.prepare("SELECT * FROM mood WHERE user_id = ?");
26 +
  const stmt = req.db.prepare("SELECT * FROM mood WHERE user_id = ?");
27 27
  const rows = stmt.all(req.params.user_id);
28 28

29 29
  res.json(rows);
30 30
});
31 31

32 -
module.exports = router;
32 +
export default router;
33 33

api/src/routes/users.js
1 -
const express = require("express");
2 -
const bcrypt = require("bcryptjs");
3 -
const db = require("../db");
1 +
import express from "express";
2 +
import bcrypt from "bcryptjs";
3 +

4 4
const router = express.Router();
5 5

6 6
// XXX: Needs translations
7 7
router.post("/", (req, res) => {
8 8
  const { email, password, first_name, last_name } = req.body;
10 10
    return res.status(400).json({ error: "Campos faltantes" });
11 11

12 12
  const hashed = bcrypt.hashSync(password, 10);
13 13

14 14
  try {
15 -
    const stmt = db.prepare(
15 +
    const stmt = req.db.prepare(
16 16
      `
17 17
        INSERT INTO users (email, password, first_name, last_name)
18 18
        VALUES (?, ?, ?, ?)
19 19
      `
20 20
    );
25 25
  } catch (err) {
26 26
    res.status(400).json({ error: "Email já cadastrado" });
27 27
  }
28 28
});
29 29

30 -
module.exports = router;
30 +
export default router;
31 31

/dev/null → api/src/setup-db.js
1 +
import Database from 'better-sqlite3';
2 +
import fs from 'fs';
3 +
import path from 'path';
4 +
import { fileURLToPath } from 'url';
5 +

6 +
const __filename = fileURLToPath(import.meta.url);
7 +
const __dirname = path.dirname(__filename);
8 +

9 +
export function setupDatabase(dbPath = 'orbit.db') {
10 +
  const db = new Database(dbPath);
11 +

12 +
  // Read and execute migrations
13 +
  const migrationsDir = path.join(__dirname, '..', 'migrations');
14 +
  const migrationFile = path.join(migrationsDir, '001_initial_schema.sql');
15 +

16 +
  if (fs.existsSync(migrationFile)) {
17 +
    const migration = fs.readFileSync(migrationFile, 'utf8');
18 +
    db.exec(migration);
19 +
  }
20 +

21 +
  return db;
22 +
}
23 +

24 +
export function setupTestDatabase() {
25 +
  // Use in-memory database for tests
26 +
  const db = new Database(':memory:');
27 +

28 +
  // Read and execute migrations
29 +
  const migrationsDir = path.join(__dirname, '..', 'migrations');
30 +
  const migrationFile = path.join(migrationsDir, '001_initial_schema.sql');
31 +

32 +
  if (fs.existsSync(migrationFile)) {
33 +
    const migration = fs.readFileSync(migrationFile, 'utf8');
34 +
    db.exec(migration);
35 +
  }
36 +

37 +
  return db;
38 +
}
/dev/null → api/tests/mood.test.js
1 +
import test from "ava";
2 +
import request from "supertest";
3 +
import { createApp } from "../src/create-app.js";
4 +
import { setupTestDb, cleanupTestDb } from "./test-helper.js";
5 +

6 +
let app;
7 +
let db;
8 +
let userId;
9 +

10 +
test.before(() => {
11 +
  db = setupTestDb();
12 +
  app = createApp(db);
13 +
});
14 +

15 +
test.beforeEach(async () => {
16 +
  cleanupTestDb();
17 +

18 +
  // Create a user for the mood tests
19 +
  const res = await request(app)
20 +
    .post("/users")
21 +
    .send({
22 +
      email: "maria@example.com",
23 +
      password: "123456",
24 +
      first_name: "Maria",
25 +
      last_name: "Silva",
26 +
    });
27 +

28 +
  userId = res.body.id;
29 +
});
30 +

31 +
test.serial("Registrar humor", async (t) => {
32 +
  const res = await request(app).post("/mood").send({
33 +
    user_id: userId,
34 +
    stress_level: 3,
35 +
    anxiety_level: 4,
36 +
    title: "Bad day",
37 +
    description: "Stressado com a faculdade",
38 +
  });
39 +

40 +
  t.is(res.status, 201);
41 +
  t.truthy(res.body.id);
42 +
});
43 +

44 +
test.serial("Listar humores do usuário", async (t) => {
45 +
  const res = await request(app).get(`/mood/${userId}`);
46 +
  t.is(res.status, 200);
47 +
  t.true(Array.isArray(res.body));
48 +
});
49 +

50 +
test.serial("Falha ao registrar humor sem stress_level", async (t) => {
51 +
  const res = await request(app).post("/mood").send({
52 +
    user_id: userId,
53 +
    anxiety_level: 4,
54 +
    title: "Test",
55 +
  });
56 +

57 +
  t.is(res.status, 400);
58 +
  t.is(res.body.error, "Campos faltantes");
59 +
});
60 +

61 +
test.serial("Listar múltiplos humores do usuário", async (t) => {
62 +
  // Ensure we have a valid userId
63 +
  t.truthy(userId, "userId should be defined");
64 +

65 +
  // Create first mood entry
66 +
  const res1 = await request(app).post("/mood").send({
67 +
    user_id: userId,
68 +
    stress_level: 3,
69 +
    anxiety_level: 4,
70 +
    title: "Morning",
71 +
    description: "Morning mood",
72 +
  });
73 +
  t.is(res1.status, 201);
74 +

75 +
  // Create second mood entry
76 +
  const res2 = await request(app).post("/mood").send({
77 +
    user_id: userId,
78 +
    stress_level: 2,
79 +
    anxiety_level: 2,
80 +
    title: "Evening",
81 +
    description: "Evening mood",
82 +
  });
83 +
  t.is(res2.status, 201);
84 +

85 +
  const res = await request(app).get(`/mood/${userId}`);
86 +
  t.is(res.status, 200);
87 +
  t.true(Array.isArray(res.body));
88 +
  t.is(res.body.length, 2);
89 +
});
90 +

/dev/null → api/tests/test-helper.js
1 +
import { setupTestDatabase } from '../src/setup-db.js';
2 +

3 +
// Store the test database globally
4 +
let testDb;
5 +

6 +
// Replace the default db connection with test database
7 +
export function setupTestDb() {
8 +
  testDb = setupTestDatabase();
9 +
  return testDb;
10 +
}
11 +

12 +
// Get the test database
13 +
export function getTestDb() {
14 +
  return testDb;
15 +
}
16 +

17 +
export function cleanupTestDb() {
18 +
  // Clear all tables
19 +
  if (testDb) {
20 +
    testDb.exec(`
21 +
      DELETE FROM mood;
22 +
      DELETE FROM users;
23 +
    `);
24 +
  }
25 +
}
/dev/null → api/tests/users.test.js
1 +
import test from "ava";
2 +
import request from "supertest";
3 +
import { createApp } from "../src/create-app.js";
4 +
import { setupTestDb, cleanupTestDb } from "./test-helper.js";
5 +

6 +
let app;
7 +
let db;
8 +

9 +
test.before(() => {
10 +
  db = setupTestDb();
11 +
  app = createApp(db);
12 +
});
13 +

14 +
test.beforeEach(() => {
15 +
  cleanupTestDb();
16 +
});
17 +

18 +
test("Criar usuário", async (t) => {
19 +
  const res = await request(app)
20 +
    .post("/users")
21 +
    .send({
22 +
      email: "john@example.com",
23 +
      password: "123456",
24 +
      first_name: "John",
25 +
      last_name: "Doe",
26 +
    });
27 +

28 +
  t.is(res.status, 201);
29 +
  t.truthy(res.body.id);
30 +
});
31 +

32 +
test("Falha ao criar usuário sem email", async (t) => {
33 +
  const res = await request(app)
34 +
    .post("/users")
35 +
    .send({
36 +
      password: "123456",
37 +
      first_name: "John",
38 +
      last_name: "Doe",
39 +
    });
40 +

41 +
  t.is(res.status, 400);
42 +
  t.is(res.body.error, "Campos faltantes");
43 +
});
44 +

45 +
test("Falha ao criar usuário com email duplicado", async (t) => {
46 +
  // First user
47 +
  await request(app)
48 +
    .post("/users")
49 +
    .send({
50 +
      email: "duplicate@example.com",
51 +
      password: "123456",
52 +
      first_name: "John",
53 +
      last_name: "Doe",
54 +
    });
55 +

56 +
  // Duplicate email
57 +
  const res = await request(app)
58 +
    .post("/users")
59 +
    .send({
60 +
      email: "duplicate@example.com",
61 +
      password: "123456",
62 +
      first_name: "Jane",
63 +
      last_name: "Smith",
64 +
    });
65 +

66 +
  t.is(res.status, 400);
67 +
  t.is(res.body.error, "Email já cadastrado");
68 +
});