refactor: streamline SVG handling and improve error messages in user and email controllers
This commit is contained in:
@ -1,35 +1,50 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watchEffect, computed} from 'vue';
|
import {ref, computed} from 'vue';
|
||||||
import vRecolorSvg from '@/directives/vRecolorSvg.ts';
|
import vRecolorSvg from '@/directives/vRecolorSvg.ts';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
svgUrl: string;
|
svg: string;
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
viewBox?: string;
|
viewBox?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const paths = ref<Array<{ clip: string; d: string; fill: "nonzero" | "evenodd" | "inherit" | undefined}>>([]);
|
const paths = ref<Array<{
|
||||||
|
'clip-rule': string | undefined;
|
||||||
|
d: string | undefined;
|
||||||
|
'fill-rule': "nonzero" | "evenodd" | "inherit" | undefined
|
||||||
|
}>>([]);
|
||||||
|
|
||||||
watchEffect(async () => {
|
const svgPaths = props.svg
|
||||||
if (!props.svgUrl) {
|
.replace(/%20/g, " ")
|
||||||
paths.value = [];
|
.replace(/%3c/g, "<")
|
||||||
return;
|
.replace(/%3e/g, ">")
|
||||||
|
.split('path')
|
||||||
|
.map(chunk => chunk.trim())
|
||||||
|
.filter((chunk) => !chunk.startsWith('data:image'))
|
||||||
|
.map((path) => path
|
||||||
|
.split('/>')[0]
|
||||||
|
.replace(/fill=.* /g, "")
|
||||||
|
.replace(/["']/g, ""));
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const path of svgPaths) {
|
||||||
|
const match = {
|
||||||
|
'clip-rule': /clip-rule=(\w+)/.exec(path),
|
||||||
|
'd': /d=(.*Z)/.exec(path),
|
||||||
|
'fill-rule': /fill-rule=(\w+)/.exec(path),
|
||||||
}
|
}
|
||||||
try {
|
paths.value.push({
|
||||||
const response = await fetch(props.svgUrl);
|
'clip-rule': match['clip-rule'] ? match['clip-rule'][1].toString() : undefined,
|
||||||
const svgText = await response.text();
|
'd': match['d'] ? match['d'][1].toString() : undefined,
|
||||||
const parser = new DOMParser();
|
'fill-rule': match['fill-rule'] ? match['fill-rule'][1].toString() as "nonzero" | "evenodd" | "inherit" : undefined,
|
||||||
const doc = parser.parseFromString(svgText, 'image/svg+xml');
|
});
|
||||||
paths.value = Array.from(doc.querySelectorAll('path')).map(path => ({
|
|
||||||
clip: path.getAttribute('clip-rule') || 'nonzero',
|
|
||||||
d: path.getAttribute('d') || '',
|
|
||||||
fill: (path.getAttribute('fill-rule') as "nonzero" | "evenodd" | "inherit" | undefined) || "inherit",
|
|
||||||
}));
|
|
||||||
} catch {
|
|
||||||
paths.value = [];
|
|
||||||
}
|
}
|
||||||
});
|
} catch (error) {
|
||||||
|
console.error('Error processing SVG paths:', error);
|
||||||
|
paths.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
const svgStyle = computed(() => ({
|
const svgStyle = computed(() => ({
|
||||||
width: props.width ? `${props.width}px` : '100%',
|
width: props.width ? `${props.width}px` : '100%',
|
||||||
@ -40,7 +55,8 @@ const svgStyle = computed(() => ({
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" :viewBox="svgStyle.viewBox" :width="svgStyle.width"
|
<svg xmlns="http://www.w3.org/2000/svg" :viewBox="svgStyle.viewBox" :width="svgStyle.width"
|
||||||
:height="svgStyle.height">
|
:height="svgStyle.height">
|
||||||
<path v-for="(path, index) in paths" :key="index" :clip-rule="path.clip" :d="path.d" :fill-rule="path.fill" v-recolor-svg/>
|
<path v-for="(path, index) in paths" :key="index" :clip-rule="path['clip-rule']" :d="path['d']"
|
||||||
|
:fill-rule="path['fill-rule']" v-recolor-svg/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -76,7 +76,7 @@ export function useReadableTextColor(bgColor: string): string {
|
|||||||
let whiteRatio = (whiteLighter + 0.05) / (whiteDarker + 0.05);
|
let whiteRatio = (whiteLighter + 0.05) / (whiteDarker + 0.05);
|
||||||
// Return black or white based on luminance
|
// Return black or white based on luminance
|
||||||
if (blackRatio >= 4.5 || whiteRatio >= 4.5) {
|
if (blackRatio >= 4.5 || whiteRatio >= 4.5) {
|
||||||
console.debug("bgL:", bgLuminance, "bL:", blackLuminance, "wL:", whiteLuminance, "bR:", blackRatio, "wR:", whiteRatio);
|
//console.debug("bgL:", bgLuminance, "bL:", blackLuminance, "wL:", whiteLuminance, "bR:", blackRatio, "wR:", whiteRatio);
|
||||||
return blackRatio > whiteRatio ? '#181818' : '#E7E7E7';
|
return blackRatio > whiteRatio ? '#181818' : '#E7E7E7';
|
||||||
}
|
}
|
||||||
// If contrast is not enough, use deep colors
|
// If contrast is not enough, use deep colors
|
||||||
@ -87,7 +87,7 @@ export function useReadableTextColor(bgColor: string): string {
|
|||||||
blackRatio = (blackLighter + 0.05) / (blackDarker + 0.05);
|
blackRatio = (blackLighter + 0.05) / (blackDarker + 0.05);
|
||||||
whiteRatio = (whiteLighter + 0.05) / (whiteDarker + 0.05);
|
whiteRatio = (whiteLighter + 0.05) / (whiteDarker + 0.05);
|
||||||
if (blackRatio >= 4.5 || whiteRatio >= 4.5) {
|
if (blackRatio >= 4.5 || whiteRatio >= 4.5) {
|
||||||
console.debug("bgL:", bgLuminance, "bL:", blackLuminanceDeep, "wL:", whiteLuminanceDeep, "bR:", blackRatio, "wR:", whiteRatio);
|
//console.debug("bgL:", bgLuminance, "bL:", blackLuminanceDeep, "wL:", whiteLuminanceDeep, "bR:", blackRatio, "wR:", whiteRatio);
|
||||||
return blackRatio > whiteRatio ? '#0B0B0B' : '#F4F4F4';
|
return blackRatio > whiteRatio ? '#0B0B0B' : '#F4F4F4';
|
||||||
}
|
}
|
||||||
// If still not enough, use absolute colors
|
// If still not enough, use absolute colors
|
||||||
@ -98,10 +98,10 @@ export function useReadableTextColor(bgColor: string): string {
|
|||||||
blackRatio = (blackLighter + 0.05) / (blackDarker + 0.05);
|
blackRatio = (blackLighter + 0.05) / (blackDarker + 0.05);
|
||||||
whiteRatio = (whiteLighter + 0.05) / (whiteDarker + 0.05);
|
whiteRatio = (whiteLighter + 0.05) / (whiteDarker + 0.05);
|
||||||
if (blackRatio >= 4.5 || whiteRatio >= 4.5) {
|
if (blackRatio >= 4.5 || whiteRatio >= 4.5) {
|
||||||
console.debug("bgL:", bgLuminance, "bL:", blackLuminanceAbsolute, "wL:", whiteLuminanceAbsolute, "bR:", blackRatio, "wR:", whiteRatio);
|
//console.debug("bgL:", bgLuminance, "bL:", blackLuminanceAbsolute, "wL:", whiteLuminanceAbsolute, "bR:", blackRatio, "wR:", whiteRatio);
|
||||||
return blackRatio > whiteRatio ? '#000000' : '#FFFFFF';
|
return blackRatio > whiteRatio ? '#000000' : '#FFFFFF';
|
||||||
}
|
}
|
||||||
console.warn(`Not enough contrast for background color: ${bgColor}. Using fallback colors.`);
|
//console.warn(`Not enough contrast for background color: ${bgColor}. Using fallback colors.`);
|
||||||
return bgLuminance > 0.5 ? blackTextColor : whiteTextColor; // Fallback to default colors
|
return bgLuminance > 0.5 ? blackTextColor : whiteTextColor; // Fallback to default colors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,6 +27,7 @@ export function useLogin() {
|
|||||||
async login(username: string, password: string): Promise<SecureUser> {
|
async login(username: string, password: string): Promise<SecureUser> {
|
||||||
return await api<SecureUser>("/users/login", {username, password}, "POST")
|
return await api<SecureUser>("/users/login", {username, password}, "POST")
|
||||||
.then((response: DataEnvelope<SecureUser> ) => {
|
.then((response: DataEnvelope<SecureUser> ) => {
|
||||||
|
if (!response.data) throw new Error("Invalid login credentials. Please try again.");
|
||||||
session.user = response.data;
|
session.user = response.data;
|
||||||
if (!session.user) throw new Error("Invalid login credentials. Please try again.");
|
if (!session.user) throw new Error("Invalid login credentials. Please try again.");
|
||||||
session.token = response.data.token || null;
|
session.token = response.data.token || null;
|
||||||
@ -37,7 +38,9 @@ export function useLogin() {
|
|||||||
localStorage.setItem("token", session.token ?? "");
|
localStorage.setItem("token", session.token ?? "");
|
||||||
return session.user;
|
return session.user;
|
||||||
})
|
})
|
||||||
.catch((err)=>{throw err}) as SecureUser;
|
.catch((envelope: DataEnvelope<any>)=>{
|
||||||
|
toast.error(envelope.message || envelope.error?.message || "An error occurred while trying to log in. Please try again later.")
|
||||||
|
}) as SecureUser;
|
||||||
},
|
},
|
||||||
async logout(): Promise<void> {
|
async logout(): Promise<void> {
|
||||||
session.user = null;
|
session.user = null;
|
||||||
|
|||||||
@ -2,16 +2,17 @@
|
|||||||
import {ref} from 'vue';
|
import {ref} from 'vue';
|
||||||
import {useLogin} from "@models/session.ts";
|
import {useLogin} from "@models/session.ts";
|
||||||
import {isMobile} from "@models/globals.ts";
|
import {isMobile} from "@models/globals.ts";
|
||||||
import vRecolorSvg from '@/directives/vRecolorSvg.ts';
|
import eye from '@/assets/svg/eye.svg';
|
||||||
|
import eyeSlash from '@/assets/svg/eye-slash.svg';
|
||||||
import BaseButton from "@components/baseComponents/BaseButton.vue";
|
import BaseButton from "@components/baseComponents/BaseButton.vue";
|
||||||
import BaseSVG from "@components/baseComponents/BaseSVG.vue";
|
import BaseSVG from "@components/baseComponents/BaseSVG.vue";
|
||||||
import BaseInput from "@components/baseComponents/BaseInput.vue";
|
import BaseInput from "@components/baseComponents/BaseInput.vue";
|
||||||
|
|
||||||
|
|
||||||
const username = ref('');
|
const username = ref('');
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
const {login} = useLogin();
|
const {login} = useLogin();
|
||||||
const showPassword = ref(false);
|
const showPassword = ref(false);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -34,12 +35,10 @@ const showPassword = ref(false);
|
|||||||
<button type="button" class="input-group-text border-start-0"
|
<button type="button" class="input-group-text border-start-0"
|
||||||
style="cursor:pointer;"
|
style="cursor:pointer;"
|
||||||
@click="showPassword = !showPassword">
|
@click="showPassword = !showPassword">
|
||||||
<BaseSVG svg-url="/src/assets/svg/eye.svg" :width="20" :height="20"
|
<BaseSVG :svg="eye" :width="20" :height="20"
|
||||||
view-box="0 0 28 28"
|
view-box="0 0 28 28" v-if="!showPassword"/>
|
||||||
v-recolor-svg v-if="!showPassword"/>
|
<BaseSVG :svg="eyeSlash" :width="20" :height="20"
|
||||||
<BaseSVG svg-url="/src/assets/svg/eye-slash.svg" :width="20" :height="20"
|
view-box="0 0 28 28" v-else/>
|
||||||
view-box="0 0 28 28"
|
|
||||||
v-recolor-svg v-else/>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,32 +1,20 @@
|
|||||||
// server/app.js
|
// server/app.js
|
||||||
|
console.log("Starting Server...");
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
// import multer from 'multer';
|
// import multer from 'multer';
|
||||||
// import path from 'path';
|
// import path from 'path';
|
||||||
import db from './models/db.js';
|
import db from './models/db.js';
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import emailController from "./controllers/emailController.js";
|
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
/**@typedef {import("../client/src/DBRecord.ts").DBRecord} DBRecord */
|
|
||||||
|
|
||||||
console.log("Starting Server...");
|
|
||||||
// test db connection
|
// test db connection
|
||||||
try {
|
try {
|
||||||
await db.query("SELECT 1")
|
await db.query("SELECT 1")
|
||||||
console.log("DB connection successful")
|
console.log("DB connection successful")
|
||||||
await db.get('users', {id: 1});
|
await db.get('users', {id: 1});
|
||||||
console.log("Users table exists");
|
console.log("Users table exists");
|
||||||
|
|
||||||
await emailController.withTimeout(emailController.transporter.verify(), 15000)
|
|
||||||
.then(() => {
|
|
||||||
console.log("Transporter is ready to send emails");
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("Error verifying transporter:", error);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
@ -52,11 +40,11 @@ const upload = multer({storage});*/
|
|||||||
|
|
||||||
// user routes
|
// user routes
|
||||||
import userRouter from './controllers/userController.js';
|
import userRouter from './controllers/userController.js';
|
||||||
|
|
||||||
app.use('/api/users', userRouter);
|
app.use('/api/users', userRouter);
|
||||||
|
|
||||||
// email routes
|
// email routes
|
||||||
app.use('/api/email', emailController.router);
|
import emailRouter from "./controllers/emailController.js";
|
||||||
|
app.use('/api/email', emailRouter);
|
||||||
|
|
||||||
// other routes
|
// other routes
|
||||||
|
|
||||||
|
|||||||
@ -144,9 +144,5 @@ async function withTimeout(promise, ms) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default router;
|
||||||
withTimeout,
|
|
||||||
transporter,
|
|
||||||
router
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|||||||
@ -41,12 +41,12 @@ router.get("/",
|
|||||||
*/
|
*/
|
||||||
async (req, res, next) => {
|
async (req, res, next) => {
|
||||||
if (!req.body || !req.body.username || !req.body.password) {
|
if (!req.body || !req.body.username || !req.body.password) {
|
||||||
return res.status(400).json({data: null, error: 'Username and password are required'});
|
return res.status(400).json({data: null, message: 'Username and password are required'});
|
||||||
}
|
}
|
||||||
await users.addNewUser(req.body.username, req.body.password)
|
await users.addNewUser(req.body.username, req.body.password)
|
||||||
.then((user) => {
|
.then((user) => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return res.status(500).json({data: null, error: 'User not found after insertion'});
|
return res.status(500).json({data: null, message: 'User not found after insertion'});
|
||||||
}
|
}
|
||||||
res.status(200).json({data: user, message: 'User added successfully'});
|
res.status(200).json({data: user, message: 'User added successfully'});
|
||||||
})
|
})
|
||||||
@ -64,21 +64,21 @@ router.get("/:identifier",
|
|||||||
*/
|
*/
|
||||||
async (req, res, next) => {
|
async (req, res, next) => {
|
||||||
const userIdentifier = req.params.identifier;
|
const userIdentifier = req.params.identifier;
|
||||||
if (!userIdentifier) return res.status(400).json({data: null, error: 'User identifier is required'});
|
if (!userIdentifier) return res.status(400).json({data: null, message: 'User identifier is required'});
|
||||||
await users.getUser(userIdentifier)
|
await users.getUser(userIdentifier)
|
||||||
.then(user => {
|
.then(user => {
|
||||||
if (!user) return res.status(404).json({data: null, error: 'User not found'});
|
if (!user) return res.status(404).json({data: null, message: 'User not found'});
|
||||||
res.status(200).json({data: user});
|
res.status(200).json({data: user});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
if (err.message === 'User not found') {
|
if (err.message === 'User not found') {
|
||||||
return res.status(404).json({data: null, error: 'User not found'});
|
return res.status(404).json({data: null, error: err});
|
||||||
}
|
}
|
||||||
if (err.message === 'Multiple users found with the same identifier, something has gone wrong') {
|
if (err.message === 'Multiple users found with the same identifier, something has gone wrong') {
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
data: null,
|
data: null,
|
||||||
error: 'Multiple users found with the same identifier, something has gone wrong'
|
error: err
|
||||||
});
|
});
|
||||||
} else next(err);
|
} else next(err);
|
||||||
} else {
|
} else {
|
||||||
@ -100,16 +100,16 @@ router.post("/login",
|
|||||||
const {username, password} = req.body;
|
const {username, password} = req.body;
|
||||||
if (!username || !password) return res.status(400).json({
|
if (!username || !password) return res.status(400).json({
|
||||||
data: null,
|
data: null,
|
||||||
error: 'Username and password are required'
|
message: 'Username and password are required'
|
||||||
});
|
});
|
||||||
await users.login(username, password).then(data => {
|
await users.login(username, password).then(data => {
|
||||||
if (!data) return res.status(401).json({data: null, error: 'Invalid username or password'});
|
if (!data) return res.status(401).json({data: null, message: 'Invalid username or password'});
|
||||||
res.status(200).json({data: data, message: 'Login successful'});
|
res.status(200).json({data: data, message: 'Login successful'});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
if (err.message === 'Invalid username or password') {
|
if (err.message === 'Invalid username or password') {
|
||||||
res.status(401).json({data: null, error: 'Invalid username or password'});
|
res.status(401).json({data: null, error: err});
|
||||||
} else next(err);
|
} else next(err);
|
||||||
} else {
|
} else {
|
||||||
next(new Error('An unhandled error occurred while login'));
|
next(new Error('An unhandled error occurred while login'));
|
||||||
@ -127,12 +127,10 @@ router.patch("/:identifier", authHandler.authenticateUser,
|
|||||||
*/
|
*/
|
||||||
async (req, res, next) => {
|
async (req, res, next) => {
|
||||||
const userIdentifier = req.params.id;
|
const userIdentifier = req.params.id;
|
||||||
if (!userIdentifier) {
|
if (!userIdentifier) return res.status(400).json({data: null, message: 'User Identifier is required'});
|
||||||
return res.status(400).json({data: null, error: 'User Identifier is required'});
|
|
||||||
}
|
|
||||||
await users.updateUser(req.body)
|
await users.updateUser(req.body)
|
||||||
.then(user => {
|
.then(user => {
|
||||||
if (!user) res.status(404).json({data: null, error: 'User not found'});
|
if (!user) res.status(404).json({data: null, message: 'User not found'});
|
||||||
else res.status(200).json({data: user, message: 'User updated successfully'});
|
else res.status(200).json({data: user, message: 'User updated successfully'});
|
||||||
})
|
})
|
||||||
.catch((err) => next(err))
|
.catch((err) => next(err))
|
||||||
@ -150,10 +148,10 @@ router.delete("/:identifier", authHandler.authenticateUser,
|
|||||||
*/
|
*/
|
||||||
async (req, res, next) => {
|
async (req, res, next) => {
|
||||||
const userIdentifier = req.params.identifier;
|
const userIdentifier = req.params.identifier;
|
||||||
if (!userIdentifier) return res.status(400).json({data: null, error: 'User identifier is required'});
|
if (!userIdentifier) return res.status(400).json({data: null, message: 'User identifier is required'});
|
||||||
await users.deleteUser(userIdentifier)
|
await users.deleteUser(userIdentifier)
|
||||||
.then(user => {
|
.then(user => {
|
||||||
if (!user) return res.status(404).json({data: null, error: 'User not found'});
|
if (!user) return res.status(404).json({data: null, message: 'User not found'});
|
||||||
res.status(200).json({data: user, message: 'User deleted successfully'});
|
res.status(200).json({data: user, message: 'User deleted successfully'});
|
||||||
})
|
})
|
||||||
.catch((err) => next(err));
|
.catch((err) => next(err));
|
||||||
|
|||||||
Reference in New Issue
Block a user