refactor(anon-chat): route upload through anonymousChatApiService

Fixes #1245. Deduplicate the anonymous-chat file upload request, which
was inlined verbatim in DocumentsSidebar.tsx and free-composer.tsx
while anonymousChatApiService.uploadDocument already existed.

Key change: service now returns a discriminated result instead of
throwing on 409. Callers need to distinguish 409 (quota exceeded, ->
gate to login) from other non-OK responses (real errors, -> throw).

  export type AnonUploadResult =
    | { ok: true; data: { filename: string; size_bytes: number } }
    | { ok: false; reason: "quota_exceeded" };

Both call sites now do:

  const result = await anonymousChatApiService.uploadDocument(file);
  if (!result.ok) {
    if (result.reason === "quota_exceeded") gate("upload more documents");
    return;
  }
  const data = result.data;

Dropped the BACKEND_URL import in both files (no longer used). Verified
zero remaining /api/v1/public/anon-chat/upload references in
surfsense_web/.
This commit is contained in:
Trevin Chow 2026-04-23 03:26:42 -07:00
parent 7245ab4046
commit a2ddf47650
3 changed files with 20 additions and 36 deletions

View file

@ -9,7 +9,7 @@ import { Switch } from "@/components/ui/switch";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { useAnonymousMode } from "@/contexts/anonymous-mode"; import { useAnonymousMode } from "@/contexts/anonymous-mode";
import { useLoginGate } from "@/contexts/login-gate"; import { useLoginGate } from "@/contexts/login-gate";
import { BACKEND_URL } from "@/lib/env-config"; import { anonymousChatApiService } from "@/lib/apis/anonymous-chat-api.service";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const ANON_ALLOWED_EXTENSIONS = new Set([ const ANON_ALLOWED_EXTENSIONS = new Set([
@ -128,24 +128,12 @@ export const FreeComposer: FC = () => {
} }
try { try {
const formData = new FormData(); const result = await anonymousChatApiService.uploadDocument(file);
formData.append("file", file); if (!result.ok) {
const res = await fetch(`${BACKEND_URL}/api/v1/public/anon-chat/upload`, { if (result.reason === "quota_exceeded") gate("upload more documents");
method: "POST",
credentials: "include",
body: formData,
});
if (res.status === 409) {
gate("upload more documents");
return; return;
} }
if (!res.ok) { const data = result.data;
const body = await res.json().catch(() => ({}));
throw new Error(body.detail || `Upload failed: ${res.status}`);
}
const data = await res.json();
if (anonMode.isAnonymous) { if (anonMode.isAnonymous) {
anonMode.setUploadedDoc({ anonMode.setUploadedDoc({
filename: data.filename, filename: data.filename,

View file

@ -68,11 +68,11 @@ import type { DocumentTypeEnum } from "@/contracts/types/document.types";
import { useDebouncedValue } from "@/hooks/use-debounced-value"; import { useDebouncedValue } from "@/hooks/use-debounced-value";
import { useMediaQuery } from "@/hooks/use-media-query"; import { useMediaQuery } from "@/hooks/use-media-query";
import { useElectronAPI } from "@/hooks/use-platform"; import { useElectronAPI } from "@/hooks/use-platform";
import { anonymousChatApiService } from "@/lib/apis/anonymous-chat-api.service";
import { documentsApiService } from "@/lib/apis/documents-api.service"; import { documentsApiService } from "@/lib/apis/documents-api.service";
import { foldersApiService } from "@/lib/apis/folders-api.service"; import { foldersApiService } from "@/lib/apis/folders-api.service";
import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service"; import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service";
import { authenticatedFetch } from "@/lib/auth-utils"; import { authenticatedFetch } from "@/lib/auth-utils";
import { BACKEND_URL } from "@/lib/env-config";
import { uploadFolderScan } from "@/lib/folder-sync-upload"; import { uploadFolderScan } from "@/lib/folder-sync-upload";
import { getSupportedExtensionsSet } from "@/lib/supported-extensions"; import { getSupportedExtensionsSet } from "@/lib/supported-extensions";
import { queries } from "@/zero/queries/index"; import { queries } from "@/zero/queries/index";
@ -1312,24 +1312,12 @@ function AnonymousDocumentsSidebar({
setIsUploading(true); setIsUploading(true);
try { try {
const formData = new FormData(); const result = await anonymousChatApiService.uploadDocument(file);
formData.append("file", file); if (!result.ok) {
const res = await fetch(`${BACKEND_URL}/api/v1/public/anon-chat/upload`, { if (result.reason === "quota_exceeded") gate("upload more documents");
method: "POST",
credentials: "include",
body: formData,
});
if (res.status === 409) {
gate("upload more documents");
return; return;
} }
if (!res.ok) { const data = result.data;
const body = await res.json().catch(() => ({}));
throw new Error(body.detail || `Upload failed: ${res.status}`);
}
const data = await res.json();
if (anonMode.isAnonymous) { if (anonMode.isAnonymous) {
anonMode.setUploadedDoc({ anonMode.setUploadedDoc({
filename: data.filename, filename: data.filename,

View file

@ -12,6 +12,10 @@ import { ValidationError } from "../error";
const BASE = "/api/v1/public/anon-chat"; const BASE = "/api/v1/public/anon-chat";
export type AnonUploadResult =
| { ok: true; data: { filename: string; size_bytes: number } }
| { ok: false; reason: "quota_exceeded" };
class AnonymousChatApiService { class AnonymousChatApiService {
private baseUrl: string; private baseUrl: string;
@ -71,7 +75,7 @@ class AnonymousChatApiService {
}); });
}; };
uploadDocument = async (file: File): Promise<{ filename: string; size_bytes: number }> => { uploadDocument = async (file: File): Promise<AnonUploadResult> => {
const formData = new FormData(); const formData = new FormData();
formData.append("file", file); formData.append("file", file);
const res = await fetch(this.fullUrl("/upload"), { const res = await fetch(this.fullUrl("/upload"), {
@ -79,11 +83,15 @@ class AnonymousChatApiService {
credentials: "include", credentials: "include",
body: formData, body: formData,
}); });
if (res.status === 409) {
return { ok: false, reason: "quota_exceeded" };
}
if (!res.ok) { if (!res.ok) {
const body = await res.json().catch(() => ({})); const body = await res.json().catch(() => ({}));
throw new Error(body.detail || `Upload failed: ${res.status}`); throw new Error(body.detail || `Upload failed: ${res.status}`);
} }
return res.json(); const data = await res.json();
return { ok: true, data };
}; };
getDocument = async (): Promise<{ filename: string; size_bytes: number } | null> => { getDocument = async (): Promise<{ filename: string; size_bytes: number } | null> => {