feat: Delete mod enpoint is functional

feat: Sanitize, md converter and validate utilities
feat: Ability to alidate, convert and sanitize CreateMod requests (still WIP)
This commit is contained in:
Gu://em_ 2025-03-30 22:53:50 +02:00
parent 36eeec2e6c
commit 480df977fb
11 changed files with 543 additions and 53 deletions

View file

@ -24,13 +24,24 @@ async function getModByName(req, res) {
async function createMod(req, res) {
try {
const status = await mod_service.createMod(req.body);
res.status(status);
await mod_service.createMod(req.body);
res.sendStatus(200);
} catch (error) {
console.error("Cannot create mod:", error.message);
console.error("ERROR: Couldn't create mod:", error.message);
handleError(error, req, res, null);
}
}
async function deleteMod(req, res) {
try {
await mod_service.deleteMod(req.params.name);
return res.sendStatus(200);
} catch (error) {
console.error("ERROR: Couldn't delete mod " + req.params.name + ":", error.message);
handleError(error, req, res, null);
}
}
module.exports = { getAllMods, getModByName, createMod };
module.exports = { getAllMods, getModByName, createMod, deleteMod };

View file

@ -1,4 +1,3 @@
// TODO promisify
const sqlite = require("better-sqlite3");
class SQLiteDatabase {

View file

@ -1,8 +1,8 @@
const { getDatabase } = require('../database/index');
const AppError = require('../utils/appError');
const db = getDatabase();
async function getAllMods() {
console.debug("Calling model");
return db.query("SELECT * FROM mods");
}
@ -26,25 +26,35 @@ async function exists(name) {
return db.exists("mods", "Name", name);
}
// --- WIP ---
async function createMod(mod_data) {
console.warn("WARNING: using a WIP function : createMod (models/mods.js)");
const { name, displayName, author, versions, otherInfos } = mod_data;
return db.prepare("INSERT INTO mods (Name, DisplayName, Author, Versions, OtherInfos) VALUES (?, ?, ?, ?, ?)", [name, displayName, author, versions, otherInfos]);
}
async function updateMod(mod_data) {
console.log("WARNING: using a WIP function : updateMod (models/mods.js)");
throw new Error("Not implemented");
// const { name, description } = mod_data;
// return db.query("INSERT INTO mods (name, description) VALUES (?, ?)", [name, description]);
const { name, displayName, author, versions, otherInfos } = mod_data;
const { description, links, tags, screenshots, license, changelogs, counts } = otherInfos;
await db.prepare("INSERT INTO mods (Name, DisplayName, Author, Versions) \
VALUES (?, ?, ?, ?)", [name, displayName, author, versions]);
// db.prepare("INSERT INTO modsDescription (Name, Description, Links, Tags, Screenshots, License, Changelogs, Counts) \
// VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [name, description, links, tags, screenshots, license, changelogs, counts]);
return;
}
async function deleteMod(name) {
console.log("WARNING: using a WIP function : deleteMod (models/mods.js)");
return db.query("DELETE FROM mods WHERE name = ?", [name]);
db.prepare("DELETE FROM mods WHERE Name = ?", [name]);
// db.prepare("DELETE FROM modsDescription WHERE Name = ?", [name]);
return;
}
// --- WIP ---
async function updateMod(mod_data) {
console.log("WARNING: using a WIP function : updateMod (models/mods.js)");
throw new AppError(501, "Not implemented");
// const { name, description } = mod_data;
// return db.query("INSERT INTO mods (name, description) VALUES (?, ?)", [name, description]);
}
module.exports = { getAllMods, getModByName, createMod, exists }
module.exports = { getAllMods, getModByName, createMod, deleteMod, exists }

View file

@ -22,4 +22,12 @@ router.get("/:name", async (req,res) => {
controller.getModByName(req, res);
});
//
router.delete("/:name", async (req,res) => {
const name = req.params.name;
console.debug("Deleting mod " + name)
controller.deleteMod(req, res);
});
module.exports = router;

View file

@ -1,7 +1,8 @@
const handleError = require("../middleware/errors");
const model = require("../models/mod");
const AppError = require("../utils/appError");
const { validateModData } = require("../utils/validation");
const { validateModData } = require("../utils/validate");
const { mdToHtml } = require("../utils/convert");
const { sanitizeModData } = require("../utils/sanitize");
async function getAllMods() {
return model.getAllMods();
@ -11,10 +12,7 @@ async function getModByName(name) {
return model.getModByName(name);
}
async function createMod(mod_data) {
// throw new AppError(501, "Not implemented");
// console.debug("Received body: ", JSON.stringify(mod_data));
async function createMod(mod_data) {
// Check body validity
await validateModData(mod_data);
@ -22,8 +20,33 @@ async function createMod(mod_data) {
// Check authenticity
//TODO no auth provider
// Convert
mod_data.otherInfos.description = await mdToHtml(mod_data.otherInfos.description);
mod_data.otherInfos.changelogs = await mdToHtml(mod_data.otherInfos.changelogs);
// Sanitize
await sanitizeModData(mod_data);
console.debug("Passed validity checks");
return model.createMod(mod_data);
model.createMod(mod_data);
return;
}
module.exports = { getAllMods, getModByName, createMod };
async function deleteMod(name) {
// Check existence
const exists = await model.exists(name);
if (!exists) {
throw new AppError(404, "Not found: Cannot find mod with this name");
}
// Check authenticity
//TODO no auth provider
model.deleteMod(name);
return;
}
module.exports = { getAllMods, getModByName, createMod, deleteMod };

11
src/utils/convert.js Normal file
View 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 };

18
src/utils/sanitize.js Normal file
View file

@ -0,0 +1,18 @@
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) {
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 };

49
src/utils/validate.js Normal file
View file

@ -0,0 +1,49 @@
const mod_model = require("../models/mod");
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");
}
}
module.exports = { validateModData }

View file

@ -1,26 +0,0 @@
const mod_model = require("../models/mod");
const AppError = require("./appError");
async function validateModData(mod_data) {
// WIP
const not_null = mod_data != null;
mod_data.name &&
mod_data.displayName &&
mod_data.author &&
mod_data.versions &&
mod_data.otherInfos;
if (!not_null) {
console.debug("Item is missing expected fields:", mod_data);
throw new AppError(400, "Bad request: missing expected fields");
}
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");
}
}
module.exports = { validateModData }