Added background animation for homepage and similar other pages, started making body validation schemas on backend and removed delay when searching

This commit is contained in:
Gu://em_ 2025-05-25 23:21:15 +02:00
parent ca96f57fb8
commit 6d48a10c1d
7 changed files with 122 additions and 17 deletions

View file

@ -14,6 +14,7 @@
"packageManager": "pnpm@10.10.0", "packageManager": "pnpm@10.10.0",
"dependencies": { "dependencies": {
"ajv": "^8.17.1", "ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"better-sqlite3": "^11.10.0", "better-sqlite3": "^11.10.0",
"cors": "^2.8.5", "cors": "^2.8.5",

View file

@ -11,6 +11,9 @@ importers:
ajv: ajv:
specifier: ^8.17.1 specifier: ^8.17.1
version: 8.17.1 version: 8.17.1
ajv-formats:
specifier: ^3.0.1
version: 3.0.1(ajv@8.17.1)
bcrypt: bcrypt:
specifier: ^5.1.1 specifier: ^5.1.1
version: 5.1.1 version: 5.1.1
@ -98,6 +101,14 @@ packages:
resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
ajv-formats@3.0.1:
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
peerDependencies:
ajv: ^8.0.0
peerDependenciesMeta:
ajv:
optional: true
ajv@8.17.1: ajv@8.17.1:
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
@ -1000,6 +1011,10 @@ snapshots:
agent-base@7.1.3: {} agent-base@7.1.3: {}
ajv-formats@3.0.1(ajv@8.17.1):
optionalDependencies:
ajv: 8.17.1
ajv@8.17.1: ajv@8.17.1:
dependencies: dependencies:
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3

View file

@ -10,7 +10,7 @@ async function getAllUsers() {
} }
async function getUserByName(name) { async function getUserByName(name) {
return await db.query("SELECT username, display_name, profile_picture, role FROM Users WHERE username = ?;", [name]); return await db.query("SELECT username, display_name, email, profile_picture, role FROM Users WHERE username = ?;", [name]);
} }
async function getUserByEmail(email) { async function getUserByEmail(email) {
@ -25,10 +25,15 @@ async function getUserPassword(name) {
return await db.query("SELECT username, password FROM Users WHERE username = ?;", [name]); return await db.query("SELECT username, password FROM Users WHERE username = ?;", [name]);
} }
//TODO test
async function exists(name) { async function exists(name) {
return await db.exists("Users", "username", name); return await db.exists("Users", "username", name);
} }
async function existsEmail(email) {
return await db.exists("Users", "email", email);
}
// --- Create --- // --- Create ---
@ -122,4 +127,4 @@ module.exports = { getAllUsers, getUserByName, getUserByEmail, getFullUserInfos,
createUser, addFavoriteMods, createUser, addFavoriteMods,
updateUser, updateUser,
deleteUser, deleteFavoriteMods, deleteUser, deleteFavoriteMods,
exists } exists, existsEmail }

View file

@ -1,5 +1,6 @@
const Ajv = require("ajv"); const Ajv = require("ajv");
const ajv = new Ajv(); const addFormats = require("ajv-formats")
const ajv = new Ajv({formats: {email: true}});
// --- Schemas --- // --- Schemas ---
//TODO //TODO
@ -7,14 +8,17 @@ const ajv = new Ajv();
const newUserSchema = { const newUserSchema = {
type: 'object', type: 'object',
properties: { properties: {
email: { type: 'string', format: 'email' }, username: { type: 'string' }, //TODO
name: { type: 'string' }, display_name: { type: 'string'},
password: { type: 'string', minLength: 3, maxLength: 30 }, email: { type: 'string', format: 'email' }, //TODO
password: { type: 'string', minLength: 6, maxLength: 255 },
profile_picture: {type: 'string'} //TODO
}, },
required: ['name', 'email', 'password'], required: ['name', 'email', 'password'],
additionalProperties: false additionalProperties: false
}; };
addFormats(ajv, ['email']);
const validateNewUserData = ajv.compile(newUserSchema); const validateNewUserData = ajv.compile(newUserSchema);

View file

@ -1,7 +1,7 @@
const model = require("../models/user"); const model = require("../models/user");
const AppError = require("../utils/appError"); const AppError = require("../utils/appError");
const cryptoUtils = require("../utils/crypto"); const cryptoUtils = require("../utils/crypto");
const { validateUserData } = require("../utils/validate_legacy"); const { validateNewUserData} = require("../schemas/user");
const { sanitizeUserData } = require("../utils/sanitize"); const { sanitizeUserData } = require("../utils/sanitize");
async function getAllUsers() { async function getAllUsers() {
@ -10,22 +10,43 @@ async function getAllUsers() {
async function getUserByName(name) { async function getUserByName(name) {
const res = await model.getUserByName(name); const res = await model.getUserByName(name);
if (res.length == 0) {
throw new AppError(404, "No user with this name", "Not found");
}
return res[0]; return res[0];
} }
async function createUser(user_data) { async function createUser(user_data) {
// Check body validity // Check body validity
// TODO try {
validateNewUserData(user_data);
} catch (err) {
throw new AppError(400, "Invalid fields", "Bad request");
}
// Sanitize // Sanitize
// TODO // TODO
// Gather data // Generate data
const { username, email, password, display_name, profile_picture, settings } = user_data
const password_hash = await cryptoUtils.hashPassword(password);
await model.createUser(username, email, password_hash, display_name, null, null); const { username, email, password, display_name, profile_picture, settings } = user_data
const password_hash = cryptoUtils.hashPassword(password);
if (await model.exists(username) ) {
throw new AppError(403, "A user with this username already exists", "Already exists");
}
if (await model.existsEmail(email)) {
throw new AppError(403, "A user with this email already exists", "Already exists");
}
if (!display_name) {
display_name = username;
}
// Write changes
await model.createUser(username, email, await password_hash, display_name, null, null);
return model.getUserByName(username); return model.getUserByName(username);
} }

View file

@ -9,10 +9,11 @@ import SearchIcon from '../../assets/search.svg'
import styles from './search.module.css'; // Optional: CSS Modules import styles from './search.module.css'; // Optional: CSS Modules
function SearchBar({ onSearch }) { function SearchBar({ onSearch, timeout }) {
const [search_input, setSearchInput] = useState(''); const [search_input, setSearchInput] = useState('');
const timeout_id = useRef(null); const timeout_id = useRef(null);
timeout = timeout | 0;
const handleInputChange = (event) => { const handleInputChange = (event) => {
@ -25,7 +26,7 @@ function SearchBar({ onSearch }) {
timeout_id.current = setTimeout(() => { timeout_id.current = setTimeout(() => {
onSearch(new_search_input); onSearch(new_search_input);
}, 500); }, timeout);
}; };
useEffect(() => { useEffect(() => {

View file

@ -1,10 +1,35 @@
.halo { .halo {
position: fixed; /* position: fixed; */
position: relative;
z-index: -1; z-index: -1;
height: 100vh; height: 100vh;
width: 100vw; width: 100vw;
background: radial-gradient(circle at 50% 200%,#353be5 0%, #353be5 45%, #00000000 80%); }
.halo::before,
.halo::after {
content: '';
position: absolute;
top: bottom;
left: 0;
width: 100%;
height: 100%;
background-size: 100% 100%;
background-repeat: no-repeat;
}
.halo::before {
background: radial-gradient(circle at 60% 200%,#353be5 0%, #353be5 45%, #00000000 85%);
opacity: 1;
animation: haloBlue 10s infinite alternate;
}
.halo::after {
background: radial-gradient(circle at 0% 210%,#353be5 10%, #4935e5 45%, #00000000 80%);
opacity: 0;
animation: haloPurple 7s infinite alternate;
} }
.background { .background {
@ -87,3 +112,36 @@
text-align: center; text-align: center;
} }
/* Animations ---------------- */
@keyframes haloBlue {
0% {
opacity: 0;
/* background-size: 50% 50%; */
}
50% {
opacity: 1;
/* background-size: 150% 150%; */
}
100% {
opacity: 0;
/* background-size: 50% 50%; */
}
}
@keyframes haloPurple {
0% {
opacity: 1;
/* background-size: 100% 50%; */
}
50% {
opacity: 0;
/* background-size: 100% 100%; */
}
100% {
opacity: 1;
/* background-size: 100% 50%; */
}
}