fix: Profilbild-Upload auf JSON/Base64 umgestellt statt multipart

POST /profile/avatar schlug live mit net::ERR_FAILED fehl (Verbindung
abgebrochen, keine Antwort) - selbst bei einer winzigen Testdatei.
CORS-Preflight lief sauber durch, nur der eigentliche multipart-POST
nicht; passt zum bereits dokumentierten Cloudflare-Verhalten bei
multipart-Uploads (siehe .gitea/workflows/*-image.yml). Umgangen durch
Umstellung auf denselben JSON-Content-Type, der fuer alle anderen
Endpunkte nachweislich zuverlaessig funktioniert: Bild wird
clientseitig als data:-URL kodiert und im JSON-Body gesendet.
Globales JSON-Body-Limit auf 5mb angehoben (deckt 2MB-Bild plus
Base64-Overhead).
This commit is contained in:
Nick Szaunig committed 2026-09-02 08:32:08 +02:00
1 parent abe1f6e8cf
commit 135738fdfc
7 files changed
+81 -49

No files matched your search

+21 -5
View File
@@ -1,4 +1,4 @@
import { apiFetch, apiFetchBlob, apiFetchFormData } from "@/lib/auth/api-client";
import { apiFetch, apiFetchBlob } from "@/lib/auth/api-client";
import { MfaEnrollment, ProfileSummary, UpdateProfileInput } from "./types";
export function updateProfile(input: UpdateProfileInput): Promise<ProfileSummary> {
@@ -22,10 +22,26 @@ export function deactivateAccount(currentPassword: string): Promise<void> {
});
}
export function uploadAvatar(file: File): Promise<void> {
const formData = new FormData();
formData.append("file", file);
return apiFetchFormData<void>("/profile/avatar", formData);
// Sent as a base64 data: URL in a plain JSON body rather than
// multipart/form-data: this deployment's Cloudflare proxy has a known
// history of mishandling multipart uploads regardless of size (see the
// registry-push workaround in .gitea/workflows/*-image.yml), while plain
// JSON POSTs to this same backend have been reliable all along.
function fileToDataUrl(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(String(reader.result));
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(file);
});
}
export async function uploadAvatar(file: File): Promise<void> {
const data = await fileToDataUrl(file);
return apiFetch<void>("/profile/avatar", {
method: "POST",
body: JSON.stringify({ data }),
});
}
export function deleteAvatar(): Promise<void> {