import type { LoaderFunctionArgs } from "react-router"; import { redirect } from "react-router"; import { allCookieCategories } from "./application/cookies/ApplicationCookies"; import { CookieCategory } from "./application/cookies/CookieCategory"; import EventsService from "./modules/events/services/.server/EventsService"; import type { UserPreferencesUpdatedDto } from "./modules/events/dtos/UserPreferencesUpdatedDto"; import AuthService from "./modules/users/services/AuthService"; import { enabledLocales, getTranslations, i18nCookie } from "./locale/i18next.server"; import { isSupportedLocale } from "./locale/i18n"; import { getCanonicalHostRedirectUrl } from "./utils/app/HostRouting.server"; import { safeRedirectPathFromRequest } from "./utils/app/RedirectSafety.server"; import { loadRootData } from "./utils/data/.server/rootData"; import { getUser, updateUserProfile } from "./utils/db/users.db.server"; import { createUserSession, getUserInfo, validateCSRFToken } from "./utils/session.server"; import { nextLightOrDarkMode, normalizeLightOrDarkMode, persistUserLightOrDarkMode } from "./utils/theme/userThemePreference.server"; import { runWithRlsContext } from "@hivegrid/db/rls-context.server"; export async function runCoreRootMiddleware(request: Request, next: () => Promise) { const userInfo = await getUserInfo(request); if (!userInfo.userId) return next(); return runWithRlsContext({ userId: userInfo.userId }, next); } export async function loadCoreRootData({ request, params }: Pick) { const canonicalRedirectUrl = getCanonicalHostRedirectUrl(request); if (canonicalRedirectUrl) { throw redirect(canonicalRedirectUrl, { status: 308 }); } return loadRootData({ request, params }); } export async function handleCoreRootAction(request: Request) { const canonicalRedirectUrl = getCanonicalHostRedirectUrl(request); if (canonicalRedirectUrl) { throw redirect(canonicalRedirectUrl, { status: 308 }); } const userInfo = await getUserInfo(request); const form = await request.clone().formData(); const action = form.get("action"); if (userInfo.userId && (action === "toggleLightOrDarkMode" || action === "setLocale" || action === "setTheme")) { await validateCSRFToken(request, { formData: form }); } const redirectTo = safeRedirectPathFromRequest(request, form.get("redirect")?.toString()); if (action === "toggleLightOrDarkMode") { const lightOrDarkMode = nextLightOrDarkMode(userInfo.lightOrDarkMode); if (userInfo.userId) { const user = await getUser(userInfo.userId); if (user) { await EventsService.createUserEventWithMutation( { request, event: "user.preferences.updated", userId: user.id, data: { old: { lightOrDarkMode: normalizeLightOrDarkMode(user.lightOrDarkMode) }, new: { lightOrDarkMode }, user: { id: user.id, email: user.email }, } satisfies UserPreferencesUpdatedDto, }, () => persistUserLightOrDarkMode(user.id, lightOrDarkMode) ); } } return createUserSession({ ...userInfo, lightOrDarkMode }, redirectTo, { request }); } if (action === "setLocale") { const lng = form.get("lng")?.toString() ?? ""; if (!isSupportedLocale(lng) || !enabledLocales.includes(lng)) { throw new Response(null, { status: 404 }); } if (userInfo.userId) { const user = await getUser(userInfo.userId); if (user) { await EventsService.createUserEventWithMutation( { request, event: "user.preferences.updated", userId: user.id, data: { old: { locale: user.locale ?? "" }, new: { locale: lng }, user: { id: user.id, email: user.email }, } satisfies UserPreferencesUpdatedDto, }, () => updateUserProfile({ locale: lng }, user.id) ); } } const localeSession = await createUserSession({ ...userInfo, lng }, redirectTo, { request }); localeSession.headers.append("Set-Cookie", await i18nCookie.serialize(lng)); return localeSession; } if (action === "setCookieConsent") { const preserveParams = ["ref", "source", "utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"]; const allowed = form.getAll("cookies[]").map((field) => field.toString()); const cookies: { category: string; allowed: boolean }[] = []; allCookieCategories.forEach((item) => { const categoryName = CookieCategory[item]; const isAllowed = allowed.some((field) => field === categoryName.toString()); cookies.push({ category: categoryName, allowed: isAllowed ?? item === CookieCategory.REQUIRED }); }); const searchParams = new URLSearchParams(); preserveParams.forEach((param) => { const value = form.get(param)?.toString(); if (value) searchParams.set(param, value); }); return createUserSession({ ...userInfo, cookies }, redirectTo, { request }); } if (action === "login") { return AuthService.loginFromRequest(request, form); } if (action === "setTheme") { const { t } = await getTranslations(request); const theme = form.get("theme")?.toString(); if (!theme) { return Response.json({ error: t("settings.theme.errors.invalidTheme") }); } return createUserSession({ ...userInfo, theme }, redirectTo, { request }); } }