feat(database): Implement DatabaseManager for managing database structure and initialization
All checks were successful
SSH Backend Deploy / ssh-deploy (push) Successful in 1m51s

feat(routes): Add camera, image, measurement, project, and video routes with Swagger documentation

feat(services): Create storageService and videoService for file management and video processing

fix(errorHandler): Enhance error handling with standardized responses and database operation wrappers
This commit is contained in:
2025-04-27 01:02:33 +02:00
parent 792bdca965
commit 4513af3aa0
30 changed files with 3006 additions and 763 deletions

View File

@@ -0,0 +1,44 @@
// src/database/connection.js
const { Client } = require('pg');
const config = require('../config');
// Création du client PostgreSQL avec la configuration centralisée
const client = new Client({
host: config.database.host,
port: config.database.port,
user: config.database.user,
password: config.database.password,
database: config.database.database
});
let isConnecting = false;
/**
* Initialise la connexion à la base de données
* Réessaie automatiquement si la connexion échoue
*/
function initDatabase() {
if (isConnecting) {
console.log('[DB] Tentative de connexion déjà en cours, ignorer...');
return;
}
console.log('[DB] Initialisation de la connexion à PostgreSQL...');
isConnecting = true;
client.connect(err => {
isConnecting = false;
if (err) {
console.error('[DB] Erreur de connexion à la base de données:', err);
setTimeout(initDatabase, config.database.reconnectInterval);
} else {
console.log('[DB] Connecté à la base de données PostgreSQL.');
}
});
}
// Initialise la connexion lors de l'importation de ce module
initDatabase();
module.exports = client;

View File

