Initial commit: charles.secourit.fr - App de suivi maintenance 360 Partners
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install --omit=dev
|
||||
COPY . .
|
||||
RUN mkdir -p data
|
||||
EXPOSE 3008
|
||||
CMD ["node", "src/index.js"]
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "charles-backend",
|
||||
"version": "1.0.0",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"start": "node src/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.21.0",
|
||||
"better-sqlite3": "^11.6.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"cors": "^2.8.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'charles-secourit-2026-secret-key';
|
||||
|
||||
function generateToken(user) {
|
||||
return jwt.sign({ id: user.id, username: user.username, role: user.role }, JWT_SECRET, { expiresIn: '7d' });
|
||||
}
|
||||
|
||||
function authMiddleware(req, res, next) {
|
||||
const token = req.headers.authorization?.replace('Bearer ', '');
|
||||
if (!token) return res.status(401).json({ error: 'Token manquant' });
|
||||
try {
|
||||
req.user = jwt.verify(token, JWT_SECRET);
|
||||
next();
|
||||
} catch { return res.status(401).json({ error: 'Token invalide' }); }
|
||||
}
|
||||
|
||||
function adminOnly(req, res, next) {
|
||||
if (req.user.role !== 'admin') return res.status(403).json({ error: 'Acces refuse' });
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = { generateToken, authMiddleware, adminOnly };
|
||||
@@ -0,0 +1,65 @@
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
const dbDir = path.join(__dirname, '..', 'data');
|
||||
const db = new Database(path.join(dbDir, 'charles.db'));
|
||||
|
||||
db.pragma('journal_mode = WAL');
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
role TEXT NOT NULL CHECK(role IN ('admin', 'client'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS domains (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
company_name TEXT NOT NULL,
|
||||
parent_id INTEGER,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
has_cloud INTEGER DEFAULT 0,
|
||||
cloud_details TEXT DEFAULT '',
|
||||
has_website INTEGER DEFAULT 0,
|
||||
website_wordpress INTEGER DEFAULT 0,
|
||||
website_managed_by TEXT DEFAULT '',
|
||||
website_details TEXT DEFAULT '',
|
||||
has_emails INTEGER DEFAULT 0,
|
||||
email_count INTEGER DEFAULT 0,
|
||||
email_addresses TEXT DEFAULT '[]',
|
||||
has_team INTEGER DEFAULT 0,
|
||||
team_members TEXT DEFAULT '[]',
|
||||
notes TEXT DEFAULT '',
|
||||
monthly_price REAL DEFAULT 0,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (parent_id) REFERENCES domains(id)
|
||||
);
|
||||
`);
|
||||
|
||||
const userCount = db.prepare('SELECT COUNT(*) as count FROM users').get().count;
|
||||
if (userCount === 0) {
|
||||
const insert = db.prepare('INSERT INTO users (username, password_hash, name, role) VALUES (?, ?, ?, ?)');
|
||||
insert.run('pierre', bcrypt.hashSync('Charles2026!', 10), 'Pierre', 'admin');
|
||||
insert.run('charles', bcrypt.hashSync('360partners!', 10), 'Charles', 'client');
|
||||
}
|
||||
|
||||
const domainCount = db.prepare('SELECT COUNT(*) as count FROM domains').get().count;
|
||||
if (domainCount === 0) {
|
||||
const insert = db.prepare('INSERT INTO domains (name, company_name, parent_id, sort_order) VALUES (?, ?, ?, ?)');
|
||||
const result = insert.run('360-partners.fr', '360 Partners', null, 0);
|
||||
const pid = result.lastInsertRowid;
|
||||
insert.run('monsieur-edgar.fr', 'Monsieur Edgar', pid, 1);
|
||||
insert.run('monsieur-edgar.com', 'Monsieur Edgar', pid, 2);
|
||||
insert.run('qwarter.fr', 'Qwarter', pid, 3);
|
||||
insert.run('agenceredo.fr', 'Agence Redo', pid, 4);
|
||||
insert.run('hub-grade.com', 'HUB-GRADE', pid, 5);
|
||||
insert.run('momentz.online', 'MOMENTZ', pid, 6);
|
||||
insert.run('momentz.paris', 'MOMENTZ', pid, 7);
|
||||
}
|
||||
|
||||
module.exports = db;
|
||||
@@ -0,0 +1,80 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const db = require('./db');
|
||||
const { generateToken, authMiddleware, adminOnly } = require('./auth');
|
||||
|
||||
const app = express();
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
app.post('/api/login', (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
const user = db.prepare('SELECT * FROM users WHERE username = ?').get(username);
|
||||
if (!user || !bcrypt.compareSync(password, user.password_hash)) {
|
||||
return res.status(401).json({ error: 'Identifiants incorrects' });
|
||||
}
|
||||
const token = generateToken(user);
|
||||
res.json({ token, user: { id: user.id, name: user.name, role: user.role } });
|
||||
});
|
||||
|
||||
app.get('/api/domains', authMiddleware, (req, res) => {
|
||||
const domains = db.prepare('SELECT * FROM domains ORDER BY sort_order, id').all();
|
||||
const parent = domains.find(d => !d.parent_id);
|
||||
const children = domains.filter(d => d.parent_id);
|
||||
if (req.user.role === 'client') {
|
||||
domains.forEach(d => { delete d.monthly_price; });
|
||||
}
|
||||
res.json({ parent, children });
|
||||
});
|
||||
|
||||
app.get('/api/domains/:id', authMiddleware, (req, res) => {
|
||||
const domain = db.prepare('SELECT * FROM domains WHERE id = ?').get(req.params.id);
|
||||
if (!domain) return res.status(404).json({ error: 'Domaine non trouve' });
|
||||
if (req.user.role === 'client') delete domain.monthly_price;
|
||||
res.json(domain);
|
||||
});
|
||||
|
||||
app.put('/api/domains/:id', authMiddleware, (req, res) => {
|
||||
const domain = db.prepare('SELECT * FROM domains WHERE id = ?').get(req.params.id);
|
||||
if (!domain) return res.status(404).json({ error: 'Domaine non trouve' });
|
||||
|
||||
const fields = [
|
||||
'has_cloud', 'cloud_details', 'has_website', 'website_wordpress',
|
||||
'website_managed_by', 'website_details', 'has_emails', 'email_count',
|
||||
'email_addresses', 'has_team', 'team_members', 'notes'
|
||||
];
|
||||
if (req.user.role === 'admin') fields.push('monthly_price');
|
||||
|
||||
const updates = {};
|
||||
fields.forEach(f => { if (req.body[f] !== undefined) updates[f] = req.body[f]; });
|
||||
updates.updated_at = new Date().toISOString();
|
||||
|
||||
const setClause = Object.keys(updates).map(k => k + ' = ?').join(', ');
|
||||
const values = Object.values(updates);
|
||||
values.push(req.params.id);
|
||||
db.prepare('UPDATE domains SET ' + setClause + ' WHERE id = ?').run(...values);
|
||||
|
||||
const updated = db.prepare('SELECT * FROM domains WHERE id = ?').get(req.params.id);
|
||||
if (req.user.role === 'client') delete updated.monthly_price;
|
||||
res.json(updated);
|
||||
});
|
||||
|
||||
app.post('/api/domains', authMiddleware, adminOnly, (req, res) => {
|
||||
const { name, company_name, parent_id } = req.body;
|
||||
if (!name || !company_name) return res.status(400).json({ error: 'Nom requis' });
|
||||
const maxOrder = db.prepare('SELECT COALESCE(MAX(sort_order), 0) as m FROM domains').get().m;
|
||||
const result = db.prepare('INSERT INTO domains (name, company_name, parent_id, sort_order) VALUES (?, ?, ?, ?)')
|
||||
.run(name, company_name, parent_id || null, maxOrder + 1);
|
||||
res.json(db.prepare('SELECT * FROM domains WHERE id = ?').get(result.lastInsertRowid));
|
||||
});
|
||||
|
||||
app.delete('/api/domains/:id', authMiddleware, adminOnly, (req, res) => {
|
||||
db.prepare('DELETE FROM domains WHERE id = ?').run(req.params.id);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
app.get('/api/health', (req, res) => res.json({ ok: true }));
|
||||
|
||||
const PORT = process.env.PORT || 3008;
|
||||
app.listen(PORT, '0.0.0.0', () => console.log('Charles backend on port ' + PORT));
|
||||
Reference in New Issue
Block a user