Commit initial : version fonctionnelle du projet

This commit is contained in:
Mouktar Kimba
2025-06-10 05:20:19 +02:00
commit a8acba240b
39 changed files with 7798 additions and 0 deletions

51
backend/src/index.js Normal file
View File

@ -0,0 +1,51 @@
// Fichier: backend/src/index.js
// Point d'entrée principal pour le serveur backend.
// --- Importation des dépendances ---
const express = require('express');
const app = express();
const db = require('./persistence'); // Gestion de la base de données
const getGreeting = require('./routes/getGreeting');
const getItems = require('./routes/getItems');
const addItem = require('./routes/addItem');
const updateItem = require('./routes/updateItem');
const deleteItem = require('./routes/deleteItem');
// --- Middleware ---
// Permet au serveur de comprendre les requêtes JSON
app.use(express.json());
// Sert les fichiers statiques (si vous en avez, par exemple une version buildée du client)
app.use(express.static(__dirname + '/static'));
// --- Définition des Routes de l'API ---
// Chaque route est associée à une fonction spécifique.
app.get('/api/greeting', getGreeting); // Route pour un message d'accueil
app.get('/api/items', getItems); // Route pour obtenir toutes les tâches
app.post('/api/items', addItem); // Route pour ajouter une nouvelle tâche
app.put('/api/items/:id', updateItem); // Route pour mettre à jour une tâche existante
app.delete('/api/items/:id', deleteItem); // Route pour supprimer une tâche
// --- Démarrage du serveur ---
// Initialise la base de données, puis démarre le serveur Express.
db.init()
.then(() => {
app.listen(3000, () => console.log('Serveur démarré et à l\'écoute sur le port 3000'));
})
.catch((err) => {
console.error("Erreur lors de l'initialisation de la base de données:", err);
process.exit(1); // Quitte l'application en cas d'erreur critique
});
// --- Gestion de la fermeture propre de l'application ---
// Cette fonction s'assure que la connexion à la base de données est bien fermée
// lorsque le processus Node.js est arrêté.
const gracefulShutdown = () => {
db.teardown()
.catch(() => {})
.then(() => process.exit());
};
// Écoute les signaux d'arrêt du système
process.on('SIGINT', gracefulShutdown); // Ctrl+C
process.on('SIGTERM', gracefulShutdown); // Signal d'arrêt standard
process.on('SIGUSR2', gracefulShutdown); // Signal utilisé par nodemon pour le redémarrage

View File

@ -0,0 +1,2 @@
if (process.env.MYSQL_HOST) module.exports = require('./mysql');
else module.exports = require('./sqlite');

View File

@ -0,0 +1,108 @@
const waitPort = require('wait-port');
const fs = require('fs');
const mysql = require('mysql2');
const {
MYSQL_HOST: HOST,
MYSQL_HOST_FILE: HOST_FILE,
MYSQL_USER: USER,
MYSQL_USER_FILE: USER_FILE,
MYSQL_PASSWORD: PASSWORD,
MYSQL_PASSWORD_FILE: PASSWORD_FILE,
MYSQL_DB: DB,
MYSQL_DB_FILE: DB_FILE,
} = process.env;
let pool;
async function init() {
const host = HOST_FILE ? fs.readFileSync(HOST_FILE) : HOST;
const user = USER_FILE ? fs.readFileSync(USER_FILE) : USER;
const password = PASSWORD_FILE ? fs.readFileSync(PASSWORD_FILE) : PASSWORD;
const database = DB_FILE ? fs.readFileSync(DB_FILE) : DB;
await waitPort({
host,
port: 3306,
timeout: 10000,
waitForDns: true,
});
pool = mysql.createPool({
connectionLimit: 5,
host,
user,
password,
database,
charset: 'utf8mb4',
}).promise(); // On utilise les promesses pour un code plus propre
// MODIFIÉ : Ajout de la colonne "dueDate" de type DATE
const createTableSql = `
CREATE TABLE IF NOT EXISTS todo_items (
id varchar(36),
name varchar(255),
completed boolean,
dueDate DATE,
PRIMARY KEY (id)
) DEFAULT CHARSET utf8mb4
`;
try {
await pool.query(createTableSql);
console.log(`Connected to mysql db at host ${HOST}`);
} catch (err) {
throw new Error(err);
}
}
async function teardown() {
await pool.end();
}
async function getItems() {
const [rows] = await pool.query('SELECT * FROM todo_items');
return rows.map((item) =>
Object.assign({}, item, {
completed: item.completed === 1,
}),
);
}
async function getItem(id) {
const [rows] = await pool.query('SELECT * FROM todo_items WHERE id=?', [id]);
if (rows.length === 0) return null;
return Object.assign({}, rows[0], {
completed: rows[0].completed === 1,
});
}
async function storeItem(item) {
// MODIFIÉ : Ajout de la colonne "dueDate" dans l'insertion
await pool.query(
'INSERT INTO todo_items (id, name, completed, dueDate) VALUES (?, ?, ?, ?)',
[item.id, item.name, item.completed ? 1 : 0, item.dueDate],
);
}
async function updateItem(id, item) {
// MODIFIÉ : Ajout de "dueDate" dans la mise à jour
await pool.query(
'UPDATE todo_items SET name=?, completed=?, dueDate=? WHERE id=?',
[item.name, item.completed ? 1 : 0, item.dueDate, id],
);
}
async function removeItem(id) {
await pool.query('DELETE FROM todo_items WHERE id = ?', [id]);
}
module.exports = {
init,
teardown,
getItems,
getItem,
storeItem,
updateItem,
removeItem,
};

