chore: heygermany customer project
This commit is contained in:
261
apps/website/lib/request-routing.ts
Normal file
261
apps/website/lib/request-routing.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
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> => {
|
||||
const response = await fetch(`${getApiBase(request)}/api/auth/get-session`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
cookie: getCookieHeader(request.cookies),
|
||||
},
|
||||
})
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user