diff --git a/frontend/src/lib/utils/helpers.js b/frontend/src/lib/utils/helpers.js index e34f1d3..9e0e4e1 100644 --- a/frontend/src/lib/utils/helpers.js +++ b/frontend/src/lib/utils/helpers.js @@ -69,6 +69,81 @@ export function isEmpty(obj) { return true; } +export function toRFC3339(dateString) { + if (!dateString) dateString = "0001-01-01T00:00:00.000Z"; + const date = new Date(dateString); + return date.toISOString(); +} + +export function fromRFC3339(dateString) { + if (!dateString) dateString = "0001-01-01T00:00:00.000Z"; + const date = new Date(dateString); + return date.toISOString().split("T")[0]; +} + +/** + * + * @param {App.Locals.User} user - The user object to format + */ +export function userDatesFromRFC3339(user) { + if (user.date_of_birth) { + user.date_of_birth = fromRFC3339(user.date_of_birth); + } + if (user.membership) { + if (user.membership.start_date) { + user.membership.start_date = fromRFC3339(user.membership.start_date); + } + if (user.membership.end_date) { + user.membership.end_date = fromRFC3339(user.membership.end_date); + } + } + if (user.licence?.issued_date) { + user.licence.issued_date = fromRFC3339(user.licence.issued_date); + } + if (user.licence?.expiration_date) { + user.licence.expiration_date = fromRFC3339(user.licence.expiration_date); + } + if (user.bank_account && user.bank_account.mandate_date_signed) { + user.bank_account.mandate_date_signed = fromRFC3339( + user.bank_account.mandate_date_signed + ); + } +} + +/** + * Formats dates in the user object to RFC3339 format + * @param {App.Locals.User} user - The user object to format + */ +export function userDatesToRFC3339(user) { + if (user.date_of_birth) { + user.date_of_birth = toRFC3339(user.date_of_birth); + } + if (user.membership) { + if (user.membership.start_date) { + user.membership.start_date = toRFC3339(user.membership.start_date); + } + if (user.membership.end_date) { + user.membership.end_date = toRFC3339(user.membership.end_date); + } + } + if (user.licence?.issued_date) { + user.licence.issued_date = toRFC3339(user.licence.issued_date); + } + if (user.licence?.expiration_date) { + user.licence.expiration_date = toRFC3339(user.licence.expiration_date); + } + if (user.bank_account && user.bank_account.mandate_date_signed) { + user.bank_account.mandate_date_signed = toRFC3339( + user.bank_account.mandate_date_signed + ); + } +} + +/** + * + * @param {object} obj - The error object to format + * @returns {array} The formatted error object + */ export function formatError(obj) { const errors = []; if (typeof obj === "object" && obj !== null) { @@ -98,3 +173,33 @@ export function formatError(obj) { } return errors; } + +/** + * + * @param {string | null} newToken - The new token for the cookie to set + * @param {import('RequestEvent>, string | null>')} event - The event object + */ +export function refreshCookie(newToken, event) { + if (newToken) { + const match = newToken.match(/jwt=([^;]+)/); + if (match) { + if (event) { + event.cookies.set("jwt", match[1], { + path: "/", + httpOnly: true, + secure: process.env.NODE_ENV === "production", // Secure in production + sameSite: "lax", + maxAge: 5 * 24 * 60 * 60, // 5 days in seconds + }); + } else { + cookies.set("jwt", match[1], { + path: "/", + httpOnly: true, + secure: process.env.NODE_ENV === "production", // Secure in production + sameSite: "lax", + maxAge: 5 * 24 * 60 * 60, // 5 days in seconds + }); + } + } + } +} diff --git a/frontend/src/lib/utils/utils.js b/frontend/src/lib/utils/utils.js deleted file mode 100644 index 408525f..0000000 --- a/frontend/src/lib/utils/utils.js +++ /dev/null @@ -1,118 +0,0 @@ -// @ts-nocheck -import { quintOut } from "svelte/easing"; -import { crossfade } from "svelte/transition"; - -export const [send, receive] = crossfade({ - duration: (d) => Math.sqrt(d * 200), - - // eslint-disable-next-line no-unused-vars - fallback(node, params) { - const style = getComputedStyle(node); - const transform = style.transform === "none" ? "" : style.transform; - - return { - duration: 600, - easing: quintOut, - css: (t) => ` - transform: ${transform} scale(${t}); - opacity: ${t} - `, - }; - }, -}); - -/** - * Validates an email field - * @file lib/utils/helpers/input.validation.ts - * @param {string} email - The email to validate - */ -export const isValidEmail = (email) => { - const EMAIL_REGEX = - /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/; - return EMAIL_REGEX.test(email.trim()); -}; -/** - * Validates a strong password field - * @file lib/utils/helpers/input.validation.ts - * @param {string} password - The password to validate - */ -export const isValidPasswordStrong = (password) => { - const strongRegex = new RegExp( - "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})" - ); - - return strongRegex.test(password.trim()); -}; -/** - * Validates a medium password field - * @file lib/utils/helpers/input.validation.ts - * @param {string} password - The password to validate - */ -export const isValidPasswordMedium = (password) => { - const mediumRegex = new RegExp( - "^(((?=.*[a-z])(?=.*[A-Z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[A-Z])(?=.*[0-9])))(?=.{6,})" - ); - - return mediumRegex.test(password.trim()); -}; - -/** - * Test whether or not an object is empty. - * @param {Record} obj - The object to test - * @returns `true` or `false` - */ - -export function isEmpty(obj) { - for (const _i in obj) { - return false; - } - return true; -} - -/** - * @typedef {Object} FormattedError - * @property {string} error - The error message - * @property {number} id - A unique identifier for the error - */ -/** - * Format Error object(s) - * @param {any} obj - The object to test - * @returns @type {FormattedError[]} - */ - -export function formatError(obj) { - /** @type {FormattedError[]} */ - const errors = []; - if (typeof obj === "object" && obj !== null) { - if (Array.isArray(obj)) { - obj.forEach((/** @type {Object} */ error) => { - Object.keys(error).map((k) => { - errors.push({ - error: error[k], - id: Math.random() * 1000, - }); - }); - }); - } else { - Object.keys(obj).map((k) => { - errors.push({ - error: obj[k], - id: Math.random() * 1000, - }); - }); - } - } else { - errors.push({ - error: obj.charAt(0).toUpperCase() + obj.slice(1), - id: 0, - }); - } - - return errors; -} - -export function toRFC3339(dateString) { - if (!dateString) dateString = "0001-01-01T00:00:00.000Z"; - const date = new Date(dateString); - return date.toISOString(); -} diff --git a/frontend/src/routes/+layout.server.js b/frontend/src/routes/+layout.server.js index d76e6b4..6cf562a 100644 --- a/frontend/src/routes/+layout.server.js +++ b/frontend/src/routes/+layout.server.js @@ -1,6 +1,58 @@ +import { BASE_API_URI } from "$lib/utils/constants"; +import { refreshCookie } from "$lib/utils/helpers"; +import { redirect } from "@sveltejs/kit"; +import { library } from "$lib/stores/library"; /** @type {import('./$types').LayoutServerLoad} */ -export async function load({ locals }) { +export async function load({ locals, cookies }) { + const jwt = cookies.get("jwt"); + try { + // Fetch subscriptions, and licence categories in parallel + const [subscriptionsResponse, licenceCategoriesResponse] = + await Promise.all([ + fetch(`${BASE_API_URI}/backend/membership/subscriptions`, { + credentials: "include", + headers: { Cookie: `jwt=${jwt}` }, + }), + fetch(`${BASE_API_URI}/backend/licence/categories`, { + credentials: "include", + headers: { Cookie: `jwt=${jwt}` }, + }), + ]); + + // Check if any of the responses are not ok + if (!subscriptionsResponse.ok || !licenceCategoriesResponse.ok) { + cookies.delete("jwt", { path: "/" }); + throw redirect(302, "/"); + } + + // Parse the JSON responses + const [subscriptionsData, licence_categoriesData] = await Promise.all([ + subscriptionsResponse.json(), + licenceCategoriesResponse.json(), + ]); + // Check if the server sent a new token + const newToken = + subscriptionsResponse.headers.get("Set-Cookie") == null + ? licenceCategoriesResponse.headers.get("Set-Cookie") + : subscriptionsResponse.headers.get("Set-Cookie"); + + refreshCookie(newToken, null); + + return { + user: locals.user, + licence_categories: licence_categoriesData.licence_categories, + subscriptions: subscriptionsData.subscriptions, + }; + } catch (error) { + console.error("Error fetching data:", error); + // In case of any error, clear the JWT cookie + cookies.delete("jwt", { path: "/" }); + //throw redirect(302, "/"); + } + return { user: locals.user, + licence_categories: [], + subscriptions: [], }; } diff --git a/frontend/src/routes/auth/about/[id]/+page.svelte b/frontend/src/routes/auth/about/[id]/+page.svelte index fbdb7b1..aff0fb4 100644 --- a/frontend/src/routes/auth/about/[id]/+page.svelte +++ b/frontend/src/routes/auth/about/[id]/+page.svelte @@ -1,18 +1,17 @@