Files
HiveGrid-Wireframes/hivegrid-cloud/apps/api/src/routes/workspace-routes.ts
T

246 lines
8.0 KiB
TypeScript

import { Hono, type Context } from "hono";
import { getApiAppDependencies } from "../app-dependencies.js";
import type { ApiEnv } from "../api-types.js";
import { apiError } from "../middleware/error-envelope.js";
import {
parseWorkspaceContextRevalidationPayload,
parseWorkspaceInviteAcceptPayload,
parseWorkspaceInviteCreatePayload,
parseWorkspaceInviteRevokePayload,
} from "../validation/request-schemas.js";
import {
handleWorkspaceContextRevalidationError,
handleWorkspaceInviteError,
parseOptionalBooleanQuery,
parseWorkspaceAuthProviderKindQuery,
parseWorkspaceInviteStatusQuery,
parseWorkspaceTenantId,
readJsonBody,
requireWorkspaceInviteServiceCredential,
requireWorkspaceServiceCredential,
toWorkspaceAuthProviderApiResponse,
toWorkspaceInviteApiResponse,
} from "./shared.js";
export function registerWorkspaceRoutes(app: Hono<ApiEnv>) {
app.get("/v1/workspaces/:tenantId/auth/providers", getWorkspaceAuthProvidersRequest);
app.post("/v1/workspaces/:tenantId/context/revalidate", postWorkspaceContextRevalidateRequest);
app.get("/v1/workspaces/:tenantId/invites", getWorkspaceInvitesRequest);
app.post("/v1/workspaces/:tenantId/invites", postWorkspaceInviteRequest);
app.post("/v1/workspaces/:tenantId/invites/:inviteId/accept", postWorkspaceInviteAcceptRequest);
app.post("/v1/workspaces/:tenantId/invites/:inviteId/revoke", postWorkspaceInviteRevokeRequest);
}
async function getWorkspaceAuthProvidersRequest(c: Context<ApiEnv>) {
const unauthorized = requireWorkspaceServiceCredential(c);
if (unauthorized) {
return unauthorized;
}
const tenantId = parseWorkspaceTenantId(c);
if (!tenantId) {
return apiError(c, 400, "invalid_request", "tenantId is required.");
}
const tenantGuard = await requireActiveWorkspaceTenant(c, tenantId);
if (tenantGuard) {
return tenantGuard;
}
try {
const providers = await getApiAppDependencies().listWorkspaceAuthProviders({
tenantId,
kind: parseWorkspaceAuthProviderKindQuery(c.req.query("kind")),
enabled: parseOptionalBooleanQuery(c.req.query("enabled")),
});
return c.json({ providers: providers.map(toWorkspaceAuthProviderApiResponse) });
} catch {
return apiError(c, 500, "workspace_auth_provider_failed", "Workspace auth provider request failed.");
}
}
async function requireActiveWorkspaceTenant(c: Context<ApiEnv>, tenantId: string) {
try {
const tenant = await getApiAppDependencies().findLegacyTenantById(tenantId);
if (!tenant) {
return apiError(c, 404, "tenant_not_found", "Workspace tenant was not found.");
}
if (!tenant.active) {
return apiError(c, 403, "tenant_inactive", "Workspace tenant is inactive.");
}
return null;
} catch {
return apiError(c, 500, "workspace_tenant_failed", "Workspace tenant validation failed.");
}
}
async function postWorkspaceContextRevalidateRequest(c: Context<ApiEnv>) {
const unauthorized = requireWorkspaceServiceCredential(c);
if (unauthorized) {
return unauthorized;
}
const tenantId = parseWorkspaceTenantId(c);
if (!tenantId) {
return apiError(c, 400, "invalid_request", "tenantId is required.");
}
const body = await readJsonBody(c.req.raw);
const payload = parseWorkspaceContextRevalidationPayload(body);
if (!payload.ok) {
return apiError(c, 400, "invalid_request", payload.error.message, { params: payload.error.params });
}
try {
const context = await getApiAppDependencies().revalidateWorkspaceContext({ tenantId, userId: payload.value.userId });
return c.json({
context: {
valid: context.valid,
tenantId: context.tenantId,
userId: context.userId,
...(payload.value.providerSlug ? { providerSlug: payload.value.providerSlug } : {}),
},
});
} catch (error) {
return handleWorkspaceContextRevalidationError(c, error);
}
}
async function getWorkspaceInvitesRequest(c: Context<ApiEnv>) {
const unauthorized = requireWorkspaceInviteServiceCredential(c);
if (unauthorized) {
return unauthorized;
}
const tenantId = parseWorkspaceTenantId(c);
if (!tenantId) {
return apiError(c, 400, "invalid_request", "tenantId is required.");
}
const tenantGuard = await requireActiveWorkspaceTenant(c, tenantId);
if (tenantGuard) {
return tenantGuard;
}
try {
const invites = await getApiAppDependencies().listWorkspaceInvites({
tenantId,
status: parseWorkspaceInviteStatusQuery(c.req.query("status")),
email: c.req.query("email"),
});
return c.json({ invites: invites.map(toWorkspaceInviteApiResponse) });
} catch {
return apiError(c, 500, "workspace_invite_failed", "Workspace invite request failed.");
}
}
async function postWorkspaceInviteRequest(c: Context<ApiEnv>) {
const unauthorized = requireWorkspaceInviteServiceCredential(c);
if (unauthorized) {
return unauthorized;
}
const tenantId = parseWorkspaceTenantId(c);
if (!tenantId) {
return apiError(c, 400, "invalid_request", "tenantId is required.");
}
const tenantGuard = await requireActiveWorkspaceTenant(c, tenantId);
if (tenantGuard) {
return tenantGuard;
}
const body = await readJsonBody(c.req.raw);
const payload = parseWorkspaceInviteCreatePayload(body);
if (!payload.ok) {
return apiError(c, 400, "invalid_request", payload.error.message, { params: payload.error.params });
}
try {
const invite = await getApiAppDependencies().createWorkspaceInvite({
tenantId,
email: payload.value.email,
firstName: payload.value.firstName,
lastName: payload.value.lastName,
type: payload.value.type,
expiresAt: payload.value.expiresAt ? new Date(payload.value.expiresAt) : null,
});
return c.json({ invite: toWorkspaceInviteApiResponse(invite), token: invite.token }, 201);
} catch (error) {
return handleWorkspaceInviteError(c, error);
}
}
async function postWorkspaceInviteAcceptRequest(c: Context<ApiEnv>) {
const unauthorized = requireWorkspaceInviteServiceCredential(c);
if (unauthorized) {
return unauthorized;
}
const tenantId = parseWorkspaceTenantId(c);
if (!tenantId) {
return apiError(c, 400, "invalid_request", "tenantId is required.");
}
const tenantGuard = await requireActiveWorkspaceTenant(c, tenantId);
if (tenantGuard) {
return tenantGuard;
}
const inviteId = c.req.param("inviteId")?.trim();
if (!inviteId) {
return apiError(c, 400, "invalid_request", "inviteId is required.");
}
const body = await readJsonBody(c.req.raw);
const payload = parseWorkspaceInviteAcceptPayload(body);
if (!payload.ok) {
return apiError(c, 400, "invalid_request", payload.error.message, { params: payload.error.params });
}
try {
const invite = await getApiAppDependencies().acceptWorkspaceInvite({
tenantId,
inviteId,
token: payload.value.token,
acceptedByUserId: payload.value.userId ?? "",
});
return c.json({ invite: toWorkspaceInviteApiResponse(invite) });
} catch (error) {
return handleWorkspaceInviteError(c, error);
}
}
async function postWorkspaceInviteRevokeRequest(c: Context<ApiEnv>) {
const unauthorized = requireWorkspaceInviteServiceCredential(c);
if (unauthorized) {
return unauthorized;
}
const tenantId = parseWorkspaceTenantId(c);
const inviteId = c.req.param("inviteId")?.trim();
if (!tenantId || !inviteId) {
return apiError(c, 400, "invalid_request", "tenantId and inviteId are required.");
}
const tenantGuard = await requireActiveWorkspaceTenant(c, tenantId);
if (tenantGuard) {
return tenantGuard;
}
const body = await readJsonBody(c.req.raw);
const payload = parseWorkspaceInviteRevokePayload(body);
if (!payload.ok) {
return apiError(c, 400, "invalid_request", payload.error.message, { params: payload.error.params });
}
try {
const invite = await getApiAppDependencies().revokeWorkspaceInvite({
tenantId,
inviteId,
revokedByUserId: payload.value.revokedByUserId ?? "",
});
return c.json({ invite: toWorkspaceInviteApiResponse(invite) });
} catch (error) {
return handleWorkspaceInviteError(c, error);
}
}