Backend and frontend filtering (search and author), dashboard now displays creations
This commit is contained in:
parent
58268a94a9
commit
683f8784a7
|
@ -5,7 +5,8 @@ const { authorizeModModification, authenticateToken } = require("../middleware/a
|
||||||
async function listMods(req, res) {
|
async function listMods(req, res) {
|
||||||
try {
|
try {
|
||||||
// Query
|
// Query
|
||||||
const query_result = await mod_service.getAllMods();
|
const filters = req.query;
|
||||||
|
const query_result = await mod_service.listMods(filters);
|
||||||
res.json(query_result);
|
res.json(query_result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, res);
|
handleError(error, res);
|
||||||
|
|
|
@ -28,7 +28,7 @@ class SQLiteDatabase {
|
||||||
// Runs with a result (ex: SELECT)
|
// Runs with a result (ex: SELECT)
|
||||||
async query(sql, params = []) {
|
async query(sql, params = []) {
|
||||||
try {
|
try {
|
||||||
if (params.length > 0) {
|
if ( params != []) {
|
||||||
return this.db.prepare(sql).all(params);
|
return this.db.prepare(sql).all(params);
|
||||||
} else {
|
} else {
|
||||||
return this.db.prepare(sql).all();
|
return this.db.prepare(sql).all();
|
||||||
|
|
|
@ -5,12 +5,21 @@ const db = getDatabase();
|
||||||
|
|
||||||
// --- Get ---
|
// --- Get ---
|
||||||
|
|
||||||
async function getAllMods() {
|
async function listMods(filters) {
|
||||||
return await db.query("SELECT name, display_name, author, description FROM Mods");
|
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) {
|
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 ---
|
// --- Exports ---
|
||||||
|
|
||||||
module.exports = { getAllMods, getModByName, getFullModInfos,
|
module.exports = { listMods, getModByName, getFullModInfos,
|
||||||
listVersions, listTags, getVersionByNumber, getVersion,
|
listVersions, listTags, getVersionByNumber, getVersion,
|
||||||
createMod, addVersion, addTags,
|
createMod, addVersion, addTags,
|
||||||
updateMod,
|
updateMod,
|
||||||
|
|
|
@ -7,8 +7,14 @@ const { sanitizeModData } = require("../utils/sanitize");
|
||||||
|
|
||||||
// --- Get ---
|
// --- Get ---
|
||||||
|
|
||||||
async function getAllMods() {
|
async function listMods(filters) {
|
||||||
return model.getAllMods();
|
//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) {
|
async function getModByName(name) {
|
||||||
|
@ -162,7 +168,7 @@ async function deleteTags(mod, tags) {
|
||||||
return { "mod": mod, "tags": res};
|
return { "mod": mod, "tags": res};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { getAllMods, getModByName, getFullModInfos,
|
module.exports = { listMods, getModByName, getFullModInfos,
|
||||||
createMod, addTags, addVersion,
|
createMod, addTags, addVersion,
|
||||||
updateMod,
|
updateMod,
|
||||||
deleteMod, deleteTags, deleteVersion };
|
deleteMod, deleteTags, deleteVersion };
|
|
@ -17,14 +17,14 @@ function SearchBar({ onSearch }) {
|
||||||
const handleInputChange = (event) => {
|
const handleInputChange = (event) => {
|
||||||
|
|
||||||
const new_search_input = event.target.value;
|
const new_search_input = event.target.value;
|
||||||
setSearchTerm(new_search_input);
|
setSearchInput(new_search_input);
|
||||||
|
|
||||||
if (timeout_id.current) {
|
if (timeout_id.current) {
|
||||||
clearTimeout(timeout_id.current);
|
clearTimeout(timeout_id.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
timeout_id.current = setTimeout(() => {
|
timeout_id.current = setTimeout(() => {
|
||||||
onSearch(newSearchTerm);
|
onSearch(new_search_input);
|
||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ function SearchBar({ onSearch }) {
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
value={search_input}
|
value={search_input}
|
||||||
onChange={handleInputChange}
|
onInput={handleInputChange}
|
||||||
className={styles.searchBarInput}
|
className={styles.searchBarInput}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
1
frontend/src/components/Title/title.jsx
Normal file
1
frontend/src/components/Title/title.jsx
Normal file
|
@ -0,0 +1 @@
|
||||||
|
//TODO
|
0
frontend/src/components/Title/title.module.css
Normal file
0
frontend/src/components/Title/title.module.css
Normal file
|
@ -1,5 +1,11 @@
|
||||||
|
// Preact
|
||||||
import { h } from '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
|
// Components
|
||||||
import Button from '../components/Buttons/button'
|
import Button from '../components/Buttons/button'
|
||||||
|
@ -13,17 +19,98 @@ import styles from '../styles/dashboard.module.css'
|
||||||
|
|
||||||
function DashboardPage() {
|
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 = () => {
|
const handleCreate = () => {
|
||||||
window.location.href('/create/mod');
|
window.location.href('/create/mod');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Page
|
||||||
|
|
||||||
|
// Base page
|
||||||
|
const base_page = (
|
||||||
|
<a href='/' target="_blank">
|
||||||
|
<img src={Logo} class="logoSmall img" alt="WF" />
|
||||||
|
<p class="logoSmall text"> dashboard </p>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (loading_creations) {
|
||||||
|
// TODO replace by icons
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{base_page}
|
||||||
|
<div class='container'>
|
||||||
|
<p>Loading</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (creations_error) {
|
||||||
|
// TODO replace by popup
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{base_page}
|
||||||
|
<div class='container'>
|
||||||
|
<p>Error: {creations_error}</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<a href='/' target="_blank">
|
{base_page}
|
||||||
<img src={Logo} class="logoSmall img" alt="WF" />
|
|
||||||
<p class="logoSmall text"> dashboard </p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class='container'>
|
<div class='container'>
|
||||||
<div className={styles.category}>
|
<div className={styles.category}>
|
||||||
<p className={styles.title}>
|
<p className={styles.title}>
|
||||||
|
@ -42,6 +129,17 @@ function DashboardPage() {
|
||||||
Your creations
|
Your creations
|
||||||
</p>
|
</p>
|
||||||
<div className={styles.tiles}>
|
<div className={styles.tiles}>
|
||||||
|
|
||||||
|
{/* 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>);
|
||||||
|
})}
|
||||||
|
|
||||||
<a className={styles.emptyTile} href='/create/mod'>
|
<a className={styles.emptyTile} href='/create/mod'>
|
||||||
<p className={styles.emptyTileText}>
|
<p className={styles.emptyTileText}>
|
||||||
<img src={Add} ></img>
|
<img src={Add} ></img>
|
||||||
|
|
|
@ -60,7 +60,6 @@ function ModPage({name}) {
|
||||||
if (token) {
|
if (token) {
|
||||||
const decoded_token = jwtDecode(token);
|
const decoded_token = jwtDecode(token);
|
||||||
if (decoded_token) {
|
if (decoded_token) {
|
||||||
console.debug('Here');
|
|
||||||
if (decoded_token.username === mod.author) {
|
if (decoded_token.username === mod.author) {
|
||||||
setOwner(true);
|
setOwner(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,30 +31,32 @@ function ModsPage() {
|
||||||
setSearchInput(new_search_input);
|
setSearchInput(new_search_input);
|
||||||
};
|
};
|
||||||
|
|
||||||
// UseEffect
|
|
||||||
useEffect(() => {
|
|
||||||
|
|
||||||
async function loadItems() {
|
async function loadItems() {
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(false);
|
setError(false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const filters = {
|
const filters = {
|
||||||
search: search_input,
|
search: search_input,
|
||||||
categories: selected_categories
|
};
|
||||||
};
|
console.debug("Searching", search_input);
|
||||||
const fetched_mods = await listMods(filters);
|
const fetched_mods = await listMods(filters);
|
||||||
setMods(fetched_mods);
|
setMods(fetched_mods);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message);
|
setError(err.message);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// useEffect
|
||||||
|
useEffect(() => {
|
||||||
loadItems();
|
loadItems();
|
||||||
}, []); // <-- Tells useEffect to run once after render
|
}, [search_input]);
|
||||||
|
|
||||||
|
|
||||||
// Base page
|
// Base page
|
||||||
const base_page = (
|
const base_page = (
|
||||||
|
|
|
@ -32,31 +32,48 @@ export async function createMod(mod_data) {
|
||||||
|
|
||||||
|
|
||||||
export async function listMods(filters) {
|
export async function listMods(filters) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/list/mods`);
|
let url_parameters = "";
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
// Parse filters
|
||||||
}
|
for (const [key, value] of Object.entries(filters)) {
|
||||||
const data = await response.json();
|
if (url_parameters === "") {
|
||||||
return data;
|
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) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch mods:', error);
|
console.error('Failed to fetch mods:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function getMod(mod_name) {
|
export async function getMod(mod_name) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/mods/${mod_name}`);
|
const response = await fetch(`${API_BASE_URL}/mods/${mod_name}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch mod:', error);
|
console.error('Failed to fetch mod:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue