feat(profile): optional gender on the owner profile and every saved person (T1)

Additive migration: nullable profiles.gender / chart_profiles.gender with a
female/male CHECK, owner-only via existing table RLS, column grants mirroring
the neighbouring birth columns (service_role for the account PATCH, no
admin_runtime). Account PATCH/GET, people POST/PUT/GET, subject resolution
and the consult route carry each person's own value; a people write only
touches gender when the key is sent. DB test runs in the gate's DB job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
Jesse_Chen
2026-09-27 18:30:57 +08:00
co-authored by Claude Opus 5.5
parent dd85d9965d
commit 371fe68b51
13 changed files with 502 additions and 4 deletions
+4 -1
View File
@@ -50,7 +50,7 @@ export async function GET() {
const admin = createAdminSupabaseClient();
let { data: profile, error: profileError } = await supabase
.from("profiles")
.select("credits,active_birth_time,birth_time_status,birth_date,active_birth_date,active_birth_timezone_offset,active_birth_provenance,reported_birth_time,birth_time_source,birth_time_period,declared_window_start,declared_window_end,birth_time_clue,uncertainty_before_minutes,uncertainty_after_minutes,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset,birth_place_label,birth_place_type,birth_place_provider,birth_place_provider_id,timezone_id,timezone_source,ayanamsa,name,birth_time,rectification_case_id")
.select("credits,active_birth_time,birth_time_status,birth_date,active_birth_date,active_birth_timezone_offset,active_birth_provenance,reported_birth_time,birth_time_source,birth_time_period,declared_window_start,declared_window_end,birth_time_clue,uncertainty_before_minutes,uncertainty_after_minutes,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset,birth_place_label,birth_place_type,birth_place_provider,birth_place_provider_id,timezone_id,timezone_source,ayanamsa,name,birth_time,rectification_case_id,gender")
.eq("id", userId)
.single();
@@ -66,6 +66,7 @@ export async function GET() {
declared_window_start: null,
declared_window_end: null,
ayanamsa: undefined,
gender: undefined,
};
profileError = withoutDeclaredWindow.error;
} else if (withoutDeclaredWindow.error && isMissingProfileColumn(withoutDeclaredWindow.error)) {
@@ -85,6 +86,7 @@ export async function GET() {
timezone_id: undefined,
timezone_source: undefined,
ayanamsa: undefined,
gender: undefined,
} : null;
profileError = fallback.error;
} else {
@@ -286,6 +288,7 @@ export async function PATCH(request: Request) {
...(payload.timezone_id !== undefined ? { timezone_id: payload.timezone_id } : {}),
...(payload.timezone_source !== undefined ? { timezone_source: payload.timezone_source } : {}),
...(payload.ayanamsa !== undefined ? { ayanamsa: payload.ayanamsa } : {}),
...(payload.gender !== undefined ? { gender: payload.gender } : {}),
};
const withoutCoordinates = baseProfile;
const invalidatesUnconfirmedApplication = Object.keys(applicationPatch).length > 0;
+1 -1
View File
@@ -431,7 +431,7 @@ export async function POST(request: Request) {
async loadProfile(profileUserId) {
const { data, error } = await supabase
.from("profiles")
.select("name,birth_date,active_birth_date,active_birth_timezone_offset,active_birth_provenance,reported_birth_time,active_birth_time,birth_time_source,birth_time_period,declared_window_start,declared_window_end,birth_time_status,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset,birth_place_label,birth_place_type,birth_place_provider,birth_place_provider_id,timezone_id,timezone_source,ayanamsa")
.select("name,birth_date,active_birth_date,active_birth_timezone_offset,active_birth_provenance,reported_birth_time,active_birth_time,birth_time_source,birth_time_period,declared_window_start,declared_window_end,birth_time_status,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset,birth_place_label,birth_place_type,birth_place_provider,birth_place_provider_id,timezone_id,timezone_source,ayanamsa,gender")
.eq("id", profileUserId)
.single();
if (error || !data) throw new ConsultationProfileTruthError("profile_unavailable");