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: 2 additions & 3 deletions src/app/api/upload/route.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@ import { env } from "@/lib/env";
import { assertAllowedFile, assertFileContentSignature, jsonError } from "@/lib/http";
import { logger } from "@/lib/logger";
import { writeAuditLog } from "@/lib/audit";
import { planDocumentName, type SupabaseLike } from "@/lib/document-naming";
import { planDocumentName } from "@/lib/document-naming";
import { createAdminClient } from "@/lib/supabase/admin";
import { AuthenticationError, requireAuthenticatedUser, unauthorizedResponse } from "@/lib/supabase/auth";
import { probeSupabaseHealth } from "@/lib/supabase/health";
Expand DownExpand Up@@ -79,9 +79,8 @@ export async function POST(request: Request) {
if (upload.error) throw new Error(upload.error.message);
uploadedPath = storagePath;

const namingSupabase = supabase as unknown as SupabaseLike;
const namePlan = await planDocumentName({
supabase: namingSupabase,
supabase,
ownerId: user.id,
fileName: file.name,
requestedTitle: uploadMetadata.title,
Expand Down
16 changes: 3 additions & 13 deletions src/lib/document-naming.ts
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
export type SupabaseLike = {
from: (table: string) => {
select: (columns?: string) => {
eq: (
column: string,
value: unknown,
) => {
limit: (count: number) => PromiseLike<{ data: unknown; error: { message: string } | null }>;
};
};
};
};
import type { SupabaseClient } from "@supabase/supabase-js";
import type { Database } from "@/lib/supabase/database.types";

export type DocumentNamePlan = {
title: string;
Expand DownExpand Up@@ -163,7 +153,7 @@ function uniqueTitle(
}

export async function planDocumentName(args: {
supabase?: SupabaseLike;
supabase?: SupabaseClient<Database>;
ownerId: string;
fileName: string;
requestedTitle?: string | null;
Expand Down
6 changes: 5 additions & 1 deletion src/lib/env.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -130,7 +130,10 @@ export const env = {
OPENAI_STRONG_ANSWER_MODEL: runtimeAnswerModel(parsedEnv.OPENAI_STRONG_ANSWER_MODEL),
} satisfies typeof parsedEnv;

export function requireServerEnv() {
export function requireServerEnv(): {
NEXT_PUBLIC_SUPABASE_URL: string;
SUPABASE_SERVICE_ROLE_KEY: string;
} {
const missing = [
["NEXT_PUBLIC_SUPABASE_URL", env.NEXT_PUBLIC_SUPABASE_URL],
["SUPABASE_SERVICE_ROLE_KEY", env.SUPABASE_SERVICE_ROLE_KEY],
Expand All@@ -143,6 +146,7 @@ export function requireServerEnv() {
}

assertExpectedSupabaseProjectConfig(env);
return env as { NEXT_PUBLIC_SUPABASE_URL: string; SUPABASE_SERVICE_ROLE_KEY: string };
}

export function requireOpenAIEnv() {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/openai.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -378,6 +378,8 @@ async function createTextResult(

try {
const client = createOpenAIClient();
// The `responses.create` overloads are incompatible with our generic call-site; the double-cast
// here works around the SDK type mismatch without changing runtime behaviour.
const request = client.responses.create(responseBody(input, options, format) as never, requestOptions(options));
const { data, request_id: requestId } = await unwrapOpenAIResponse(request as unknown as APIPromiseLike<unknown>);
const completion = extractCompletionStatus(data);
Expand Down
18 changes: 15 additions & 3 deletions src/lib/rag.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
import { createAdminClient } from "@/lib/supabase/admin";
import type { Database } from "@/lib/supabase/database.types";
import {
embedTextWithTelemetry,
generateStructuredTextResult,
Expand DownExpand Up@@ -1747,7 +1748,16 @@ export function invalidateRagCachesForDocumentMutation(ownerId: string) {
invalidateAnonymousSharedRagCaches();
}

async function insertRagQuery(row: Record<string, unknown>) {
interface RagQueryInsert {
owner_id?: string | null;
query: string;
answer?: string | null;
source_chunk_ids?: string[] | null;
model?: string | null;
metadata?: Record<string, unknown>;
}

async function insertRagQuery(row: RagQueryInsert) {
const supabase = createAdminClient();
// Redact potential-PHI raw query text centrally so every logRagQuery caller is
// covered, and fold a stable hash + retention flag into metadata (RET-H4).
Expand All@@ -1759,10 +1769,12 @@ async function insertRagQuery(row: Record<string, unknown>) {
query: queryTextForStorage(rawQuery),
metadata: { ...existingMetadata, ...queryPrivacyMetadata(rawQuery) },
};
await supabase.from("rag_queries").insert(safeRow);
await supabase
.from("rag_queries")
.insert(safeRow as Database["public"]["Tables"]["rag_queries"]["Insert"]);
}

async function logRagQuery(row: Record<string, unknown>) {
async function logRagQuery(row: RagQueryInsert) {
if (env.RAG_AWAIT_QUERY_LOGS) {
await insertRagQuery(row);
return;
Expand Down
7 changes: 4 additions & 3 deletions src/lib/supabase/admin.ts
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
import { createClient } from "@supabase/supabase-js";
import { env, requireServerEnv } from "@/lib/env";
import { requireServerEnv } from "@/lib/env";
import type { Database } from "./database.types";

export function createAdminClient() {
requireServerEnv();
const { NEXT_PUBLIC_SUPABASE_URL: url, SUPABASE_SERVICE_ROLE_KEY: key } = requireServerEnv();

return createClient(env.NEXT_PUBLIC_SUPABASE_URL!, env.SUPABASE_SERVICE_ROLE_KEY!, {
return createClient<Database>(url, key, {
auth: {
autoRefreshToken: false,
persistSession: false,
Expand Down
Loading
Loading