diff --git a/api.js b/api.js new file mode 100644 index 0000000..f515dee --- /dev/null +++ b/api.js @@ -0,0 +1,24 @@ +const express = require('express'); +const router = express.Router(); +const cors = require('cors'); +const projectRoutes = require('./routes/projectRoutes'); +const measurementRoutes = require('./routes/measurementRoutes'); +const videoRoutes = require('./routes/videoRoutes'); +const imageRoutes = require('./routes/imageRoutes'); +const uploadRoutes = require('./routes/uploadRoutes'); +const fileWatcher = require('./src/data/filewatcher.js'); + +router.use(cors({ + origin: ['http://127.0.0.1:5500', 'http://localhost:5500', 'http://localhost:3000'], + methods: ['GET', 'POST', 'PUT', 'DELETE'], + allowedHeaders: ['Content-Type'], + credentials: true, +})); + +router.use('/', projectRoutes); +router.use('/', measurementRoutes); +router.use('/', videoRoutes); +router.use('/', imageRoutes); +router.use('/', uploadRoutes); + +module.exports = router; diff --git a/routes/api.js b/routes/api.js deleted file mode 100644 index dbb7686..0000000 --- a/routes/api.js +++ /dev/null @@ -1,655 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const db = require('../db'); -const path = require('path'); -const fs = require('fs'); -const ffmpeg = require('../ffmpeg'); -const fileUtils = require('../utils/fileUtils'); -const serverError = require('../utils/serverError'); -const multer = require('multer'); -const video = require('../utils/video'); -const dbTester = require('../test/tester'); -const cors = require('cors'); // Import the cors package -const projectManager = require('../src/project/projectManager.js'); -const measureManager = require('../src/measure/measureManager.js'); -const fileWatcher = require('../src/data/filewatcher.js'); - -router.use(cors({ - origin: ['http://127.0.0.1:5500', 'http://localhost:5500', 'http://localhost:3000'], - methods: ['GET', 'POST', 'PUT', 'DELETE'], - allowedHeaders: ['Content-Type'], - credentials: true, -})); - -/** - * @swagger - * /projects: - * get: - * description: Use to request all projects - * responses: - * 200: - * description: A successful response - * 500: - * description: Internal server error - */ -router.get('/projects', async (req, res) => { - try { - const projects = await projectManager.getAllProjects(); - res.json(projects); - } catch (error) { - serverError.sendError('Error getting all projects:', res, error); - } -}); - -/** - * @swagger - * /projects/{id}: - * get: - * description: Use to request a specific project by id - * parameters: - * - in: path - * name: id - * required: true - * description: Numeric ID of the project to retrieve. - * schema: - * type: integer - * responses: - * 200: - * description: A successful response - * 500: - * description: Internal server error - */ -router.get('/projects/:id', async (req, res) => { - const projectId = req.params.id; - if (!projectId || isNaN(projectId)) { - return res.status(400).json({ error: 'Invalid project ID' }); - } - try { - const project = await projectManager.getProjectById(projectId); - res.json(project); - } catch (error) { - serverError.sendError('Error getting project by ID:', res, error); - } -}); - -/** - * @swagger - * /projects/{id}/videos: - * get: - * description: Use to request all videos for a specific project by project id - * parameters: - * - in: path - * name: id - * required: true - * description: Numeric ID of the project to retrieve videos for. - * schema: - * type: integer - * responses: - * 200: - * description: A successful response - * 500: - * description: Internal server error - */ -router.get('/projects/:id/videos', async (req, res) => { - const projectId = req.params.id; - if (!projectId || isNaN(projectId)) { - return res.status(400).json({ error: 'Invalid project ID' }); - } - try { - const videos = await projectManager.getVideosByProjectId(projectId); - res.json(videos); - } - catch (error) { - serverError.sendError('Error getting videos by project ID:', res, error); - } -}); - -/** - * @swagger - * /projects/{id}/measurements: - * get: - * description: Use to request all measurements for a specific project by project id - * parameters: - * - in: path - * name: id - * required: true - * description: Numeric ID of the project to retrieve measurements for. - * schema: - * type: integer - * responses: - * 200: - * description: A successful response - * 500: - * description: Internal server error - */ -router.get('/projects/:id/measurements', async (req, res) => { - const projectId = req.params.id; - if (!projectId || isNaN(projectId)) { - return res.status(400).json({ error: 'Invalid project ID' }); - } - try { - const measurements = await projectManager.getMeasurementsByProjectId(projectId); - res.json(measurements); - } - catch (error) { - serverError.sendError('Error getting measurements by project ID:', res, error); - } -}); - -/** - * @swagger - * /projects: - * post: - * description: Use to add a new project - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * name: - * type: string - * description: - * type: string - * responses: - * 201: - * description: Project added successfully - * 500: - * description: Internal server error - */ -router.post('/projects', async (req, res) => { - const { name, description } = req.body; - if (!name || !description) { - return res.status(400).json({ error: 'Name and description are required' }); - } - try { - project = await projectManager.createProject(name, description, new Date(), 0); - projectManager.createProjectDirectory(project.id); - res.status(201).json({ message: 'Project added successfully', id: project.id }); - } - catch (error) { - serverError.sendError('Error creating project:', res, error); - } -}); - -/** - * @swagger - * /projects/{id}: - * delete: - * description: Use to delete a project by ID - * parameters: - * - in: path - * name: id - * required: true - * description: Numeric ID of the project to delete. - * schema: - * type: integer - * responses: - * 200: - * description: Project deleted successfully - * 404: - * description: No project found with this ID - * 500: - * description: Internal server error - */ -router.delete('/projects/:id', async (req, res) => { - const projectId = req.params.id; - if (!projectId || isNaN(projectId)) { - return res.status(400).json({ error: 'Invalid project ID' }); - } - - try { - projectManager.deleteProjectDirectory(projectId); - projectManager.deleteProjectById(projectId); - res.status(200).json({ message: 'Project deleted successfully', id: projectId }); - } catch (error) { - serverError.sendError('Error deleting project:', res, error); - } -}); - -/** - * @swagger - * /measurements: - * get: - * description: Use to request all measurements - * responses: - * 200: - * description: A successful response - * 500: - * description: Internal server error - */ -router.get('/measurements', (req, res) => { - const query = 'SELECT * FROM public.measurements'; - db.query(query, (err, results) => { - if (err) { - serverError.sendError('Erreur lors de la récupération des mesures:', res, err); - } - res.json(results.rows); - }); -}); - -/** - * @swagger - * /measurements/{id}: - * get: - * description: Use to request a specific measurement by id - * parameters: - * - in: path - * name: id - * required: true - * description: Numeric ID of the measurement to retrieve. - * schema: - * type: integer - * responses: - * 200: - * description: A successful response - * 500: - * description: Internal server error - */ -router.get('/measurements/:id', (req, res) => { - const measurementId = req.params.id; - if (!measurementId || isNaN(measurementId)) { - return res.status(400).json({ error: 'Invalid measurement ID' }); - } - const query = 'SELECT * FROM public.measurements WHERE id = $1'; - db.query(query, [measurementId], (err, results) => { - if (err) { - serverError.sendError('Erreur lors de la récupération de la mesure:', res, err); - } - res.json(results.rows); - }); -}); - -router.get('/measurements/:projectId/:orderId', async (req, res) => { - const projectId = req.params.projectId; - const orderId = req.params.orderId; - if (!projectId || isNaN(projectId) || !orderId || isNaN(orderId)) { - return res.status(400).json({ error: 'Invalid project ID or order ID' }); - } - try { - const measurement = await measureManager.getMeasurement(projectId, orderId); - res.json(measurement); - } catch (error) { - serverError.sendError('Error getting measurement:', res, error); - } -}); - -/** - * @swagger - * /measurements: - * post: - * description: Use to add a new measurement - * responses: - * 201: - * description: Measurement added successfully - * 500: - * description: Internal server error - */ -router.post('/measurements', (req, res) => { - const { project_id, timestamp, image_path, temperature, humidity} = req.body; - if (!project_id || !timestamp || !image_path || !temperature || !humidity) { - return res.status(400).json({ error: 'All fields are required' }); - } - const query = 'INSERT INTO public.measurements (project_id, timestamp, image_path, temperature, humidity) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id'; - db.query(query, [project_id, timestamp, image_path, temperature, humidity], (err, results) => { - if (err) { - serverError.sendError('Erreur lors de l\'ajout de la mesure:', res, err); - } - res.status(201).json({ message: 'Mesure ajoutée avec succès', id: results.rows[0].id }); - }); -}); - -/** - * @swagger - * /measurements/{id}: - * delete: - * description: Use to delete a measurement by ID - * parameters: - * - in: path - * name: id - * required: true - * description: Numeric ID of the measurement to delete. - * schema: - * type: integer - * responses: - * 200: - * description: Measurement deleted successfully - * 404: - * description: No measurement found with this ID - * 500: - * description: Internal server error - */ -router.delete('/measurements/:id', async (req, res) => { - const measurementId = req.params.id; - if (!measurementId || isNaN(measurementId)) { - return res.status(400).json({ error: 'Invalid measurement ID' }); - } - try { - const measurement = await measureManager.deleteMeasurement(measurementId); - res.status(200).json({ message: 'Measurement deleted successfully', id: measurementId }); - } catch (error) { - serverError.sendError('Error deleting measurement:', res, error); - } -}); - -router.delete('/measurements/:projectId/:orderId', async (req, res) => { - const projectId = req.params.projectId; - const orderId = req.params.orderId; - if (!projectId || isNaN(projectId) || !orderId || isNaN(orderId)) { - return res.status(400).json({ error: 'Invalid project ID or order ID' }); - } - try { - const measurement = await measureManager.deleteMeasurementByOrderId(projectId, orderId); - res.status(200).json({ message: 'Measurement deleted successfully', id: measurement.id }); - } catch (error) { - serverError.sendError('Error deleting measurement:', res, error); - } -}); - -/** - * @swagger - * /videos: - * get: - * description: Use to request all videos - * responses: - * 200: - * description: A successful response - * 500: - * description: Internal server error - */ -router.get('/videos', (req, res) => { - const query = 'SELECT * FROM public.videos'; - db.query(query, (err, results) => { - if (err) { - serverError.sendError('Erreur lors de la récupération des vidéos:', res, err); - } - res.json(results.rows); - }); -}); - -/** - * @swagger - * /videos/{id}: - * get: - * description: Use to request a specific video by id - * parameters: - * - in: path - * name: id - * required: true - * description: Numeric ID of the video to retrieve. - * schema: - * type: integer - * responses: - * 200: - * description: A successful response - * 500: - * description: Internal server error - */ -router.get('/videos/:id', (req, res) => { - const videoId = req.params.id; - if (!videoId || isNaN(videoId)) { - return res.status(400).json({ error: 'Invalid video ID' }); - } - const query = 'SELECT * FROM public.videos WHERE id = $1'; - db.query(query, [videoId], (err, results) => { - if (err) { - serverError.sendError('Erreur lors de la récupération de la vidéo:', res, err); - } - res.json(results.rows); - }); -}); - -/** - * @swagger - * /videos: - * post: - * description: Use to create a new video - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * project_id: - * type: integer - * measurement_ids: - * type: string - * video_path: - * type: string - * duration: - * type: number - * resolution: - * type: string - * name: - * type: string - * responses: - * 201: - * description: Video created successfully - * 500: - * description: Internal server error - */ -router.post('/videos', (req, res) => { - const { project_id, measurement_ids, video_path, duration, resolution, name } = req.body; - if (!project_id || !measurement_ids || !video_path || !duration || !resolution || !name) { - return res.status(400).json({ error: 'All fields are required' }); - } - - const list_ids = measurement_ids.split(','); - const image_count = list_ids.length; - const videoPath = '/videos/' + name + '.mp4'; - - const query_first = 'SELECT timestamp FROM public.measurements WHERE id = $1'; - const query_last = 'SELECT timestamp FROM public.measurements WHERE id = $1'; - - db.query(query_first, [list_ids[0]], (err, results) => { - if (err) { - serverError.sendError('Erreur lors de la récupération du timestamp de la première image:', res, err); - } - const start_timestamp = results.rows[0].timestamp; - - db.query(query_last, [list_ids[image_count - 1]], (err, results) => { - if (err) { - serverError.sendError('Erreur lors de la récupération du timestamp de la dernière image:', res, err); - } - const end_timestamp = results.rows[0].timestamp; - const fps = image_count / duration; - - const query = 'INSERT INTO public.videos (project_id, measurement_ids, video_path, start_timestamp, end_timestamp, image_count, resolution, duration, fps, status, name) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id'; - db.query(query, [project_id, measurement_ids, videoPath, start_timestamp, end_timestamp, image_count, resolution, duration, fps, 0, name], (err, results) => { - if (err) { - serverError.sendError('Erreur lors de l\'ajout de la vidéo:', res, err); - } - res.status(201).json({ message: 'Vidéo ajoutée avec succès', id: results.rows[0].id }); - }); - }); - }); -}); - -/** - * @swagger - * /videos/{id}: - * delete: - * description: Use to delete a video by ID - * parameters: - * - in: path - * name: id - * required: true - * description: Numeric ID of the video to delete. - * schema: - * type: integer - * responses: - * 200: - * description: Video deleted successfully - * 404: - * description: No video found with this ID - * 500: - * description: Internal server error - */ -router.delete('/videos/:id', (req, res) => { - const videoId = req.params.id; - if (!videoId || isNaN(videoId)) { - return res.status(400).json({ error: 'Invalid video ID' }); - } - const query = 'DELETE FROM public.videos WHERE id = $1 RETURNING id'; - db.query(query, [videoId], (err, results) => { - if (err) { - serverError.sendError('Erreur lors de la suppression de la vidéo:', res, err); - } - if (results.rowCount === 0) { - return res.status(404).json({ error: 'Aucune vidéo trouvée avec cet ID.' }); - } - res.status(200).json({ message: 'Vidéo supprimée avec succès', id: videoId }); - }); -}); - -// Test Image -/** - * @swagger - * /smile: - * get: - * description: Use to request the smile image - * responses: - * 200: - * description: A successful response - * 404: - * description: Image not found - */ -router.get('/smile', (req, res) => { - const imagePath = dbTester.getSmileImage(); - fs.access(imagePath, fs.constants.F_OK, (err) => { - if (err) { - console.error('Image not found:', err); - return res.status(404).json({ error: 'Image not found' }); - } - res.sendFile(imagePath); - }); -}); - -/** - * @swagger - * /image/{filename}: - * get: - * description: Use to request a specific image by filename - * parameters: - * - in: path - * name: filename - * required: true - * description: Name of the image file to retrieve. - * schema: - * type: string - * example: image.jpg - * responses: - * 200: - * description: A successful response - * 404: - * description: Image not found - */ -router.get('/image/:filename', (req, res) => { - const imagePath = path.join('/storage/image', req.params.filename); - fs.access(imagePath, fs.constants.F_OK, (err) => { - if (err) { - console.error('Image not found:', err); - return res.status(404).json({ error: 'Image not found' }); - } - res.sendFile(imagePath); - }); -}); - -const upload = multer({ storage: multer.memoryStorage() }); - -/** - * @swagger - * /upload: - * post: - * description: Use to upload an image - * requestBody: - * required: true - * content: - * multipart/form-data: - * schema: - * type: object - * properties: - * image: - * type: string - * format: binary - * projectId: - * type: integer - * orderId: - * type: integer - * responses: - * 200: - * description: Image uploaded successfully - * 400: - * description: All fields are required - * 500: - * description: Internal server error - */ -router.post('/upload', upload.single('image'), async (req, res) => { - const { projectId, orderId } = req.body; - const image = req.file; // Multer adds the file to req.file - - if (!image || !projectId || !orderId) { - return res.status(400).json({ error: 'All fields are required' }); - } - - try { - const imagePath = await measureManager.uploadMeasureImage(image, projectId, orderId); - res.json({ message: 'Image uploaded successfully', path: imagePath }); - } catch (error) { - serverError.sendError('Error uploading image:', res, error); - } -}); - -/** - * @swagger - * /uploadmeasurement: - * post: - * description: Use to upload a measurement with an image - * requestBody: - * required: true - * content: - * multipart/form-data: - * schema: - * type: object - * properties: - * image: - * type: string - * format: binary - * projectId: - * type: integer - * timestamp: - * type: string - * format: date-time - * temperature: - * type: number - * humidity: - * type: number - * responses: - * 200: - * description: Measurement uploaded successfully - * 400: - * description: All fields are required - * 500: - * description: Internal server error - */ -router.post('/uploadmeasurement', upload.single('image'), async (req, res) => { - const { projectId, timestamp, temperature, humidity } = req.body; - const image = req.file; // Multer adds the file to req.file - - if (!image || !projectId || !timestamp || !temperature || !humidity) { - return res.status(400).json({ error: 'All fields are required' }); - } - - try { - const nextOrderId = await measureManager.getNextOrderId(projectId); - const imagePath = await measureManager.uploadMeasureImage(image, projectId, nextOrderId); - const measurement = await measureManager.addMeasureToProject(projectId, timestamp, imagePath, temperature, humidity, nextOrderId); - res.json({ message: 'Measurement uploaded successfully', path: imagePath, id: measurement.id }); - } catch (error) { - serverError.sendError('Error uploading measurement:', res, error); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/routes/imageRoutes.js b/routes/imageRoutes.js new file mode 100644 index 0000000..e6eeffe --- /dev/null +++ b/routes/imageRoutes.js @@ -0,0 +1,89 @@ +const express = require('express'); +const router = express.Router(); +const path = require('path'); +const fs = require('fs'); +const dbTester = require('../test/tester'); +const db = require('../db'); +const serverError = require('../utils/serverError'); + +/** + * @swagger + * /smile: + * get: + * summary: Retrieve a smile image + * responses: + * 200: + * description: Smile image retrieved successfully + * content: + * image/jpeg: + * schema: + * type: string + * format: binary + * 404: + * description: Image not found + */ +router.get('/smile', (req, res) => { + const imagePath = dbTester.getSmileImage(); + fs.access(imagePath, fs.constants.F_OK, (err) => { + if (err) { + console.error('Image not found:', err); + return res.status(404).json({ error: 'Image not found' }); + } + res.sendFile(imagePath); + }); +}); + +/** + * @swagger + * /images/{projectId}/{orderId}: + * get: + * summary: Retrieve an image by project ID and order ID + * parameters: + * - in: path + * name: projectId + * required: true + * schema: + * type: string + * description: The ID of the project + * - in: path + * name: orderId + * required: true + * schema: + * type: string + * description: The ID of the order + * responses: + * 200: + * description: Image retrieved successfully + * content: + * image/jpeg: + * schema: + * type: string + * format: binary + * 404: + * description: Image not found + * 500: + * description: Internal server error + */ +router.get('/images/:projectId/:orderId', (req, res) => { + const projectId = req.params.projectId; + const orderId = req.params.orderId; + const query = 'SELECT path FROM public.measurements WHERE project_id = $1 AND order_id = $2'; + db.query(query, [projectId, orderId], (err, results) => { + if (err) { + return serverError.sendError('Error getting image:', res, err); + } + if (results.rows.length === 0) { + return res.status(404).json({ error: 'Image not found' }); + } + const imagePath = results.rows[0].path; + fs.access(imagePath, fs.constants.F_OK, (err) => { + if (err) { + console.error('Image not found:', err); + return res.status(404).json({ error: 'Image not found' }); + } + res.download(imagePath); + }); + }); +}); + +module.exports = router; diff --git a/routes/measurementRoutes.js b/routes/measurementRoutes.js new file mode 100644 index 0000000..38abff1 --- /dev/null +++ b/routes/measurementRoutes.js @@ -0,0 +1,240 @@ +const express = require('express'); +const router = express.Router(); +const db = require('../db'); +const measureManager = require('../src/measure/measureManager'); +const serverError = require('../utils/serverError'); + +/** + * @swagger + * /measurements: + * get: + * summary: Récupérer toutes les mesures + * description: Récupère toutes les mesures de la base de données. + * responses: + * 200: + * description: Une liste de mesures. + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/Measurement' + * 500: + * description: Erreur serveur. + */ +router.get('/measurements', (req, res) => { + const query = 'SELECT * FROM public.measurements'; + db.query(query, (err, results) => { + if (err) { + serverError.sendError('Erreur lors de la récupération des mesures:', res, err); + } + res.json(results.rows); + }); +}); + +/** + * @swagger + * /measurements/{id}: + * get: + * summary: Récupérer une mesure par ID + * description: Récupère une mesure spécifique en utilisant son ID. + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID de la mesure + * responses: + * 200: + * description: Une mesure. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Measurement' + * 400: + * description: ID de mesure invalide. + * 500: + * description: Erreur serveur. + */ +router.get('/measurements/:id', (req, res) => { + const measurementId = req.params.id; + if (!measurementId || isNaN(measurementId)) { + return res.status(400).json({ error: 'Invalid measurement ID' }); + } + const query = 'SELECT * FROM public.measurements WHERE id = $1'; + db.query(query, [measurementId], (err, results) => { + if (err) { + serverError.sendError('Erreur lors de la récupération de la mesure:', res, err); + } + res.json(results.rows); + }); +}); + +/** + * @swagger + * /measurements/{projectId}/{orderId}: + * get: + * summary: Récupérer une mesure par project ID et order ID + * description: Récupère une mesure spécifique en utilisant le project ID et order ID. + * parameters: + * - in: path + * name: projectId + * schema: + * type: integer + * required: true + * description: ID du projet + * - in: path + * name: orderId + * schema: + * type: integer + * required: true + * description: ID de la commande + * responses: + * 200: + * description: Une mesure. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Measurement' + * 400: + * description: ID de projet ou de commande invalide. + * 500: + * description: Erreur serveur. + */ +router.get('/measurements/:projectId/:orderId', async (req, res) => { + const projectId = req.params.projectId; + const orderId = req.params.orderId; + if (!projectId || isNaN(projectId) || !orderId || isNaN(orderId)) { + return res.status(400).json({ error: 'Invalid project ID or order ID' }); + } + try { + const measurement = await measureManager.getMeasurement(projectId, orderId); + res.json(measurement); + } catch (error) { + serverError.sendError('Error getting measurement:', res, error); + } +}); + +/** + * @swagger + * /measurements: + * post: + * summary: Ajouter une nouvelle mesure + * description: Ajoute une nouvelle mesure à la base de données. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * project_id: + * type: integer + * timestamp: + * type: string + * format: date-time + * image_path: + * type: string + * temperature: + * type: number + * humidity: + * type: number + * responses: + * 201: + * description: Mesure ajoutée avec succès. + * 400: + * description: Tous les champs sont requis. + * 500: + * description: Erreur serveur. + */ +router.post('/measurements', (req, res) => { + const { project_id, timestamp, image_path, temperature, humidity } = req.body; + if (!project_id || !timestamp || !image_path || !temperature || !humidity) { + return res.status(400).json({ error: 'All fields are required' }); + } + const query = 'INSERT INTO public.measurements (project_id, timestamp, image_path, temperature, humidity) VALUES ($1, $2, $3, $4, $5) RETURNING id'; + db.query(query, [project_id, timestamp, image_path, temperature, humidity], (err, results) => { + if (err) { + serverError.sendError('Erreur lors de l\'ajout de la mesure:', res, err); + } + res.status(201).json({ message: 'Mesure ajoutée avec succès', id: results.rows[0].id }); + }); +}); + +/** + * @swagger + * /measurements/{id}: + * delete: + * summary: Supprimer une mesure par ID + * description: Supprime une mesure spécifique en utilisant son ID. + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID de la mesure + * responses: + * 200: + * description: Mesure supprimée avec succès. + * 400: + * description: ID de mesure invalide. + * 500: + * description: Erreur serveur. + */ +router.delete('/measurements/:id', async (req, res) => { + const measurementId = req.params.id; + if (!measurementId || isNaN(measurementId)) { + return res.status(400).json({ error: 'Invalid measurement ID' }); + } + try { + const measurement = await measureManager.deleteMeasurement(measurementId); + res.status(200).json({ message: 'Measurement deleted successfully', id: measurementId }); + } catch (error) { + serverError.sendError('Error deleting measurement:', res, error); + } +}); + +/** + * @swagger + * /measurements/{projectId}/{orderId}: + * delete: + * summary: Supprimer une mesure par project ID et order ID + * description: Supprime une mesure spécifique en utilisant le project ID et order ID. + * parameters: + * - in: path + * name: projectId + * schema: + * type: integer + * required: true + * description: ID du projet + * - in: path + * name: orderId + * schema: + * type: integer + * required: true + * description: ID de la commande + * responses: + * 200: + * description: Mesure supprimée avec succès. + * 400: + * description: ID de projet ou de commande invalide. + * 500: + * description: Erreur serveur. + */ +router.delete('/measurements/:projectId/:orderId', async (req, res) => { + const projectId = req.params.projectId; + const orderId = req.params.orderId; + if (!projectId || isNaN(projectId) || !orderId || isNaN(orderId)) { + return res.status(400).json({ error: 'Invalid project ID or order ID' }); + } + try { + const measurement = await measureManager.deleteMeasurementByOrderId(projectId, orderId); + res.status(200).json({ message: 'Measurement deleted successfully', id: measurement.id }); + } catch (error) { + serverError.sendError('Error deleting measurement:', res, error); + } +}); + +module.exports = router; diff --git a/routes/projectRoutes.js b/routes/projectRoutes.js new file mode 100644 index 0000000..ee29573 --- /dev/null +++ b/routes/projectRoutes.js @@ -0,0 +1,225 @@ +const express = require('express'); +const router = express.Router(); +const projectManager = require('../src/project/projectManager'); +const serverError = require('../utils/serverError'); + +/** + * @swagger + * /projects: + * get: + * summary: Récupérer tous les projets + * description: Récupère tous les projets disponibles. + * responses: + * 200: + * description: Une liste de projets. + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/Project' + * 500: + * description: Erreur serveur. + */ +router.get('/projects', async (req, res) => { + try { + const projects = await projectManager.getAllProjects(); + res.json(projects); + } catch (error) { + serverError.sendError('Error getting all projects:', res, error); + } +}); + +/** + * @swagger + * /projects/{id}: + * get: + * summary: Récupérer un projet par ID + * description: Récupère un projet spécifique en utilisant son ID. + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID du projet + * responses: + * 200: + * description: Un projet. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Project' + * 400: + * description: ID de projet invalide. + * 500: + * description: Erreur serveur. + */ +router.get('/projects/:id', async (req, res) => { + const projectId = req.params.id; + if (!projectId || isNaN(projectId)) { + return res.status(400).json({ error: 'Invalid project ID' }); + } + try { + const project = await projectManager.getProjectById(projectId); + res.json(project); + } catch (error) { + serverError.sendError('Error getting project by ID:', res, error); + } +}); + +/** + * @swagger + * /projects/{id}/videos: + * get: + * summary: Récupérer les vidéos d'un projet par ID + * description: Récupère les vidéos associées à un projet spécifique en utilisant son ID. + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID du projet + * responses: + * 200: + * description: Une liste de vidéos. + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/Video' + * 400: + * description: ID de projet invalide. + * 500: + * description: Erreur serveur. + */ +router.get('/projects/:id/videos', async (req, res) => { + const projectId = req.params.id; + if (!projectId || isNaN(projectId)) { + return res.status(400).json({ error: 'Invalid project ID' }); + } + try { + const videos = await projectManager.getVideosByProjectId(projectId); + res.json(videos); + } catch (error) { + serverError.sendError('Error getting videos by project ID:', res, error); + } +}); + +/** + * @swagger + * /projects/{id}/measurements: + * get: + * summary: Récupérer les mesures d'un projet par ID + * description: Récupère les mesures associées à un projet spécifique en utilisant son ID. + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID du projet + * responses: + * 200: + * description: Une liste de mesures. + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/Measurement' + * 400: + * description: ID de projet invalide. + * 500: + * description: Erreur serveur. + */ +router.get('/projects/:id/measurements', async (req, res) => { + const projectId = req.params.id; + if (!projectId || isNaN(projectId)) { + return res.status(400).json({ error: 'Invalid project ID' }); + } + try { + const measurements = await projectManager.getMeasurementsByProjectId(projectId); + res.json(measurements); + } catch (error) { + serverError.sendError('Error getting measurements by project ID:', res, error); + } +}); + +/** + * @swagger + * /projects: + * post: + * summary: Ajouter un nouveau projet + * description: Ajoute un nouveau projet à la base de données. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * description: + * type: string + * responses: + * 201: + * description: Projet ajouté avec succès. + * 400: + * description: Le nom et la description sont requis. + * 500: + * description: Erreur serveur. + */ +router.post('/projects', async (req, res) => { + const { name, description } = req.body; + if (!name || !description) { + return res.status(400).json({ error: 'Name and description are required' }); + } + try { + const project = await projectManager.createProject(name, description, new Date(), 0); + projectManager.createProjectDirectory(project.id); + res.status(201).json({ message: 'Project added successfully', id: project.id }); + } catch (error) { + serverError.sendError('Error creating project:', res, error); + } +}); + +/** + * @swagger + * /projects/{id}: + * delete: + * summary: Supprimer un projet par ID + * description: Supprime un projet spécifique en utilisant son ID. + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID du projet + * responses: + * 200: + * description: Projet supprimé avec succès. + * 400: + * description: ID de projet invalide. + * 500: + * description: Erreur serveur. + */ +router.delete('/projects/:id', async (req, res) => { + const projectId = req.params.id; + if (!projectId || isNaN(projectId)) { + return res.status(400).json({ error: 'Invalid project ID' }); + } + try { + projectManager.deleteProjectDirectory(projectId); + projectManager.deleteProjectById(projectId); + res.status(200).json({ message: 'Project deleted successfully', id: projectId }); + } catch (error) { + serverError.sendError('Error deleting project:', res, error); + } +}); + +module.exports = router; diff --git a/routes/uploadRoutes.js b/routes/uploadRoutes.js new file mode 100644 index 0000000..6c208d3 --- /dev/null +++ b/routes/uploadRoutes.js @@ -0,0 +1,130 @@ +const express = require('express'); +const router = express.Router(); +const multer = require('multer'); +const measureManager = require('../src/measure/measureManager'); +const serverError = require('../utils/serverError'); + +const upload = multer({ storage: multer.memoryStorage() }); + +/** + * @swagger + * /upload: + * post: + * summary: Télécharger une image + * description: Télécharge une image pour un projet et un ordre spécifiques. + * consumes: + * - multipart/form-data + * parameters: + * - in: formData + * name: image + * type: file + * description: Fichier image à télécharger + * - in: formData + * name: projectId + * type: integer + * description: ID du projet + * - in: formData + * name: orderId + * type: integer + * description: ID de la commande + * responses: + * 200: + * description: Image téléchargée avec succès. + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * path: + * type: string + * 400: + * description: Tous les champs sont requis. + * 500: + * description: Erreur serveur. + */ +router.post('/upload', upload.single('image'), async (req, res) => { + const { projectId, orderId } = req.body; + const image = req.file; + + if (!image || !projectId || !orderId) { + return res.status(400).json({ error: 'All fields are required' }); + } + + try { + const imagePath = await measureManager.uploadMeasureImage(image, projectId, orderId); + res.json({ message: 'Image uploaded successfully', path: imagePath }); + } catch (error) { + serverError.sendError('Error uploading image:', res, error); + } +}); + +/** + * @swagger + * /uploadmeasurement: + * post: + * summary: Télécharger une mesure avec une image + * description: Télécharge une mesure avec une image pour un projet spécifique. + * consumes: + * - multipart/form-data + * parameters: + * - in: formData + * name: image + * type: file + * description: Fichier image à télécharger + * - in: formData + * name: projectId + * type: integer + * description: ID du projet + * - in: formData + * name: timestamp + * type: string + * format: date-time + * description: Horodatage de la mesure + * - in: formData + * name: temperature + * type: number + * description: Température mesurée + * - in: formData + * name: humidity + * type: number + * description: Humidité mesurée + * responses: + * 200: + * description: Mesure téléchargée avec succès. + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * path: + * type: string + * id: + * type: integer + * 400: + * description: Tous les champs sont requis. + * 500: + * description: Erreur serveur. + */ +router.post('/uploadmeasurement', upload.single('image'), async (req, res) => { + const { projectId, timestamp, temperature, humidity } = req.body; + const image = req.file; + + if (!image || !projectId || !timestamp || !temperature || !humidity) { + return res.status(400).json({ error: 'All fields are required' }); + } + + try { + const nextOrderId = await measureManager.getNextOrderId(projectId); + const imagePath = await measureManager.uploadMeasureImage(image, projectId, nextOrderId); + const measurement = await measureManager.addMeasureToProject(projectId, timestamp, imagePath, temperature, humidity, nextOrderId); + res.json({ message: 'Measurement uploaded successfully', path: imagePath, id: measurement.id }); + } catch (error) { + serverError.sendError('Error uploading measurement:', res, error); + } +}); + +module.exports = router; diff --git a/routes/videoRoutes.js b/routes/videoRoutes.js new file mode 100644 index 0000000..d799bff --- /dev/null +++ b/routes/videoRoutes.js @@ -0,0 +1,183 @@ +const express = require('express'); +const router = express.Router(); +const db = require('../db'); +const serverError = require('../utils/serverError'); + +/** + * @swagger + * /videos: + * get: + * summary: Récupérer toutes les vidéos + * description: Récupère toutes les vidéos de la base de données. + * responses: + * 200: + * description: Une liste de vidéos. + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/Video' + * 500: + * description: Erreur serveur. + */ +router.get('/videos', (req, res) => { + const query = 'SELECT * FROM public.videos'; + db.query(query, (err, results) => { + if (err) { + serverError.sendError('Erreur lors de la récupération des vidéos:', res, err); + } + res.json(results.rows); + }); +}); + +/** + * @swagger + * /videos/{id}: + * get: + * summary: Récupérer une vidéo par ID + * description: Récupère une vidéo spécifique en utilisant son ID. + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID de la vidéo + * responses: + * 200: + * description: Une vidéo. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Video' + * 400: + * description: ID de vidéo invalide. + * 500: + * description: Erreur serveur. + */ +router.get('/videos/:id', (req, res) => { + const videoId = req.params.id; + if (!videoId || isNaN(videoId)) { + return res.status(400).json({ error: 'Invalid video ID' }); + } + const query = 'SELECT * FROM public.videos WHERE id = $1'; + db.query(query, [videoId], (err, results) => { + if (err) { + serverError.sendError('Erreur lors de la récupération de la vidéo:', res, err); + } + res.json(results.rows); + }); +}); + +/** + * @swagger + * /videos: + * post: + * summary: Ajouter une nouvelle vidéo + * description: Ajoute une nouvelle vidéo à la base de données. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * project_id: + * type: integer + * measurement_ids: + * type: string + * video_path: + * type: string + * duration: + * type: number + * resolution: + * type: string + * name: + * type: string + * responses: + * 201: + * description: Vidéo ajoutée avec succès. + * 400: + * description: Tous les champs sont requis. + * 500: + * description: Erreur serveur. + */ +router.post('/videos', (req, res) => { + const { project_id, measurement_ids, video_path, duration, resolution, name } = req.body; + if (!project_id || !measurement_ids || !video_path || !duration || !resolution || !name) { + return res.status(400).json({ error: 'All fields are required' }); + } + + const list_ids = measurement_ids.split(','); + const image_count = list_ids.length; + const videoPath = '/videos/' + name + '.mp4'; + + const query_first = 'SELECT timestamp FROM public.measurements WHERE id = $1'; + const query_last = 'SELECT timestamp FROM public.measurements WHERE id = $1'; + + db.query(query_first, [list_ids[0]], (err, results) => { + if (err) { + serverError.sendError('Erreur lors de la récupération du timestamp de la première image:', res, err); + } + const start_timestamp = results.rows[0].timestamp; + + db.query(query_last, [list_ids[image_count - 1]], (err, results) => { + if (err) { + serverError.sendError('Erreur lors de la récupération du timestamp de la dernière image:', res, err); + } + const end_timestamp = results.rows[0].timestamp; + const fps = image_count / duration; + + const query = 'INSERT INTO public.videos (project_id, measurement_ids, video_path, start_timestamp, end_timestamp, image_count, resolution, duration, fps, status, name) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id'; + db.query(query, [project_id, measurement_ids, videoPath, start_timestamp, end_timestamp, image_count, resolution, duration, fps, 0, name], (err, results) => { + if (err) { + serverError.sendError('Erreur lors de l\'ajout de la vidéo:', res, err); + } + res.status(201).json({ message: 'Vidéo ajoutée avec succès', id: results.rows[0].id }); + }); + }); + }); +}); + +/** + * @swagger + * /videos/{id}: + * delete: + * summary: Supprimer une vidéo par ID + * description: Supprime une vidéo spécifique en utilisant son ID. + * parameters: + * - in: path + * name: id + * schema: + * type: integer + * required: true + * description: ID de la vidéo + * responses: + * 200: + * description: Vidéo supprimée avec succès. + * 400: + * description: ID de vidéo invalide. + * 404: + * description: Aucune vidéo trouvée avec cet ID. + * 500: + * description: Erreur serveur. + */ +router.delete('/videos/:id', (req, res) => { + const videoId = req.params.id; + if (!videoId || isNaN(videoId)) { + return res.status(400).json({ error: 'Invalid video ID' }); + } + const query = 'DELETE FROM public.videos WHERE id = $1 RETURNING id'; + db.query(query, [videoId], (err, results) => { + if (err) { + serverError.sendError('Erreur lors de la suppression de la vidéo:', res, err); + } + if (results.rowCount === 0) { + return res.status(404).json({ error: 'Aucune vidéo trouvée avec cet ID.' }); + } + res.status(200).json({ message: 'Vidéo supprimée avec succès', id: videoId }); + }); +}); + +module.exports = router; diff --git a/server.js b/server.js index 4e914be..3778a51 100644 --- a/server.js +++ b/server.js @@ -16,7 +16,7 @@ app.use(cors({ })); // Importer les routes -const apiRoutes = require('./routes/api'); +const apiRoutes = require('./api'); app.use('/api', apiRoutes); // Swagger dependencies diff --git a/src/data/filewatcher.js b/src/data/filewatcher.js index 7aa3fc1..9fa18fb 100644 --- a/src/data/filewatcher.js +++ b/src/data/filewatcher.js @@ -7,9 +7,9 @@ async function checkAndRemoveInvalidEntries() { console.log('Checking for invalid entries...'); try { const measurementsRes = await db.query('SELECT id, path FROM measurements'); - console.log('Fetched measurements:', measurementsRes.rows); + //console.log('Fetched measurements:', measurementsRes.rows); for (const row of measurementsRes.rows) { - console.log('Checking file path:', row.path); + //console.log('Checking file path:', row.path); if (!fs.existsSync(row.path)) { // Remove invalid entry await db.query('DELETE FROM measurements WHERE id = $1', [row.id]); @@ -19,7 +19,7 @@ async function checkAndRemoveInvalidEntries() { // Scan all images in storage const allImages = await storageManager.scanAllImages(); - console.log('Scanned all images:', allImages); + //console.log('Scanned all images:', allImages); for (const imagePath of allImages) { const entryRes = await db.query('SELECT id FROM measurements WHERE path = $1', [imagePath]); if (entryRes.rows.length === 0) { diff --git a/utils/dbUtils.js b/utils/dbUtils.js deleted file mode 100644 index f17ae91..0000000 --- a/utils/dbUtils.js +++ /dev/null @@ -1,91 +0,0 @@ -const db = require('../db'); // Assurez-vous que le chemin est correct -const { get } = require('../routes/api'); - -const projectsTable = { - create: async (name, description, startDate, status) => { - const query = 'INSERT INTO public.projects (name, description, start_date, status) VALUES ($1, $2, $3, $4) RETURNING *'; - const values = [name, description, startDate, status]; - const res = await db.query(query, values); - return res.rows[0]; - }, - update: async (id, name, description, startDate, status) => { - const query = 'UPDATE public.projects SET name = $1, description = $2, start_date = $3, status = $4 WHERE id = $5 RETURNING *'; - const values = [name, description, startDate, status, id]; - const res = await db.query(query, values); - return res.rows[0]; - }, - delete: async (id) => { - const query = 'DELETE FROM public.projects WHERE id = $1'; - const values = [id]; - await db.query(query, values); - }, - getById: async (id) => { - const query = 'SELECT * FROM public.projects WHERE id = $1'; - const values = [id]; - const res = await db.query(query, values); - return res.rows[0]; - }, - getAll: async () => { - const query = 'SELECT * FROM public.projects'; - const res = await db.query(query); - return res.rows; - } -}; - -const measurementsTable = { - create: async (projectId, timestamp, imagePath, temperature, humidity, completed) => { - const query = 'INSERT INTO public.measurements (project_id, timestamp, image_path, temperature, humidity, completed) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *'; - const values = [projectId, timestamp, imagePath, temperature, humidity, completed]; - const res = await db.query(query, values); - return res.rows[0]; - }, - update: async (id, projectId, timestamp, imagePath, temperature, humidity, completed) => { - const query = 'UPDATE public.measurements SET project_id = $1, timestamp = $2, image_path = $3, temperature = $4, humidity = $5, completed = $6 WHERE id = $7 RETURNING *'; - const values = [projectId, timestamp, imagePath, temperature, humidity, completed, id]; - const res = await db.query(query, values); - return res.rows[0]; - }, - delete: async (id) => { - const query = 'DELETE FROM public.measurements WHERE id = $1'; - const values = [id]; - await db.query(query, values); - }, - getById: async (id) => { - const query = 'SELECT * FROM public.measurements WHERE id = $1'; - const values = [id]; - const res = await db.query(query, values); - return res.rows[0]; - } -}; - -const videosTable = { - create: async (projectId, measurementIds, videoPath, startTimestamp, endTimestamp, imageCount, resolution, duration, fps, status, name) => { - const query = 'INSERT INTO public.videos (project_id, measurement_ids, video_path, start_timestamp, end_timestamp, image_count, resolution, duration, fps, status, name) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING *'; - const values = [projectId, measurementIds, videoPath, startTimestamp, endTimestamp, imageCount, resolution, duration, fps, status, name]; - const res = await db.query(query, values); - return res.rows[0]; - }, - update: async (id, projectId, measurementIds, videoPath, startTimestamp, endTimestamp, imageCount, resolution, duration, fps, status, name) => { - const query = 'UPDATE public.videos SET project_id = $1, measurement_ids = $2, video_path = $3, start_timestamp = $4, end_timestamp = $5, image_count = $6, resolution = $7, duration = $8, fps = $9, status = $10, name = $11 WHERE id = $12 RETURNING *'; - const values = [projectId, measurementIds, videoPath, startTimestamp, endTimestamp, imageCount, resolution, duration, fps, status, name, id]; - const res = await db.query(query, values); - return res.rows[0]; - }, - delete: async (id) => { - const query = 'DELETE FROM public.videos WHERE id = $1'; - const values = [id]; - await db.query(query, values); - }, - getById: async (id) => { - const query = 'SELECT * FROM public.videos WHERE id = $1'; - const values = [id]; - const res = await db.query(query, values); - return res.rows[0]; - } -}; - -module.exports = { - projectsTable, - measurementsTable, - videosTable -}; \ No newline at end of file diff --git a/utils/fileUtils.js b/utils/fileUtils.js deleted file mode 100644 index d3fdb96..0000000 --- a/utils/fileUtils.js +++ /dev/null @@ -1,40 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const PROJECTS_DIR = path.join('/storage'); - -/** - * Crée un dossier pour un projet donné. - * @param {number} projectId - L'ID du projet. - * @param {string} baseDir - Le répertoire de base où le dossier du projet sera créé. - */ -function createProjectDirectory(projectId) { - console.log('PROJECTS_DIR:', PROJECTS_DIR); - const projectDir = path.join(PROJECTS_DIR, `${projectId}`); - console.log(`Creating directory: ${projectDir}`); - if (!fs.existsSync(projectDir)) { - fs.mkdirSync(projectDir, { recursive: true }); - console.log(`Directory created: ${projectDir}`); - } else { - console.log(`Directory already exists: ${projectDir}`); - } -} - -/** - * Supprime le dossier d'un projet donné. - * @param {number} projectId - L'ID du projet. - */ -function deleteProjectDirectory(projectId) { - console.log('PROJECTS_DIR:', PROJECTS_DIR); - const projectDir = path.join(PROJECTS_DIR, `${projectId}`); - if (fs.existsSync(projectDir)) { - fs.rmSync(projectDir, { recursive: true, force: true }); - console.log(`Directory deleted: ${projectDir}`); - } else { - console.log(`Directory does not exist: ${projectDir}`); - } -} - -module.exports = { - createProjectDirectory, - deleteProjectDirectory -}; \ No newline at end of file