Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,86 @@
|
||||
// P4 Files (AK-P4a) — shared loader/action guard for Files routes.
|
||||
//
|
||||
// Resolves the authenticated tenant context and the user's permission set once,
|
||||
// so routes can gate consistently. Module-not-active handling differs by intent
|
||||
// (§ "404 vs locked" decision):
|
||||
// - GET loaders pass `requireModule: false` and render a locked/empty VM when
|
||||
// `hasModule` is false (mirrors walking-skeleton.tsx).
|
||||
// - Write actions pass `requireModule: true` → 404 (CatalogError.notFound) when
|
||||
// the module is off, and additionally require an explicit permission.
|
||||
|
||||
import { CatalogError, foundationErrors, requirePermission } from "@toolctrl/foundation";
|
||||
import { requireAuth } from "~/utils/loaders.middleware";
|
||||
import { getTenantIdFromUrl } from "~/utils/services/.server/urlService";
|
||||
import { getUserInfo } from "~/utils/session.server";
|
||||
import { tenantHasModule } from "~/utils/connector/tenantConnector.server";
|
||||
import { getPermissionsByUser } from "~/utils/db/permissions/userRoles.db.server";
|
||||
import { FileServiceError, type FileVisibilityContext } from "./fileService.server";
|
||||
|
||||
export const FILES_MODULE_KEY = "files";
|
||||
|
||||
export type FilesRouteContext = FileVisibilityContext & {
|
||||
tenantId: string;
|
||||
hasModule: boolean;
|
||||
};
|
||||
|
||||
export async function loadFilesContext({
|
||||
request,
|
||||
params,
|
||||
requireModule = false,
|
||||
}: {
|
||||
request: Request;
|
||||
params: Record<string, string | undefined>;
|
||||
requireModule?: boolean;
|
||||
}): Promise<FilesRouteContext> {
|
||||
await requireAuth({ request, params });
|
||||
const [tenantId, userInfo] = await Promise.all([getTenantIdFromUrl(params), getUserInfo(request)]);
|
||||
if (!tenantId || !userInfo.userId) {
|
||||
throw new CatalogError(foundationErrors.notFound);
|
||||
}
|
||||
const [hasModule, permissions] = await Promise.all([
|
||||
tenantHasModule({ tenantId, userId: userInfo.userId, moduleKey: FILES_MODULE_KEY }),
|
||||
getPermissionsByUser(userInfo.userId, tenantId),
|
||||
]);
|
||||
if (requireModule && !hasModule) {
|
||||
// Mutations on a disabled module look "not found" (Bauprinzip A.5.3).
|
||||
throw new CatalogError(foundationErrors.notFound, { module: FILES_MODULE_KEY });
|
||||
}
|
||||
return { tenantId, userId: userInfo.userId, permissions, hasModule };
|
||||
}
|
||||
|
||||
/** Require a files.* permission; throws 404 (CatalogError.notFound) if missing. */
|
||||
export function requireFilesPermission(ctx: FilesRouteContext, permission: string): FilesRouteContext {
|
||||
requirePermission({ tenantId: ctx.tenantId, userId: ctx.userId, permissions: ctx.permissions }, permission);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bare 404 Response for throwing from Files loaders/actions. The body text is
|
||||
* never shown to users (the route ErrorBoundary renders ServerError); we keep a
|
||||
* `return new Response` helper so call sites can `throw notFoundResponse()`
|
||||
* without inlining a hardcoded user-facing literal.
|
||||
*/
|
||||
export function notFoundResponse(): Response {
|
||||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
|
||||
/** Bare 400 Response for invalid action input (same rationale as notFoundResponse). */
|
||||
export function badRequestResponse(): Response {
|
||||
return new Response("Bad Request", { status: 400 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a Files action error to a JSON Response for resource routes (the upload
|
||||
* endpoints are called by the browser uploader via fetch, not via the HTML error
|
||||
* boundary). CatalogError keeps its status + i18nKey; FileServiceError is a 400
|
||||
* with its Klartext message key. Unknown errors are rethrown (→ 500).
|
||||
*/
|
||||
export function fileActionErrorResponse(error: unknown): Response {
|
||||
if (error instanceof CatalogError) {
|
||||
return Response.json({ error: error.i18nKey, code: error.code }, { status: error.status });
|
||||
}
|
||||
if (error instanceof FileServiceError) {
|
||||
return Response.json({ error: error.messageKey }, { status: 400 });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
Reference in new issue
Block a user