@@ -1,302 +1,47 @@
const db = require('../../db.js');
// src/database/database_manager.js
/**
* Ce fichier est conservé pour la rétrocompatibilité mais redirige vers les nouveaux modèles.
* Il sera progressivement supprimé lorsque toutes les références auront été mises à jour.
*/
// Fonctions de gestion de la base de données interne
const Project = require('../models/Project');
const Measurement = require('../models/Measurement');
const Video = require('../models/Video');
const Camera = require('../models/Camera');
async function create_database() {
const queries = [
`CREATE TABLE IF NOT EXISTS projects (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
start_date DATE,
status INTEGER NOT NULL CHECK (status = ANY (ARRAY [0, 1, 2, 3]))
);`,
`ALTER TABLE projects OWNER TO timelapse;`,
`CREATE TABLE IF NOT EXISTS measurements (
id SERIAL PRIMARY KEY,
project_id INTEGER REFERENCES projects ON DELETE CASCADE,
timestamp TIMESTAMP NOT NULL,
path VARCHAR(255),
temperature DOUBLE PRECISION,
humidity DOUBLE PRECISION,
order_id INTEGER NOT NULL,
CONSTRAINT unique_project_photo_order UNIQUE (project_id, order_id)
);`,
`ALTER TABLE measurements OWNER TO timelapse;`,
`CREATE TABLE IF NOT EXISTS videos (
id SERIAL PRIMARY KEY,
project_id INTEGER REFERENCES projects ON DELETE CASCADE,
measurement_ids TEXT NOT NULL,
video_file VARCHAR(255),
resolution VARCHAR(255),
duration INTEGER,
status INTEGER NOT NULL CHECK (status = ANY (ARRAY [0, 1, 2, 3])),
name VARCHAR(255),
progress DOUBLE PRECISION,
started_at TIMESTAMP,
updated_at TIMESTAMP,
eta DOUBLE PRECISION
);`,
`ALTER TABLE videos OWNER TO timelapse;`,
`CREATE TABLE IF NOT EXISTS camera (
id SERIAL PRIMARY KEY,
interval INTEGER NOT NULL,
maintenance INTEGER NOT NULL,
active INTEGER DEFAULT 0 NOT NULL
);`,
`ALTER TABLE camera OWNER TO timelapse;`
];
try {
for (const query of queries) {
await db.query(query);
}
console.log('Database tables created or verified successfully.');
} catch (err) {
console.error('Error creating database tables:', err);
throw err;
}
}
async function check_database_existence() {
const query = `
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN ('projects', 'measurements', 'videos', 'camera');
`;
try {
const result = await db.query(query);
const existingTables = result.rows.map(row => row.table_name);
const requiredTables = ['projects', 'measurements', 'videos', 'camera'];
const missingTables = requiredTables.filter(table => !existingTables.includes(table));
if (missingTables.length > 0) {
console.error('Missing or improperly constructed tables:', missingTables);
throw new Error(`The following tables are missing or not properly constructed: ${missingTables.join(', ')}`);
} else {
console.log('All required tables exist and are properly constructed.');
}
} catch (err) {
console.error('Error checking database tables:', err);
throw err;
}
}
async function delete_database() {
const queries = [
`DROP TABLE IF EXISTS videos;`,
`DROP TABLE IF EXISTS measurements;`,
`DROP TABLE IF EXISTS projects;`,
`DROP TABLE IF EXISTS camera;`
];
try {
for (const query of queries) {
await db.query(query);
}
console.log('Database tables deleted successfully.');
} catch (err) {
console.error('Error deleting database tables:', err);
throw err;
}
}
async function init_function() {
try {
await check_database_existence();
} catch (err) {
console.error('Database check failed:', err);
try {
await delete_database();
await create_database();
console.log('Database initialized successfully.');
} catch (err) {
console.error('Error initializing database:', err);
throw err;
}
} finally {
console.log('Database initialization process completed.');
}
}
init_function()
.then(() => console.log('Database initialization completed.'))
.catch(err => console.error('Error during database initialization:', err));
// Fonctions pour les projets
function handleDatabaseOperation(operation) {
return async (...args) => {
try {
return await operation(...args);
} catch (err) {
console.error(`Error during database operation: ${operation.name}`, err);
throw err;
}
};
}
const project = {
get_all_projects: handleDatabaseOperation(async () => {
const query = `SELECT * FROM projects;`;
return (await db.query(query)).rows;
}),
get_project_by_id: handleDatabaseOperation(async (id) => {
const query = `SELECT * FROM projects WHERE id = $1;`;
return (await db.query(query, [id])).rows[0];
}),
create_project: handleDatabaseOperation(async (name, description, start_date, status) => {
const query = `INSERT INTO projects (name, description, start_date, status) VALUES ($1, $2, $3, $4) RETURNING *;`;
return (await db.query(query, [name, description, start_date, status])).rows[0];
}),
edit_project_by_id: handleDatabaseOperation(async (id, updates) => {
const fields = Object.keys(updates).map((key, index) => `${key} = $${index + 2}`).join(', ');
const values = [id, ...Object.values(updates)];
const query = `UPDATE projects SET ${fields} WHERE id = $1 RETURNING *;`;
return (await db.query(query, values)).rows[0];
}),
delete_project_by_id: handleDatabaseOperation(async (id) => {
const query = `DELETE FROM projects WHERE id = $1;`;
await db.query(query, [id]);
}),
find_current_rendering_project: handleDatabaseOperation(async () => {
const query = `SELECT * FROM projects WHERE status = 1;`;
return (await db.query(query)).rows[0];
}),
// Structure de redirection pour la compatibilité
const database_manager = {
project: {
get_all_projects: Project.getAllProjects,
get_project_by_id: Project.getProjectById,
create_project: Project.createProject,
edit_project_by_id: Project.updateProject,
delete_project: Project.deleteProject,
find_current_rendering_project: Project.findCurrentRenderingProject
},
measurement: {
get_all_measurements: Measurement.getAllMeasurements,
get_measurement_by_id: Measurement.getMeasurementById,
get_measurement_by_project_id_and_order_id: Measurement.getMeasurementByProjectAndOrderId,
get_measurements_by_project_id: Measurement.getMeasurementsByProjectId,
create_measurement: Measurement.createMeasurement,
edit_measurement_by_id: Measurement.updateMeasurement,
delete_measurement: Measurement.deleteMeasurement,
get_next_order_id: Measurement.getNextOrderId
},
video: {
get_all_videos: Video.getAllVideos,
get_video_by_id: Video.getVideoById,
get_videos_by_project_id: Video.getVideosByProjectId,
create_video: Video.createVideo,
edit_video_by_id: Video.updateVideo,
delete_video: Video.deleteVideo
},
capture: {
get_camera: Camera.getCamera,
edit_camera: Camera.updateCamera,
init_camera: Camera.initializeCamera
}
};
const measurement = {
get_all_measurements: handleDatabaseOperation(async () => {
const query = `SELECT * FROM measurements;`;
return (await db.query(query)).rows;
}),
get_measurement_by_id: handleDatabaseOperation(async (id) => {
const query = `SELECT * FROM measurements WHERE id = $1;`;
return (await db.query(query, [id])).rows[0];
}),
get_measurement_by_project_and_order_id: handleDatabaseOperation(async (project_id, order_id) => {
const query = `SELECT * FROM measurements WHERE project_id = $1 AND order_id = $2;`;
return (await db.query(query, [project_id, order_id])).rows[0];
}),
get_measurements_by_project_id: handleDatabaseOperation(async (project_id) => {
const query = `SELECT * FROM measurements WHERE project_id = $1;`;
return (await db.query(query, [project_id])).rows;
}),
create_measurement: handleDatabaseOperation(async (project_id, timestamp, path, temperature, humidity, order_id) => {
const query = `INSERT INTO measurements (project_id, timestamp, path, temperature, humidity, order_id) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *;`;
return (await db.query(query, [project_id, timestamp, path, temperature, humidity, order_id])).rows[0];
}),
edit_measurement_by_id: handleDatabaseOperation(async (id, updates) => {
const fields = Object.keys(updates).map((key, index) => `${key} = $${index + 2}`).join(', ');
const values = [id, ...Object.values(updates)];
const query = `UPDATE measurements SET ${fields} WHERE id = $1 RETURNING *;`;
return (await db.query(query, values)).rows[0];
}),
edit_measurement_by_project_and_order_id: handleDatabaseOperation(async (project_id, order_id, updates) => {
const fields = Object.keys(updates).map((key, index) => `${key} = $${index + 3}`).join(', ');
const values = [project_id, order_id, ...Object.values(updates)];
const query = `UPDATE measurements SET ${fields} WHERE project_id = $1 AND order_id = $2 RETURNING *;`;
return (await db.query(query, values)).rows[0];
}),
delete_measurement_by_id: handleDatabaseOperation(async (id) => {
const query = `DELETE FROM measurements WHERE id = $1;`;
await db.query(query, [id]);
}),
get_next_order_id: handleDatabaseOperation(async (project_id) => {
const query = `SELECT COALESCE(MAX(order_id), 0) + 1 AS next_order_id FROM measurements WHERE project_id = $1;`;
const result = await db.query(query, [project_id]);
return result.rows[0].next_order_id;
})
};
const video = {
get_all_videos: handleDatabaseOperation(async () => {
const query = `SELECT * FROM videos;`;
return (await db.query(query)).rows;
}),
get_video_by_id: handleDatabaseOperation(async (id) => {
const query = `SELECT * FROM videos WHERE id = $1;`;
return (await db.query(query, [id])).rows[0];
}),
get_videos_by_project_id: handleDatabaseOperation(async (project_id) => {
const query = `SELECT * FROM videos WHERE project_id = $1;`;
return (await db.query(query, [project_id])).rows;
}),
create_video: handleDatabaseOperation(async (projectId, measurementIds, name, resolution, duration, status = 0) => {
const query = `INSERT INTO public.videos (project_id, measurement_ids, name, resolution, duration, status) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id;`;
const values = [projectId, measurementIds, name, resolution, duration, status];
return (await db.query(query, values)).rows[0];
}),
edit_video_by_id: handleDatabaseOperation(async (id, updates) => {
const fields = Object.keys(updates).map((key, index) => `${key} = $${index + 2}`).join(', ');
const values = [id, ...Object.values(updates)];
const query = `UPDATE videos SET ${fields} WHERE id = $1 RETURNING *;`;
return (await db.query(query, values)).rows[0];
}),
update_video_file_path_by_id: handleDatabaseOperation(async (id, video_file) => {
const query = `UPDATE videos SET video_file = $1 WHERE id = $2 RETURNING *;`;
return (await db.query(query, [video_file, id])).rows[0];
}
),
delete_video_by_id: handleDatabaseOperation(async (id) => {
const query = `DELETE FROM videos WHERE id = $1;`;
await db.query(query, [id]);
}),
get_unfinished_videos: handleDatabaseOperation(async () => {
// récupérer liste des vidéos dont le status est = 0, 2 ou 3
const query = `SELECT * FROM videos WHERE status IN (0, 2, 3);`;
return (await db.query(query)).rows;
}),
};
const capture = {
get_camera: handleDatabaseOperation(async () => {
const query = `SELECT * FROM camera WHERE id = 1;`;
return (await db.query(query)).rows[0];
}),
edit_camera: handleDatabaseOperation(async (id, updates) => {
const fields = Object.keys(updates).map((key, index) => `${key} = $${index + 2}`).join(', ');
const values = [id, ...Object.values(updates)];
const query = `UPDATE camera SET ${fields} WHERE id = $1 RETURNING *;`;
return (await db.query(query, values)).rows[0];
}),
delete_camera: handleDatabaseOperation(async (id) => {
const query = `DELETE FROM camera WHERE id = $1;`;
await db.query(query, [id]);
}),
init_camera: handleDatabaseOperation(async (id, interval, nb_images, maintenance, stop_flag, idle) => {
const query = `INSERT INTO camera (id, interval, nb_images, maintenance, stop_flag, idle) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *;`;
const values = [id, interval, nb_images, maintenance, stop_flag, idle];
return (await db.query(query, values)).rows[0];
}),
};
// zone de test
async function test_zone(){
//
}
test_zone();
// Export des modules
module.exports = {
project,
measurement,
video,
capture,
};
module.exports = database_manager;