Functionnal basic versions implementation on the backend and made the emty small card component for frontend

This commit is contained in:
Gu://em_ 2025-05-18 15:46:13 +02:00
parent 683f8784a7
commit 45900e9033
9 changed files with 329 additions and 104 deletions

View file

@ -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 };
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 };

View file

@ -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 };
deleteMod, deleteModVersion, deleteTags,
exists, listAllModVersions };

View file

@ -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;

View file

@ -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 };
createMod, updateMod, deleteMod,
createModVersion, modifyModVersion, getModVersions, deleteModVersion,
addTags, deleteTags };

View file

@ -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 (
<a className={styles.emptyCard} href={href}>
<p className={styles.emptyCardText}>{item}</p>
</a>
);
} else if (variant === 'mod') {
const item_page = "/mods/" + item.name;
return (
<a className={styles.Card} href={href}>
<div className={styles.CardText}>
{item.display_name}
</div>
</a>
);
}
}
export default SmallCard;

View file

@ -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;
}

View file

@ -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;

View file

@ -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
</p>
<div className={styles.tiles}>
<div className={styles.emptyTile}>
<p className={styles.emptyTileText}>
You have no favorites for the moment
</p>
</div>
<SmallCard
variant='empty'
item={"You have no favorites for the moment"}
/>
</div>
</div>
<div className={styles.category}>
@ -132,19 +133,20 @@ function DashboardPage() {
{/* Temporary, missing card component */}
{creations.map( (item) => {
console.debug(item.name);
return (<div className={styles.emptyTile}>
<div className={styles.emptyTileText}>
{item.display_name}
</div>
</div>);
return (
<SmallCard
variant='mod'
href={'/mods/'+item.name}
item={item}
/>
);
})}
<a className={styles.emptyTile} href='/create/mod'>
<p className={styles.emptyTileText}>
<img src={Add} ></img>
</p>
</a>
<SmallCard
variant='empty'
item={<img src={Add} ></img>}
href='/create/mod'
/>
</div>
</div>
<div className={styles.toolbar}>

View file

@ -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 {