From 45900e903327e627a8db0e319a76f96211679345 Mon Sep 17 00:00:00 2001
From: "Gu://em_"
Date: Sun, 18 May 2025 15:46:13 +0200
Subject: [PATCH] Functionnal basic versions implementation on the backend and
made the emty small card component for frontend
---
backend/src/controllers/mods.js | 63 ++++++-
backend/src/models/mod.js | 68 +++++---
backend/src/routes/mods.js | 22 +++
backend/src/services/modService.js | 155 +++++++++++-------
frontend/src/components/Cards/small_card.jsx | 34 ++++
.../components/Cards/small_card.module.css | 51 ++++++
.../src/components/NavBar/navbar.module.css | 2 +-
frontend/src/pages/dashboard.jsx | 34 ++--
frontend/src/styles/dashboard.module.css | 4 +-
9 files changed, 329 insertions(+), 104 deletions(-)
create mode 100644 frontend/src/components/Cards/small_card.jsx
create mode 100644 frontend/src/components/Cards/small_card.module.css
diff --git a/backend/src/controllers/mods.js b/backend/src/controllers/mods.js
index 9d2de1d..0d49fd2 100644
--- a/backend/src/controllers/mods.js
+++ b/backend/src/controllers/mods.js
@@ -2,6 +2,9 @@ const handleError = require("../middleware/errors");
const mod_service = require("../services/modService");
const { authorizeModModification, authenticateToken } = require("../middleware/auth");
+
+// Mods
+
async function listMods(req, res) {
try {
// Query
@@ -65,5 +68,63 @@ async function deleteMod(req, res) {
}
+// Versions
-module.exports = { listMods, getModByName, createMod, modifyMod, deleteMod };
\ No newline at end of file
+async function createModVersion(req, res) {
+ try {
+ // Authorize
+ await authorizeModModification(req);
+ // Query
+ const name = req.params.name
+ const version_data = req.body;
+ const query_result = await mod_service.createModVersion(name, version_data);
+ res.json(query_result);
+ } catch (error) {
+ handleError(error, res);
+ }
+}
+
+async function modifyModVersion(req, res) {
+ try {
+ // Authorize
+ await authorizeModModification(req);
+ // Query
+ const name = req.params.name
+ const version_data = req.body;
+ const query_result = await mod_service.modifyModVersion(name, version_data);
+ res.json(query_result);
+ } catch (error) {
+ handleError(error, res);
+ }
+}
+
+async function getModVersions(req, res) {
+ try {
+ // Query
+ const name = req.params.name
+ const filters = req.query;
+ const query_result = await mod_service.getModVersions(name, filters);
+ res.json(query_result);
+ } catch (error) {
+ handleError(error, res);
+ }
+}
+
+async function deleteModVersion(req, res) {
+ try {
+ // Authorize
+ await authorizeModModification(req);
+ // Query
+ const name = req.params.name
+ const version_infos = req.body;
+ const query_result = await mod_service.deleteModVersion(name, version_infos);
+ res.json(query_result);
+ } catch (error) {
+ handleError(error, res);
+ }
+}
+
+
+
+module.exports = { listMods, getModByName, createMod, modifyMod, deleteMod,
+ createModVersion, modifyModVersion, getModVersions, deleteModVersion };
\ No newline at end of file
diff --git a/backend/src/models/mod.js b/backend/src/models/mod.js
index 598b265..c2a5b90 100644
--- a/backend/src/models/mod.js
+++ b/backend/src/models/mod.js
@@ -6,7 +6,6 @@ const db = getDatabase();
// --- Get ---
async function listMods(filters) {
- console.debug(filters);
return await db.query(`SELECT name, display_name, author, description FROM Mods
WHERE
(CASE WHEN @search IS NOT NULL THEN
@@ -34,29 +33,44 @@ async function getFullModInfos(name) {
return [await base_infos, await other_infos, await tags];
}
-async function listVersions(mod_name) {
- return await db.query("SELECT * FROM ModVersions WHERE mod = ?", [mod_name]);
-}
-
async function listTags(mod_name) {
return await db.query(`SELECT tag FROM ModTags 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]);
+//TODO rm
+async function listAllModVersions() {
+ return await db.query(`SELECT * FROM ModVersions`);
}
-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]);
+async function getModVersions(mod_name, filters) {
+ return await db.query(`SELECT * FROM ModVersions
+ WHERE mod = @mod_name
+ AND
+ (CASE WHEN @version_number IS NOT NULL THEN
+ version_number = @version_number ELSE TRUE END)
+ AND
+ (CASE WHEN @channel IS NOT NULL THEN
+ channel = @channel ELSE TRUE END)
+ AND
+ (CASE WHEN @game_version IS NOT NULL THEN
+ game_version = @game_version ELSE TRUE END)
+ AND
+ (CASE WHEN @platform IS NOT NULL THEN
+ platform = @platform ELSE TRUE END)
+ AND
+ (CASE WHEN @environment IS NOT NULL THEN
+ environment = @environment ELSE TRUE END)
+ ;`, {mod_name, ...filters});
+}
+
+async function getStrictModVersion(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 ---
@@ -97,11 +111,15 @@ async function createMod(name, display_name, author, description, mod_infos) {
return;
}
-async function addVersion(mod, version_number, channel, changelog, release_date, game_version, platform, environment, url) {
+async function createModVersion(mod, version_infos) {
+ const { version_number, channel, changelog, release_date, game_version, platform, environment, url } = version_infos;
+
await db.run(`INSERT INTO ModVersions (mod, version_number, channel, changelog, release_date, game_version, environment, platform, url)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);`,
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);`,
[mod, version_number, channel, changelog, release_date, game_version, environment, platform, url]);
+
+
return;
}
@@ -143,7 +161,7 @@ async function deleteMod(name) {
return;
}
-async function deleteVersion(name, version_number, game_version, platform, environment) {
+async function deleteModVersion(name, version_number, game_version, platform, environment) {
await db.run(`DELETE FROM ModVersions WHERE mod = ?
AND version_number = ?
AND game_version = ?
@@ -195,8 +213,8 @@ async function containsTag(name, tag) {
// --- Exports ---
module.exports = { listMods, getModByName, getFullModInfos,
- listVersions, listTags, getVersionByNumber, getVersion,
- createMod, addVersion, addTags,
+ getModVersions, listTags, getStrictModVersion,
+ createMod, createModVersion, addTags,
updateMod,
- deleteMod, deleteVersion, deleteTags,
- exists };
\ No newline at end of file
+ deleteMod, deleteModVersion, deleteTags,
+ exists, listAllModVersions };
\ No newline at end of file
diff --git a/backend/src/routes/mods.js b/backend/src/routes/mods.js
index 82c1e02..aec98a3 100644
--- a/backend/src/routes/mods.js
+++ b/backend/src/routes/mods.js
@@ -24,4 +24,26 @@ router.delete("/:name", async (req,res) => {
});
+
+// Create a mod version
+router.post("/:name/versions", async (req, res) => {
+ controller.createModVersion(req, res);
+});
+
+// Modify a mod version
+router.put("/:name/versions", async (req,res) => {
+ controller.modifyModVersion(req,res);
+});
+
+// Get mod versions
+router.get("/:name/versions", async (req,res) => {
+ controller.getModVersions(req, res);
+});
+
+// Delete mod version
+router.delete("/:name/versions", async (req,res) => {
+ controller.deleteModVersion(req, res);
+});
+
+
module.exports = router;
\ No newline at end of file
diff --git a/backend/src/services/modService.js b/backend/src/services/modService.js
index e26fdc4..f1bf981 100644
--- a/backend/src/services/modService.js
+++ b/backend/src/services/modService.js
@@ -5,7 +5,8 @@ const { mdToHtml } = require("../utils/convert");
const { sanitizeModData } = require("../utils/sanitize");
-// --- Get ---
+// --- Mods ---
+
async function listMods(filters) {
//TODO Validate filters
@@ -17,6 +18,7 @@ async function listMods(filters) {
return await model.listMods({...filters});
}
+
async function getModByName(name) {
const res = await model.getModByName(name);
if (res.length == 0) {
@@ -25,6 +27,7 @@ async function getModByName(name) {
return res[0];
}
+
async function getFullModInfos(name) {
const [base_infos, other_infos, tags] = await model.getFullModInfos(name);
// Check
@@ -37,17 +40,6 @@ async function getFullModInfos(name) {
return res;
}
-async function getModVersion(infos) {
- const { mod, version_number, game_version, platform, environment} = infos;
- const res = await 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) {
@@ -67,46 +59,9 @@ async function createMod(mod_data, author) {
await model.createMod(name, display_name, author, description, mod_infos);
// Return
- return getModByName(name);
+ return await 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
@@ -114,9 +69,6 @@ async function updateMod(diff_data) {
}
-
-// Delete
-
async function deleteMod(name) {
// Check existence
@@ -138,22 +90,102 @@ async function deleteMod(name) {
return mod;
}
-async function deleteVersion(version_infos) {
+
+
+// --- Versions ---
+
+
+async function createModVersion(mod_name, version_data) {
+
+ // Validate
+ //TODO
+ console.warn("Skipping validity checks for createModVersion");
+
+ // Generate data
+ const { 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 = Date.now();
+
+ // Write changes
+ await model.createModVersion(mod_name, {version_number, channel, changelog,
+ release_date, game_version, platform, environment, url}); // Database
+
+ // Return
+ return await model.getStrictModVersion(mod_name, version_number, game_version, platform, environment );
+}
+
+
+async function modifyModVersion(infos) {
+ throw new AppError(501, "Not implemented");
+}
+
+
+async function getModVersions(mod_name, filters) {
+
+ // Validation
+ //TODO
+ console.warn("Skipping getModVersions validation (Not implemented)");
+
+ filters.version_number = filters.version_number || null;
+ filters.channel = filters.channel || null;
+ filters.game_version = filters.game_version || null;
+ filters.platform = filters.platform || null;
+ filters.environment = filters.environment || null;
+
+ // Query
+ const res= await model.getModVersions(mod_name, {...filters});
+ return res;
+}
+
+// async function getModVersion(version_infos) {
+// const { mod, version_number, game_version, platform, environment} = version_infos;
+// const res = await 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];
+// }
+
+
+async function deleteModVersion(mod_name, version_infos) {
// Validate
// TODO
// Generate data
- const res = await getModVersion(version_infos);
- const { mod, version_number, game_version, platform, environment} = version_infos;
+ const { version_number, game_version, platform, environment } = version_infos;
+ const res = await model.getStrictModVersion(mod_name, version_number, game_version, platform, environment );
// Write changes to db
- await model.deleteVersion(mod, version_number, game_version, platform, environment);
+ await model.deleteModVersion(mod_name, version_number, game_version, platform, environment);
// Return
return res;
}
+
+// --- Tags ---
+
+
+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};
+
+}
+
+
async function deleteTags(mod, tags) {
// Validate (check existence)
@@ -168,7 +200,10 @@ async function deleteTags(mod, tags) {
return { "mod": mod, "tags": res};
}
+
+
+
module.exports = { listMods, getModByName, getFullModInfos,
- createMod, addTags, addVersion,
- updateMod,
- deleteMod, deleteTags, deleteVersion };
\ No newline at end of file
+ createMod, updateMod, deleteMod,
+ createModVersion, modifyModVersion, getModVersions, deleteModVersion,
+ addTags, deleteTags };
\ No newline at end of file
diff --git a/frontend/src/components/Cards/small_card.jsx b/frontend/src/components/Cards/small_card.jsx
new file mode 100644
index 0000000..539c9f2
--- /dev/null
+++ b/frontend/src/components/Cards/small_card.jsx
@@ -0,0 +1,34 @@
+// Preact
+import { h } from 'preact' // Necessary ?
+
+// Styles
+import styles from './small_card.module.css'
+
+// Images
+import Thumbnail from '../../assets/mod.svg'
+import Banner from '../../assets/example.jpg'
+
+function SmallCard({item, variant, href}) {
+ console.debug(variant, item);
+
+ if (variant === 'empty') {
+ return (
+
+ {item}
+
+ );
+ } else if (variant === 'mod') {
+
+ const item_page = "/mods/" + item.name;
+ return (
+
+
+ {item.display_name}
+
+
+ );
+ }
+
+}
+
+export default SmallCard;
\ No newline at end of file
diff --git a/frontend/src/components/Cards/small_card.module.css b/frontend/src/components/Cards/small_card.module.css
new file mode 100644
index 0000000..e3cdbef
--- /dev/null
+++ b/frontend/src/components/Cards/small_card.module.css
@@ -0,0 +1,51 @@
+.emptyCard {
+ height: 100%;
+ width: 22em;
+ align-items: center;
+ display: flex;
+
+ background-color: #141414;
+ border: #3a3a3a .1rem solid;
+ border-radius: 1rem;
+
+ box-sizing: border-box;
+}
+
+.emptyCardText {
+
+ padding: 1em;
+ width: 100%;
+
+ color: #9a9a9a;
+
+ text-align: center;
+ vertical-align: middle;
+
+ user-select: none;
+}
+
+.Card {
+ height: 100%;
+ width: 22em;
+ align-items: center;
+ display: flex;
+
+ background-color: #141414;
+ border: #3a3a3a .1rem solid;
+ border-radius: 1rem;
+
+ box-sizing: border-box;
+}
+
+.CardText {
+
+ padding: 1em;
+ width: 100%;
+
+ color: #9a9a9a;
+
+ text-align: center;
+ vertical-align: middle;
+
+ user-select: none;
+}
\ No newline at end of file
diff --git a/frontend/src/components/NavBar/navbar.module.css b/frontend/src/components/NavBar/navbar.module.css
index d28bfae..b6e6e0a 100644
--- a/frontend/src/components/NavBar/navbar.module.css
+++ b/frontend/src/components/NavBar/navbar.module.css
@@ -6,7 +6,7 @@
margin: 1rem;
height: 5rem;
- min-width: 29rem; /* TODO update when changing navbar items (not automatic)*/
+ min-width: fit-content;
background-color: #1a1a1a;
color: #eaeaea;
diff --git a/frontend/src/pages/dashboard.jsx b/frontend/src/pages/dashboard.jsx
index 82e9b7e..18b04f7 100644
--- a/frontend/src/pages/dashboard.jsx
+++ b/frontend/src/pages/dashboard.jsx
@@ -9,6 +9,7 @@ import { listMods } from '../services/mods';
// Components
import Button from '../components/Buttons/button'
+import SmallCard from '../components/Cards/small_card';
// Images
import Logo from '../assets/logo.png'
@@ -18,6 +19,7 @@ import Add from '../assets/add.svg'
import styles from '../styles/dashboard.module.css'
function DashboardPage() {
+ //TODO takes too long to load
// useStates
@@ -117,11 +119,10 @@ function DashboardPage() {
Favorites
-
-
- You have no favorites for the moment
-
-
+
@@ -132,19 +133,20 @@ function DashboardPage() {
{/* Temporary, missing card component */}
{creations.map( (item) => {
- console.debug(item.name);
- return (
-
- {item.display_name}
-
-
);
+ return (
+
+ );
})}
-
-
-
-
-
+
}
+ href='/create/mod'
+ />
diff --git a/frontend/src/styles/dashboard.module.css b/frontend/src/styles/dashboard.module.css
index f19540c..a408b5d 100644
--- a/frontend/src/styles/dashboard.module.css
+++ b/frontend/src/styles/dashboard.module.css
@@ -47,7 +47,7 @@
}
.emptyTile {
- height: 99%;
+ height: 100%;
width: 22em;
align-items: center;
display: flex;
@@ -55,6 +55,8 @@
background-color: #141414;
border: #3a3a3a .1rem solid;
border-radius: 1rem;
+
+ box-sizing: border-box;
}
.emptyTileText {