View File

@ -0,0 +1,124 @@
const sqlite3 = require('sqlite3').verbose();
const fs = require('fs');
const location = process.env.SQLITE_DB_LOCATION || '/etc/todos/todo.db';
let db;
function init() {
const dirName = require('path').dirname(location);
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName, { recursive: true });
}
return new Promise((acc, rej) => {
db = new sqlite3.Database(location, (err) => {
if (err) return rej(err);
if (process.env.NODE_ENV !== 'test')
console.log(`Using sqlite database at ${location}`);
// MODIFIÉ : Ajout de la colonne "dueDate" de type DATE
const createTableSql = `
CREATE TABLE IF NOT EXISTS todo_items (
id varchar(36),
name varchar(255),
completed boolean,
"dueDate" DATE
)
`;
db.run(createTableSql, (err) => {
if (err) return rej(err);
acc();
});
});
});
}
async function teardown() {
return new Promise((acc, rej) => {
db.close((err) => {
if (err) rej(err);
else acc();
});
});
}
async function getItems() {
return new Promise((acc, rej) => {
db.all('SELECT * FROM todo_items', (err, rows) => {
if (err) return rej(err);
acc(
rows.map((item) =>
Object.assign({}, item, {
completed: item.completed === 1,
}),
),
);
});
});
}
async function getItem(id) {
return new Promise((acc, rej) => {
db.all('SELECT * FROM todo_items WHERE id=?', [id], (err, rows) => {
if (err) return rej(err);
acc(
rows.map((item) =>
Object.assign({}, item, {
completed: item.completed === 1,
}),
)[0],
);
});
});
}
async function storeItem(item) {
return new Promise((acc, rej) => {
// MODIFIÉ : Ajout de la colonne "dueDate" dans l'insertion
db.run(
'INSERT INTO todo_items (id, name, completed, "dueDate") VALUES (?, ?, ?, ?)',
// MODIFIÉ : Ajout de item.dueDate aux valeurs
[item.id, item.name, item.completed ? 1 : 0, item.dueDate],
(err) => {
if (err) return rej(err);
acc();
},
);
});
}
async function updateItem(id, item) {
return new Promise((acc, rej) => {
// MODIFIÉ : Ajout de "dueDate" dans la mise à jour
db.run(
'UPDATE todo_items SET name=?, completed=?, "dueDate"=? WHERE id = ?',
// MODIFIÉ : Ajout de item.dueDate aux valeurs
[item.name, item.completed ? 1 : 0, item.dueDate, id],
(err) => {
if (err) return rej(err);
acc();
},
);
});
}
async function removeItem(id) {
return new Promise((acc, rej) => {
db.run('DELETE FROM todo_items WHERE id = ?', [id], (err) => {
if (err) return rej(err);
acc();
});
});
}
module.exports = {
init,
teardown,
getItems,
getItem,
storeItem,
updateItem,
removeItem,
};

View File

@ -0,0 +1,22 @@
const db = require('../persistence');
const { v4: uuid } = require('uuid');
module.exports = async (req, res) => {
// MODIFIÉ : On récupère 'name' et 'dueDate' du corps de la requête
const { name, dueDate } = req.body;
// Ajout d'une petite validation
if (!name || !dueDate) {
return res.status(400).send({ error: 'Le nom et la date d\'échéance sont requis.' });
}
const item = {
id: uuid(),
name: name,
completed: false,
dueDate: dueDate, // MODIFIÉ : On ajoute la date d'échéance à l'objet
};
await db.storeItem(item);
res.status(201).send(item); // On utilise 201 Created pour un ajout réussi
};

View File

@ -0,0 +1,6 @@
const db = require('../persistence');
module.exports = async (req, res) => {
await db.removeItem(req.params.id);
res.sendStatus(200);
};

View File

@ -0,0 +1,7 @@
const GREETING = 'Hello world!';
module.exports = async (req, res) => {
res.send({
greeting: GREETING,
});
};

View File

@ -0,0 +1,6 @@
const db = require('../persistence');
module.exports = async (req, res) => {
const items = await db.getItems();
res.send(items);
};

View File

@ -0,0 +1,26 @@
const db = require('../persistence');
module.exports = async (req, res) => {
// ÉTAPE 1 : Récupérer la tâche actuelle pour avoir ses valeurs par défaut
const currentItem = await db.getItem(req.params.id);
if (!currentItem) {
return res.status(404).send({ error: 'Tâche non trouvée.' });
}
// ÉTAPE 2 : Créer l'objet mis à jour.
// On utilise les nouvelles valeurs si elles existent dans la requête,
// sinon on garde les anciennes valeurs de 'currentItem'.
const updatedItem = {
name: req.body.name !== undefined ? req.body.name : currentItem.name,
completed: req.body.completed !== undefined ? req.body.completed : currentItem.completed,
dueDate: req.body.dueDate !== undefined ? req.body.dueDate : currentItem.dueDate,
};
// ÉTAPE 3 : Sauvegarder l'objet complet en base de données
await db.updateItem(req.params.id, updatedItem);
// ÉTAPE 4 : Renvoyer la tâche mise à jour au client
const item = await db.getItem(req.params.id);
res.send(item);
};

View File