Changed file structure to implement the frontend properly and created frontend template
This commit is contained in:
parent
feceab03b1
commit
32c0ffd715
145
backend/.gitignore
vendored
Normal file
145
backend/.gitignore
vendored
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# vitepress build output
|
||||||
|
**/.vitepress/dist
|
||||||
|
|
||||||
|
# vitepress cache directory
|
||||||
|
**/.vitepress/cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# Vscode files
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# Data
|
||||||
|
data/*
|
||||||
|
|
||||||
|
# Temporary
|
||||||
|
tests/
|
19
backend/config/config.json
Normal file
19
backend/config/config.json
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"port": 8000,
|
||||||
|
|
||||||
|
"users": {
|
||||||
|
"admin": {
|
||||||
|
"username": "admin",
|
||||||
|
"password": "admin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"database": {
|
||||||
|
"type": "sqlite"
|
||||||
|
},
|
||||||
|
|
||||||
|
"auth": {
|
||||||
|
"JWT_secret": "HGF7654EGBNKJNBJH6754356788GJHGY",
|
||||||
|
"tokenExpiry": "1h"
|
||||||
|
}
|
||||||
|
}
|
35
backend/package.json
Normal file
35
backend/package.json
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"name": "wf-radio-backend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js",
|
||||||
|
"start-auto": "pnpx nodemon server.js",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"packageManager": "pnpm@10.5.2",
|
||||||
|
"dependencies": {
|
||||||
|
"ajv": "^8.17.1",
|
||||||
|
"bcrypt": "^5.1.1",
|
||||||
|
"better-sqlite3": "^11.9.1",
|
||||||
|
"dompurify": "^3.2.4",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"jsdom": "^26.0.0",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"marked": "^15.0.7",
|
||||||
|
"mysql": "^2.18.1"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": [
|
||||||
|
"bcrypt",
|
||||||
|
"better-sqlite3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.1.9"
|
||||||
|
}
|
||||||
|
}
|
1884
backend/pnpm-lock.yaml
Normal file
1884
backend/pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
38
backend/server.js
Normal file
38
backend/server.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// --- Imports ---
|
||||||
|
|
||||||
|
const express = require("express");
|
||||||
|
const app = express();
|
||||||
|
const configManager = require("./src/utils/configManager");
|
||||||
|
const { connectDatabase, initDatabase } = require('./src/database/index');
|
||||||
|
|
||||||
|
|
||||||
|
// --- Load configuration ---
|
||||||
|
const config = configManager.loadConfig();
|
||||||
|
|
||||||
|
// --- Body parsing ---
|
||||||
|
app.use(express.json()); // Necessary to parse JSON bodies
|
||||||
|
|
||||||
|
// Database connection
|
||||||
|
(async () => {
|
||||||
|
|
||||||
|
|
||||||
|
// --- Database connection ---
|
||||||
|
await connectDatabase();
|
||||||
|
await initDatabase();
|
||||||
|
|
||||||
|
// --- Routing ---
|
||||||
|
app.use("/", require("./src/routes/index"));
|
||||||
|
app.use("/mods", require("./src/routes/mods"));
|
||||||
|
app.use("/users", require("./src/routes/users"));
|
||||||
|
app.use("/list", require("./src/routes/list"));
|
||||||
|
app.use("/login", require("./src/routes/login"));
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
// --- Launch ---
|
||||||
|
|
||||||
|
const port = config.port;
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log("Server listening on port " + port + "...");
|
||||||
|
})
|
17
backend/src/controllers/auth.js
Normal file
17
backend/src/controllers/auth.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
const handleError = require("../middleware/errors");
|
||||||
|
const authService = require("../services/authService");
|
||||||
|
|
||||||
|
async function login(req, res) {
|
||||||
|
try {
|
||||||
|
const username = req.body.username;
|
||||||
|
const email = req.body.email;
|
||||||
|
const password = req.body.password
|
||||||
|
const token = await authService.login(username, email, password);
|
||||||
|
res.json({ token });
|
||||||
|
} catch (err) {
|
||||||
|
handleError(err, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { login };
|
14
backend/src/controllers/index.js
Normal file
14
backend/src/controllers/index.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
const index_service = require("../services/indexService");
|
||||||
|
const handleError = require("../middleware/errors");
|
||||||
|
|
||||||
|
|
||||||
|
async function getVersion(req, res) {
|
||||||
|
try {
|
||||||
|
const query_result = await index_service.getVersion();
|
||||||
|
res.json(query_result);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getVersion };
|
0
backend/src/controllers/modpacks.js
Normal file
0
backend/src/controllers/modpacks.js
Normal file
68
backend/src/controllers/mods.js
Normal file
68
backend/src/controllers/mods.js
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
const handleError = require("../middleware/errors");
|
||||||
|
const mod_service = require("../services/modService");
|
||||||
|
const { authorizeModModification, authenticateToken } = require("../middleware/auth");
|
||||||
|
|
||||||
|
async function listMods(req, res) {
|
||||||
|
try {
|
||||||
|
// Query
|
||||||
|
const query_result = await mod_service.getAllMods();
|
||||||
|
res.json(query_result);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createMod(req, res) {
|
||||||
|
try {
|
||||||
|
// Authenticate
|
||||||
|
await authenticateToken(req);
|
||||||
|
// Query
|
||||||
|
const mod_data = req.body;
|
||||||
|
const user = req.token_infos.username;
|
||||||
|
const query_result = await mod_service.createMod(mod_data, user);
|
||||||
|
res.json(query_result);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function modifyMod(req, res) {
|
||||||
|
try {
|
||||||
|
// Authorize
|
||||||
|
authorizeModModification(req);
|
||||||
|
// Query
|
||||||
|
const mod_data = req.body;
|
||||||
|
const query_result = await mod_service.modifyMod(mod_data);
|
||||||
|
res.json(query_result);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getModByName(req, res) {
|
||||||
|
try {
|
||||||
|
// Query
|
||||||
|
const name = req.params.name
|
||||||
|
const query_result = await mod_service.getModByName(name);
|
||||||
|
res.json(query_result);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteMod(req, res) {
|
||||||
|
try {
|
||||||
|
// Authorize
|
||||||
|
authorizeModModification(req);
|
||||||
|
// Query
|
||||||
|
const name = req.params.name
|
||||||
|
const query_result = await mod_service.deleteMod(name);
|
||||||
|
res.json(query_result);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = { listMods, getModByName, createMod, modifyMod, deleteMod };
|
63
backend/src/controllers/users.js
Normal file
63
backend/src/controllers/users.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
const handleError = require("../middleware/errors");
|
||||||
|
const user_service = require("../services/userService")
|
||||||
|
const { authorizeUserModification } = require("../middleware/auth");
|
||||||
|
|
||||||
|
|
||||||
|
async function listUsers(req, res) {
|
||||||
|
try {
|
||||||
|
// Query
|
||||||
|
const query_result = await user_service.getAllUsers();
|
||||||
|
res.json(query_result);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUserByName(req, res) {
|
||||||
|
try {
|
||||||
|
// Query
|
||||||
|
const query_result = await user_service.getUserByName(req.params.name);
|
||||||
|
res.json(query_result);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createUser(req, res) {
|
||||||
|
try {
|
||||||
|
// Query
|
||||||
|
const query_result = await user_service.createUser(req.body);
|
||||||
|
res.json(query_result);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function modifyUser(req, res) {
|
||||||
|
try {
|
||||||
|
// Query
|
||||||
|
const diff_data = req.body;
|
||||||
|
const query_result = await user_service.modifyUser(diff_data);
|
||||||
|
res.json(query_result);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteUser(req, res) {
|
||||||
|
try {
|
||||||
|
// Authenticate
|
||||||
|
await authorizeUserModification(req);
|
||||||
|
// Query
|
||||||
|
const user = req.params.name;
|
||||||
|
const token_user = req.token_infos
|
||||||
|
const query_result = await user_service.deleteUser(user, token_user);
|
||||||
|
return res.json(query_result);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = { listUsers, getUserByName, createUser, modifyUser, deleteUser };
|
112
backend/src/database/index.js
Normal file
112
backend/src/database/index.js
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
const MySQLDatabase = require("./mysql");
|
||||||
|
const SQLiteDatabase = require("./sqlite");
|
||||||
|
const { getConfig } = require("../utils/configManager");
|
||||||
|
|
||||||
|
let db;
|
||||||
|
|
||||||
|
async function connectDatabase() {
|
||||||
|
|
||||||
|
// Get config
|
||||||
|
const config = await getConfig();
|
||||||
|
|
||||||
|
// Choose database type
|
||||||
|
if (config.database.type === "mysql") {
|
||||||
|
db = new MySQLDatabase(config.database);
|
||||||
|
} else if (config.database.type === "sqlite") {
|
||||||
|
db = new SQLiteDatabase(config.database);
|
||||||
|
} else {
|
||||||
|
throw new Error("Invalid database type: ", config.database.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect
|
||||||
|
await db.connect();
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Setups the database by creating the tables and the default objects
|
||||||
|
async function initDatabase() {
|
||||||
|
|
||||||
|
if (!db) {
|
||||||
|
throw new Error("Database is not connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Users ---
|
||||||
|
|
||||||
|
// Uers table
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS Users (
|
||||||
|
username TINYTEXT PRIMARY KEY,
|
||||||
|
display_name TINYTEXT NOT NULL,
|
||||||
|
email TINYTEXT NOT NULL,
|
||||||
|
password TINYTEXT NOT NULL,
|
||||||
|
profile_picture LONGTEXT,
|
||||||
|
role TINYTEXT NOT NULL,
|
||||||
|
settings LONGTEXT
|
||||||
|
);`);
|
||||||
|
|
||||||
|
// --- Mods ---
|
||||||
|
|
||||||
|
// Mods table
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS Mods (
|
||||||
|
name TINYTEXT PRIMARY KEY,
|
||||||
|
display_name TINYTEXT NOT NULL,
|
||||||
|
author TINYTEXT NOT NULL,
|
||||||
|
description TINYTEXT NOT NULL,
|
||||||
|
|
||||||
|
FOREIGN KEY (author) REFERENCES Users(username)
|
||||||
|
);`);
|
||||||
|
|
||||||
|
// Mods complementary infos
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS ModInfos (
|
||||||
|
mod TINYTEXT PRIMARY KEY,
|
||||||
|
full_description TEXT NOT NULL,
|
||||||
|
license TINYTEXT,
|
||||||
|
custom_license TEXT,
|
||||||
|
links TEXT,
|
||||||
|
creation_date TINYTEXT NOT NULL,
|
||||||
|
downloads_count INT NOT NULL,
|
||||||
|
|
||||||
|
FOREIGN KEY (mod) REFERENCES Mods(name)
|
||||||
|
);`);
|
||||||
|
|
||||||
|
// Mods tags
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS ModTags (
|
||||||
|
mod TINYTEXT NOT NULL,
|
||||||
|
tag TINYTEXT NOT NULL,
|
||||||
|
|
||||||
|
FOREIGN KEY (mod) REFERENCES Mods(name)
|
||||||
|
);`);
|
||||||
|
|
||||||
|
// Mods versions
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS ModVersions (
|
||||||
|
mod TINYTEXT NOT NULL,
|
||||||
|
version_number TINYTEXT NOT NULL,
|
||||||
|
channel TINYTEXT NOT NULL,
|
||||||
|
changelog TEXT NOT NULL,
|
||||||
|
release_date TINYTEXT NOT NULL,
|
||||||
|
game_version TINYTEXT NOT NULL,
|
||||||
|
platform TINYTEXT NOT NULL,
|
||||||
|
environment TINYTEXT NOT NULL,
|
||||||
|
url TINYTEXT NOT NULL,
|
||||||
|
|
||||||
|
FOREIGN KEY (mod) REFERENCES Mods(name)
|
||||||
|
);`);
|
||||||
|
|
||||||
|
// User favorites (mods)
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS UserFavoriteMods (
|
||||||
|
username TINYTEXT NOT NULL,
|
||||||
|
mod TINYTEXT NOT NULL,
|
||||||
|
|
||||||
|
FOREIGN KEY (username) REFERENCES Users(username),
|
||||||
|
FOREIGN KEY (mod) REFERENCES Mods(name)
|
||||||
|
);`);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getDatabase() {
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = { getDatabase, connectDatabase, initDatabase };
|
30
backend/src/database/mysql.js
Normal file
30
backend/src/database/mysql.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
class MySQLDatabase {
|
||||||
|
constructor(config) {
|
||||||
|
const mysql = require("mysql2/promise");
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async connect() {
|
||||||
|
this.db = await mysql.createConnection({
|
||||||
|
host: this.config.host,
|
||||||
|
user: this.config.user,
|
||||||
|
password: this.config.password,
|
||||||
|
database: this.config.database,
|
||||||
|
});
|
||||||
|
console.log("Connected to MySQL");
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
await this.db.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
async query(sql, params) {
|
||||||
|
throw new Error("Not implemented"); //TODO
|
||||||
|
const [results] = await this.db.execute(sql, params);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = MySQLDatabase;
|
70
backend/src/database/sqlite.js
Normal file
70
backend/src/database/sqlite.js
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
const sqlite = require("better-sqlite3");
|
||||||
|
|
||||||
|
class SQLiteDatabase {
|
||||||
|
|
||||||
|
constructor(config) {
|
||||||
|
this.config = config;
|
||||||
|
this.db = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect() {
|
||||||
|
try {
|
||||||
|
this.db = new sqlite("./data/sqlite.db");
|
||||||
|
// this.db.pragma("journal_mode = WAL");
|
||||||
|
console.log("Connected to SQLite");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error connecting to SQLite database: ", err);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
if (this.db && this.db.open) {
|
||||||
|
this.db.close();
|
||||||
|
console.debug("Closed database connection");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async query(sql, params = []) {
|
||||||
|
try {
|
||||||
|
if (params.length > 0) {
|
||||||
|
return this.db.prepare(sql).all(params);
|
||||||
|
} else {
|
||||||
|
return this.db.prepare(sql).all();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error executing prepared query:", err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async exec(sql) {
|
||||||
|
try {
|
||||||
|
return this.db.exec(sql);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error executing statement:", err)}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async prepare(sql, params = []) {
|
||||||
|
try {
|
||||||
|
if (params.length > 0) {
|
||||||
|
return this.db.prepare(sql).run(params);
|
||||||
|
} else {
|
||||||
|
return this.db.prepare(sql);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error executing prepared statement:", err)}
|
||||||
|
}
|
||||||
|
|
||||||
|
async exists(table, attribute, value) {
|
||||||
|
try {
|
||||||
|
return this.db.prepare(`SELECT COUNT(*) FROM ${table} WHERE ${attribute} = ?`).get(value)['COUNT(*)'] > 0;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error checking item existence");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = SQLiteDatabase;
|
85
backend/src/middleware/auth.js
Normal file
85
backend/src/middleware/auth.js
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
const { getModByName } = require("../services/modService");
|
||||||
|
const { getModpackByName } = require("../services/modpackService");
|
||||||
|
const { getUserByName } = require("../services/userService");
|
||||||
|
const { verifyToken } = require("../utils/crypto");
|
||||||
|
const AppError = require("../utils/appError");
|
||||||
|
|
||||||
|
|
||||||
|
async function authenticateToken(req) {
|
||||||
|
|
||||||
|
const token = req.header("Authorization");
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
throw new AppError(401, "Missing authorization header", "Unauthorized");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
req.token_infos = await verifyToken(token);
|
||||||
|
console.debug("Authorizing token from", req.token_infos);
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppError(403, "Forbidden: Error verifying the authorization token");
|
||||||
|
}
|
||||||
|
|
||||||
|
return req.token_infos;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function authorizeModModification(req) {
|
||||||
|
|
||||||
|
// Auth token
|
||||||
|
await authenticateToken(req);
|
||||||
|
// Get mod infos
|
||||||
|
if (!req.params || !req.params.name) {
|
||||||
|
throw new AppError(400, "No mod name was scpecified", "Bad request");
|
||||||
|
}
|
||||||
|
const mod_name = req.params.name;
|
||||||
|
const mod = await getModByName(mod_name);
|
||||||
|
if (!mod) {
|
||||||
|
throw new AppError(404, "No mod was found with this name", "Not found");
|
||||||
|
}
|
||||||
|
// Authorize
|
||||||
|
if ( mod.author != req.token_infos.username) {
|
||||||
|
throw new AppError(401, "Mod author differs from current user", "Unauthorized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function authorizeModpackModification(req) {
|
||||||
|
|
||||||
|
// Auth token
|
||||||
|
await authenticateToken(req);
|
||||||
|
// Get mod infos
|
||||||
|
if (!req.params || !req.params.name) {
|
||||||
|
throw new AppError(400, "No mod name was scpecified", "Bad request");
|
||||||
|
}
|
||||||
|
const modpack_name = req.params.name;
|
||||||
|
const modpack = await getModpackByName(modpack_name);
|
||||||
|
if (!modpack) {
|
||||||
|
throw new AppError(404, "No mod was found with this name", "Not found");
|
||||||
|
}
|
||||||
|
// Authorize
|
||||||
|
if ( modpack.author != req.token_infos.username) {
|
||||||
|
throw new AppError(401, "Mod author differs from current user", "Unauthorized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function authorizeUserModification(req) {
|
||||||
|
|
||||||
|
// Auth token
|
||||||
|
await authenticateToken(req);
|
||||||
|
// Get mod infos
|
||||||
|
if (!req.params || !req.params.name) {
|
||||||
|
throw new AppError(400, "No mod name was scpecified", "Bad request");
|
||||||
|
}
|
||||||
|
const user_name = req.params.name;
|
||||||
|
const user = await getUserByName(user_name);
|
||||||
|
if (!user) {
|
||||||
|
throw new AppError(404, "No user was found with this name", "Not found");
|
||||||
|
}
|
||||||
|
// Authorize
|
||||||
|
if ( user.username != req.token_infos.username) {
|
||||||
|
throw new AppError(401, "User to modify differs from current user", "Unauthorized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = { authenticateToken, authorizeModModification, authorizeModpackModification, authorizeUserModification };
|
31
backend/src/middleware/errors.js
Normal file
31
backend/src/middleware/errors.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
const AppError = require("../utils/appError");
|
||||||
|
|
||||||
|
const handleError = (err, res) => {
|
||||||
|
|
||||||
|
// Send error infos
|
||||||
|
if (err instanceof AppError) {
|
||||||
|
|
||||||
|
// Log
|
||||||
|
if (err.statusCode == 500) {
|
||||||
|
console.error("Error:", err.message);
|
||||||
|
if (err.debugMsg) {
|
||||||
|
console.debug(" >", err.debugMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response
|
||||||
|
return res.status(err.statusCode).json({
|
||||||
|
status: err.status,
|
||||||
|
message: err.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default error
|
||||||
|
console.error("Error:", err.message);
|
||||||
|
res.status(500).json({
|
||||||
|
message: 'Internal server error',
|
||||||
|
status: 500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = handleError;
|
12
backend/src/models/index.js
Normal file
12
backend/src/models/index.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
const configManager = require("../utils/configManager");
|
||||||
|
|
||||||
|
async function getVersion() {
|
||||||
|
|
||||||
|
const version = await configManager.getVersion();
|
||||||
|
const res = {
|
||||||
|
version: version
|
||||||
|
};
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getVersion }
|
192
backend/src/models/mod.js
Normal file
192
backend/src/models/mod.js
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
const { getDatabase } = require('../database/index');
|
||||||
|
const AppError = require('../utils/appError');
|
||||||
|
const db = getDatabase();
|
||||||
|
|
||||||
|
|
||||||
|
// --- Get ---
|
||||||
|
|
||||||
|
async function getAllMods() {
|
||||||
|
return await db.query("SELECT name, display_name, author, description FROM Mods");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getModByName(name) {
|
||||||
|
return await db.query("SELECT name, display_name, author FROM Mods WHERE name = ?;", [name]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getModFullInfos(name) {
|
||||||
|
|
||||||
|
// Query
|
||||||
|
const base_infos = db.query(`SELECT * FROM Mods WHERE name = ?`, [name]);
|
||||||
|
const other_infos = db.query(`SELECT full_description, license, links, creation_date, downloads_count
|
||||||
|
FROM ModInfos WHERE name = ?`, [name]);
|
||||||
|
const tags = getModTags(name);
|
||||||
|
|
||||||
|
// Merge
|
||||||
|
const res = {...await base_infos, ...await other_infos, ...tags};
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listVersions(mod_name) {
|
||||||
|
return await db.query("SELECT * FROM ModVersions WHERE mod = ?", [mod_name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getVersionByNumber(mod_name, version_number) {
|
||||||
|
return await db.query(`SELECT * FROM ModVersions
|
||||||
|
WHERE mod = ?
|
||||||
|
AND version_number = ?;`,
|
||||||
|
[mod_name, version_number]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getVersion(mod_name, version_number, game_version, platform, environment) {
|
||||||
|
return await db.query(`SELECT * FROM ModVersions
|
||||||
|
WHERE mod = ?
|
||||||
|
AND version_number = ?
|
||||||
|
AND game_version = ?
|
||||||
|
AND platform = ?
|
||||||
|
AND environment = ?;`,
|
||||||
|
[mod_name, version_number, game_version, platform, environment]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Create ---
|
||||||
|
|
||||||
|
async function createMod(name, display_name, author, description, mod_infos) {
|
||||||
|
|
||||||
|
// Extract infos
|
||||||
|
const { full_description, license, links, creation_date, tags } = mod_infos;
|
||||||
|
|
||||||
|
// Mods table
|
||||||
|
await db.prepare("INSERT INTO Mods (name, display_name, author, description) \
|
||||||
|
VALUES (?, ?, ?, ?)",
|
||||||
|
[name, display_name, author, description]);
|
||||||
|
|
||||||
|
// ModInfos table
|
||||||
|
await db.prepare(`INSERT INTO ModInfos (mod, full_description, license, links, creation_date, downloads_count)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||||
|
[name, full_description, license.type, links.toString(), creation_date, 0]);
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
if (tags) {
|
||||||
|
const tags_proc = addTags(name, tags, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// License
|
||||||
|
if (license.type == "custom") {
|
||||||
|
await db.prepare(`UPDATE ModInfos SET custom_license = ?
|
||||||
|
WHERE mod = ?`,
|
||||||
|
[license.content, name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Await
|
||||||
|
if (tags) {
|
||||||
|
await tags_proc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addVersion(mod, version_number, channel, changelog, release_date, game_version, platform, environment, url) {
|
||||||
|
|
||||||
|
await db.prepare(`INSERT INTO ModVersions (mod, version_number, channel, changelog, release_date, game_version, environment, platform, url)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);`,
|
||||||
|
[mod, version_number, channel, changelog, release_date, game_version, environment, platform, url]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addTags(mod, tags) {
|
||||||
|
// Add asynchronously
|
||||||
|
const promises = tags.map(async (tag) => {
|
||||||
|
db.query(`INSERT INTO ModTags (mod, tag)
|
||||||
|
VALUES (?, ?);`,
|
||||||
|
[mod, tag]);
|
||||||
|
});
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Update ---
|
||||||
|
|
||||||
|
async function updateMod(name, display_name, author, description) {
|
||||||
|
|
||||||
|
if (display_name) {
|
||||||
|
await updateModAttributes(name, "display_name", display_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (author) {
|
||||||
|
await updateModAttribute(nale, "author", author);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (description) {
|
||||||
|
await updateModAttribute(name, "description", description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Delete ---
|
||||||
|
|
||||||
|
async function deleteMod(name) {
|
||||||
|
await db.prepare("DELETE FROM Mods WHERE name = ?", [name]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteVersion(name, version_number, game_version, platform, environment) {
|
||||||
|
await db.prepare(`DELETE FROM ModVersions WHERE mod = ?
|
||||||
|
AND version_number = ?
|
||||||
|
AND game_version = ?
|
||||||
|
AND platform = ?
|
||||||
|
AND environment = ?;`,
|
||||||
|
[name, version_number, game_version, platform, environment]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteTags(mod, tags) {
|
||||||
|
// Remove asynchronously
|
||||||
|
const promises = tags.map(async (tag) => {
|
||||||
|
db.query(`DELETE FROM ModTags
|
||||||
|
WHERE mod = ? AND tag = ?;`, [mod, tag]);
|
||||||
|
});
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Utils ---
|
||||||
|
|
||||||
|
async function updateModAttribute(name, attribute, value) {
|
||||||
|
await db.prepare(`UPDATE Mods SET ${attribute} = ? WHERE name = ?`, [value, name]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateModInfosAttribute(name, attribute, value) {
|
||||||
|
await db.prepare(`UPDATE ModInfos SET ${attribute} = ? WHERE name = ?`, [value, name]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exists(name) {
|
||||||
|
return db.exists("Mods", "name", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function containsVersion(name, version_number, game_version, platform, environment) {
|
||||||
|
throw new AppError(501, "Not implemented");
|
||||||
|
// return db.exists("Mods", "name", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function containsTag(name, tag) {
|
||||||
|
throw new AppError(501, "Not implemented");
|
||||||
|
// return db.exists("Mods", "name", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Exports ---
|
||||||
|
|
||||||
|
module.exports = { getAllMods, getModByName, getModFullInfos,
|
||||||
|
listVersions, getVersionByNumber, getVersion,
|
||||||
|
createMod, addVersion, addTags,
|
||||||
|
updateMod,
|
||||||
|
deleteMod, deleteVersion, deleteTags,
|
||||||
|
exists };
|
0
backend/src/models/modpack.js
Normal file
0
backend/src/models/modpack.js
Normal file
125
backend/src/models/user.js
Normal file
125
backend/src/models/user.js
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
const { getDatabase } = require('../database/index');
|
||||||
|
const AppError = require('../utils/appError');
|
||||||
|
const db = getDatabase();
|
||||||
|
|
||||||
|
|
||||||
|
// --- Get ---
|
||||||
|
|
||||||
|
async function getAllUsers() {
|
||||||
|
return db.query("SELECT username, display_name, email, profile_picture FROM Users");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUserByName(name) {
|
||||||
|
return await db.query("SELECT username, display_name, profile_picture, role FROM Users WHERE username = ?;", [name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUserByEmail(email) {
|
||||||
|
return await db.query("SELECT email, username FROM Users WHERE email = ?;", [email]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFullUserInfos(name) {
|
||||||
|
return await db.query("SELECT username, display_name, email, profile_picture, role, settings FROM Users WHERE username = ?;", [name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUserPassword(name) {
|
||||||
|
return await db.query("SELECT username, password FROM Users WHERE username = ?;", [name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exists(name) {
|
||||||
|
return await db.exists("Users", "username", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Create ---
|
||||||
|
|
||||||
|
async function createUser( username, email, password, displayName, profilePicture, settings ) {
|
||||||
|
|
||||||
|
// Create user
|
||||||
|
await db.prepare(`INSERT INTO Users (username, email, password, display_name, role )
|
||||||
|
VALUES (?, ?, ?, ?, ? )`,
|
||||||
|
[username, email, password, displayName, "user"]);
|
||||||
|
|
||||||
|
// Handle nullable fields
|
||||||
|
if (profilePicture) {
|
||||||
|
await updateUserAttribute(username, "profile_picture", profilePicture);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings) {
|
||||||
|
await updateUserAttribute(username, "settings", settings);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addFavoriteMods(username, favs) {
|
||||||
|
|
||||||
|
const promises = favs.map(async (mod) => {
|
||||||
|
db.query(`INSERT INTO UserFavoriteMods
|
||||||
|
(username, mod) VALUES (?, ?);`,
|
||||||
|
[username, mod]);
|
||||||
|
|
||||||
|
});
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Update ---
|
||||||
|
|
||||||
|
async function updateUser(username, display_name, email, profile_picture, settings) {
|
||||||
|
|
||||||
|
if (display_name) {
|
||||||
|
await updateUserAttribute(username, "display_name", display_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
await updateUserAttribute(username, "email", email);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile_picture) {
|
||||||
|
await updateUserAttribute(username, "profile_picture", profile_picture)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings) {
|
||||||
|
await updateUserAttribute(username, "settings", settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateUserPassword(username, password) {
|
||||||
|
await db.prepare(`UPDATE Users SET password = ? WHERE username = ?`, [password, username]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateUserAttribute(username, attribute, value) {
|
||||||
|
await db.prepare(`UPDATE Users SET ${attribute} = ? WHERE username = ?`, [value, username]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Delete ---
|
||||||
|
|
||||||
|
async function deleteUser(username) {
|
||||||
|
await db.prepare("DELETE FROM Users WHERE username = ?", [username]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteFavoriteMods(username, favs) {
|
||||||
|
|
||||||
|
const promises = favs.map(async (mod) => {
|
||||||
|
db.query(`DELETE FROM UserFavoriteMods
|
||||||
|
WHERE username = ? AND mod = ?;`, [username, mod]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Await
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Exports ---
|
||||||
|
|
||||||
|
module.exports = { getAllUsers, getUserByName, getUserByEmail, getFullUserInfos, getUserPassword,
|
||||||
|
createUser, addFavoriteMods,
|
||||||
|
updateUser,
|
||||||
|
deleteUser, deleteFavoriteMods,
|
||||||
|
exists }
|
11
backend/src/routes/index.js
Normal file
11
backend/src/routes/index.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
const express = require("express");
|
||||||
|
const controller = require("../controllers/index");
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
|
||||||
|
router.get('/version', async (res, req) => {
|
||||||
|
controller.getVersion(res, req);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
24
backend/src/routes/list.js
Normal file
24
backend/src/routes/list.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
const express = require("express");
|
||||||
|
const { listMods } = require("../controllers/mods");
|
||||||
|
// const { listModpacks } = require("../controllers/modpacks");
|
||||||
|
const { listUsers } = require("../controllers/users");
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// List mods
|
||||||
|
router.get("/mods", async (req,res) => {
|
||||||
|
listMods(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
|
// List modpacks
|
||||||
|
// router.get("/modpacks", async (req,res) => {
|
||||||
|
// listModpacks(req, res);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// List users
|
||||||
|
router.get("/users", async (req,res) => {
|
||||||
|
listUsers(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = router;
|
11
backend/src/routes/login.js
Normal file
11
backend/src/routes/login.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
const express = require("express");
|
||||||
|
const controller = require("../controllers/auth");
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// Login
|
||||||
|
router.post("/", async (req, res) => {
|
||||||
|
controller.login(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
0
backend/src/routes/modpacks.js
Normal file
0
backend/src/routes/modpacks.js
Normal file
27
backend/src/routes/mods.js
Normal file
27
backend/src/routes/mods.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
const express = require("express");
|
||||||
|
const controller = require("../controllers/mods");
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// Create a mod
|
||||||
|
router.post("/", async (req, res) => {
|
||||||
|
controller.createMod(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Modify mod
|
||||||
|
router.put("/:name", async (req,res) => {
|
||||||
|
controller.modifyMod(req,res);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get mod infos
|
||||||
|
router.get("/:name", async (req,res) => {
|
||||||
|
controller.getModByName(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete mod
|
||||||
|
router.delete("/:name", async (req,res) => {
|
||||||
|
controller.deleteMod(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = router;
|
27
backend/src/routes/users.js
Normal file
27
backend/src/routes/users.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
const express = require("express");
|
||||||
|
const controller = require("../controllers/users");
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// List users
|
||||||
|
router.get("/", async (req,res) => {
|
||||||
|
controller.listUsers(req,res);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a user
|
||||||
|
router.post("/", async (req, res) => {
|
||||||
|
controller.createUser(req, res);
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get user infos
|
||||||
|
router.get("/:name", async (req,res) => {
|
||||||
|
controller.getUserByName(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete user
|
||||||
|
router.delete("/:name", async (req,res) => {
|
||||||
|
controller.deleteUser(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = router;
|
21
backend/src/schemas/auth.js
Normal file
21
backend/src/schemas/auth.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
const Ajv = require("ajv");
|
||||||
|
const ajv = new Ajv();
|
||||||
|
|
||||||
|
// --- Schemas ---
|
||||||
|
|
||||||
|
const AuthUserSchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
email: { type: 'string', format: 'email' },
|
||||||
|
password: { type: 'string', minLength: 3, maxLength: 30 },
|
||||||
|
},
|
||||||
|
required: ['email', 'password'],
|
||||||
|
additionalProperties: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateAuthUserData = ajv.compile(AuthUserSchema);
|
||||||
|
|
||||||
|
|
||||||
|
// --- Exports ---
|
||||||
|
|
||||||
|
module.exports = { validateAuthUserData, validateAuthNodeData };
|
21
backend/src/schemas/mod.js
Normal file
21
backend/src/schemas/mod.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
const Ajv = require("ajv");
|
||||||
|
const ajv = new Ajv();
|
||||||
|
|
||||||
|
// --- Schemas ---
|
||||||
|
//TODO
|
||||||
|
|
||||||
|
const newModSchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: { type: 'string'},
|
||||||
|
},
|
||||||
|
required: ['name'],
|
||||||
|
additionalProperties: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateNewModData = ajv.compile(newModSchema);
|
||||||
|
|
||||||
|
|
||||||
|
// --- Exports ---
|
||||||
|
|
||||||
|
module.exports = { validateNewModData };
|
21
backend/src/schemas/modpack.js
Normal file
21
backend/src/schemas/modpack.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
const Ajv = require("ajv");
|
||||||
|
const ajv = new Ajv();
|
||||||
|
|
||||||
|
// --- Schemas ---
|
||||||
|
//TODO
|
||||||
|
|
||||||
|
const newModpackSchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: { type: 'string'},
|
||||||
|
},
|
||||||
|
required: ['name'],
|
||||||
|
additionalProperties: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateNewModpackData = ajv.compile(newModpackSchema);
|
||||||
|
|
||||||
|
|
||||||
|
// --- Exports ---
|
||||||
|
|
||||||
|
module.exports = { validateNewModpackData };
|
23
backend/src/schemas/user.js
Normal file
23
backend/src/schemas/user.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
const Ajv = require("ajv");
|
||||||
|
const ajv = new Ajv();
|
||||||
|
|
||||||
|
// --- Schemas ---
|
||||||
|
//TODO
|
||||||
|
|
||||||
|
const newUserSchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
email: { type: 'string', format: 'email' },
|
||||||
|
name: { type: 'string' },
|
||||||
|
password: { type: 'string', minLength: 3, maxLength: 30 },
|
||||||
|
},
|
||||||
|
required: ['name', 'email', 'password'],
|
||||||
|
additionalProperties: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateNewUserData = ajv.compile(newUserSchema);
|
||||||
|
|
||||||
|
|
||||||
|
// --- Exports ---
|
||||||
|
|
||||||
|
module.exports = { validateNewUserData };
|
85
backend/src/services/authService.js
Normal file
85
backend/src/services/authService.js
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
const userModel = require("../models/user");
|
||||||
|
const AppError = require("../utils/appError");
|
||||||
|
const cryptoUtils = require("../utils/crypto");
|
||||||
|
const configManager = require("../utils/configManager");
|
||||||
|
const validate = require("../utils/validate_legacy");
|
||||||
|
|
||||||
|
const JWT_Secret = configManager.getJWTSecret();
|
||||||
|
|
||||||
|
async function login(username, email, password) {
|
||||||
|
|
||||||
|
// Check for null
|
||||||
|
if (!(username || email) || !password) {
|
||||||
|
throw new AppError(400, "Bad request", "missing credentials");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user data
|
||||||
|
let user_get;
|
||||||
|
if (email) { // If matches email
|
||||||
|
user_get = await userModel.getUserByEmail(email);
|
||||||
|
} else
|
||||||
|
if (username) { // if matches username
|
||||||
|
user_get = await userModel.getUserByName(username);
|
||||||
|
} else {
|
||||||
|
console.debug("Failed finding user, weird...")
|
||||||
|
throw new AppError(401, "Unauthorized", "Invalid credentials");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user exists
|
||||||
|
if (!user_get || user_get.length == 0) {
|
||||||
|
// throw new AppError(401, "Unauthorized: No user with this name");
|
||||||
|
throw new AppError(401, "Unauthorized", "Invalid credentials");
|
||||||
|
}
|
||||||
|
// Just in case
|
||||||
|
if (user_get.length > 1) {
|
||||||
|
throw new AppError(500, "Internal server error", "Found multiple users with this name or email, please contact administration");
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = user_get[0];
|
||||||
|
|
||||||
|
// Get user password
|
||||||
|
const saved_password_get = await userModel.getUserPassword(user.username);
|
||||||
|
// Check if retrieved password sucessfully
|
||||||
|
if (!saved_password_get || saved_password_get.length == 0) {
|
||||||
|
throw new AppError(500, "Unable to retrieve user password");
|
||||||
|
}
|
||||||
|
saved_password = saved_password_get[0].password;
|
||||||
|
// Check if retrieved password sucessfully again
|
||||||
|
if (!saved_password) {
|
||||||
|
throw new AppError(500, "Unable to retrieve user password");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if passwords match
|
||||||
|
const passwords_match = await cryptoUtils.passwordsMatch(password, saved_password)
|
||||||
|
if (!passwords_match) {
|
||||||
|
// throw new AppError(401, "Unauthorized: Invalid password");
|
||||||
|
console.debug(password, "differs from", saved_password);
|
||||||
|
throw new AppError(401, "Unauthorized", "Invalid credentials");
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = { type: "user",
|
||||||
|
username: user.username,
|
||||||
|
email: user.email,
|
||||||
|
role: user.role };
|
||||||
|
|
||||||
|
const token = await cryptoUtils.signToken(payload);
|
||||||
|
return token;
|
||||||
|
|
||||||
|
// // Check if passwords match
|
||||||
|
// const passwords_match = await bcrypt.compare(password, user[0].password);
|
||||||
|
// if (!passwords_match) {
|
||||||
|
// // throw new AppError(401, "Unauthorized: Invalid password");
|
||||||
|
// console.debug("Password doesn't match")
|
||||||
|
// throw new AppError(401, "Unauthorized", "Invalid credentials");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return jwt.sign({ username: user[0].username, role: user[0].role }, await JWT_Secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
// function authorizeRole(user, roles) {
|
||||||
|
// if (!user || !roles.includes(user.role)) {
|
||||||
|
// throw new AppError(401, "Unauthorized: You don't have the necessary permissions to access this resource");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
module.exports = { login };
|
7
backend/src/services/indexService.js
Normal file
7
backend/src/services/indexService.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
const model = require("../models/index");
|
||||||
|
|
||||||
|
async function getVersion() {
|
||||||
|
return model.getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getVersion }
|
164
backend/src/services/modService.js
Normal file
164
backend/src/services/modService.js
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
const model = require("../models/mod");
|
||||||
|
const AppError = require("../utils/appError");
|
||||||
|
const { validateModData } = require("../utils/validate_legacy");
|
||||||
|
const { mdToHtml } = require("../utils/convert");
|
||||||
|
const { sanitizeModData } = require("../utils/sanitize");
|
||||||
|
|
||||||
|
|
||||||
|
// --- Get ---
|
||||||
|
|
||||||
|
async function getAllMods() {
|
||||||
|
return model.getAllMods();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getModByName(name) {
|
||||||
|
const res = model.getModByName(name);
|
||||||
|
if (res.length == 0) {
|
||||||
|
throw new AppError(404, "Cannot find mod with this name", "Not found");
|
||||||
|
}
|
||||||
|
return res[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFullModInfos(name) {
|
||||||
|
const res = model.getFullModInfos(name);
|
||||||
|
if (res.length == 0) {
|
||||||
|
throw new AppError(404, "Cannot find mod with this name", "Not found");
|
||||||
|
}
|
||||||
|
return res[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getModVersion(infos) {
|
||||||
|
const { mod, version_number, game_version, platform, environment} = infos;
|
||||||
|
const res = model.getModVersion(mod, version_number, game_version, platform, environment);
|
||||||
|
if (res.length == 0) {
|
||||||
|
throw new AppError(404, "Cannot find mod with this name", "Not found");
|
||||||
|
}
|
||||||
|
return res[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Create ---
|
||||||
|
|
||||||
|
async function createMod(mod_data, author) {
|
||||||
|
|
||||||
|
// Check body validity
|
||||||
|
//TODO
|
||||||
|
console.warn("Skipping validity checks for createMod");
|
||||||
|
// await validateModData(mod_data);
|
||||||
|
|
||||||
|
// Generate data
|
||||||
|
const { name, display_name, description, mod_infos } = mod_data;
|
||||||
|
mod_infos.full_description = await mdToHtml(mod_infos.full_description); // Convert
|
||||||
|
await sanitizeModData(mod_data); // Sanitize
|
||||||
|
//TODO
|
||||||
|
mod_infos.creation_date = 0
|
||||||
|
|
||||||
|
// Write changes to database
|
||||||
|
await model.createMod(name, display_name, author, description, mod_infos);
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return getModByName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addVersion(version_data) {
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
//TODO
|
||||||
|
console.warn("Skipping validity checks for addVersion");
|
||||||
|
|
||||||
|
// Generate data
|
||||||
|
const { mod_name, version_number, channel, changelog, game_version,
|
||||||
|
platform, environment, url } = version_data; // Split
|
||||||
|
changelog = await mdToHtml(changelog); // Convert
|
||||||
|
await sanitizeModData(mod_data); // Sanitize
|
||||||
|
const release_date = (new Date()).toLocaleDateString();
|
||||||
|
|
||||||
|
// Write changes
|
||||||
|
await model.addVersion(mod_name, version_number, channel, changelog,
|
||||||
|
release_date, game_version, platform, environment, url); // Database
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return await model.getModVersion(mod_name, version_number, game_version, platform, environment );
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addTags(mod, tags) {
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
//TODO
|
||||||
|
console.warn("Skipping validity checks for addTags");
|
||||||
|
|
||||||
|
// Write changes
|
||||||
|
await model.addTags(mod, tags);
|
||||||
|
|
||||||
|
// Return
|
||||||
|
const { tags:res } = await model.getFullModInfos(mod);
|
||||||
|
return { "mod": mod, "tags": res};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Update ---
|
||||||
|
|
||||||
|
async function updateMod(diff_data) {
|
||||||
|
//TODO
|
||||||
|
throw new AppError(501, "Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
|
||||||
|
async function deleteMod(name) {
|
||||||
|
|
||||||
|
// Check existence
|
||||||
|
const mod = await model.getModByName(name);
|
||||||
|
if (!mod) {
|
||||||
|
throw new AppError(404, "No was found with this name", "Not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorize
|
||||||
|
// TODO move outside of this function
|
||||||
|
if (mod.author != mod.user) {
|
||||||
|
throw new AppError(403, "You don't have the necessary permissions to execute this action", "Forbidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write changes to database
|
||||||
|
await model.deleteMod(name);
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteVersion(version_infos) {
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
// Generate data
|
||||||
|
const res = await getModVersion(version_infos);
|
||||||
|
const { mod, version_number, game_version, platform, environment} = version_infos;
|
||||||
|
|
||||||
|
// Write changes to db
|
||||||
|
await model.deleteVersion(mod, version_number, game_version, platform, environment);
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteTags(mod, tags) {
|
||||||
|
|
||||||
|
// Validate (check existence)
|
||||||
|
//TODO
|
||||||
|
console.warn("Skipping validity checks for deleteTags");
|
||||||
|
|
||||||
|
// Wites changes to db
|
||||||
|
await model.deleteTags(mod, tags);
|
||||||
|
|
||||||
|
// Return
|
||||||
|
const { tags:res } = await model.getFullModInfos(mod);
|
||||||
|
return { "mod": mod, "tags": res};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getAllMods, getModByName, getFullModInfos,
|
||||||
|
createMod, addTags, addVersion,
|
||||||
|
updateMod,
|
||||||
|
deleteMod, deleteTags, deleteVersion };
|
0
backend/src/services/modpackService.js
Normal file
0
backend/src/services/modpackService.js
Normal file
46
backend/src/services/userService.js
Normal file
46
backend/src/services/userService.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
const model = require("../models/user");
|
||||||
|
const AppError = require("../utils/appError");
|
||||||
|
const cryptoUtils = require("../utils/crypto");
|
||||||
|
const { validateUserData } = require("../utils/validate_legacy");
|
||||||
|
const { sanitizeUserData } = require("../utils/sanitize");
|
||||||
|
|
||||||
|
async function getAllUsers() {
|
||||||
|
return await model.getAllUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUserByName(name) {
|
||||||
|
const res = await model.getUserByName(name);
|
||||||
|
return res[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createUser(user_data) {
|
||||||
|
|
||||||
|
// Check body validity
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
// Sanitize
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
// Gather data
|
||||||
|
const { username, email, password, display_name, profile_picture, settings } = user_data
|
||||||
|
const password_hash = await cryptoUtils.hashPassword(password);
|
||||||
|
|
||||||
|
await model.createUser(username, email, password_hash, display_name, null, null);
|
||||||
|
return model.getUserByName(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteUser(name, token_user) {
|
||||||
|
|
||||||
|
// Check existence
|
||||||
|
const exists = await model.exists(name);
|
||||||
|
if (!exists) {
|
||||||
|
throw new AppError(404, "Cannot find user with this name", "Not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await model.getUserByName(name);
|
||||||
|
await model.deleteUser(name);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getAllUsers, getUserByName, createUser, deleteUser };
|
27
backend/src/utils/appError.js
Normal file
27
backend/src/utils/appError.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
class AppError extends Error {
|
||||||
|
constructor(statusCode, message, status = "", debugMsg = "") {
|
||||||
|
super(message);
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
this.debugMsg = debugMsg;
|
||||||
|
// Get status
|
||||||
|
if (status === "") {
|
||||||
|
if (statusCode.toString().startsWith("4")) {
|
||||||
|
this.status = "Fail";
|
||||||
|
} else {
|
||||||
|
this.status = "Error";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.tryCatch = (controller) => async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
await controller(req, res, next);
|
||||||
|
} catch(err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AppError;
|
89
backend/src/utils/configManager.js
Normal file
89
backend/src/utils/configManager.js
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
// --- Define constants ---
|
||||||
|
|
||||||
|
// Imports
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const { version } = require("../../package.json");
|
||||||
|
|
||||||
|
|
||||||
|
// Var decalaration
|
||||||
|
const config_folder = "config";
|
||||||
|
const config_file_name = "config.json"
|
||||||
|
|
||||||
|
// Global variables
|
||||||
|
let config = {};
|
||||||
|
|
||||||
|
// --- Default config ---
|
||||||
|
|
||||||
|
const default_config = {
|
||||||
|
|
||||||
|
"port": 8000,
|
||||||
|
|
||||||
|
"users": {
|
||||||
|
"admin": {
|
||||||
|
"username": "admin",
|
||||||
|
"password": "admin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"database": {
|
||||||
|
"type": "sqlite"
|
||||||
|
},
|
||||||
|
|
||||||
|
"auth" : {
|
||||||
|
"JWT_secret": "HGF7654EGBNKJNBJH6754356788GJHGY",
|
||||||
|
"tokenExpiry": "1h"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Functions ---
|
||||||
|
|
||||||
|
function loadConfig() {
|
||||||
|
|
||||||
|
let user_config;
|
||||||
|
|
||||||
|
// Parse
|
||||||
|
try {
|
||||||
|
// Get user config
|
||||||
|
user_config = JSON.parse(fs.readFileSync(path.resolve(path.join(config_folder, config_file_name))));
|
||||||
|
|
||||||
|
// Warns
|
||||||
|
if (!user_config.auth || !user_config.auth.JWT_secret) {
|
||||||
|
console.warn("WARNING: No JWT secret provided, using the default one. Please note that using the default secret is a major security risk.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge default and user configs (default values)
|
||||||
|
config = { ...default_config, ...user_config };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
// Error messages
|
||||||
|
console.debug("Error:", err)
|
||||||
|
console.error("Error loading configuration, using the default settings");
|
||||||
|
console.debug("Search path:", path.resolve("./"));
|
||||||
|
console.debug("Config file:", path.resolve(path.join(config_folder, config_file_name)))
|
||||||
|
|
||||||
|
config = default_config;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getJWTSecret() {
|
||||||
|
return config.auth.JWT_secret || process.env.JWT_secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getVersion() {
|
||||||
|
// Could be done with process.env.npm_package_version
|
||||||
|
// but may not work without without npm
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exports
|
||||||
|
module.exports = { loadConfig, getConfig, getVersion, getJWTSecret };
|
11
backend/src/utils/convert.js
Normal file
11
backend/src/utils/convert.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
const marked = require("marked");
|
||||||
|
|
||||||
|
async function mdToHtml(md_content) {
|
||||||
|
if (md_content) {
|
||||||
|
return marked.parse(md_content);
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { mdToHtml };
|
61
backend/src/utils/crypto.js
Normal file
61
backend/src/utils/crypto.js
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// --- Imports ---
|
||||||
|
const jwt = require("jsonwebtoken");
|
||||||
|
const bcrypt = require("bcrypt");
|
||||||
|
const { getConfig, getJWTSecret } = require("./configManager");
|
||||||
|
|
||||||
|
|
||||||
|
// --- Config ---
|
||||||
|
|
||||||
|
// Declarations
|
||||||
|
let JWT_Secret;
|
||||||
|
let token_expiry;
|
||||||
|
// Constant values
|
||||||
|
const saltRounds = 12;
|
||||||
|
// Load
|
||||||
|
(async () => {
|
||||||
|
const config = await getConfig();
|
||||||
|
JWT_Secret = await getJWTSecret();
|
||||||
|
token_expiry = config.auth.tokenExpiry;
|
||||||
|
signature_algorithm = config.auth.signatureAlgorithm;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
// --- Functions ---
|
||||||
|
|
||||||
|
async function hashPassword(passwd) {
|
||||||
|
const hash = bcrypt.hashSync(passwd, saltRounds);
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function passwordsMatch(password, hashed_password) {
|
||||||
|
return await bcrypt.compare(password, hashed_password);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function signToken(payload, options = null) {
|
||||||
|
if (options == null) {
|
||||||
|
return jwt.sign(payload, JWT_Secret, { expiresIn: token_expiry, });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return jwt.sign(payload, JWT_Secret, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function verifyToken(token) {
|
||||||
|
return new Promise( async (resolve, reject) => {
|
||||||
|
await jwt.verify( token, JWT_Secret, (err, user) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(user);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Exports ---
|
||||||
|
module.exports = { passwordsMatch, hashPassword, verifyToken, signToken };
|
19
backend/src/utils/sanitize.js
Normal file
19
backend/src/utils/sanitize.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
const createDOMPurify = require("dompurify");
|
||||||
|
const { JSDOM } = require("jsdom");
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
const window = new JSDOM("").window;
|
||||||
|
const DOMPurify = createDOMPurify(window);
|
||||||
|
|
||||||
|
async function sanitizeText(text) {
|
||||||
|
return DOMPurify.sanitize(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sanitizeModData(mod_data) {
|
||||||
|
console.warn("Skipping sanitanization (not implemented)");
|
||||||
|
// mod_data.displayName = await sanitizeText(mod_data.displayName);
|
||||||
|
// mod_data.otherInfos.description = await sanitizeText(mod_data.otherInfos.description);
|
||||||
|
// mod_data.otherInfos.changelogs = await sanitizeText(mod_data.otherInfos.changelogs);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { sanitizeText, sanitizeModData };
|
51
backend/src/utils/validate.js
Normal file
51
backend/src/utils/validate.js
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
// --- Imports ---
|
||||||
|
const AppError = require("./appError");
|
||||||
|
|
||||||
|
|
||||||
|
// --- Functions ---
|
||||||
|
|
||||||
|
async function validateNewModData(mod_data) {
|
||||||
|
|
||||||
|
throw new AppError(501, "Not implemented");
|
||||||
|
//TODO
|
||||||
|
// try {
|
||||||
|
// node_schemas.validateNewModData(node_data);
|
||||||
|
// } catch (err) {
|
||||||
|
// throw new AppError(400, "Missing or invalid fields", "Bad request", err);
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function validateNewUserData(user_data) {
|
||||||
|
|
||||||
|
throw new AppError(501, "Not implemented");
|
||||||
|
//TODO
|
||||||
|
// try {
|
||||||
|
// node_schemas.validateNewUserData(node_data);
|
||||||
|
// } catch (err) {
|
||||||
|
// throw new AppError(400, "Missing or invalid fields", "Bad request", err);
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validateCretendials(identifier, password) {
|
||||||
|
|
||||||
|
throw new AppError(501, "Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Utils ---
|
||||||
|
|
||||||
|
async function isEmail(text) {
|
||||||
|
const email_regex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
|
||||||
|
return email_regex.test(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isID(text) {
|
||||||
|
const id_regex = /[a-zA-Z0-9_]+/;
|
||||||
|
return id_regex.test(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = { validateNewModData, validateNewUserData, isEmail, isID };
|
98
backend/src/utils/validate_legacy.js
Normal file
98
backend/src/utils/validate_legacy.js
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
const mod_model = require("../models/mod");
|
||||||
|
const user_model = require("../models/user");
|
||||||
|
const AppError = require("./appError");
|
||||||
|
|
||||||
|
async function validateModData(mod_data) {
|
||||||
|
//TODO WIP
|
||||||
|
// Check fields existence
|
||||||
|
const not_null = mod_data &&
|
||||||
|
Object.keys(mod_data).length == 5 &&
|
||||||
|
mod_data.name &&
|
||||||
|
mod_data.displayName &&
|
||||||
|
mod_data.author &&
|
||||||
|
mod_data.versions != null;
|
||||||
|
|
||||||
|
// mod_data.otherInfos != null &&
|
||||||
|
// Object.keys(mod_data.otherInfos).length == 0 &&
|
||||||
|
// mod_data.otherInfos.description != null &&
|
||||||
|
// mod_data.otherInfos.links != null &&
|
||||||
|
// mod_data.otherInfos.tags != null &&
|
||||||
|
// mod_data.otherInfos.screenshots != null &&
|
||||||
|
// mod_data.otherInfos.license != null &&
|
||||||
|
// mod_data.otherInfos.changelogs != null;
|
||||||
|
|
||||||
|
if (!not_null) {
|
||||||
|
console.debug("Item is missing expected fields:", mod_data);
|
||||||
|
throw new AppError(400, "Bad request", "Missing expected fields");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check fields format (check if sanitized)
|
||||||
|
const is_valid_name = /^[a-zA-Z0-9_]+$/.test(mod_data.name);
|
||||||
|
const is_valid_displayName = true;
|
||||||
|
// const is_valid_displayName = /^[a-zA-Z0-9_]+$/.test(mod_data.name); // Temporary
|
||||||
|
// const
|
||||||
|
|
||||||
|
const is_valid = is_valid_name && is_valid_displayName;
|
||||||
|
if (!is_valid) {
|
||||||
|
console.debug("Fields are not following the expected formats");
|
||||||
|
throw new AppError(400, "Bad request", "The provided fields don't match the expected format");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if mod already exists
|
||||||
|
const exists = await mod_model.exists(mod_data.name);
|
||||||
|
if (exists) {
|
||||||
|
console.debug("Error: Item already exists");
|
||||||
|
throw new AppError(403, "Forbidden", "Content with this name already exists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function validateUserData(user_data) {
|
||||||
|
throw new AppError(501, "Not implemented");
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
|
||||||
|
// Check fields existence
|
||||||
|
// ...
|
||||||
|
|
||||||
|
if (!not_null) {
|
||||||
|
console.debug("Missing expected fields:", mod_data);
|
||||||
|
throw new AppError(400, "Bad request: Missing expected fields");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check fields format (check if sanitized)
|
||||||
|
const is_valid_username = /^[a-zA-Z0-9_]+$/.test(user_data.username);
|
||||||
|
// const is_valid_email = ...
|
||||||
|
// ...
|
||||||
|
|
||||||
|
const is_valid = is_valid_username && is_valid_email;
|
||||||
|
if (!is_valid) {
|
||||||
|
console.debug("Fields are not following the expected formats");
|
||||||
|
throw new AppError(400, "Bad request: The provided fields don't match the expected format");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user already exists
|
||||||
|
const exists = await user_model.exists(user_data.username);
|
||||||
|
if (exists) {
|
||||||
|
console.debug("Error: User already exists");
|
||||||
|
throw new AppError(403, "Forbidden: User with this name already exists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validateCretendials(identifier, password) {
|
||||||
|
|
||||||
|
throw new AppError(501, "Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isEmail(text) {
|
||||||
|
const email_regex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
|
||||||
|
return email_regex.test(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isID(text) {
|
||||||
|
const id_regex = /[a-zA-Z0-9_]+/;
|
||||||
|
return id_regex.test(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = { validateModData, validateUserData, isEmail, isID };
|
Loading…
Reference in a new issue