import { resolveAyanamsa } from "./ayanamsa.ts"; import { applyPersistedBirthTime, birthTimePersistenceValues } from "./birth-time-intake-model.ts"; import { readChartBirthFingerprint } from "./chart-birth-truth.ts"; import { selectedBirthPlace } from "./home-profile.ts"; import type { Profile } from "./home-types.ts"; import type { ProfileGender } from "./profile-gender.ts"; /** * The owner's own profile write (`PATCH /api/account`), shared by the home * onboarding and the 星盘档案 edit view (TASK-self-edit-avatar-menu-20260928 * S1, BUG-1081). The owner is never a `chart_profiles` row; every birth-data * save goes through the account route, which checks `birth_time_source` and * derives the birth-time status on the server. * * Birth-data saves never carry `gender` (TASK-consult-gender-optional): the * owner's gender is its own one-field PATCH, so a birth-data save can never * clear it. */ export function selfProfilePatchBody(nextProfile: Profile) { const birthPlace = selectedBirthPlace(nextProfile); return { name: nextProfile.name.trim() || null, birth_date: nextProfile.date || null, ...birthTimePersistenceValues(nextProfile), country_code: nextProfile.countryCode, province_code: nextProfile.provinceCode || null, city_code: nextProfile.cityCode || null, district_code: nextProfile.districtCode || null, birth_place_label: nextProfile.birthPlaceLabel || null, birth_place_type: nextProfile.birthPlaceType || null, birth_place_provider: nextProfile.birthPlaceProvider || null, birth_place_provider_id: nextProfile.birthPlaceProviderId || null, latitude: birthPlace?.lat ?? null, longitude: birthPlace?.lon ?? null, timezone_id: nextProfile.timezoneId || null, timezone_offset: birthPlace?.tz ?? null, timezone_source: nextProfile.timezoneSource || null, ayanamsa: resolveAyanamsa(nextProfile), }; } export type SelfProfileSaved = Readonly<{ /** The draft with the birth-time truth the server returned applied. */ profile: Profile; /** The chart page's `profileFingerprint` of the saved row, when the route returned one. */ fingerprint: string | null; }>; type Fetch = typeof fetch; export async function saveSelfProfile(nextProfile: Profile, fetchImpl: Fetch = fetch): Promise { const response = await fetchImpl("/api/account", { method: "PATCH", credentials: "same-origin", headers: { "content-type": "application/json" }, body: JSON.stringify(selfProfilePatchBody(nextProfile)), }); const payload = await response.json().catch(() => null) as { error?: string; birthTime?: unknown; chartBirth?: { fingerprint?: unknown }; } | null; if (!response.ok) { throw new Error(payload?.error || "账户资料暂时无法保存。"); } const savedProfile = applyPersistedBirthTime(nextProfile, payload?.birthTime); return { profile: savedProfile, fingerprint: readChartBirthFingerprint(payload) }; } /** The owner's optional gender, on its own; null clears it. */ export async function saveSelfGender(gender: ProfileGender | null, fetchImpl: Fetch = fetch): Promise { const response = await fetchImpl("/api/account", { method: "PATCH", credentials: "same-origin", headers: { "content-type": "application/json" }, body: JSON.stringify({ gender }), }); const payload = await response.json().catch(() => null) as { error?: string } | null; if (!response.ok) throw new Error(payload?.error || "性别没保存上,再试一次。"); }