diff --git a/backend/src/controllers/mods.js b/backend/src/controllers/mods.js index f397183..9d2de1d 100644 --- a/backend/src/controllers/mods.js +++ b/backend/src/controllers/mods.js @@ -5,7 +5,8 @@ const { authorizeModModification, authenticateToken } = require("../middleware/a async function listMods(req, res) { try { // Query - const query_result = await mod_service.getAllMods(); + const filters = req.query; + const query_result = await mod_service.listMods(filters); res.json(query_result); } catch (error) { handleError(error, res); diff --git a/backend/src/database/sqlite.js b/backend/src/database/sqlite.js index b299c4d..855d9a2 100644 --- a/backend/src/database/sqlite.js +++ b/backend/src/database/sqlite.js @@ -28,7 +28,7 @@ class SQLiteDatabase { // Runs with a result (ex: SELECT) async query(sql, params = []) { try { - if (params.length > 0) { + if ( params != []) { return this.db.prepare(sql).all(params); } else { return this.db.prepare(sql).all(); diff --git a/backend/src/models/mod.js b/backend/src/models/mod.js index e5a8905..598b265 100644 --- a/backend/src/models/mod.js +++ b/backend/src/models/mod.js @@ -5,12 +5,21 @@ const db = getDatabase(); // --- Get --- -async function getAllMods() { - return await db.query("SELECT name, display_name, author, description FROM Mods"); +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 + name LIKE '%' || @search || '%' OR + display_name LIKE '%' || @search || '%' OR + description LIKE '%' || @search || '%' + ELSE TRUE END) AND + (CASE WHEN @author IS NOT NULL THEN author = @author ELSE TRUE END); + `, filters); } async function getModByName(name) { - return await db.query("SELECT name, display_name, author FROM Mods WHERE name = ?;", [name]); + return await db.query("SELECT name, display_name, author FROM Mods WHERE name = @name;", {name: name}); } @@ -185,7 +194,7 @@ async function containsTag(name, tag) { // --- Exports --- -module.exports = { getAllMods, getModByName, getFullModInfos, +module.exports = { listMods, getModByName, getFullModInfos, listVersions, listTags, getVersionByNumber, getVersion, createMod, addVersion, addTags, updateMod, diff --git a/backend/src/services/modService.js b/backend/src/services/modService.js index 758db49..e26fdc4 100644 --- a/backend/src/services/modService.js +++ b/backend/src/services/modService.js @@ -7,8 +7,14 @@ const { sanitizeModData } = require("../utils/sanitize"); // --- Get --- -async function getAllMods() { - return model.getAllMods(); +async function listMods(filters) { + //TODO Validate filters + console.warn("Skipping full filters validation: Not implemented"); + + filters.author = filters.author || null; + filters.search = filters.search || null; + + return await model.listMods({...filters}); } async function getModByName(name) { @@ -162,7 +168,7 @@ async function deleteTags(mod, tags) { return { "mod": mod, "tags": res}; } -module.exports = { getAllMods, getModByName, getFullModInfos, +module.exports = { listMods, getModByName, getFullModInfos, createMod, addTags, addVersion, updateMod, deleteMod, deleteTags, deleteVersion }; \ No newline at end of file diff --git a/frontend/src/components/Fields/search.jsx b/frontend/src/components/Fields/search.jsx index f50ee8f..8ea3e6b 100644 --- a/frontend/src/components/Fields/search.jsx +++ b/frontend/src/components/Fields/search.jsx @@ -17,14 +17,14 @@ function SearchBar({ onSearch }) { const handleInputChange = (event) => { const new_search_input = event.target.value; - setSearchTerm(new_search_input); + setSearchInput(new_search_input); if (timeout_id.current) { clearTimeout(timeout_id.current); } timeout_id.current = setTimeout(() => { - onSearch(newSearchTerm); + onSearch(new_search_input); }, 500); }; @@ -43,7 +43,7 @@ function SearchBar({ onSearch }) { type="text" placeholder="Search" value={search_input} - onChange={handleInputChange} + onInput={handleInputChange} className={styles.searchBarInput} /> diff --git a/frontend/src/components/Title/title.jsx b/frontend/src/components/Title/title.jsx new file mode 100644 index 0000000..ea64188 --- /dev/null +++ b/frontend/src/components/Title/title.jsx @@ -0,0 +1 @@ +//TODO \ No newline at end of file diff --git a/frontend/src/components/Title/title.module.css b/frontend/src/components/Title/title.module.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/pages/dashboard.jsx b/frontend/src/pages/dashboard.jsx index dad1675..82e9b7e 100644 --- a/frontend/src/pages/dashboard.jsx +++ b/frontend/src/pages/dashboard.jsx @@ -1,5 +1,11 @@ +// Preact import { h } from 'preact'; -import { useState } from 'preact/hooks'; +import { useState, useEffect } from 'preact/hooks'; +import Cookies from 'js-cookie' +import { jwtDecode } from 'jwt-decode'; + +// Functions +import { listMods } from '../services/mods'; // Components import Button from '../components/Buttons/button' @@ -13,17 +19,98 @@ import styles from '../styles/dashboard.module.css' function DashboardPage() { + // useStates + + const [creations, setCreations] = useState([]); + const [loading_creations, setLoadingCreations] = useState(true) + const [creations_error, setCreationsError] = useState(null) + + + // Functions + + let user; + async function loadUserInformations() { + //TODO use a service + const token = Cookies.get('authToken'); + if (token) { + const decoded_token = jwtDecode(token); + if (decoded_token) { + user = decoded_token.username + } + } + + if (!user) { + console.error("Cannot retrieve user from token"); + location.replace('/login') + } + } + + async function loadCreations() { + + setLoadingCreations(true); + setCreationsError(false); + + try { + const fetched_items = await listMods({author: user}); + setCreations(fetched_items); + } catch (err) { + setCreationsError(err.message); + } finally { + setLoadingCreations(false); + } + } + + + // useEffects + useEffect(async ()=> { + await loadUserInformations(); + await loadCreations(); + }, []); + + + // Handles + const handleCreate = () => { window.location.href('/create/mod'); } + + // Page + + // Base page + const base_page = ( + + WF +

dashboard

+
+ ); + + if (loading_creations) { + // TODO replace by icons + return ( + <> + {base_page} +
+

Loading

+
+ + ); + } + if (creations_error) { + // TODO replace by popup + return ( + <> + {base_page} +
+

Error: {creations_error}

+
+ + ); + } + return ( <> - - WF -

dashboard

-
- + {base_page}

@@ -42,6 +129,17 @@ function DashboardPage() { Your creations

+ + {/* Temporary, missing card component */} + {creations.map( (item) => { + console.debug(item.name); + return (
+
+ {item.display_name} +
+
); + })} +

diff --git a/frontend/src/pages/mod_page.jsx b/frontend/src/pages/mod_page.jsx index 4e2320f..47cc66a 100644 --- a/frontend/src/pages/mod_page.jsx +++ b/frontend/src/pages/mod_page.jsx @@ -60,7 +60,6 @@ function ModPage({name}) { if (token) { const decoded_token = jwtDecode(token); if (decoded_token) { - console.debug('Here'); if (decoded_token.username === mod.author) { setOwner(true); } diff --git a/frontend/src/pages/mods.jsx b/frontend/src/pages/mods.jsx index bcb00f6..9ce6ce6 100644 --- a/frontend/src/pages/mods.jsx +++ b/frontend/src/pages/mods.jsx @@ -31,30 +31,32 @@ function ModsPage() { setSearchInput(new_search_input); }; - // UseEffect - useEffect(() => { - async function loadItems() { - - setLoading(true); - setError(false); - - try { - const filters = { - search: search_input, - categories: selected_categories - }; - const fetched_mods = await listMods(filters); - setMods(fetched_mods); - } catch (err) { - setError(err.message); - } finally { - setLoading(false); - } + async function loadItems() { + + setLoading(true); + setError(false); + + try { + const filters = { + search: search_input, + }; + console.debug("Searching", search_input); + const fetched_mods = await listMods(filters); + setMods(fetched_mods); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); } + } + + // useEffect + useEffect(() => { loadItems(); - }, []); // <-- Tells useEffect to run once after render + }, [search_input]); + // Base page const base_page = ( diff --git a/frontend/src/services/mods.js b/frontend/src/services/mods.js index a3cea46..7bf810b 100644 --- a/frontend/src/services/mods.js +++ b/frontend/src/services/mods.js @@ -32,31 +32,48 @@ export async function createMod(mod_data) { export async function listMods(filters) { - try { - const response = await fetch(`${API_BASE_URL}/list/mods`); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - return data; + try { + let url_parameters = ""; + + // Parse filters + for (const [key, value] of Object.entries(filters)) { + if (url_parameters === "") { + url_parameters += "?" + } else { + url_parameters += "&" + } + + url_parameters += `${key}=${value}`; + } + + // Query + const response = await fetch(`${API_BASE_URL}/list/mods/${url_parameters}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + // Return + const data = await response.json(); + return data; + } catch (error) { - console.error('Failed to fetch mods:', error); - throw error; + console.error('Failed to fetch mods:', error); + throw error; } } export async function getMod(mod_name) { try { - const response = await fetch(`${API_BASE_URL}/mods/${mod_name}`); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - return data; + const response = await fetch(`${API_BASE_URL}/mods/${mod_name}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + return data; } catch (error) { - console.error('Failed to fetch mod:', error); - throw error; + console.error('Failed to fetch mod:', error); + throw error; } }