Files
heygermany-e2e-mrg5yqhb/apps/website/lib/request-routing.ts
e2e f0093328d8
Some checks failed
Main / Setup and Test (push) Has been cancelled
Main / Build Website (push) Has been cancelled
chore: heygermany customer project
2026-07-11 11:30:11 +02:00

270 lines
7.4 KiB
TypeScript

import { match } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'
import type { UserSession } from '@heygermany/functions/src/application-types.d.js'
import type { DB, UserRole, UserType } from '@heygermany/sdk'
import { UserTypes } from '@heygermany/sdk'
import type { SupportedLocale } from '@heygermany/sdk/config'
import { LOCALES, DEFAULT_LOCALE } from '@/config'
type RequestSnapshot = {
url: string
pathname: string
search: string
headers: Headers
cookies: Map<string, string>
}
type RequestRoutingResult =
| {
type: 'redirect'
location: string
}
| {
type: 'continue'
locale: string
}
const LOCALE_EXEMPT_PREFIXES = ['/pilot']
const LOCALE_EXEMPT_LOCALES: Record<string, SupportedLocale> = {
'/pilot': 'de',
}
const getApiBase = (request: RequestSnapshot) => {
return process.env.VITE_API_BASE_URL || process.env.API_URL || new URL(request.url).origin
}
function shouldBypassRouting(pathname: string): boolean {
return (
pathname.startsWith('/api/') ||
pathname === '/api' ||
pathname.startsWith('/_build/') ||
pathname === '/_build' ||
pathname.startsWith('/_serverFn/') ||
pathname === '/_serverFn' ||
pathname.startsWith('/@') ||
pathname.startsWith('/src/') ||
pathname.startsWith('/node_modules/') ||
/\.[a-z0-9]+$/i.test(pathname)
)
}
const getCookieHeader = (cookies: Map<string, string>) => {
return Array.from(cookies.entries())
.map(([name, value]) => `${name}=${value}`)
.join('; ')
}
const getBetterAuthSession = async (
request: RequestSnapshot
): Promise<UserSession | null> => {
let response: Response
try {
response = await fetch(`${getApiBase(request)}/api/auth/get-session`, {
method: 'GET',
headers: {
cookie: getCookieHeader(request.cookies),
},
})
} catch (error) {
// The session lookup must never crash SSR — a transient backend outage or
// unreachable API should degrade to "logged out", not 500 the whole page.
console.warn('[request-routing] get-session fetch failed, treating as no session:', error)
return null
}
if (!response.ok) {
return null
}
const data = (await response.json()) as {
user?: {
id: string
name: string
role: UserRole
type: UserType
} | null
} | null
if (!data?.user) {
return null
}
return {
userId: data.user.id,
name: data.user.name,
role: data.user.role,
type: data.user.type,
}
}
function getLocale(request: RequestSnapshot): string {
try {
const cookieLocale = request.cookies.get('NEXT_LOCALE')
if (cookieLocale && LOCALES.includes(cookieLocale as SupportedLocale)) {
return cookieLocale
}
const headers = Object.fromEntries(request.headers.entries()) as Negotiator.Headers
const languages = new Negotiator({ headers }).languages()
const validLanguages = languages.filter((lang) => {
try {
Intl.getCanonicalLocales(lang)
return true
} catch {
return false
}
})
if (validLanguages.length === 0) {
return DEFAULT_LOCALE
}
return match(validLanguages, LOCALES, DEFAULT_LOCALE)
} catch (error) {
console.error('[Middleware] Error in locale detection:', error)
return DEFAULT_LOCALE
}
}
function hasLocale(pathname: string): boolean {
return LOCALES.some((locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`)
}
function isLocaleExemptPath(pathname: string): boolean {
return LOCALE_EXEMPT_PREFIXES.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`))
}
function getLocaleExemptPathLocale(pathname: string): SupportedLocale | null {
for (const [prefix, locale] of Object.entries(LOCALE_EXEMPT_LOCALES)) {
if (pathname === prefix || pathname.startsWith(`${prefix}/`)) {
return locale
}
}
return null
}
function getLocaleFromPath(pathname: string): string | null {
for (const locale of LOCALES) {
if (pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`) {
return locale
}
}
return null
}
function removeLocalePrefix(pathname: string): string {
const locale = getLocaleFromPath(pathname)
if (locale) {
return pathname.slice(`/${locale}`.length) || '/'
}
return pathname
}
function requiresAuth(pathname: string): { required: boolean; type?: DB.UserType } {
const pathWithoutLocale = removeLocalePrefix(pathname)
if (pathWithoutLocale.includes('/auth/login')) {
return { required: false }
}
if (pathWithoutLocale.startsWith('/backoffice')) {
return { required: true, type: UserTypes.BACKOFFICE }
}
if (pathWithoutLocale.startsWith('/company')) {
return { required: true, type: UserTypes.COMPANY }
}
if (pathWithoutLocale.startsWith('/jobs/application')) {
return { required: true, type: UserTypes.CANDIDATE }
}
return { required: false }
}
const toAbsoluteUrl = (request: RequestSnapshot, location: string) => {
return new URL(location, request.url).toString()
}
export const getResolvedLocale = (pathname: string) => {
return getLocaleFromPath(pathname) || getLocaleExemptPathLocale(pathname) || DEFAULT_LOCALE
}
export async function resolveRequestRouting(
request: RequestSnapshot
): Promise<RequestRoutingResult> {
if (shouldBypassRouting(request.pathname)) {
return {
type: 'continue',
locale: getResolvedLocale(request.pathname),
}
}
const fullUrl = request.pathname + request.search
if (!hasLocale(request.pathname) && !isLocaleExemptPath(request.pathname)) {
const locale = getLocale(request)
return {
type: 'redirect',
location: toAbsoluteUrl(request, `/${locale}${request.pathname}${request.search}`),
}
}
const userSession = await getBetterAuthSession(request)
const authCheck = requiresAuth(request.pathname)
if (authCheck.required) {
const currentLocale = getLocaleFromPath(request.pathname) || DEFAULT_LOCALE
if (userSession?.type !== UserTypes.BACKOFFICE) {
if (!userSession || userSession.type !== authCheck.type) {
let loginPath = ''
if (authCheck.type === UserTypes.BACKOFFICE) {
loginPath = `/${currentLocale}/backoffice/auth/login`
} else if (authCheck.type === UserTypes.COMPANY) {
loginPath = `/${currentLocale}/company/auth/login`
} else if (authCheck.type === UserTypes.CANDIDATE) {
loginPath = `/${currentLocale}/jobs/apply`
}
const returnUrl = encodeURIComponent(fullUrl)
return {
type: 'redirect',
location: toAbsoluteUrl(request, `${loginPath}?return=${returnUrl}`),
}
}
}
}
if (userSession) {
const pathWithoutLocale = removeLocalePrefix(request.pathname)
const currentLocale = getLocaleFromPath(request.pathname) || DEFAULT_LOCALE
if (pathWithoutLocale.includes('/auth/login')) {
let dashboardPath = ''
if (userSession.type === UserTypes.BACKOFFICE) {
dashboardPath = `/${currentLocale}/backoffice/candidates`
} else if (userSession.type === UserTypes.COMPANY) {
dashboardPath = `/${currentLocale}/company/candidates`
} else if (userSession.type === UserTypes.CANDIDATE) {
dashboardPath = `/${currentLocale}/jobs/application`
}
if (dashboardPath) {
return {
type: 'redirect',
location: toAbsoluteUrl(request, dashboardPath),
}
}
}
}
return {
type: 'continue',
locale: getResolvedLocale(request.pathname),
}
}