Add conception-assistant web application with journal management

- 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 <noreply@anthropic.com>
This commit is contained in:
Augustin 2025-09-24 16:17:28 +02:00
parent 3b7722d38a
commit 750e2c4fe8
12 changed files with 378 additions and 3 deletions

1
.gitignore vendored
View File

@ -36,6 +36,7 @@ build/Release
# Dependency directories
node_modules/
package-lock.json
jspm_packages/
# TypeScript v1 declaration files

12
app.js
View File

@ -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}`);

0
assets/css/style.css Normal file
View File

2
config/.env.example Normal file
View File

@ -0,0 +1,2 @@
# Server Configuration
PORT=3000

13
package.json Normal file
View File

@ -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"
}
}

187
routes/api.js Normal file
View File

@ -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} nexiste 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 nexiste 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;

50
routes/index.js Normal file
View File

@ -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(`
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>À propos - Journal de Conception</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/assets/css/style.css">
</head>
<body>
<h1>Journal de Conception</h1>
<p>
Cette application aide les équipes à réaliser un suivi structuré et collaboratif de la conception de leurs projets. Elle permet darchiver les étapes clés, dassurer la traçabilité des décisions, et de simplifier la coordination.
</p>
<h2>Historique</h2>
<p>
Ce projet est du besoin de centraliser et dorganiser les notes de conception lors du développement de projets techniques.
</p>
<h2>Équipe</h2>
<ul>
<li>Augustin ROUX Développeur et Concepteur principal</li>
</ul>
<h2>Contact</h2>
<p>
Email : augustin.j.l.roux@gmail.com<br>
Dépôt Git : https://gitea.legion-muyue.fr/Muyue/conception-assistant
</p>
<footer>
<p>
© 2025 Journal de Conception.
</p>
</footer>
</body>
</html>
`);
});
module.exports = router;

0
tests/all.test.js Normal file
View File

10
views/footer.js Normal file
View File

@ -0,0 +1,10 @@
function getFooter(){
return `
<footer>
<p>&copy; 2025 Conception-Assistant.</p>
<p><a href="/about">A propos</a></p>
</footer>
`;
}
module.exports = { getFooter };

26
views/header.js Normal file
View File

@ -0,0 +1,26 @@
function getHeader() {
return `
<header>
<div>
<h1>Mon Journal de Conception</h1>
<select id="skeleton-select" title="Niveau de squelette">
<option value="low">Squelette : Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
<button id="validate-skeleton" title="Valider niveau de squelette">Valider</button>
</div>
<nav>
<button id="theme-toggle" title="Basculer Black & White">🌓</button>
<button id="export-pdf" title="Exporter en PDF">📄 PDF</button>
<button id="export-md" title="Exporter en Markdown">📝 MD</button>
<label for="import-md" title="Importer Markdown">
<input id="import-md" type="file" accept=".md" style="display:none;">
MD
</label>
</nav>
</header>
`;
}
module.exports = { getHeader };

46
views/main.js Normal file
View File

@ -0,0 +1,46 @@
function getMain() {
return `
<main>
<section id="table-of-contents">
<h2>Table des matières</h2>
<nav id="toc-nav">
<!--
Exemple de structure hiérarchique:
<ul>
<li><a href="#section1">1. Introduction</a>
<ul>
<li><a href="#subsection1-1">1.1 Objectifs</a></li>
<li><a href="#subsection1-2">1.2 Contexte</a></li>
</ul>
</li>
</ul>
-->
</nav>
</section>
<section id="design-journal">
<h2>Journal de conception</h2>
<div id="journal-editor" contenteditable="true">
<!-- Zone décriture principale -->
</div>
</section>
<section id="ai-assistant">
<h2>Assistant IA</h2>
<button id="activate-rephrase">Reformuler un passage</button>
<button id="check-inconsistencies">Vérifier incohérences</button>
<button id="check-duplications">Vérifier duplications</button>
<button id="give-advice">Conseil sur le contenu</button>
<div>
<input type="number" id="liberty-repeat-count" min="1" placeholder="Combien ditérations?" />
<button id="liberty-mode">Mode Liberté : auto-suites</button>
</div>
<div id="ai-assistant-feedback">
<!-- Résultats et retours IA affichés ici -->
</div>
</section>
</main>
`;
}
module.exports = { getMain };

34
views/page.js Normal file
View File

@ -0,0 +1,34 @@
const { getHeader } = require('./header');
const { getMain } = require('./main');
const { getFooter } = require('./footer');
function getHead(){
return `
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Conception-Assistant</title>
<link rel="stylesheet" href="/assets/css/style.css">
</head>
`;
}
function getBody(){
return `
<body>`
+ getHeader()
+ getMain()
+ getFooter() +
`</body>
</html>
`;
}
function getPage() {
return getHead() + getBody();
}
module.exports = { getPage };