fix: resolve final review findings (wiki cache tenant isolation, meeting search pagination, ambient test)
This commit is contained in:
@@ -145,6 +145,13 @@ export class OpenProjectClient {
|
||||
this.authHeader = `Basic ${btoa(`apikey:${this.apiKey}`)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a unique cache key scoped to both baseUrl and apiKey for multi-tenant cache isolation.
|
||||
*/
|
||||
getCacheKey(): string {
|
||||
return `${this.baseUrl}#${this.apiKey}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an HTTP GET request to OpenProject API v3.
|
||||
*/
|
||||
|
||||
+35
-12
@@ -378,21 +378,44 @@ export async function searchMeetings(
|
||||
const opClient = resolveClient(client);
|
||||
const needle = params.query.toLowerCase().trim();
|
||||
|
||||
// Fetch candidate meetings scoped to project if specified
|
||||
const candidateBatchSize = Math.max(params.pageSize ?? 50, 50);
|
||||
const meetingsResult = await listMeetings(
|
||||
{
|
||||
projectId: params.projectId,
|
||||
offset: 1,
|
||||
pageSize: candidateBatchSize,
|
||||
},
|
||||
opClient
|
||||
);
|
||||
// Fetch candidate meetings scoped to project if specified, across multiple pages up to cap
|
||||
const maxCandidates = 250;
|
||||
const candidateBatchSize = Math.min(maxCandidates, Math.max(params.pageSize ?? 50, 50));
|
||||
const candidateMeetings: MeetingSummary[] = [];
|
||||
let candidateOffset = 1;
|
||||
|
||||
const candidateMeetings = meetingsResult.elements;
|
||||
while (candidateMeetings.length < maxCandidates) {
|
||||
const meetingsResult = await listMeetings(
|
||||
{
|
||||
projectId: params.projectId,
|
||||
offset: candidateOffset,
|
||||
pageSize: candidateBatchSize,
|
||||
},
|
||||
opClient
|
||||
);
|
||||
|
||||
const elements = meetingsResult.elements;
|
||||
if (elements.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
candidateMeetings.push(...elements);
|
||||
|
||||
// Stop if all meetings fetched or fewer elements returned than requested page size
|
||||
if (
|
||||
candidateMeetings.length >= meetingsResult.total ||
|
||||
elements.length < candidateBatchSize
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
candidateOffset += 1;
|
||||
}
|
||||
|
||||
const scopedCandidates = candidateMeetings.slice(0, maxCandidates);
|
||||
|
||||
// Process candidate meetings concurrently
|
||||
const matchPromises = candidateMeetings.map(async (meeting) => {
|
||||
const matchPromises = scopedCandidates.map(async (meeting) => {
|
||||
const titleMatches = meeting.title.toLowerCase().includes(needle);
|
||||
const locationMatches = Boolean(
|
||||
meeting.location && meeting.location.toLowerCase().includes(needle)
|
||||
|
||||
@@ -76,7 +76,7 @@ export async function getOpenApiSpec(
|
||||
client?: OpenProjectClient
|
||||
): Promise<unknown> {
|
||||
const activeClient = resolveClient(client);
|
||||
const cacheKey = activeClient.baseUrl;
|
||||
const cacheKey = activeClient.getCacheKey?.() ?? activeClient.baseUrl;
|
||||
|
||||
if (options?.refresh) {
|
||||
openApiCache.delete(cacheKey);
|
||||
|
||||
@@ -63,16 +63,16 @@ export interface SearchWikiPagesParams {
|
||||
refreshCache?: boolean;
|
||||
}
|
||||
|
||||
// In-memory cache for discovered wiki pages keyed by client baseUrl
|
||||
// In-memory cache for discovered wiki pages keyed by client cache key
|
||||
const wikiPagesCache = new Map<string, Map<number, WikiPageDetail>>();
|
||||
const discoveredBaseUrls = new Set<string>();
|
||||
const discoveredCacheKeys = new Set<string>();
|
||||
|
||||
/**
|
||||
* Clears the in-memory wiki discovery cache.
|
||||
*/
|
||||
export function clearWikiCache(): void {
|
||||
wikiPagesCache.clear();
|
||||
discoveredBaseUrls.clear();
|
||||
discoveredCacheKeys.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,8 +209,8 @@ export async function getWikiPage(
|
||||
|
||||
const detail = normalizeWikiPageDetail(pageResource, attachments);
|
||||
|
||||
// Update in-memory cache for this client baseUrl if initialized
|
||||
const cacheKey = opClient.baseUrl || "default";
|
||||
// Update in-memory cache for this client cache key if initialized
|
||||
const cacheKey = opClient.getCacheKey?.() ?? (opClient.baseUrl || "default");
|
||||
let cacheMap = wikiPagesCache.get(cacheKey);
|
||||
if (!cacheMap) {
|
||||
cacheMap = new Map<number, WikiPageDetail>();
|
||||
@@ -259,10 +259,10 @@ export async function searchWikiPages(
|
||||
client?: OpenProjectClient
|
||||
): Promise<WikiPageSummary[]> {
|
||||
const opClient = resolveClient(client);
|
||||
const cacheKey = opClient.baseUrl || "default";
|
||||
const cacheKey = opClient.getCacheKey?.() ?? (opClient.baseUrl || "default");
|
||||
|
||||
let cacheMap = wikiPagesCache.get(cacheKey);
|
||||
const needsDiscovery = !cacheMap || !discoveredBaseUrls.has(cacheKey) || params?.refreshCache;
|
||||
const needsDiscovery = !cacheMap || !discoveredCacheKeys.has(cacheKey) || params?.refreshCache;
|
||||
|
||||
if (params?.refreshCache && cacheMap) {
|
||||
cacheMap.clear();
|
||||
@@ -355,7 +355,7 @@ export async function searchWikiPages(
|
||||
}
|
||||
}
|
||||
|
||||
discoveredBaseUrls.add(cacheKey);
|
||||
discoveredCacheKeys.add(cacheKey);
|
||||
}
|
||||
|
||||
// 3. Resolve target project ID if provided
|
||||
|
||||
Reference in New Issue
Block a user