151 lines
3.6 KiB
TypeScript
151 lines
3.6 KiB
TypeScript
import { createContext, redirect, type LoaderFunctionArgs } from 'react-router'
|
|
|
|
import { requestIdContext, userContext } from '@repo/auth/context'
|
|
import { request as req } from '@repo/util/request'
|
|
|
|
import { CACHE_NAME, CACHE_TTL_SECONDS } from '@/conf'
|
|
import type { Address } from '@/routes/_.$orgid.enrollments.buy/review'
|
|
|
|
export type Subscription = {
|
|
billing_day: number
|
|
payment_method: 'PIX' | 'BANK_SLIP' | 'MANUAL'
|
|
}
|
|
|
|
export type Workspace = {
|
|
id: string
|
|
name: string
|
|
cnpj: string
|
|
}
|
|
|
|
export type WorkspaceContextProps = {
|
|
activeWorkspace: Workspace
|
|
workspaces: Workspace[]
|
|
subscription: Subscription | null
|
|
address: Address | null
|
|
test_mode: boolean
|
|
blocked: boolean
|
|
}
|
|
|
|
type HttpResponse = {
|
|
items: {
|
|
sk: string
|
|
name: string
|
|
cnpj: string
|
|
}[]
|
|
preferred_org_id?: string
|
|
}
|
|
|
|
export const workspaceContext = createContext<WorkspaceContextProps>()
|
|
|
|
export const workspaceMiddleware = async (
|
|
{ params, request, context }: LoaderFunctionArgs,
|
|
next: () => Promise<Response>
|
|
): Promise<Response> => {
|
|
const orgId = params.orgid
|
|
const requestId = context.get(requestIdContext)
|
|
const user = context.get(userContext)!
|
|
|
|
const cacheKey = buildWorkspaceCacheKey(request, user.sub, orgId)
|
|
const cached = await getWorkspaceFromCache(cacheKey)
|
|
|
|
if (cached) {
|
|
context.set(workspaceContext, cached)
|
|
return next()
|
|
}
|
|
|
|
console.log(`[${new Date().toISOString()}] [${requestId}] Cache miss`)
|
|
|
|
const r = await req({
|
|
url: `/users/${user.sub}/orgs?limit=25`,
|
|
request,
|
|
context
|
|
})
|
|
|
|
if (!r.ok) {
|
|
throw new Response(await r.text(), { status: r.status })
|
|
}
|
|
|
|
const { items, preferred_org_id } = (await r.json()) as HttpResponse
|
|
const workspaces: Workspace[] = items.map(({ sk, name, cnpj }) => {
|
|
const [, id] = sk.split('#')
|
|
return { id, name, cnpj }
|
|
})
|
|
|
|
const activeWorkspace = workspaces.find(({ id }) => id === orgId)
|
|
|
|
if (!activeWorkspace) {
|
|
const url = new URL(request.url)
|
|
const fallback = preferred_org_id
|
|
? (workspaces.find(({ id }) => id === preferred_org_id) ?? workspaces[0])
|
|
: workspaces[0]
|
|
|
|
if (!fallback) {
|
|
throw new Response(null, { status: 403 })
|
|
}
|
|
|
|
throw redirect(`/${fallback.id}${url.pathname}`)
|
|
}
|
|
|
|
const org = (await req({
|
|
url: `/orgs/${activeWorkspace.id}`,
|
|
request,
|
|
context
|
|
}).then((r) => r.json())) as any
|
|
|
|
const workspace: WorkspaceContextProps = {
|
|
activeWorkspace,
|
|
workspaces,
|
|
subscription: org?.['subscription'] || null,
|
|
address: org?.['address'] || null,
|
|
test_mode: 'test_mode' in org,
|
|
blocked: 'subscription_frozen' in org
|
|
}
|
|
context.set(workspaceContext, workspace)
|
|
saveToCache(cacheKey, workspace)
|
|
|
|
return await next()
|
|
}
|
|
|
|
function buildWorkspaceCacheKey(
|
|
request: Request,
|
|
userId: string,
|
|
orgId?: string
|
|
) {
|
|
const url = new URL(request.url)
|
|
url.pathname = `/__cache/workspace/${userId}/${orgId ?? 'none'}`
|
|
url.search = ''
|
|
|
|
return new Request(url.toString(), {
|
|
method: 'GET'
|
|
})
|
|
}
|
|
|
|
async function getWorkspaceFromCache(
|
|
key: Request
|
|
): Promise<WorkspaceContextProps | null> {
|
|
const cache: Cache = await caches.open(CACHE_NAME)
|
|
const cached: Response | undefined = await cache.match(key)
|
|
|
|
if (!cached) {
|
|
return null
|
|
}
|
|
|
|
return (await cached.json()) as WorkspaceContextProps
|
|
}
|
|
|
|
async function saveToCache(
|
|
key: Request,
|
|
workspace: WorkspaceContextProps
|
|
): Promise<void> {
|
|
const cache: Cache = await caches.open(CACHE_NAME)
|
|
|
|
const response = new Response(JSON.stringify(workspace), {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Cache-Control': `public, max-age=${CACHE_TTL_SECONDS}`
|
|
}
|
|
})
|
|
|
|
await cache.put(key, response)
|
|
}
|