84 lines
2.8 KiB
TypeScript
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
|
|
});
|
|
}
|