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

Pedro Lucas Porcellis porcellis@eletrotupi.com 6 months ago 634e68b8ab478e7ccb51cd03e1af8382cc520611
Parents: 36300f0
12 file(s) changed
  • api/migrations/001_initial_schema.sql +23 -0
  • api/orbit.db +0 -0
  • api/package.json +3 -3
  • api/src/app.js +4 -10
  • api/src/create-app.js +21 -0
  • api/src/db.js +4 -3
  • api/src/routes/mood.js +6 -6
  • api/src/routes/users.js +5 -5
  • api/src/setup-db.js +37 -0
  • api/tests/mood.test.js +90 -0
  • api/tests/test-helper.js +24 -0
  • api/tests/users.test.js +68 -0
api/migrations/001_initial_schema.sql
@@ -0,0 +1,24 @@
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);
api/orbit.db
Binary file brotha
api/package.json
@@ -4,13 +4,13 @@ "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",
api/src/app.js
@@ -1,12 +1,6 @@
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;
api/src/create-app.js
@@ -0,0 +1,22 @@
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
@@ -1,4 +1,5 @@
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,14 +1,14 @@
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 (?, ?, ?, ?, ?)
@@ -23,11 +23,11 @@ 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,6 +1,6 @@
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
@@ -12,7 +12,7 @@
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 (?, ?, ?, ?)
@@ -27,5 +27,5 @@ res.status(400).json({ error: "Email já cadastrado" });
27 27 }
28 28 });
29 29
30 - module.exports = router;
30 + export default router;
31 31
api/src/setup-db.js
@@ -0,0 +1,38 @@
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 + }
api/tests/mood.test.js
@@ -0,0 +1,90 @@
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 +
api/tests/test-helper.js
@@ -0,0 +1,25 @@
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 + }
api/tests/users.test.js
@@ -0,0 +1,68 @@
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 + });