134 lines
4.9 KiB
TypeScript
134 lines
4.9 KiB
TypeScript
import { redirect } from "react-router";
|
|
import { db } from "../../db.server";
|
|
import { Params } from "react-router";
|
|
import UrlUtils from "../../app/UrlUtils";
|
|
import { getHostRoutingContextFromRequest } from "../../app/HostRouting.server";
|
|
import { isWebRoutePath } from "../../app/PublicRouteSegments";
|
|
import { cachified, clearCacheKey } from "../../cache.server";
|
|
import { loadRequiredWebConfig } from "@toolctrl/config";
|
|
import { absoluteAppRootPath } from "~/utils/app/CrossHostUrls";
|
|
|
|
export type TenantResolverSource = "app-route-param" | "admin-query-param" | "none" | "invalid-app-route";
|
|
|
|
export type TenantResolverResult =
|
|
| { source: "app-route-param"; tenant: string }
|
|
| { source: "admin-query-param"; tenant: string }
|
|
| { source: "none"; tenant: null }
|
|
| { source: "invalid-app-route"; tenant: null };
|
|
|
|
export function resolveTenantLookupSource({ request, params }: { request?: Request; params: Params }): TenantResolverResult {
|
|
if (!request) {
|
|
return getTenantResolverResultFromParams(params);
|
|
}
|
|
|
|
const context = getHostRoutingContextFromRequest(request);
|
|
if (context === "admin") {
|
|
const tenant = normalizeTenantParam(new URL(request.url).searchParams.get("tenantId"));
|
|
return tenant ? { source: "admin-query-param", tenant } : { source: "none", tenant: null };
|
|
}
|
|
|
|
if (context !== "app") {
|
|
return { source: "none", tenant: null };
|
|
}
|
|
|
|
return getTenantResolverResultForAppRequest(request, params);
|
|
}
|
|
|
|
export async function getTenantIdFromUrl(params: Params) {
|
|
const { tenant } = params;
|
|
const normalizedTenant = typeof tenant === "string" ? tenant.trim() : "";
|
|
if (!normalizedTenant) {
|
|
// eslint-disable-next-line no-console
|
|
console.log("[urlService] getTenantIdFromUrl(): Redirecting to app root");
|
|
throw redirect(absoluteAppRootPath(loadRequiredWebConfig().routing));
|
|
}
|
|
let tenantFromParams = await cachified({
|
|
key: `tenantIdOrSlug:${normalizedTenant}`,
|
|
ttl: 1000 * 60 * 60 * 24,
|
|
getFreshValue: () => getTenantByIdOrSlug(normalizedTenant),
|
|
});
|
|
if (!tenantFromParams) {
|
|
tenantFromParams = await getTenantByIdOrSlug(normalizedTenant);
|
|
if (tenantFromParams) {
|
|
clearCacheKey(`tenant:${normalizedTenant}`);
|
|
clearCacheKey(`tenant:${tenantFromParams}`);
|
|
clearCacheKey(`tenantIdOrSlug:${normalizedTenant}`);
|
|
clearCacheKey(`tenantIdOrSlug:${tenantFromParams}`);
|
|
clearCacheKey(`tenantSimple:${tenantFromParams}`);
|
|
}
|
|
}
|
|
if (!tenantFromParams) {
|
|
// eslint-disable-next-line no-console
|
|
console.log("[urlService] getTenantIdFromUrl(): Redirecting to app root");
|
|
throw redirect(absoluteAppRootPath(loadRequiredWebConfig().routing));
|
|
}
|
|
return tenantFromParams;
|
|
}
|
|
|
|
export async function getTenantIdOrNull({ request, params }: { request: Request; params: Params }) {
|
|
const resolverResult = resolveTenantLookupSource({ request, params });
|
|
|
|
if (resolverResult.source === "none") {
|
|
return null;
|
|
}
|
|
|
|
if (resolverResult.source === "invalid-app-route") {
|
|
// eslint-disable-next-line no-console
|
|
console.log("[urlService] getTenantIdOrNull(): Redirecting to app root");
|
|
throw redirect(absoluteAppRootPath(loadRequiredWebConfig().routing));
|
|
}
|
|
|
|
const tenantFromParams = await getTenantByIdOrSlug(resolverResult.tenant);
|
|
if (!tenantFromParams) {
|
|
// eslint-disable-next-line no-console
|
|
console.log("[urlService] getTenantIdOrNull(): Redirecting to app root");
|
|
throw redirect(absoluteAppRootPath(loadRequiredWebConfig().routing));
|
|
}
|
|
return tenantFromParams;
|
|
}
|
|
|
|
export async function getTenantByIdOrSlug(tenant: string | undefined) {
|
|
return (
|
|
(
|
|
await db.tenant.findFirst({
|
|
where: { OR: [{ slug: tenant }, { id: tenant }] },
|
|
select: { id: true },
|
|
})
|
|
)?.id ?? ""
|
|
);
|
|
}
|
|
|
|
function getTenantResolverResultFromParams(params: Params): TenantResolverResult {
|
|
const tenant = normalizeTenantParam(params.tenant);
|
|
return tenant ? { source: "app-route-param", tenant } : { source: "none", tenant: null };
|
|
}
|
|
|
|
function getTenantResolverResultForAppRequest(request: Request, params: Params): TenantResolverResult {
|
|
const pathname = new URL(request.url).pathname;
|
|
if (pathname === "/" || pathname === "/app" || isWebRoutePath(pathname)) {
|
|
return { source: "none", tenant: null };
|
|
}
|
|
|
|
const tenant = normalizeTenantParam(params.tenant) ?? getLegacyAppTenantFromPathname(pathname);
|
|
|
|
if (tenant) {
|
|
return { source: "app-route-param", tenant };
|
|
}
|
|
|
|
return { source: "invalid-app-route", tenant: null };
|
|
}
|
|
|
|
function getLegacyAppTenantFromPathname(pathname: string) {
|
|
const normalizedPathname = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
const strippedPathname = UrlUtils.stripTrailingSlash(normalizedPathname);
|
|
if (strippedPathname === "/app") {
|
|
return null;
|
|
}
|
|
|
|
return normalizedPathname.match(/^\/app\/([^/]+)/)?.[1] ?? null;
|
|
}
|
|
|
|
function normalizeTenantParam(tenant: Params["tenant"] | null) {
|
|
return typeof tenant === "string" && tenant.trim() ? tenant : null;
|
|
}
|