api: flesh initial version of the backend api (just a test)
Pedro Lucas Porcellis porcellis@eletrotupi.com · 10 months ago
634e68b8ab478e7ccb51cd03e1af8382cc520611 Browse filesParents
36300f0Changed 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);api/orbit.db
added/dev/null → api/orbit.db
Binary file
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
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
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 +
});