feat: initial import — ClickerZ formation project (Express + React/Vite)

This commit is contained in:
2026-03-15 14:29:33 +01:00
commit 4e93753250
118 changed files with 71039 additions and 0 deletions

15
Backend/.env.sample Executable file
View File

@@ -0,0 +1,15 @@
# .env.sample - Sample Environment Variables
# Application Configuration
APP_PORT=3310
APP_SECRET=YOUR_APP_SECRET_KEY
# Database Configuration
DB_HOST=localhost
DB_PORT=3306
DB_USER=YOUR_DATABASE_USERNAME
DB_PASSWORD=YOUR_DATABASE_PASSWORD
DB_NAME=YOUR_DATABASE_NAME
# Frontend URL (for CORS configuration)
FRONTEND_URL=http://localhost:3000

3
Backend/.gitignore vendored Executable file
View File

@@ -0,0 +1,3 @@
node_modules
coverage
.env

21
Backend/LICENSE Executable file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Tetardtek
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

27
Backend/README.md Executable file
View File

@@ -0,0 +1,27 @@
# B-TetaRdPG
## Description
Here is the BACKEND part of TetaRdPG project.
It's an basic RPG game.
You can make fight againest monster ... and more !
## Installation & Start
(Don't forget to use F-TetaRdPG project with)
1. git clone https://github.com/Tetardtek/B-TetaRdPG.git
2. cd B-TetaRdPG
3. Create your own .env file (use env.sample for example)
4. npm i
5. You have to use npm run db:migrate && npm run db:seed (to create you own DB project)
6. npm run dev
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Technology
- [Node.js](https://nodejs.org/) - A JavaScript runtime built on Chrome's V8 JavaScript engine.
- [Express](https://expressjs.com/) - A fast, unopinionated, minimalist web framework for Node.js.
- [MySQL](https://www.mysql.com/) - An open-source relational database management system.
- [Jsonwebtoken](https://jwt.io/introduction) - JSON Web Token implementation for Node.js.
## Contact
[![Discord](https://raw.githubusercontent.com/rahuldkjain/github-profile-readme-generator/master/src/images/icons/Social/discord.svg)](https://discord.com/users/235413280103858176)

36
Backend/database/client.js Executable file
View File

@@ -0,0 +1,36 @@
// Get variables from .env file for database connection
const { DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME } = process.env;
// Create a connection pool to the database
const mysql = require("mysql2/promise");
const client = mysql.createPool({
host: DB_HOST,
port: DB_PORT,
user: DB_USER,
password: DB_PASSWORD,
database: DB_NAME,
});
// Try to get a connection to the database
client
.getConnection()
.then((connection) => {
console.info(`Using database ${DB_NAME}`);
connection.release();
})
.catch((error) => {
console.warn(
"Warning:",
"Failed to establish a database connection.",
"Please check your database credentials in the .env file if you need a database access."
);
console.error("Error message:", error.message);
});
// Store database name into client for further uses
client.databaseName = DB_NAME;
// Ready to export
module.exports = client;

9
Backend/database/schema.sql Executable file
View File

@@ -0,0 +1,9 @@
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
nickname VARCHAR(30) NOT NULL,
mail VARCHAR(90) NOT NULL,
password VARCHAR(200) NOT NULL,
tetardcoin INT default 0
);

17
Backend/index.js Executable file
View File

@@ -0,0 +1,17 @@
// Load environment variables from .env file
require("dotenv").config();
// Import the Express application from src/app.js
const app = require("./src/app");
// Get the port from the environment variables
const port = process.env.APP_PORT;
// Start the server and listen on the specified port
app
.listen(port, () => {
console.info(`Server is listening on port ${port}`);
})
.on("error", (err) => {
console.error("Error:", err.message);
});

52
Backend/migrate.js Executable file
View File

@@ -0,0 +1,52 @@
// Load environment variables from .env file
require("dotenv").config();
const fs = require("node:fs");
const path = require("node:path");
// Build the path to the schema SQL file
const schema = path.join(__dirname, "database", "schema.sql");
// Get database connection details from .env file
const { DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME } = process.env;
// Update the database schema
const mysql = require("mysql2/promise");
const migrate = async () => {
try {
// Read the SQL statements from the schema file
const sql = fs.readFileSync(schema, "utf8");
// Create a specific connection to the database
const database = await mysql.createConnection({
host: DB_HOST,
port: DB_PORT,
user: DB_USER,
password: DB_PASSWORD,
multipleStatements: true, // Allow multiple SQL statements
});
// Drop the existing database if it exists
await database.query(`drop database if exists ${DB_NAME}`);
// Create a new database with the specified name
await database.query(`create database ${DB_NAME}`);
// Switch to the newly created database
await database.query(`use ${DB_NAME}`);
// Execute the SQL statements to update the database schema
await database.query(sql);
// Close the database connection
database.end();
console.info(`${DB_NAME} updated from ${schema} 🆙`);
} catch (err) {
console.error("Error updating the database:", err.message);
}
};
// Run the migration function
migrate();

1839
Backend/package-lock.json generated Executable file

File diff suppressed because it is too large Load Diff

22
Backend/package.json Executable file
View File

@@ -0,0 +1,22 @@
{
"scripts": {
"dev": "nodemon index.js",
"db:migrate": "node migrate.js",
"db:seed": "node seed.js",
"build": "node migrate.js",
"start": "node index.js"
},
"dependencies": {
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"dotenv": "^16.4.1",
"express": "^4.18.2",
"joi": "^17.13.1",
"jsonwebtoken": "^9.0.2",
"mysql2": "^3.9.1",
"nodemailer": "^6.9.9"
},
"devDependencies": {
"nodemon": "^3.0.3"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

33
Backend/seed.js Executable file
View File

@@ -0,0 +1,33 @@
// eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}]
// Load environment variables from .env file
require("dotenv").config();
// Import database client
const database = require("./database/client");
const insertUsers = async () => {
return database.query(`
INSERT INTO users (nickname, mail, password, tetardcoin) VALUES
('Tetardtek', 'kvnn64@gmail.com', '$2b$10$4VWdZ7SANvRr7qn3k6LAEu6eGApGQUvPOqcCCmgzVLKNlSpBL0rGa', 1000)
`);
};
const seed = async () => {
try {
await database.query("START TRANSACTION");
await insertUsers();
await database.query("COMMIT");
database.end();
console.info(`${database.databaseName} filled from ${__filename} 🌱`);
} catch (err) {
await database.query("ROLLBACK");
console.error("Error filling the database:", err.message);
}
};
seed();

152
Backend/src/app.js Executable file
View File

@@ -0,0 +1,152 @@
// Load the express module to create a web application
const express = require("express");
const app = express();
// Configure it
/* ************************************************************************* */
// CORS Handling: Why is the current code commented out and do I need to define specific allowed origins for my project?
// CORS (Cross-Origin Resource Sharing) is a security mechanism in web browsers that blocks requests from a different domain than the server.
// You may find the following magic line in forums:
// app.use(cors());
// You should NOT do that: such code uses the `cors` module to allow all origins, which can pose security issues.
// For this pedagogical template, the CORS code is commented out to show the need for defining specific allowed origins.
// To enable CORS and define allowed origins:
// 1. Install the `cors` module in the backend directory
// 2. Uncomment the line `const cors = require("cors");`
// 3. Uncomment the section `app.use(cors({ origin: [...] }))`
// 4. Be sure to only have URLs in the array with domains from which you want to allow requests.
// For example: ["http://mysite.com", "http://another-domain.com"]
const cors = require("cors");
app.use(
cors({
origin: [
process.env.FRONTEND_URL, // keep this one, after checking the value in `backend/.env`
"http://mysite.com",
"http://another-domain.com",
],
})
);
/* ************************************************************************* */
// Request Parsing: Understanding the purpose of this part
// Request parsing is necessary to extract data sent by the client in an HTTP request.
// For example to access the body of a POST request.
// The current code contains different parsing options as comments to demonstrate different ways of extracting data.
// 1. `express.json()`: Parses requests with JSON data.
// 2. `express.urlencoded()`: Parses requests with URL-encoded data.
// 3. `express.text()`: Parses requests with raw text data.
// 4. `express.raw()`: Parses requests with raw binary data.
// Uncomment one or more of these options depending on the format of the data sent by your client:
app.use(express.json());
// app.use(express.urlencoded());
// app.use(express.text());
// app.use(express.raw());
/* ************************************************************************* */
// Cookies: Why and how to use the `cookie-parser` module?
// Cookies are small pieces of data stored in the client's browser. They are often used to store user-specific information or session data.
// The `cookie-parser` module allows us to parse and manage cookies in our Express application. It parses the `Cookie` header in incoming requests and populates `req.cookies` with an object containing the cookies.
// To use `cookie-parser`, make sure it is installed in `backend/package.json` (you may need to install it separately):
// npm install cookie-parser
// Then, require the module and use it as middleware in your Express application:
// const cookieParser = require("cookie-parser");
// app.use(cookieParser());
// Once `cookie-parser` is set up, you can read and set cookies in your routes.
// For example, to set a cookie named "username" with the value "john":
// res.cookie("username", "john");
// To read the value of a cookie named "username":
// const username = req.cookies.username;
/* ************************************************************************* */
// Import the API routes from the router module
const path = require("path");
const router = require("./router");
// Mount the API routes under the "/api" endpoint
app.use("/api", router);
/* ************************************************************************* */
// Production-ready setup: What is it for, and when should I enable it?
// The code includes commented sections to set up a production environment where the frontend and backend are served from the same server.
// What it's for:
// - Serving frontend static files from the backend, which is useful when building a single-page application with React, Angular, etc.
// - Redirecting unhandled requests (e.g., all requests not matching a defined API route) to the frontend's index.html. This allows the frontend to handle client-side routing.
// When to enable it:
// It depends on your project and its structure. If you are developing a single-page application, you'll enable these sections when you are ready to deploy your project to production.
// To enable production configuration:
// 1. Uncomment the lines related to serving static files and redirecting unhandled requests.
// 2. Ensure that the `reactBuildPath` points to the correct directory where your frontend's build artifacts are located.
// const reactBuildPath = `${__dirname}/../../frontend/dist`;
// // Serve react resources
// app.use(express.static(reactBuildPath));
// // Redirect unhandled requests to the react index file
// app.get("*", (req, res) => {
// res.sendFile(`${reactBuildPath}/index.html`);
// });
app.use("*", (req, res) => {
if (req.originalUrl.includes("assets")) {
res.sendFile(
path.resolve(__dirname, `../../frontend/dist/${req.originalUrl}`)
);
} else {
res.sendFile(path.resolve(__dirname, `../../frontend/dist/index.html`));
}
});
/* ************************************************************************* */
// Middleware for Error Logging (Uncomment to enable)
// Important: Error-handling middleware should be defined last, after other app.use() and routes calls.
/*
// Define a middleware function to log errors
const logErrors = (err, req, res, next) => {
// Log the error to the console for debugging purposes
console.error(err);
console.error("on req:", req.method, req.path);
// Pass the error to the next middleware in the stack
next(err);
};
// Mount the logErrors middleware globally
app.use(logErrors);
*/
/* ************************************************************************* */
module.exports = app;

View File

@@ -0,0 +1,421 @@
require("dotenv").config();
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const Joi = require("joi");
const nodemailer = require("nodemailer");
const tables = require("../tables");
const secretKey = process.env.APP_SECRET;
const saltRounds = 10;
const passwordSchema = Joi.string()
.min(8)
.regex(/[A-Z]/)
.message("Le mot de passe doit contenir au moins une majuscule.")
.regex(/\d/)
.message("Le mot de passe doit contenir au moins un chiffre.")
.regex(/[!@#$%^&*()_+{}[\]:;<>,.?~\\/-]/)
.message("Le mot de passe doit contenir au moins un caractère spécial.");
const nicknameSchema = Joi.string()
.alphanum()
.min(3)
.message("Le pseudo doit contenir au moins 3 caractères alphanumériques.");
const emailSchema = Joi.string().email().message("Format d'e-mail invalide.");
const validatePasswordComplexity = async (password) => {
await passwordSchema.validateAsync(password);
};
const validateEmail = async (email) => {
await emailSchema.validateAsync(email);
};
const validateUniqueNickname = async (nickname) => {
await nicknameSchema.validateAsync(nickname);
const existingUserByNickname = await tables.users.getByNickname(
nickname
);
if (existingUserByNickname) {
throw new Error("Ce pseudo est déjà pris.");
}
};
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
const generateResetToken = (user) => {
const resetToken = jwt.sign({ user: user.id }, resetTokenSecret, {
expiresIn: "1h",
});
const base64Token = Buffer.from(resetToken).toString("base64");
return base64Token;
};
const sendPasswordResetEmail = async (user, resetToken) => {
const resetLink = `${process.env.FRONTEND_URL}/reset-password/${resetToken}`;
const mailOptions = {
from: "INSERT_YOUR_EMAIL_ADDRESS_HERE",
to: user.mail,
subject: "Réinitialisation de mot de passe",
html: `
<p>Bonjour ${user.firstname},</p>
<p>Vous avez demandé une réinitialisation de mot de passe pour votre compte.</p>
<p>Cliquez sur le lien ci-dessous pour réinitialiser votre mot de passe :</p>
<a href="${resetLink}">Réinitialiser le mot de passe</a>
<p>Si vous n'avez pas demandé cette réinitialisation, veuillez ignorer ce message.</p>
<p>Merci,</p>
`,
};
await transporter.sendMail(mailOptions);
};
const forgottenPassword = async (req, res) => {
const { mail } = req.body;
try {
const user = await tables.users.getByMail(mail);
if (!user) {
return res.status(404).json({ message: "Utilisateur non trouvé" });
}
const resetToken = generateResetToken(user);
await sendPasswordResetEmail(user, resetToken);
return res.status(200).json({
message: "Envoi d'un e-mail de réinitialisation du mot de passe",
});
} catch (error) {
console.error(
"Erreur dans l'envoi de l'e-mail de réinitialisation du mot de passe:",
error
);
return res.status(500).json({
message:
"Erreur dans l'envoi de l'e-mail de réinitialisation du mot de passe",
});
}
};
const resetPassword = async (req, res) => {
const { password } = req.body;
const resetToken = decodeURIComponent(req.params.token);
try {
const decodedToken = jwt.verify(resetToken, resetTokenSecret);
const user = await tables.users.read(decodedToken.user);
if (!user) {
return res.status(404).json({ message: "Utilisateur non trouvé" });
}
if (!password) {
return res.status(400).json({ message: "Nouveau mot de passe manquant" });
}
const { error } = Joi.string()
.min(8)
.regex(/[A-Z]/)
.message("Le mot de passe doit contenir au moins une majuscule.")
.regex(/\d/)
.message("Le mot de passe doit contenir au moins un chiffre.")
.regex(/[!@#$%^&*()_+{}[\]:;<>,.?~\\/-]/)
.message("Le mot de passe doit contenir au moins un caractère spécial.")
.validate(password);
if (error) {
return res.status(400).json({ message: error.details[0].message });
}
const hashedPassword = await bcrypt.hash(password, saltRounds);
await tables.users.edit(user.id, { password: hashedPassword });
return res
.status(200)
.json({ message: "Réinitialisation du mot de passe réussie" });
} catch (error) {
console.error("Erreur de réinitialisation du mot de passe:", error);
return res.status(500).json({
message: "Erreur de réinitialisation du mot de passe",
error,
});
}
};
const login = async (req, res) => {
const { mail, password } = req.body;
try {
const user = await tables.users.getByMail(mail);
if (!user) {
return res.status(401).json({ message: "Adresse email introuvable" });
}
const passwordMatch = await bcrypt.compare(password, user.password);
if (!passwordMatch) {
return res.status(401).json({ message: "Mot de passe incorrect" });
}
const token = jwt.sign({ user: user.id }, secretKey);
return res.status(200).json({
message: "Connexion réussie",
user: { ...user, credits: user.credits },
token,
});
} catch (error) {
console.error("Erreur lors de la connexion :", error);
return res.status(500).json({ message: "Erreur interne du serveur" });
}
};
const browse = async (req, res, next) => {
try {
const users = await tables.users.readAll();
res.json(users);
} catch (err) {
next(err);
}
};
const read = async (req, res, next) => {
try {
const { id } = req.params;
const { field } = req.query;
const user = await tables.users.read(id);
if (field && user && user[field]) {
res.json({ [field]: user[field] });
} else if (user) {
res.json(user);
} else {
res.sendStatus(404);
}
} catch (err) {
next(err);
}
};
const edit = async (req, res) => {
const userId = req.params.id;
try {
if (!req.body) {
return res.status(400).json({ message: "Corps de la requête vide" });
}
const {
nickname,
mail,
newPassword,
tetardcoin,
currentPassword,
} = req.body;
const user = await tables.users.read(userId);
if (!user) {
return res.status(404).json({ message: "Utilisateur non trouvé" });
}
const isCurrentPasswordCorrect = await bcrypt.compare(
currentPassword,
user.password
);
if (!isCurrentPasswordCorrect) {
return res.status(401).json({ message: "Mot de passe actuel incorrect" });
}
const errors = {};
if (nickname !== undefined) {
if (nickname.trim() === "") {
errors.nickname = "Le pseudo ne peut pas être vide";
} else if (nickname.trim() !== user.nickname.trim()) {
try {
await validateUniqueNickname(nickname);
} catch (error) {
errors.nickname = error.message;
}
}
}
if (mail !== undefined && mail.trim() === "") {
errors.mail = "L'e-mail ne peut pas être vide";
}
if (newPassword !== undefined && newPassword.trim() !== "") {
try {
await validatePasswordComplexity(newPassword.trim());
} catch (error) {
errors.newPassword = error.message;
}
}
if (Object.keys(errors).length > 0) {
return res.status(400).json({ errors });
}
const updatedFields = {};
if (nickname !== undefined) {
updatedFields.nickname = nickname;
}
if (mail !== undefined) {
updatedFields.mail = mail;
}
if (newPassword !== undefined && newPassword.trim() !== "") {
updatedFields.password = await bcrypt.hash(
newPassword.trim(),
saltRounds
);
}
const affectedRows = await tables.users.edit(userId, updatedFields);
if (affectedRows === 0) {
return res.status(500).json({ message: "Échec de la mise à jour" });
}
const editedUser = await tables.users.read(userId);
return res.json({ message: "Mis à jour", user: editedUser });
} catch (error) {
return res.status(500).json({
message: "Erreur lors de la mise à jour de l'utilisateur",
error: error.message,
});
}
};
const add = async (req, res, next) => {
try {
const { nickname, mail, password, confirmPassword } =
req.body;
const existingUserByMail = await tables.users.getByMail(mail);
if (existingUserByMail) {
return res
.status(400)
.json({ message: "Cette adresse e-mail est déjà enregistrée." });
}
const existingUserByNickname = await tables.users.getByNickname(
nickname
);
if (existingUserByNickname) {
return res.status(400).json({ message: "Ce pseudo est déjà pris." });
}
await validateEmail(mail);
if (password !== confirmPassword) {
return res
.status(400)
.json({ message: "Les mots de passe ne correspondent pas." });
}
try {
await passwordSchema.validateAsync(password);
} catch (validationError) {
return res.status(400).json({ message: validationError.message });
}
const hashedPassword = await bcrypt.hash(password, saltRounds);
const user = {
firstname,
lastname,
nickname,
mail,
password: hashedPassword,
tetardcoin: 1000,
};
const insertId = await tables.users.create(user);
const token = jwt.sign({ user: user.id }, secretKey);
res.status(201).json({ insertId, token });
return insertId;
} catch (err) {
console.error("Erreur lors de l'enregistrement de l'utilisateur :", err);
return next(err);
}
};
const destroy = async (req, res) => {
try {
const userId = req.params.id;
const { currentPassword } = req.body;
if (!currentPassword) {
return res.status(400).json({
errors: {
currentPassword:
"Le mot de passe actuel est requis pour la suppression du compte.",
},
});
}
const user = await tables.users.getById(userId);
const isCurrentPasswordCorrect = await bcrypt.compare(
currentPassword,
user.password
);
if (!isCurrentPasswordCorrect) {
return res.status(401).json({
errors: {
currentPassword: "Le mot de passe actuel est incorrect.",
},
});
}
await tables.users.delete(userId);
res.sendStatus(204);
} catch (err) {
console.error("Erreur lors de la suppression du compte :", err);
res.status(500).json({
errors: {
general: "Une erreur s'est produite lors de la suppression du compte.",
},
});
}
return Promise.resolve();
};
module.exports = {
browse,
read,
edit,
add,
destroy,
login,
forgottenPassword,
resetPassword,
};

View File

@@ -0,0 +1,24 @@
const jwt = require("jsonwebtoken");
const secretKey = process.env.APP_SECRET;
const verifyToken = (req, res, next) => {
const token = req.header("x-auth-token");
if (!token) {
return res
.status(401)
.json({ message: "Access denied. No token provided." });
}
try {
const decoded = jwt.verify(token, secretKey);
req.user = decoded.user;
next();
return null;
} catch (error) {
return res.status(401).json({ message: "Invalid token." });
}
};
module.exports = verifyToken;

View File

@@ -0,0 +1,16 @@
// Import database client
const database = require("../../database/client");
// Provide database access through AbstractManager class
class AbstractManager {
constructor({ table }) {
// Store the table name
this.table = table;
// Provide access to the database client
this.database = database;
}
}
// Ready to export
module.exports = AbstractManager;

101
Backend/src/models/UserManager.js Executable file
View File

@@ -0,0 +1,101 @@
const AbstractManager = require("./AbstractManager");
class UserManager extends AbstractManager {
constructor() {
super({ table: "users" });
}
// The C of CRUD - Create operation
async create(user) {
const { nickname, mail, tetardcoin, password } = user;
const [result] = await this.database.query(
`INSERT INTO ${this.table} (nickname, mail, tetardcoin, password) VALUES (?, ?, ?, ?)`,
[nickname, mail, tetardcoin, password]
);
return result.insertId;
}
// The Rs of CRUD - Read operations
async read(id, field) {
if (field) {
const [rows] = await this.database.query(
`SELECT ?? FROM ${this.table} WHERE id = ?`,
[field, id]
);
if (rows.length === 0) {
return null;
}
return rows[0][field];
}
const [rows] = await this.database.query(
`SELECT * FROM ${this.table} WHERE id = ?`,
[id]
);
if (rows.length === 0) {
return null;
}
return rows[0];
}
async getByMail(mail) {
const [rows] = await this.database.query(
`SELECT * FROM ${this.table} WHERE mail = ?`,
[mail]
);
if (rows.length === 0) {
return null;
}
return rows[0];
}
async readAll() {
const [rows] = await this.database.query(`SELECT * FROM ${this.table}`);
return rows;
}
// The U of CRUD - Update operation
async edit(id, updatedFields) {
const allowedFields = [
"mail",
"nickname",
"tetardcoin",
"password",
];
const fieldsToUpdate = Object.keys(updatedFields).filter((field) =>
allowedFields.includes(field)
);
const updateValues = fieldsToUpdate.map((field) => updatedFields[field]);
if (fieldsToUpdate.length === 0) {
return 0;
}
const updateQuery = `UPDATE ${this.table} SET ${fieldsToUpdate
.map((field) => `${field} = ?`)
.join(", ")} WHERE id = ?`;
updateValues.push(id);
const [result] = await this.database.query(updateQuery, updateValues);
return result.affectedRows;
}
// The D of CRUD - Delete operation
async delete(id) {
await this.database.query(`DELETE FROM ${this.table} WHERE id = ?`, [id]);
}
}
module.exports = UserManager;

26
Backend/src/router.js Executable file
View File

@@ -0,0 +1,26 @@
const express = require("express");
const router = express.Router();
/* ************************************************************************* */
// Define Your API Routes Here
/* ************************************************************************* */
// Import Controllers
const userControllers = require("./controllers/userControllers");
const verifyToken = require("./middlewares/verifyToken");
// User management
router.get("/users", verifyToken, userControllers.browse);
router.get("/users/:id", userControllers.read);
router.get("/users/:id/field", userControllers.read);
router.put("/users/:id", userControllers.edit);
router.post("/users", userControllers.add);
router.delete("/users/:id", userControllers.destroy);
router.post("/login", userControllers.login);
/* ************************************************************************* */
module.exports = router;

0
Backend/src/services/.gitkeep Executable file
View File

38
Backend/src/tables.js Executable file
View File

@@ -0,0 +1,38 @@
/* ************************************************************************* */
// Register Data Managers for Tables
/* ************************************************************************* */
// Import the manager modules responsible for handling data operations on the tables
const UserManager = require("./models/UserManager");
const managers = [
UserManager,
// Add other managers here
];
// Create an empty object to hold data managers for different tables
const tables = {};
// Register each manager as data access point for its table
managers.forEach((ManagerClass) => {
const manager = new ManagerClass();
tables[manager.table] = manager;
});
/* ************************************************************************* */
// Use a Proxy to customize error messages when trying to access a non-existing table
// Export the Proxy instance with custom error handling
module.exports = new Proxy(tables, {
get(obj, prop) {
// Check if the property (table) exists in the tables object
if (prop in obj) return obj[prop];
// If the property (table) does not exist, throw a ReferenceError with a custom error message
throw new ReferenceError(
`tables.${prop} is not defined. Did you register it in ${__filename}?`
);
},
});