Files
NianAIGC/lib/server/usage-context.ts

84 lines
2.8 KiB
TypeScript

import type { AuthSession } from "@/lib/auth/session";
import { hasSuperAdminAccess } from "@/lib/auth/permissions";
import {
getOrganizationApiConfig,
listOrganizations,
type OrganizationInfo
} from "@/lib/server/organization-client";
import type { UsageContext } from "@/lib/types";
const ORGANIZATION_CACHE_MS = 5 * 60 * 1000;
const organizationCache = new Map<string, {
expiresAt: number;
organizationId: string;
organizationName?: string;
}>();
export async function resolvePlatformUsageContext(session: AuthSession): Promise<UsageContext> {
const { user } = session;
const context: UsageContext = {
source: "platform",
accountId: user.id,
username: user.username,
displayName: user.displayName,
role: hasSuperAdminAccess(user) ? "super_admin" : user.role,
tenantId: user.tenantId,
organizationId: user.organizationId,
organizationName: user.organizationName
};
if (user.organizationId) return context;
if (!user.tenantId) return context;
const cached = organizationCache.get(user.tenantId);
if (cached && cached.expiresAt > Date.now()) {
return {
...context,
organizationId: cached.organizationId,
organizationName: cached.organizationName
};
}
const config = getOrganizationApiConfig(session.accessToken);
if (config.defaultOrganizationId && config.tenantId === user.tenantId) {
cacheOrganization(user.tenantId, {
organizationId: config.defaultOrganizationId
});
return { ...context, organizationId: config.defaultOrganizationId };
}
const organization = await findOrganizationForTenant(user.tenantId, session.accessToken);
if (!organization) return context;
cacheOrganization(user.tenantId, organization);
return {
...context,
organizationId: organization.organizationId,
organizationName: organization.organizationName
};
}
export function clearUsageOrganizationCacheForTests() {
organizationCache.clear();
}
async function findOrganizationForTenant(tenantId: string, accessToken?: string): Promise<OrganizationInfo | null> {
const contexts = accessToken ? [{ accessToken }, {}] : [{}];
for (const context of contexts) {
try {
const organizations = await listOrganizations(context);
const match = organizations.find((item) => String(item.organizationBindTenantId ?? "") === tenantId);
if (match) return match;
} catch {
// Organization lookup must never block a generation request. Missing matches remain unassigned.
}
}
return null;
}
function cacheOrganization(tenantId: string, organization: { organizationId: string; organizationName?: string }) {
organizationCache.set(tenantId, {
expiresAt: Date.now() + ORGANIZATION_CACHE_MS,
organizationId: organization.organizationId,
organizationName: organization.organizationName
});
}