diff --git a/.gitignore b/.gitignore index 417a017..f281873 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ build/Release # Dependency directories node_modules/ +package-lock.json jspm_packages/ # TypeScript v1 declaration files diff --git a/app.js b/app.js index a992c5a..06e4d12 100644 --- a/app.js +++ b/app.js @@ -1,13 +1,19 @@ const express = require('express'); +const path = require('path'); + +const indexRoutes = require('./routes/index'); +const apiRoutes = require('./routes/api'); + const app = express(); const port = process.env.PORT || 3000; app.use(express.json()); app.use(express.urlencoded({ extended: true })); -app.get('/', (req, res) => { - res.send('Hello World!'); -}); +app.use('/assets', express.static(path.join(__dirname, 'assets'))); + +app.use('/', indexRoutes); +app.use('/api', apiRoutes); app.listen(port, () => { console.log(`Server running on port ${port}`); diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..e69de29 diff --git a/config/.env.example b/config/.env.example new file mode 100644 index 0000000..ab6c4f1 --- /dev/null +++ b/config/.env.example @@ -0,0 +1,2 @@ +# Server Configuration +PORT=3000 \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..4808722 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "conception-assistant", + "version": "1.0.0", + "main": "app.js", + "scripts": { + "start": "node app.js", + "test": "node tests/all.test.js" + }, + "dependencies": { + "uuid": "^13.0.0", + "express": "^4.18.2" + } +} \ No newline at end of file diff --git a/routes/api.js b/routes/api.js new file mode 100644 index 0000000..d80d072 --- /dev/null +++ b/routes/api.js @@ -0,0 +1,187 @@ +const express = require('express'); +const router = express.Router(); +const fs = require('fs'); +const path = require('path'); +const { v4: uuidv4 } = require('uuid'); + +function modifMd(id, modifications) { + if (id === undefined) throw new Error('id obligatoire'); + if (!Array.isArray(modifications) || modifications.length === 0) throw new Error('modifications requises'); + + const dataDir = path.resolve(__dirname, '../data'); + const mapPath = path.join(dataDir, 'uuid_map.json'); + if (!fs.existsSync(mapPath)) throw new Error('uuid_map.json inexistant'); + + const map = JSON.parse(fs.readFileSync(mapPath, 'utf8')); + const uuid = map[id]; + if (!uuid) throw new Error(`Aucun fichier pour l'id ${id}`); + + const mdPath = path.join(dataDir, `${uuid}.md`); + if (!fs.existsSync(mdPath)) throw new Error('Fichier markdown inexistant'); + let lignes = fs.readFileSync(mdPath, 'utf8').split('\n'); + + modifications = modifications.slice().sort((a, b) => a.debut - b.debut); + + let offset = 0; + for (let m of modifications) { + let debut = m.debut + offset; + let fin = m.fin + offset; + const remplacement = Array.isArray(m.contenu) ? m.contenu : m.contenu.split('\n'); + if (debut < 0 || fin >= lignes.length || debut > fin) + throw new Error('Plage invalide (début/fin) pour une modification'); + + const avant = lignes.slice(0, debut); + const apres = lignes.slice(fin + 1); + lignes = [...avant, ...remplacement, ...apres]; + offset += remplacement.length - (fin - debut + 1); + } + + const nouveau = lignes.join('\n'); + fs.writeFileSync(mdPath, nouveau, { encoding: 'utf8', flag: 'w' }); + return { id, uuid, path: mdPath, modifications }; +} + + +function createMd(markdownContent = "# Titre\nContenu...") { + const uuid = uuidv4(); + + const dataDir = path.resolve(__dirname, '../data'); + const mdPath = path.join(dataDir, `${uuid}.md`); + const mapPath = path.join(dataDir, 'uuid_map.json'); + + fs.writeFileSync(mdPath, markdownContent, { encoding: 'utf8', flag: 'w' }); + + let map = {}; + if (fs.existsSync(mapPath)) { + map = JSON.parse(fs.readFileSync(mapPath, 'utf8')); + } + let id = Object.keys(map).at(-1); + if (id == undefined){ + map[0] = uuid; + } else { + id = +id+1 + map[id] = uuid; + } + + fs.writeFileSync(mapPath, JSON.stringify(map, null, 2), 'utf8'); + + return { id, uuid, path: mdPath, markdownContent}; +} + +function readMd(id = undefined) { + const dataDir = path.resolve(__dirname, '../data'); + const mapPath = path.join(dataDir, 'uuid_map.json'); + + let map = {}; + if (fs.existsSync(mapPath)) { + map = JSON.parse(fs.readFileSync(mapPath, 'utf8')); + } + + if (id !== undefined) { + const uuid = map[id]; + if (!uuid) { + throw new Error(`Aucun fichier trouvé pour l'id ${id}`); + } + const mdPath = path.join(dataDir, `${uuid}.md`); + if (!fs.existsSync(mdPath)) { + throw new Error(`Le fichier ${mdPath} n’existe pas`); + } + const markdownContent = fs.readFileSync(mdPath, 'utf8'); + return [{ id, uuid, path: mdPath, markdownContent }]; + } else { + const results = []; + for (const [curId, uuid] of Object.entries(map)) { + const mdPath = path.join(dataDir, `${uuid}.md`); + if (fs.existsSync(mdPath)) { + const markdownContent = fs.readFileSync(mdPath, 'utf8'); + results.push({ id: curId, uuid, path: mdPath, markdownContent }); + } + } + return results; + } +} + +function modifMd(id, newMarkdownContent) { + if (id === undefined) throw new Error('id obligatoire'); + const dataDir = path.resolve(__dirname, '../data'); + const mapPath = path.join(dataDir, 'uuid_map.json'); + + if (!fs.existsSync(mapPath)) throw new Error('uuid_map.json inexistant'); + let map = JSON.parse(fs.readFileSync(mapPath, 'utf8')); + const uuid = map[id]; + if (!uuid) throw new Error(`Aucun fichier trouvé pour l'id ${id}`); + + const mdPath = path.join(dataDir, `${uuid}.md`); + if (!fs.existsSync(mdPath)) throw new Error('Le fichier markdown n’existe pas'); + fs.writeFileSync(mdPath, newMarkdownContent, { encoding: 'utf8', flag: 'w' }); + return { id, uuid, path: mdPath, newMarkdownContent }; +} + +function deteMd(id) { + if (id === undefined) throw new Error('id obligatoire'); + const dataDir = path.resolve(__dirname, '../data'); + const mapPath = path.join(dataDir, 'uuid_map.json'); + + if (!fs.existsSync(mapPath)) throw new Error('uuid_map.json inexistant'); + let map = JSON.parse(fs.readFileSync(mapPath, 'utf8')); + const uuid = map[id]; + if (!uuid) throw new Error(`Aucun fichier trouvé pour l'id ${id}`); + + const mdPath = path.join(dataDir, `${uuid}.md`); + if (fs.existsSync(mdPath)) fs.unlinkSync(mdPath); + delete map[id]; + + fs.writeFileSync(mapPath, JSON.stringify(map, null, 2), 'utf8'); + return { id, deleted: true }; +} + +// GET /api/journals - Récupérer tous les journaux +router.get('/journals', (req, res) => { + res.json({ + success: true, + data: readMd() + }); +}); + +// POST /api/journals - Créer un nouveau journal +router.post('/journals', (req, res) => { + const { content } = req.body; + + res.status(201).json({ + success: true, + data: createMd(content) + }); +}); + +// GET /api/journals/:id - Récupérer un journal spécifique +router.get('/journals/:id', (req, res) => { + const { id } = req.params; + + res.json({ + success: true, + data: readMd(id) + }); +}); + +// PUT /api/journals/:id - Mettre à jour un journal +router.put('/journals/:id', (req, res) => { + const { id } = req.params; + const { start, end, content } = req.body; + + res.json({ + success: true, + data: modifMd(id, {start, end, content}) + }); +}); + +// DELETE /api/journals/:id - Supprimer un journal +router.delete('/journals/:id', (req, res) => { + const { id } = req.params; + + res.json({ + success: true, + data: deteMd(id) + }); +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/index.js b/routes/index.js new file mode 100644 index 0000000..460e0c2 --- /dev/null +++ b/routes/index.js @@ -0,0 +1,50 @@ +const express = require('express'); +const router = express.Router(); +const { getPage } = require('../views/page'); + +// Route principale +router.get('/', (req, res) => { + res.send(getPage()); +}); + +// Route à propos +router.get('/about', (req, res) => { + res.send(` + + +
+ ++ Cette application aide les équipes à réaliser un suivi structuré et collaboratif de la conception de leurs projets. Elle permet d’archiver les étapes clés, d’assurer la traçabilité des décisions, et de simplifier la coordination. +
++ Ce projet est né du besoin de centraliser et d’organiser les notes de conception lors du développement de projets techniques. +
+
+ Email : augustin.j.l.roux@gmail.com
+ Dépôt Git : https://gitea.legion-muyue.fr/Muyue/conception-assistant
+