Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ const baseMetadata: Metadata = {
},
};

/**
* Generates application metadata with a request-aware base URL.
*
* @returns The application metadata, including its resolved base URL.
*/
export async function generateMetadata(): Promise<Metadata> {
const requestHeaders = await headers();
return {
Expand Down
8 changes: 8 additions & 0 deletions src/components/DocumentViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1995,6 +1995,14 @@ function DocumentOverviewLanding({
);
}

/**
* Renders the clinical document viewer with source previews, extracted content, summaries, and document tools.
*
* @param documentId - The identifier of the document to load.
* @param initialPage - The page to display initially in the source preview.
* @param chunkId - An optional indexed passage to pin and scroll into view.
* @returns The document viewer interface.
*/
export function DocumentViewer({
documentId,
initialPage,
Expand Down
15 changes: 15 additions & 0 deletions src/lib/api-rate-limit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,21 @@ export async function consumeApiRateLimit(args: {
};
}

/**
* Applies an API rate limit to an owner or anonymous subject.
*
* Anonymous requests to answer and document upload buckets are constrained by
* both subject-specific and aggregate bucket limits. Limiter unavailability may
* use an in-memory fallback when permitted.
*
* @param args - Rate-limiting configuration and request subject.
* @param args.subject - The authenticated owner or anonymous subject to limit.
* @param args.bucket - The API resource bucket being limited.
* @param args.limit - Optional subject-specific request limit.
* @param args.windowSeconds - Optional subject-specific rate-limit window.
* @param args.allowInMemoryFallbackOnUnavailable - Whether local fallback may be used when the durable limiter is unavailable.
* @returns The computed rate-limit outcome.
*/
export async function consumeSubjectApiRateLimit(args: {
supabase: SupabaseAdmin;
subject: RateLimitSubject;
Expand Down
8 changes: 7 additions & 1 deletion src/lib/document-viewer-navigation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
/** Build a useful-page link without retaining evidence pinned to a different page. */
/**
* Builds a URL for a specific document page in the PDF preview section.
*
* @param documentId - The document identifier to include in the URL
* @param page - The requested page number, normalized to an integer of at least 1
* @returns A document page URL with the encoded document identifier and normalized page number
*/
export function documentPageHref(documentId: string, page: number) {
const normalizedPage = Number.isFinite(page) ? Math.max(1, Math.trunc(page)) : 1;
const params = new URLSearchParams({ page: String(normalizedPage) });
Expand Down
18 changes: 16 additions & 2 deletions src/lib/metadata-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ export type MetadataBaseOptions = {
allowRequestOrigin?: boolean;
};

/** Parse an absolute HTTP(S) URL without allowing invalid configuration to escape. */
/**
* Parses a value as an HTTP or HTTPS URL.
*
* @param value - The URL value to parse.
* @returns The parsed URL when it uses HTTP or HTTPS; `undefined` otherwise.
*/
function httpUrl(value: string | undefined) {
const candidate = value?.trim();
if (!candidate) return undefined;
Expand All @@ -16,7 +21,16 @@ function httpUrl(value: string | undefined) {
}
}

/** Resolve metadata from validated configuration, trusted deployment state, or an explicit dev fallback. */
/**
* Resolves the base URL used for metadata generation.
*
* Sources are considered in order: configured site URL, trusted deployment domain,
* and the request origin when explicitly enabled.
*
* @param requestHeaders - Headers used to derive the request origin.
* @param options - Configuration controlling the fallback sources.
* @returns A valid HTTP or HTTPS metadata base URL, or `undefined` when none can be resolved.
*/
export function resolveMetadataBase(requestHeaders: Headers, options: MetadataBaseOptions = {}) {
const configuredUrl = httpUrl(options.configuredSiteUrl);
if (configuredUrl) return configuredUrl;
Expand Down
21 changes: 18 additions & 3 deletions src/lib/public-api-access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ type AdminClient = ReturnType<typeof createAdminClient>;

export type RateLimitSubject = { kind: "owner"; ownerId: string } | { kind: "anonymous"; subjectKey: string };

/** Read the deployment proxy's appended address from a forwarded IP chain. */
/**
* Extracts the last non-empty IP address from a proxy forwarding header value.
*
* @param value - A comma-separated proxy forwarding header value
* @returns The last trimmed IP address, or an empty string when no address is present
*/
function trustedProxyIp(value: string | null) {
const forwarded = value
?.split(",")
Expand All @@ -20,7 +25,12 @@ function trustedProxyIp(value: string | null) {
return forwarded?.at(-1) ?? "";
}

/** Select the strongest deployment-owned request identity signal available. */
/**
* Determines the request's source IP from trusted proxy forwarding headers.
*
* @param request - The request containing the proxy forwarding headers
* @returns The last non-empty address from `x-forwarded-for`, the address from `x-real-ip`, or `"unknown-ip"` when neither header provides an address
*/
function requestIpSignal(request: Request) {
return (
trustedProxyIp(request.headers.get("x-forwarded-for")) ||
Expand All @@ -29,7 +39,12 @@ function requestIpSignal(request: Request) {
);
}

/** Derive a stable, non-reversible quota subject for an anonymous caller. */
/**
* Creates a stable anonymous rate-limit subject key from the request's trusted proxy IP signal.
*
* @param request - The request from which to derive the IP signal
* @returns A prefixed, truncated SHA-256 hash of the source IP signal
*/
export function anonymousApiSubjectKey(request: Request) {
// Trust only the deployment proxy's appended forwarding entry. Ignore the
// caller-controlled Cloudflare/User-Agent values and any leading XFF entries:
Expand Down