124 lines
5.4 KiB
TypeScript
124 lines
5.4 KiB
TypeScript
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<Response>) {
|
|
const userInfo = await getUserInfo(request);
|
|
if (!userInfo.userId) return next();
|
|
return runWithRlsContext({ userId: userInfo.userId }, next);
|
|
}
|
|
|
|
export async function loadCoreRootData({ request, params }: Pick<LoaderFunctionArgs, "request" | "params">) {
|
|
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 });
|
|
}
|
|
}
|