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 {