From 750e2c4fe898c8e8c2a99ba2d8b0ae2d009917f7 Mon Sep 17 00:00:00 2001 From: Augustin Date: Wed, 24 Sep 2025 16:17:28 +0200 Subject: [PATCH] Add conception-assistant web application with journal management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Express server with routes for index and API endpoints - CRUD operations for markdown journals via REST API - Modular view components (header, main, footer, page) - UUID-based file management system for journals - Content-editable journal editor with AI assistant features - Package.json with express and uuid dependencies - Basic project structure with assets, config, tests, views directories 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .gitignore | 1 + app.js | 12 ++- assets/css/style.css | 0 config/.env.example | 2 + package.json | 13 +++ routes/api.js | 187 +++++++++++++++++++++++++++++++++++++++++++ routes/index.js | 50 ++++++++++++ tests/all.test.js | 0 views/footer.js | 10 +++ views/header.js | 26 ++++++ views/main.js | 46 +++++++++++ views/page.js | 34 ++++++++ 12 files changed, 378 insertions(+), 3 deletions(-) create mode 100644 assets/css/style.css create mode 100644 config/.env.example create mode 100644 package.json create mode 100644 routes/api.js create mode 100644 routes/index.js create mode 100644 tests/all.test.js create mode 100644 views/footer.js create mode 100644 views/header.js create mode 100644 views/main.js create mode 100644 views/page.js 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(` + + + + + À propos - Journal de Conception + + + + +

Journal de Conception

+

+ 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. +

+

Historique

+

+ Ce projet est né du besoin de centraliser et d’organiser les notes de conception lors du développement de projets techniques. +

+

Équipe

+ +

Contact

+

+ Email : augustin.j.l.roux@gmail.com
+ Dépôt Git : https://gitea.legion-muyue.fr/Muyue/conception-assistant +

+
+

+ © 2025 Journal de Conception. +

+
+ + + + `); +}); + +module.exports = router; \ No newline at end of file diff --git a/tests/all.test.js b/tests/all.test.js new file mode 100644 index 0000000..e69de29 diff --git a/views/footer.js b/views/footer.js new file mode 100644 index 0000000..edd90c0 --- /dev/null +++ b/views/footer.js @@ -0,0 +1,10 @@ +function getFooter(){ + return ` +
+

© 2025 Conception-Assistant.

+

A propos

+
+ `; +} + +module.exports = { getFooter }; \ No newline at end of file diff --git a/views/header.js b/views/header.js new file mode 100644 index 0000000..a315164 --- /dev/null +++ b/views/header.js @@ -0,0 +1,26 @@ +function getHeader() { + return ` +
+
+

Mon Journal de Conception

+ + +
+ +
+ `; +} + +module.exports = { getHeader }; diff --git a/views/main.js b/views/main.js new file mode 100644 index 0000000..8b09f3a --- /dev/null +++ b/views/main.js @@ -0,0 +1,46 @@ +function getMain() { + return ` +
+
+

Table des matières

+ +
+ +
+

Journal de conception

+
+ +
+
+ +
+

Assistant IA

+ + + + +
+ + +
+
+ +
+
+
+ `; +} + +module.exports = { getMain }; diff --git a/views/page.js b/views/page.js new file mode 100644 index 0000000..af7aec0 --- /dev/null +++ b/views/page.js @@ -0,0 +1,34 @@ +const { getHeader } = require('./header'); +const { getMain } = require('./main'); +const { getFooter } = require('./footer'); + +function getHead(){ + return ` + + + + + + Conception-Assistant + + + `; +} + +function getBody(){ + return ` + ` + + getHeader() + + getMain() + + getFooter() + + ` + + `; +} + + +function getPage() { + return getHead() + getBody(); +} + +module.exports = { getPage }; \ No newline at end of file