From 042ea5cc502a9a20e491a00b6f6dc023ee531b1a Mon Sep 17 00:00:00 2001 From: Kerboul Date: Tue, 11 Feb 2025 18:30:32 +0100 Subject: [PATCH] =?UTF-8?q?Suppression=20du=20fichier=20Swagger=20pour=20l?= =?UTF-8?q?e=20sch=C3=A9ma=20de=20mesure=20et=20mise=20=C3=A0=20jour=20des?= =?UTF-8?q?=20descriptions=20dans=20les=20routes=20d'images=20et=20de=20pr?= =?UTF-8?q?ojets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routes/imageRoutes.js | 64 +++++++------- routes/measurementRoutes.js | 170 +++++++++++++++++++++-------------- routes/projectRoutes.js | 124 +++++++++++++------------- routes/swagger.yml | 27 ------ routes/videoRoutes.js | 172 +++++++++++++++++++++++------------- 5 files changed, 310 insertions(+), 247 deletions(-) delete mode 100644 routes/swagger.yml diff --git a/routes/imageRoutes.js b/routes/imageRoutes.js index e6eeffe..6f2e262 100644 --- a/routes/imageRoutes.js +++ b/routes/imageRoutes.js @@ -13,7 +13,7 @@ const serverError = require('../utils/serverError'); * summary: Retrieve a smile image * responses: * 200: - * description: Smile image retrieved successfully + * description: A smile image * content: * image/jpeg: * schema: @@ -23,67 +23,65 @@ const serverError = require('../utils/serverError'); * 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); - }); + 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 + * summary: Retrieve an image by project and order ID * parameters: * - in: path * name: projectId * required: true * schema: * type: string - * description: The ID of the project + * description: The project ID * - in: path * name: orderId * required: true * schema: * type: string - * description: The ID of the order + * description: The order ID * responses: * 200: - * description: Image retrieved successfully + * description: An image file * content: - * image/jpeg: + * application/octet-stream: * 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) => { + 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) { - console.error('Image not found:', err); - return res.status(404).json({ error: 'Image not found' }); + return serverError.sendError('Error getting image:', res, err); } - res.download(imagePath); - }); - }); + 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 index 38abff1..911e880 100644 --- a/routes/measurementRoutes.js +++ b/routes/measurementRoutes.js @@ -18,18 +18,32 @@ const serverError = require('../utils/serverError'); * schema: * type: array * items: - * $ref: '#/components/schemas/Measurement' + * type: object + * properties: + * id: + * type: integer + * project_id: + * type: integer + * timestamp: + * type: string + * format: date-time + * image_path: + * type: string + * temperature: + * type: number + * humidity: + * type: number * 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); - }); + 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); + }); }); /** @@ -51,24 +65,38 @@ router.get('/measurements', (req, res) => { * content: * application/json: * schema: - * $ref: '#/components/schemas/Measurement' + * type: object + * properties: + * id: + * type: integer + * project_id: + * type: integer + * timestamp: + * type: string + * format: date-time + * image_path: + * type: string + * temperature: + * type: number + * humidity: + * type: number * 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); - }); + 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); + }); }); /** @@ -96,24 +124,38 @@ router.get('/measurements/:id', (req, res) => { * content: * application/json: * schema: - * $ref: '#/components/schemas/Measurement' + * type: object + * properties: + * id: + * type: integer + * project_id: + * type: integer + * timestamp: + * type: string + * format: date-time + * image_path: + * type: string + * temperature: + * type: number + * humidity: + * type: number * 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); - } + 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); + } }); /** @@ -149,17 +191,17 @@ router.get('/measurements/:projectId/:orderId', async (req, res) => { * 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 }); - }); + 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 }); + }); }); /** @@ -184,16 +226,16 @@ router.post('/measurements', (req, res) => { * 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); - } + 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); + } }); /** @@ -224,17 +266,17 @@ router.delete('/measurements/:id', async (req, res) => { * 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); - } + 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 index ee29573..216405d 100644 --- a/routes/projectRoutes.js +++ b/routes/projectRoutes.js @@ -17,17 +17,17 @@ const serverError = require('../utils/serverError'); * schema: * type: array * items: - * $ref: '#/components/schemas/Project' + * type: object * 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); - } + try { + const projects = await projectManager.getAllProjects(); + res.json(projects); + } catch (error) { + serverError.sendError('Error getting all projects:', res, error); + } }); /** @@ -49,23 +49,23 @@ router.get('/projects', async (req, res) => { * content: * application/json: * schema: - * $ref: '#/components/schemas/Project' + * type: object * 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); - } + 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); + } }); /** @@ -89,23 +89,23 @@ router.get('/projects/:id', async (req, res) => { * schema: * type: array * items: - * $ref: '#/components/schemas/Video' + * type: object * 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); - } + 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); + } }); /** @@ -129,23 +129,23 @@ router.get('/projects/:id/videos', async (req, res) => { * schema: * type: array * items: - * $ref: '#/components/schemas/Measurement' + * type: object * 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); - } + 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); + } }); /** @@ -174,17 +174,17 @@ router.get('/projects/:id/measurements', async (req, res) => { * 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); - } + 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); + } }); /** @@ -209,17 +209,17 @@ router.post('/projects', async (req, res) => { * 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); - } + 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/swagger.yml b/routes/swagger.yml deleted file mode 100644 index b9de586..0000000 --- a/routes/swagger.yml +++ /dev/null @@ -1,27 +0,0 @@ -components: - schemas: - Measurement: - type: object - properties: - id: - type: integer - description: The unique identifier for a measurement. - project_id: - type: integer - description: The ID of the project associated with the measurement. - timestamp: - type: string - format: date-time - description: The timestamp of the measurement. - image_path: - type: string - description: The path to the image associated with the measurement. - temperature: - type: number - description: The temperature recorded in the measurement. - humidity: - type: number - description: The humidity recorded in the measurement. - completed: - type: boolean - description: Indicates whether the measurement is completed. diff --git a/routes/videoRoutes.js b/routes/videoRoutes.js index d799bff..ceaa597 100644 --- a/routes/videoRoutes.js +++ b/routes/videoRoutes.js @@ -17,18 +17,43 @@ const serverError = require('../utils/serverError'); * schema: * type: array * items: - * $ref: '#/components/schemas/Video' + * type: object + * properties: + * id: + * type: integer + * project_id: + * type: integer + * measurement_ids: + * type: string + * video_path: + * type: string + * start_timestamp: + * type: string + * end_timestamp: + * type: string + * image_count: + * type: integer + * resolution: + * type: string + * duration: + * type: number + * fps: + * type: number + * status: + * type: integer + * name: + * type: string * 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); - }); + 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); + }); }); /** @@ -50,24 +75,49 @@ router.get('/videos', (req, res) => { * content: * application/json: * schema: - * $ref: '#/components/schemas/Video' + * type: object + * properties: + * id: + * type: integer + * project_id: + * type: integer + * measurement_ids: + * type: string + * video_path: + * type: string + * start_timestamp: + * type: string + * end_timestamp: + * type: string + * image_count: + * type: integer + * resolution: + * type: string + * duration: + * type: number + * fps: + * type: number + * status: + * type: integer + * name: + * type: string * 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); - }); + 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); + }); }); /** @@ -104,40 +154,40 @@ router.get('/videos/:id', (req, res) => { * 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 { 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 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'; + 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) => { + db.query(query_first, [list_ids[0]], (err, results) => { if (err) { - serverError.sendError('Erreur lors de la récupération du timestamp de la dernière image:', res, err); + serverError.sendError('Erreur lors de la récupération du timestamp de la première image:', res, err); } - const end_timestamp = results.rows[0].timestamp; - const fps = image_count / duration; + const start_timestamp = results.rows[0].timestamp; - 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 }); + 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 }); + }); }); - }); - }); + }); }); /** @@ -164,20 +214,20 @@ router.post('/videos', (req, res) => { * 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 }); - }); + 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;