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:
parent
3b7722d38a
commit
750e2c4fe8
1
.gitignore
vendored
1
.gitignore
vendored
@ -36,6 +36,7 @@ build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
package-lock.json
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
|
12
app.js
12
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}`);
|
||||
|
0
assets/css/style.css
Normal file
0
assets/css/style.css
Normal file
2
config/.env.example
Normal file
2
config/.env.example
Normal file
@ -0,0 +1,2 @@
|
||||
# Server Configuration
|
||||
PORT=3000
|
13
package.json
Normal file
13
package.json
Normal 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
187
routes/api.js
Normal 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} 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;
|
50
routes/index.js
Normal file
50
routes/index.js
Normal 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 d’archiver les étapes clés, d’assurer la traçabilité des décisions, et de simplifier la coordination.
|
||||
</p>
|
||||
<h2>Historique</h2>
|
||||
<p>
|
||||
Ce projet est né du besoin de centraliser et d’organiser 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
0
tests/all.test.js
Normal file
10
views/footer.js
Normal file
10
views/footer.js
Normal file
@ -0,0 +1,10 @@
|
||||
function getFooter(){
|
||||
return `
|
||||
<footer>
|
||||
<p>© 2025 Conception-Assistant.</p>
|
||||
<p><a href="/about">A propos</a></p>
|
||||
</footer>
|
||||
`;
|
||||
}
|
||||
|
||||
module.exports = { getFooter };
|
26
views/header.js
Normal file
26
views/header.js
Normal 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
46
views/main.js
Normal 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 d’ité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
34
views/page.js
Normal 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 };
|
Loading…
x
Reference in New Issue
Block a user