diff --git a/.gitignore b/.gitignore index cc7b141..31c4556 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,5 @@ testem.log # System files .DS_Store Thumbs.db +/backend/package-lock.json +/backend/node_modules diff --git a/backend/.env b/backend/.env new file mode 100644 index 0000000..8ec56d1 --- /dev/null +++ b/backend/.env @@ -0,0 +1,7 @@ +ALLOWED_ORIGIN=http://localhost:4300 +PORT=3333 +JWT_SECRET=d1bcf750e6246f1d75a2b32e3ff9ac7a8507b1d27462fc3223b966a86e3ef28762efcfc90a50e20b1888b1c8d7ce107260784b6d3ac9cb3662f499a7b272b1d926e3e0232de3581f25cde6e6da3dd0f62ee00fbe8bbfc3425c5abc0ad3b53f9b9c75875fe57629c9f1fe01ffb280d5605a54ab90ca15ae4cb6c43e298448e95bffd55582a16d18867d3c5db1cb316ba5fc9dfbdde1d8ef523a2d35f425aacd42286058801e79dc0b7c3fd3f9430ef3696e0fbcef5f28ba12ba3e38ee7e9b8f79d9ae51fb81f13528e9008d917d5be6145f3ab9a621dc89aef1f4df09bcdcce9f109f4f792623061ad1cfe541097fdf3695cfb72673ece58db49894d25f486e99 +DB_HOST=localhost +DB_USER=eni +DB_PASSWORD=gkAAUlq2e)0*ROLO +DB_DATABASE=eni diff --git a/backend/config/cors.js b/backend/config/cors.js new file mode 100644 index 0000000..a8ad589 --- /dev/null +++ b/backend/config/cors.js @@ -0,0 +1,16 @@ +import dotenv from 'dotenv'; +import * as path from "node:path"; + +const envFile = `.env`; +dotenv.config({ path: path.resolve(process.cwd(), envFile), override: true }); + +const allowedOrigin = process.env.ALLOWED_ORIGIN || 'http://localhost:4200'; + +const corsOptions = { + origin: allowedOrigin, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization'], + credentials: true, +}; + +export default corsOptions; diff --git a/backend/config/db.js b/backend/config/db.js new file mode 100644 index 0000000..84f613f --- /dev/null +++ b/backend/config/db.js @@ -0,0 +1,13 @@ +import mariadb from 'mariadb'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const pool = mariadb.createPool({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_DATABASE +}); + +export default pool; \ No newline at end of file diff --git a/backend/controllers/apiController.js b/backend/controllers/apiController.js new file mode 100644 index 0000000..1b0bf12 --- /dev/null +++ b/backend/controllers/apiController.js @@ -0,0 +1,6 @@ +export default function getConnect(req, res) { + res.json({ + message: 'Connexion : OK', + status: true + }); +}; \ No newline at end of file diff --git a/backend/controllers/authController.js b/backend/controllers/authController.js new file mode 100644 index 0000000..b10d207 --- /dev/null +++ b/backend/controllers/authController.js @@ -0,0 +1,143 @@ +import pool from "../config/db.js"; +import jwt from "jsonwebtoken"; +import bcrypt from 'bcryptjs'; + +// Création d'un utilisateur +export const register = async (req, res) => { + let conn; + try { + conn = await pool.getConnection(); + const { email, firstname, lastname, password_hash } = req.body; + if (!email || !firstname || !lastname || !password_hash) { + return res.status(400).json({ + message: 'Tout les champs sont requis !', + status: false + }); + } + const existingUsers = await conn.query( + 'SELECT * FROM users WHERE email = ?', + [email] + ); + if (existingUsers.length > 0) { + return res.status(409).json({ + message: 'L\'utilisateur existe déjà !', + status: false + }); + } + // Hash du mot de passe + const hashedPassword = await bcrypt.hash(password_hash, 10); + const result = await conn.query( + 'INSERT INTO users (email, firstname, lastname, password_hash) VALUES (?, ?, ?, ?, ?)', + [email, firstname, lastname, hashedPassword] + ); + res.status(201).json({ + message: 'L\'utilisateur à bien été enregistré !', + status: true + }); + } catch (err) { + console.error('Erreur interne :', err); + return res.status(500).json({ + message: 'Erreur interne du serveur.', + status: false + }); + } finally { + if (conn) await conn.release(); + } +}; + +// Connexion +export const login = async (req, res) => { + let conn; + try { + conn = await pool.getConnection(); + const { email, password } = req.body; + // Chercher l'utilisateur + const users = await conn.query( + 'SELECT * FROM users WHERE email = ?', + [email] + ); + if (users.length === 0) { + return res.status(401).json({ + message: 'Identifiant incorrect', + status: false + }); + } + const user = users[0]; + // Verify password + const isPasswordValid = await bcrypt.compare(password, user.password_hash); + if (!isPasswordValid) { + return res.status(401).json({ + message: 'Identifiant incorrect', + status: false + }); + } + // Generate JWT token + const token = jwt.sign( + { + id: user.id, + }, + process.env.JWT_SECRET || 'fallback_secret', + { expiresIn: '12h' } + ); + await conn.query( + 'UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = ?', + [user.id] + ); + await setLogs(email + " s'est connecté !") + res.cookie('jwt', token, { + httpOnly: true, + secure: true, + sameSite: 'Strict', + maxAge: 12 * 60 * 60 * 1000 + }); + res.json({ + message: 'Connexion réussie', + status: true + }); + } catch (err) { + console.error('Erreur interne :', err); + return res.status(500).json({ + message: 'Erreur interne du serveur.', + status: false + }); + } finally { + if (conn) await conn.release(); + } +}; + +// Déconnecter l'utilisateur +export const logout = (req, res) => { + res.clearCookie('jwt', { + httpOnly: true, + secure: true, + sameSite: 'Strict', + }); + res.status(200).json({ + message: 'Successfully logged out', + status: true + }); +}; + +// Vérification de la connexion sur la page pour guard angular +export const checkAuth = (req, res) => { + const token = req.cookies.jwt; + if (!token) { + return res.status(401).json({ + message: 'Non identifié', + status: false + }); + } + try { + req.user = jwt.verify(token, process.env.JWT_SECRET); + res.status(200).json({ + message: 'identifié', + status: true + }); + } catch (err) { + console.error('Erreur interne :', err); + return res.status(500).json({ + message: 'Erreur interne du serveur.', + status: false + }); + } +}; diff --git a/backend/middleware/apiKeyMiddleware.js b/backend/middleware/apiKeyMiddleware.js new file mode 100644 index 0000000..de9dbb2 --- /dev/null +++ b/backend/middleware/apiKeyMiddleware.js @@ -0,0 +1,24 @@ +export const apiKeyMiddleware = async (req, res, next) => { + const authHeader = req.header('Authorization'); + if (!authHeader) { + return res.status(403).json({ + message: 'Forbidden: No API Key provided', + status: false + }); + } + try { + if (authHeader !== "9IgFg8cnUS4XJE7Q91A0XjrWnjbnBhdk98jcI6fV1n6NAEYz31SHicge8Vkq0bCGvfKsjylb19ouri6FFUeNC1PgPvwrNCC3G5jcz4PLInlFanzf47hCsBJw4IXuhNHC"){ + return res.status(403).json({ + message: 'Forbidden: Invalid API Key', + status: false + }); + } + next(); + } catch (err) { + console.error('Erreur interne :', err); + return res.status(500).json({ + message: 'Erreur interne du serveur.', + status: false + }); + } +}; diff --git a/backend/middleware/tokenJWTMiddleware.js b/backend/middleware/tokenJWTMiddleware.js new file mode 100644 index 0000000..c6fd495 --- /dev/null +++ b/backend/middleware/tokenJWTMiddleware.js @@ -0,0 +1,25 @@ +import jwt from 'jsonwebtoken'; + +export const verifyToken = async (req, res, next) => { + // Récupérer le token depuis le cookie 'jxwt' + const token = req.cookies['jwt']; + + if (!token) { + return res.status(403).json({ + message: 'Token is required', + status: false, + }); + } + + // Vérifier le token + jwt.verify(token, process.env.JWT_SECRET || 'fallback_secret', (err, decoded) => { + if (err) { + return res.status(401).json({ + message: 'Invalid or expired token', + status: false + }); + } + req.user = decoded; + next(); + }); +}; diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..153e5d2 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,24 @@ +{ + "name": "api_eni_angular", + "version": "1.0.0", + "type": "module", + "description": "API ENI Angular", + "main": "index.js", + "scripts": { + "start": "node server.js" + }, + "author": "Johan Leroy", + "license": "ISC", + "dependencies": { + "bcryptjs": "^3.0.2", + "cookie-parser": "^1.4.7", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.18.2", + "jsonwebtoken": "^9.0.2", + "mariadb": "^3.4.0", + "multer": "^1.4.5-lts.1", + "url": "^0.11.4", + "uuid": "^11.1.0" + } +} diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js new file mode 100644 index 0000000..573f5a6 --- /dev/null +++ b/backend/routes/apiRoutes.js @@ -0,0 +1,53 @@ +import express from "express"; +const router = express.Router(); +import apiController from "../controllers/apiController.js"; +import { + register, + login, + checkAuth, + logout, +} from "../controllers/authController.js"; +import {verifyToken} from "../middleware/tokenJWTMiddleware.js"; +import multer from "multer"; +import { v4 as uuidv4 } from 'uuid'; +import path from "node:path"; + +router.get('', apiController); + +// Upload +const generateFileName = (originalName) => { + const ext = path.extname(originalName); + const now = new Date(); + const timestamp = now.getFullYear().toString() + + String(now.getMonth() + 1).padStart(2, '0') + + String(now.getDate()).padStart(2, '0') + + String(now.getHours()).padStart(2, '0') + + String(now.getMinutes()).padStart(2, '0') + + String(now.getSeconds()).padStart(2, '0'); + const uuid = uuidv4(); + return `${timestamp}_${uuid}${ext}`; +}; +const storage = multer.diskStorage({ + destination: function (req, file, cb) { + cb(null, 'uploads/'); // Stockage des fichiers dans le dossier uploads/ + }, + filename: function (req, file, cb) { + cb(null, generateFileName(file.originalname)); + } +}); +const upload = multer({ storage: storage }); +router.post('/upload', verifyToken, upload.single('file'), (req, res) => { + if (!req.file) { + return res.status(400).json({ message: 'Aucun fichier fourni' }); + } + const filePath = `/uploads/${req.file.filename}`; + res.json({ filePath }); +}); + +// Authentification et utilisateur +router.post('/auth/register', register); +router.post('/auth/login', login); +router.post('/auth/logout', logout); +router.get('/auth/check-auth', verifyToken, checkAuth); + +export default router; diff --git a/backend/server.js b/backend/server.js new file mode 100644 index 0000000..a838b9c --- /dev/null +++ b/backend/server.js @@ -0,0 +1,82 @@ +import express from 'express'; +import cors from 'cors'; +import apiRoutes from './routes/apiRoutes.js'; +import corsOptions from './config/cors.js' +import {apiKeyMiddleware} from './middleware/apiKeyMiddleware.js'; +import dotenv from 'dotenv'; +import cookieParser from 'cookie-parser'; +import pool from "./config/db.js"; +import * as path from "node:path"; +import { fileURLToPath } from 'url'; + +const envFile = `.env`; +dotenv.config({ path: path.resolve(process.cwd(), envFile), override: true }); + +const app = express(); + +// Utiliser CORS +app.use(cors(corsOptions)); + +// Utilisation des cookies +app.use(cookieParser()); + +// Middleware pour parser les corps de requêtes JSON +app.use(express.json()); + +// Utilisation des fichiers/images +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +app.use('/uploads', express.static(path.join(__dirname, 'uploads'))); + +// Middleware pour vérifier la clé API (Authorization) +app.use('/', apiKeyMiddleware); + +// Utiliser les routes API +app.use('/', apiRoutes); + +// Middleware pour les routes non trouvées +app.use((req, res) => { + res.status(404).json({ + message: 'Forbidden: Invalid Request', + status: false + }); +}); + +// Test de conenxion à la base de donnée +const testDatabaseConnection = async () => { + let conn; + try { + conn = await pool.getConnection(); + console.log('✅ Connexion à la base de données réussie ! 🎉'); + } catch (err) { + console.error('❌ Erreur de connexion à la base de données :', err.message); + throw new Error('⛔ Impossible de se connecter à la base de données.'); + } finally { + if (conn) await conn.end(); + } +}; + +// Lancer le serveur +const startServer = async () => { + while (true) { + try { + console.log(`🚀 Lancement de l'API en cours... 🔧`); + await testDatabaseConnection(); + app.listen(process.env.PORT, () => { + console.log(`📢 NODE_ENV = ${process.env.NODE_ENV} 🌍`); + console.log(`📢 Chargement du fichier : ${envFile} 📄`); + console.log(`🚀 API démarrée sur le port ${process.env.PORT} 🎯`); + console.log(`🌍 Allowed Origin : ${process.env.ALLOWED_ORIGIN} 🔗`); + console.log(`✅ L'API est lancée et prête à l'emploi ! 🎉`); + }); + break; + } catch (err) { + console.error('❌ Le serveur n’a pas pu démarrer :', err.message); + console.log('⏳ Nouvelle tentative de démarrage dans 5 secondes... 🔄'); + await new Promise((resolve) => setTimeout(resolve, 5000)); + } + } +}; + +startServer(); + diff --git a/package.json b/package.json index 13eb373..b699ad7 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "scripts": { "ng": "ng", - "start": "ng serve", + "start": "ng serve --port=4300", "build": "ng build", "watch": "ng build --watch --configuration development", "test": "ng test" diff --git a/src/app/_component/sidbar/sidbar.component.html b/src/app/_component/sidbar/sidbar.component.html index ea40355..7dd8ee8 100644 --- a/src/app/_component/sidbar/sidbar.component.html +++ b/src/app/_component/sidbar/sidbar.component.html @@ -22,12 +22,6 @@
Ma bibliothèque
- - -$16.00
+$21.15
+$12.00
+$18.40
+$16.00
+$21.15
+$12.00
+$18.40
+