Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/utils/services/.server/urlService.ts
T

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;
}