From 39a6737f4edbe3c10cf869321a88d382bdda3d72 Mon Sep 17 00:00:00 2001 From: TurtleWolfe Date: Fri, 5 Jun 2026 22:47:45 +0000 Subject: [PATCH] =?UTF-8?q?fix(types):=20regenerate=20Supabase=20types=20?= =?UTF-8?q?=E2=80=94=20surfaces=20+=20fixes=20broken=20payment-retry=20sch?= =?UTF-8?q?ema=20drift?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regenerated src/lib/supabase/types.ts from the live schema via the Supabase Management API (GET /v1/projects/{ref}/types/typescript — no local CLI install). The old generated types were stale and were MASKING real bugs: 1. auth_audit_logs.Row was missing success/error_message → removed the boundary-cast workaround added in #131 (UserAuditTrail.tsx). 2. The fresh types exposed that payment_intents.retry_count + parent_intent_id DON'T EXIST in the live DB — yet payment-service.ts (FR-009 retry limit) and usePaymentRetryStatus.ts read/write them. retryPayment() would fail at runtime; only the mocked unit tests passed. The columns ARE in the monolithic migration (lines 69-76, #43/B1) — they were just never applied to prod (same prod-vs-migration drift class as #49). Applied the idempotent ADD COLUMN IF NOT EXISTS to prod via the Management API + verified; the payment-retry feature now actually works. 3. My #26 recordSystemMessage insert was missing the required messages. sequence_number — the fresh (stricter) types caught it; added the placeholder :0 (the assign_sequence_number() trigger overwrites it, matching message-service.ts:335/516). Also picks up edge_idempotency_keys (the #130 table) now that it's in the types. Gates: type-check clean (was failing on retry_count before the prod fix), lint clean, 41 affected tests pass (UserAuditTrail / payment retry / group membership). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../UserAuditTrail/UserAuditTrail.tsx | 7 +- src/lib/supabase/types.ts | 1187 ++++++++++------- src/services/messaging/group-service.ts | 4 + 3 files changed, 719 insertions(+), 479 deletions(-) diff --git a/src/components/molecular/UserAuditTrail/UserAuditTrail.tsx b/src/components/molecular/UserAuditTrail/UserAuditTrail.tsx index 2afcfd83..88414fbc 100644 --- a/src/components/molecular/UserAuditTrail/UserAuditTrail.tsx +++ b/src/components/molecular/UserAuditTrail/UserAuditTrail.tsx @@ -94,12 +94,7 @@ export default function UserAuditTrail({ setEntries([]); return; } - // Cast via `unknown`: the generated src/lib/supabase/types.ts is stale — - // its `auth_audit_logs.Row` omits the `success` / `error_message` columns - // that exist in the live table + the monolithic migration, so the query - // builder mis-infers a SelectQueryError. The runtime row has `success`. - // (Regenerating the Supabase types is tracked separately, out of #23 scope.) - setEntries((data ?? []) as unknown as UserAuditEntry[]); + setEntries((data ?? []) as UserAuditEntry[]); } void load(); diff --git a/src/lib/supabase/types.ts b/src/lib/supabase/types.ts index aa162fae..7aae097e 100644 --- a/src/lib/supabase/types.ts +++ b/src/lib/supabase/types.ts @@ -10,7 +10,7 @@ export type Database = { // Allows to automatically instantiate createClient with right options // instead of createClient(URL, KEY) __InternalSupabase: { - PostgrestVersion: '13.0.5'; + PostgrestVersion: '14.5'; }; graphql_public: { Tables: { @@ -39,746 +39,987 @@ export type Database = { }; public: { Tables: { - payment_intents: { + audit_logs: { Row: { - amount: number; created_at: string; - currency: string; - customer_email: string; - description: string | null; - expires_at: string; + details: Json | null; + event_type: string; id: string; - idempotency_key: string | null; - interval: string | null; - metadata: Json | null; - parent_intent_id: string | null; - retry_count: number; - template_user_id: string; - type: string; + ip_address: unknown; + user_agent: string | null; + user_id: string | null; }; Insert: { - amount: number; created_at?: string; - currency?: string; - customer_email: string; - description?: string | null; - expires_at?: string; + details?: Json | null; + event_type: string; id?: string; - idempotency_key?: string | null; - interval?: string | null; - metadata?: Json | null; - parent_intent_id?: string | null; - retry_count?: number; - template_user_id: string; - type: string; + ip_address?: unknown; + user_agent?: string | null; + user_id?: string | null; }; Update: { - amount?: number; created_at?: string; - currency?: string; - customer_email?: string; - description?: string | null; - expires_at?: string; + details?: Json | null; + event_type?: string; id?: string; - idempotency_key?: string | null; - interval?: string | null; - metadata?: Json | null; - parent_intent_id?: string | null; - retry_count?: number; - template_user_id?: string; - type?: string; + ip_address?: unknown; + user_agent?: string | null; + user_id?: string | null; }; - Relationships: [ - { - foreignKeyName: 'payment_intents_parent_intent_id_fkey'; - columns: ['parent_intent_id']; - isOneToOne: false; - referencedRelation: 'payment_intents'; - referencedColumns: ['id']; - }, - ]; + Relationships: []; }; - payment_provider_config: { + auth_audit_logs: { Row: { - config_status: string; created_at: string; - enabled: boolean; - features: Json | null; + error_message: string | null; + event_data: Json | null; + event_type: string; id: string; - priority: number; - provider: string; - updated_at: string; + ip_address: unknown; + success: boolean; + user_agent: string | null; + user_id: string | null; }; Insert: { - config_status?: string; created_at?: string; - enabled?: boolean; - features?: Json | null; + error_message?: string | null; + event_data?: Json | null; + event_type: string; id?: string; - priority?: number; - provider: string; - updated_at?: string; + ip_address?: unknown; + success?: boolean; + user_agent?: string | null; + user_id?: string | null; }; Update: { - config_status?: string; created_at?: string; - enabled?: boolean; - features?: Json | null; + error_message?: string | null; + event_data?: Json | null; + event_type?: string; id?: string; - priority?: number; - provider?: string; - updated_at?: string; + ip_address?: unknown; + success?: boolean; + user_agent?: string | null; + user_id?: string | null; }; Relationships: []; }; - payment_results: { + conversation_keys: { Row: { - charged_amount: number | null; - charged_currency: string | null; + conversation_id: string; created_at: string; - error_code: string | null; - error_message: string | null; + encrypted_shared_secret: string; id: string; - intent_id: string; - provider: string; - provider_fee: number | null; - status: string; - transaction_id: string; - updated_at: string; - verification_method: string | null; - webhook_verified: boolean; + key_version: number; + user_id: string; }; Insert: { - charged_amount?: number | null; - charged_currency?: string | null; + conversation_id: string; created_at?: string; - error_code?: string | null; - error_message?: string | null; + encrypted_shared_secret: string; id?: string; - intent_id: string; - provider: string; - provider_fee?: number | null; - status: string; - transaction_id: string; - updated_at?: string; - verification_method?: string | null; - webhook_verified?: boolean; + key_version?: number; + user_id: string; }; Update: { - charged_amount?: number | null; - charged_currency?: string | null; + conversation_id?: string; created_at?: string; - error_code?: string | null; - error_message?: string | null; + encrypted_shared_secret?: string; id?: string; - intent_id?: string; - provider?: string; - provider_fee?: number | null; - status?: string; - transaction_id?: string; - updated_at?: string; - verification_method?: string | null; - webhook_verified?: boolean; + key_version?: number; + user_id?: string; }; Relationships: [ { - foreignKeyName: 'payment_results_intent_id_fkey'; - columns: ['intent_id']; + foreignKeyName: 'conversation_keys_conversation_id_fkey'; + columns: ['conversation_id']; isOneToOne: false; - referencedRelation: 'payment_intents'; + referencedRelation: 'conversations'; referencedColumns: ['id']; }, ]; }; - subscriptions: { + conversation_members: { Row: { - canceled_at: string | null; - cancellation_reason: string | null; + archived: boolean; + conversation_id: string; + id: string; + joined_at: string; + key_status: string; + key_version_joined: number; + left_at: string | null; + muted: boolean; + role: string; + user_id: string; + }; + Insert: { + archived?: boolean; + conversation_id: string; + id?: string; + joined_at?: string; + key_status?: string; + key_version_joined?: number; + left_at?: string | null; + muted?: boolean; + role?: string; + user_id: string; + }; + Update: { + archived?: boolean; + conversation_id?: string; + id?: string; + joined_at?: string; + key_status?: string; + key_version_joined?: number; + left_at?: string | null; + muted?: boolean; + role?: string; + user_id?: string; + }; + Relationships: [ + { + foreignKeyName: 'conversation_members_conversation_id_fkey'; + columns: ['conversation_id']; + isOneToOne: false; + referencedRelation: 'conversations'; + referencedColumns: ['id']; + }, + { + foreignKeyName: 'conversation_members_user_id_fkey'; + columns: ['user_id']; + isOneToOne: false; + referencedRelation: 'user_profiles'; + referencedColumns: ['id']; + }, + ]; + }; + conversations: { + Row: { + archived_by_participant_1: boolean; + archived_by_participant_2: boolean; created_at: string; - current_period_end: string | null; - current_period_start: string | null; - customer_email: string; - failed_payment_count: number; - grace_period_expires: string | null; + created_by: string | null; + current_key_version: number; + group_name: string | null; id: string; - next_billing_date: string | null; - plan_amount: number; - plan_interval: string; - provider: string; - provider_subscription_id: string; - retry_schedule: Json | null; - status: string; - template_user_id: string; - updated_at: string; + is_group: boolean; + last_message_at: string | null; + participant_1_id: string | null; + participant_2_id: string | null; }; Insert: { - canceled_at?: string | null; - cancellation_reason?: string | null; + archived_by_participant_1?: boolean; + archived_by_participant_2?: boolean; created_at?: string; - current_period_end?: string | null; - current_period_start?: string | null; - customer_email: string; - failed_payment_count?: number; - grace_period_expires?: string | null; + created_by?: string | null; + current_key_version?: number; + group_name?: string | null; + id?: string; + is_group?: boolean; + last_message_at?: string | null; + participant_1_id?: string | null; + participant_2_id?: string | null; + }; + Update: { + archived_by_participant_1?: boolean; + archived_by_participant_2?: boolean; + created_at?: string; + created_by?: string | null; + current_key_version?: number; + group_name?: string | null; + id?: string; + is_group?: boolean; + last_message_at?: string | null; + participant_1_id?: string | null; + participant_2_id?: string | null; + }; + Relationships: [ + { + foreignKeyName: 'conversations_created_by_fkey'; + columns: ['created_by']; + isOneToOne: false; + referencedRelation: 'user_profiles'; + referencedColumns: ['id']; + }, + { + foreignKeyName: 'conversations_participant_1_id_fkey'; + columns: ['participant_1_id']; + isOneToOne: false; + referencedRelation: 'user_profiles'; + referencedColumns: ['id']; + }, + { + foreignKeyName: 'conversations_participant_2_id_fkey'; + columns: ['participant_2_id']; + isOneToOne: false; + referencedRelation: 'user_profiles'; + referencedColumns: ['id']; + }, + ]; + }; + edge_idempotency_keys: { + Row: { + created_at: string; + function_name: string; + id: string; + idempotency_key: string; + result: Json; + }; + Insert: { + created_at?: string; + function_name: string; id?: string; - next_billing_date?: string | null; - plan_amount: number; - plan_interval: string; - provider: string; - provider_subscription_id: string; - retry_schedule?: Json | null; - status: string; - template_user_id: string; - updated_at?: string; + idempotency_key: string; + result: Json; }; Update: { - canceled_at?: string | null; - cancellation_reason?: string | null; created_at?: string; - current_period_end?: string | null; - current_period_start?: string | null; - customer_email?: string; - failed_payment_count?: number; - grace_period_expires?: string | null; + function_name?: string; id?: string; - next_billing_date?: string | null; - plan_amount?: number; - plan_interval?: string; - provider?: string; - provider_subscription_id?: string; - retry_schedule?: Json | null; - status?: string; - template_user_id?: string; - updated_at?: string; + idempotency_key?: string; + result?: Json; }; Relationships: []; }; - webhook_events: { + group_keys: { Row: { + conversation_id: string; created_at: string; - event_data: Json; - event_type: string; + created_by: string; + encrypted_key: string; id: string; - processed: boolean; - processed_at: string | null; - processing_attempts: number; - processing_error: string | null; - provider: string; - provider_event_id: string; - related_payment_id: string | null; - related_subscription_id: string | null; - signature: string; - signature_verified: boolean; + key_version: number; + user_id: string; }; Insert: { + conversation_id: string; created_at?: string; - event_data: Json; - event_type: string; + created_by: string; + encrypted_key: string; id?: string; - processed?: boolean; - processed_at?: string | null; - processing_attempts?: number; - processing_error?: string | null; - provider: string; - provider_event_id: string; - related_payment_id?: string | null; - related_subscription_id?: string | null; - signature: string; - signature_verified?: boolean; + key_version?: number; + user_id: string; }; Update: { + conversation_id?: string; created_at?: string; - event_data?: Json; - event_type?: string; + created_by?: string; + encrypted_key?: string; id?: string; - processed?: boolean; - processed_at?: string | null; - processing_attempts?: number; - processing_error?: string | null; - provider?: string; - provider_event_id?: string; - related_payment_id?: string | null; - related_subscription_id?: string | null; - signature?: string; - signature_verified?: boolean; + key_version?: number; + user_id?: string; }; Relationships: [ { - foreignKeyName: 'webhook_events_related_payment_id_fkey'; - columns: ['related_payment_id']; + foreignKeyName: 'group_keys_conversation_id_fkey'; + columns: ['conversation_id']; isOneToOne: false; - referencedRelation: 'payment_results'; + referencedRelation: 'conversations'; referencedColumns: ['id']; }, { - foreignKeyName: 'webhook_events_related_subscription_id_fkey'; - columns: ['related_subscription_id']; + foreignKeyName: 'group_keys_created_by_fkey'; + columns: ['created_by']; isOneToOne: false; - referencedRelation: 'subscriptions'; + referencedRelation: 'user_profiles'; + referencedColumns: ['id']; + }, + { + foreignKeyName: 'group_keys_user_id_fkey'; + columns: ['user_id']; + isOneToOne: false; + referencedRelation: 'user_profiles'; referencedColumns: ['id']; }, ]; }; - user_profiles: { + messages: { Row: { - avatar_url: string | null; - bio: string | null; + conversation_id: string; created_at: string; - display_name: string | null; + deleted: boolean; + delivered_at: string | null; + edited: boolean; + edited_at: string | null; + encrypted_content: string; id: string; - updated_at: string; - username: string | null; - welcome_message_sent: boolean; + initialization_vector: string; + is_system_message: boolean; + key_version: number; + read_at: string | null; + sender_id: string; + sequence_number: number; + system_message_type: string | null; }; Insert: { - avatar_url?: string | null; - bio?: string | null; + conversation_id: string; created_at?: string; - display_name?: string | null; - id: string; - updated_at?: string; - username?: string | null; - welcome_message_sent?: boolean; + deleted?: boolean; + delivered_at?: string | null; + edited?: boolean; + edited_at?: string | null; + encrypted_content: string; + id?: string; + initialization_vector: string; + is_system_message?: boolean; + key_version?: number; + read_at?: string | null; + sender_id: string; + sequence_number: number; + system_message_type?: string | null; }; Update: { - avatar_url?: string | null; - bio?: string | null; + conversation_id?: string; created_at?: string; - display_name?: string | null; + deleted?: boolean; + delivered_at?: string | null; + edited?: boolean; + edited_at?: string | null; + encrypted_content?: string; id?: string; - updated_at?: string; - username?: string | null; - welcome_message_sent?: boolean; + initialization_vector?: string; + is_system_message?: boolean; + key_version?: number; + read_at?: string | null; + sender_id?: string; + sequence_number?: number; + system_message_type?: string | null; }; Relationships: [ { - foreignKeyName: 'user_profiles_id_fkey'; - columns: ['id']; - isOneToOne: true; - referencedRelation: 'users'; + foreignKeyName: 'messages_conversation_id_fkey'; + columns: ['conversation_id']; + isOneToOne: false; + referencedRelation: 'conversations'; + referencedColumns: ['id']; + }, + { + foreignKeyName: 'messages_sender_id_fkey'; + columns: ['sender_id']; + isOneToOne: false; + referencedRelation: 'user_profiles'; referencedColumns: ['id']; }, ]; }; oauth_states: { Row: { + created_at: string; + expires_at: string; id: string; - state_token: string; + ip_address: unknown; provider: string; - session_id: string | null; return_url: string | null; - ip_address: string | null; - user_agent: string | null; + session_id: string | null; + state_token: string; used: boolean; - created_at: string; - expires_at: string; + user_agent: string | null; }; Insert: { + created_at?: string; + expires_at?: string; id?: string; - state_token: string; + ip_address?: unknown; provider: string; - session_id?: string | null; return_url?: string | null; - ip_address?: string | null; - user_agent?: string | null; + session_id?: string | null; + state_token: string; used?: boolean; - created_at?: string; - expires_at?: string; + user_agent?: string | null; }; Update: { + created_at?: string; + expires_at?: string; id?: string; - state_token?: string; + ip_address?: unknown; provider?: string; - session_id?: string | null; return_url?: string | null; - ip_address?: string | null; - user_agent?: string | null; + session_id?: string | null; + state_token?: string; used?: boolean; - created_at?: string; - expires_at?: string; + user_agent?: string | null; }; Relationships: []; }; - rate_limit_attempts: { + payment_intents: { Row: { + amount: number; + created_at: string; + currency: string; + customer_email: string; + description: string | null; + expires_at: string; id: string; - identifier: string; - attempt_type: string; - ip_address: string | null; - user_agent: string | null; - window_start: string; - attempt_count: number; - locked_until: string | null; + idempotency_key: string | null; + interval: string | null; + metadata: Json | null; + parent_intent_id: string | null; + retry_count: number; + template_user_id: string; + type: string; + }; + Insert: { + amount: number; + created_at?: string; + currency?: string; + customer_email: string; + description?: string | null; + expires_at?: string; + id?: string; + idempotency_key?: string | null; + interval?: string | null; + metadata?: Json | null; + parent_intent_id?: string | null; + retry_count?: number; + template_user_id: string; + type: string; + }; + Update: { + amount?: number; + created_at?: string; + currency?: string; + customer_email?: string; + description?: string | null; + expires_at?: string; + id?: string; + idempotency_key?: string | null; + interval?: string | null; + metadata?: Json | null; + parent_intent_id?: string | null; + retry_count?: number; + template_user_id?: string; + type?: string; + }; + Relationships: [ + { + foreignKeyName: 'payment_intents_parent_intent_id_fkey'; + columns: ['parent_intent_id']; + isOneToOne: false; + referencedRelation: 'payment_intents'; + referencedColumns: ['id']; + }, + ]; + }; + payment_provider_config: { + Row: { + config_status: string; created_at: string; + enabled: boolean; + features: Json | null; + id: string; + priority: number; + provider: string; updated_at: string; }; Insert: { - id?: string; - identifier: string; - attempt_type: string; - ip_address?: string | null; - user_agent?: string | null; - window_start?: string; - attempt_count?: number; - locked_until?: string | null; + config_status?: string; created_at?: string; + enabled?: boolean; + features?: Json | null; + id?: string; + priority?: number; + provider: string; updated_at?: string; }; Update: { - id?: string; - identifier?: string; - attempt_type?: string; - ip_address?: string | null; - user_agent?: string | null; - window_start?: string; - attempt_count?: number; - locked_until?: string | null; + config_status?: string; created_at?: string; + enabled?: boolean; + features?: Json | null; + id?: string; + priority?: number; + provider?: string; updated_at?: string; }; Relationships: []; }; - auth_audit_logs: { + payment_results: { Row: { + charged_amount: number | null; + charged_currency: string | null; created_at: string; - event_data: Json | null; - event_type: string; + error_code: string | null; + error_message: string | null; id: string; - ip_address: string | null; - user_agent: string | null; - user_id: string | null; + intent_id: string; + provider: string; + provider_fee: number | null; + status: string; + transaction_id: string; + updated_at: string; + verification_method: string | null; + webhook_verified: boolean; }; Insert: { + charged_amount?: number | null; + charged_currency?: string | null; created_at?: string; - event_data?: Json | null; - event_type: string; + error_code?: string | null; + error_message?: string | null; id?: string; - ip_address?: string | null; - user_agent?: string | null; - user_id?: string | null; + intent_id: string; + provider: string; + provider_fee?: number | null; + status: string; + transaction_id: string; + updated_at?: string; + verification_method?: string | null; + webhook_verified?: boolean; }; Update: { + charged_amount?: number | null; + charged_currency?: string | null; created_at?: string; - event_data?: Json | null; - event_type?: string; + error_code?: string | null; + error_message?: string | null; id?: string; - ip_address?: string | null; - user_agent?: string | null; - user_id?: string | null; + intent_id?: string; + provider?: string; + provider_fee?: number | null; + status?: string; + transaction_id?: string; + updated_at?: string; + verification_method?: string | null; + webhook_verified?: boolean; }; Relationships: [ { - foreignKeyName: 'auth_audit_logs_user_id_fkey'; - columns: ['user_id']; + foreignKeyName: 'payment_results_intent_id_fkey'; + columns: ['intent_id']; isOneToOne: false; - referencedRelation: 'users'; + referencedRelation: 'payment_intents'; referencedColumns: ['id']; }, ]; }; - // ======================================================================== - // Messaging Tables (added for type safety) - // ======================================================================== - user_connections: { + profiles: { Row: { - id: string; - requester_id: string; - addressee_id: string; - status: 'pending' | 'accepted' | 'blocked' | 'declined'; + avatar_url: string | null; + bio: string | null; created_at: string; + display_name: string | null; + id: string; updated_at: string; }; Insert: { - id?: string; - requester_id: string; - addressee_id: string; - status: 'pending' | 'accepted' | 'blocked' | 'declined'; + avatar_url?: string | null; + bio?: string | null; created_at?: string; + display_name?: string | null; + id: string; updated_at?: string; }; Update: { - id?: string; - requester_id?: string; - addressee_id?: string; - status?: 'pending' | 'accepted' | 'blocked' | 'declined'; + avatar_url?: string | null; + bio?: string | null; created_at?: string; + display_name?: string | null; + id?: string; updated_at?: string; }; Relationships: []; }; - conversations: { + rate_limit_attempts: { Row: { - id: string; - participant_1_id: string | null; - participant_2_id: string | null; - last_message_at: string | null; - archived_by_participant_1: boolean; - archived_by_participant_2: boolean; + attempt_count: number; + attempt_type: string; created_at: string; - is_group: boolean; - group_name: string | null; - created_by: string | null; - current_key_version: number; + id: string; + identifier: string; + ip_address: unknown; + locked_until: string | null; + updated_at: string; + user_agent: string | null; + window_start: string; }; Insert: { - id?: string; - participant_1_id?: string | null; - participant_2_id?: string | null; - last_message_at?: string | null; - archived_by_participant_1?: boolean; - archived_by_participant_2?: boolean; + attempt_count?: number; + attempt_type: string; created_at?: string; - is_group?: boolean; - group_name?: string | null; - created_by?: string | null; - current_key_version?: number; + id?: string; + identifier: string; + ip_address?: unknown; + locked_until?: string | null; + updated_at?: string; + user_agent?: string | null; + window_start?: string; }; Update: { - id?: string; - participant_1_id?: string | null; - participant_2_id?: string | null; - last_message_at?: string | null; - archived_by_participant_1?: boolean; - archived_by_participant_2?: boolean; + attempt_count?: number; + attempt_type?: string; created_at?: string; - is_group?: boolean; - group_name?: string | null; - created_by?: string | null; - current_key_version?: number; + id?: string; + identifier?: string; + ip_address?: unknown; + locked_until?: string | null; + updated_at?: string; + user_agent?: string | null; + window_start?: string; }; Relationships: []; }; - conversation_members: { + subscriptions: { Row: { + canceled_at: string | null; + cancellation_reason: string | null; + created_at: string; + current_period_end: string | null; + current_period_start: string | null; + customer_email: string; + failed_payment_count: number; + grace_period_expires: string | null; id: string; - conversation_id: string; - user_id: string; - role: 'owner' | 'member'; - joined_at: string; - left_at: string | null; - key_version_joined: number; - key_status: 'active' | 'pending'; - archived: boolean; - muted: boolean; + next_billing_date: string | null; + plan_amount: number; + plan_interval: string; + provider: string; + provider_subscription_id: string; + retry_schedule: Json | null; + status: string; + template_user_id: string; + updated_at: string; }; Insert: { + canceled_at?: string | null; + cancellation_reason?: string | null; + created_at?: string; + current_period_end?: string | null; + current_period_start?: string | null; + customer_email: string; + failed_payment_count?: number; + grace_period_expires?: string | null; id?: string; - conversation_id: string; - user_id: string; - role?: 'owner' | 'member'; - joined_at?: string; - left_at?: string | null; - key_version_joined?: number; - key_status?: 'active' | 'pending'; - archived?: boolean; - muted?: boolean; + next_billing_date?: string | null; + plan_amount: number; + plan_interval: string; + provider: string; + provider_subscription_id: string; + retry_schedule?: Json | null; + status: string; + template_user_id: string; + updated_at?: string; }; Update: { + canceled_at?: string | null; + cancellation_reason?: string | null; + created_at?: string; + current_period_end?: string | null; + current_period_start?: string | null; + customer_email?: string; + failed_payment_count?: number; + grace_period_expires?: string | null; id?: string; - conversation_id?: string; - user_id?: string; - role?: 'owner' | 'member'; - joined_at?: string; - left_at?: string | null; - key_version_joined?: number; - key_status?: 'active' | 'pending'; - archived?: boolean; - muted?: boolean; + next_billing_date?: string | null; + plan_amount?: number; + plan_interval?: string; + provider?: string; + provider_subscription_id?: string; + retry_schedule?: Json | null; + status?: string; + template_user_id?: string; + updated_at?: string; }; Relationships: []; }; - group_keys: { + typing_indicators: { Row: { - id: string; conversation_id: string; + id: string; + is_typing: boolean; + updated_at: string; user_id: string; - key_version: number; - encrypted_key: string; - created_at: string; - created_by: string; }; Insert: { - id?: string; conversation_id: string; + id?: string; + is_typing?: boolean; + updated_at?: string; user_id: string; - key_version?: number; - encrypted_key: string; - created_at?: string; - created_by: string; }; Update: { - id?: string; conversation_id?: string; + id?: string; + is_typing?: boolean; + updated_at?: string; user_id?: string; - key_version?: number; - encrypted_key?: string; - created_at?: string; - created_by?: string; }; - Relationships: []; + Relationships: [ + { + foreignKeyName: 'typing_indicators_conversation_id_fkey'; + columns: ['conversation_id']; + isOneToOne: false; + referencedRelation: 'conversations'; + referencedColumns: ['id']; + }, + { + foreignKeyName: 'typing_indicators_user_id_fkey'; + columns: ['user_id']; + isOneToOne: false; + referencedRelation: 'user_profiles'; + referencedColumns: ['id']; + }, + ]; }; - messages: { + user_connections: { Row: { - id: string; - conversation_id: string; - sender_id: string; - encrypted_content: string; - initialization_vector: string; - sequence_number: number; - deleted: boolean; - edited: boolean; - edited_at: string | null; - delivered_at: string | null; - read_at: string | null; + addressee_id: string; created_at: string; - key_version: number; - is_system_message: boolean; - system_message_type: string | null; + id: string; + requester_id: string; + status: string; + updated_at: string; }; Insert: { - id?: string; - conversation_id: string; - sender_id: string; - encrypted_content: string; - initialization_vector: string; - sequence_number?: number; - deleted?: boolean; - edited?: boolean; - edited_at?: string | null; - delivered_at?: string | null; - read_at?: string | null; + addressee_id: string; created_at?: string; - key_version?: number; - is_system_message?: boolean; - system_message_type?: string | null; + id?: string; + requester_id: string; + status: string; + updated_at?: string; }; Update: { - id?: string; - conversation_id?: string; - sender_id?: string; - encrypted_content?: string; - initialization_vector?: string; - sequence_number?: number; - deleted?: boolean; - edited?: boolean; - edited_at?: string | null; - delivered_at?: string | null; - read_at?: string | null; + addressee_id?: string; created_at?: string; - key_version?: number; - is_system_message?: boolean; - system_message_type?: string | null; + id?: string; + requester_id?: string; + status?: string; + updated_at?: string; }; - Relationships: []; + Relationships: [ + { + foreignKeyName: 'user_connections_addressee_id_fkey'; + columns: ['addressee_id']; + isOneToOne: false; + referencedRelation: 'user_profiles'; + referencedColumns: ['id']; + }, + { + foreignKeyName: 'user_connections_requester_id_fkey'; + columns: ['requester_id']; + isOneToOne: false; + referencedRelation: 'user_profiles'; + referencedColumns: ['id']; + }, + ]; }; user_encryption_keys: { Row: { - id: string; - user_id: string; - public_key: Json; - encryption_salt: string | null; + created_at: string; device_id: string | null; + encryption_salt: string | null; expires_at: string | null; + id: string; + public_key: Json; revoked: boolean; - created_at: string; + user_id: string; }; Insert: { - id?: string; - user_id: string; - public_key: Json; - encryption_salt?: string | null; + created_at?: string; device_id?: string | null; + encryption_salt?: string | null; expires_at?: string | null; + id?: string; + public_key: Json; revoked?: boolean; - created_at?: string; + user_id: string; }; Update: { - id?: string; - user_id?: string; - public_key?: Json; - encryption_salt?: string | null; + created_at?: string; device_id?: string | null; + encryption_salt?: string | null; expires_at?: string | null; + id?: string; + public_key?: Json; revoked?: boolean; - created_at?: string; + user_id?: string; }; - Relationships: []; + Relationships: [ + { + foreignKeyName: 'user_encryption_keys_user_id_fkey'; + columns: ['user_id']; + isOneToOne: false; + referencedRelation: 'user_profiles'; + referencedColumns: ['id']; + }, + ]; }; - conversation_keys: { + user_profiles: { Row: { - id: string; - conversation_id: string; - user_id: string; - encrypted_shared_secret: string; - key_version: number; + avatar_url: string | null; + bio: string | null; created_at: string; + display_name: string | null; + id: string; + is_admin: boolean; + updated_at: string; + username: string | null; + welcome_message_sent: boolean; }; Insert: { - id?: string; - conversation_id: string; - user_id: string; - encrypted_shared_secret: string; - key_version?: number; + avatar_url?: string | null; + bio?: string | null; created_at?: string; + display_name?: string | null; + id: string; + is_admin?: boolean; + updated_at?: string; + username?: string | null; + welcome_message_sent?: boolean; }; Update: { - id?: string; - conversation_id?: string; - user_id?: string; - encrypted_shared_secret?: string; - key_version?: number; + avatar_url?: string | null; + bio?: string | null; created_at?: string; + display_name?: string | null; + id?: string; + is_admin?: boolean; + updated_at?: string; + username?: string | null; + welcome_message_sent?: boolean; }; Relationships: []; }; - typing_indicators: { + webhook_events: { Row: { + created_at: string; + event_data: Json; + event_type: string; id: string; - conversation_id: string; - user_id: string; - is_typing: boolean; - updated_at: string; + last_retry_at: string | null; + next_retry_at: string | null; + permanently_failed: boolean; + processed: boolean; + processed_at: string | null; + processing_attempts: number; + processing_error: string | null; + provider: string; + provider_event_id: string; + related_payment_id: string | null; + related_subscription_id: string | null; + retry_count: number; + signature: string; + signature_verified: boolean; }; Insert: { + created_at?: string; + event_data: Json; + event_type: string; id?: string; - conversation_id: string; - user_id: string; - is_typing?: boolean; - updated_at?: string; + last_retry_at?: string | null; + next_retry_at?: string | null; + permanently_failed?: boolean; + processed?: boolean; + processed_at?: string | null; + processing_attempts?: number; + processing_error?: string | null; + provider: string; + provider_event_id: string; + related_payment_id?: string | null; + related_subscription_id?: string | null; + retry_count?: number; + signature: string; + signature_verified?: boolean; }; Update: { + created_at?: string; + event_data?: Json; + event_type?: string; id?: string; - conversation_id?: string; - user_id?: string; - is_typing?: boolean; - updated_at?: string; + last_retry_at?: string | null; + next_retry_at?: string | null; + permanently_failed?: boolean; + processed?: boolean; + processed_at?: string | null; + processing_attempts?: number; + processing_error?: string | null; + provider?: string; + provider_event_id?: string; + related_payment_id?: string | null; + related_subscription_id?: string | null; + retry_count?: number; + signature?: string; + signature_verified?: boolean; }; - Relationships: []; + Relationships: [ + { + foreignKeyName: 'webhook_events_related_payment_id_fkey'; + columns: ['related_payment_id']; + isOneToOne: false; + referencedRelation: 'payment_results'; + referencedColumns: ['id']; + }, + { + foreignKeyName: 'webhook_events_related_subscription_id_fkey'; + columns: ['related_subscription_id']; + isOneToOne: false; + referencedRelation: 'subscriptions'; + referencedColumns: ['id']; + }, + ]; }; }; Views: { [_ in never]: never; }; Functions: { - delete_user: { - Args: Record; - Returns: void; + admin_audit_trends: { + Args: { + p_burst_gap?: string; + p_burst_min_attempts?: number; + p_end?: string; + p_start?: string; + }; + Returns: Json; }; + admin_auth_stats: { Args: never; Returns: Json }; + admin_conversation_list: { + Args: { p_limit?: number; p_offset?: number }; + Returns: Json; + }; + admin_list_users: { + Args: { p_limit?: number; p_offset?: number; p_search?: string }; + Returns: Json; + }; + admin_messaging_stats: { Args: never; Returns: Json }; + admin_messaging_trends: { + Args: { p_end?: string; p_start?: string; p_top_limit?: number }; + Returns: Json; + }; + admin_overview: { + Args: { p_end?: string; p_start?: string }; + Returns: Json; + }; + admin_payment_stats: { Args: never; Returns: Json }; + admin_payment_trends: { + Args: { p_end?: string; p_start?: string }; + Returns: Json; + }; + admin_user_stats: { Args: never; Returns: Json }; check_rate_limit: { Args: { - p_identifier: string; p_attempt_type: string; - p_ip_address: string | null; + p_identifier: string; + p_ip_address?: unknown; }; Returns: Json; }; + cleanup_old_audit_logs: { Args: never; Returns: undefined }; + is_conversation_member: { + Args: { check_user_id?: string; conv_id: string }; + Returns: boolean; + }; + is_conversation_owner: { + Args: { check_user_id?: string; conv_id: string }; + Returns: boolean; + }; record_failed_attempt: { Args: { - p_identifier: string; p_attempt_type: string; - p_ip_address: string | null; + p_identifier: string; + p_ip_address?: unknown; }; - Returns: void; + Returns: undefined; }; }; Enums: { diff --git a/src/services/messaging/group-service.ts b/src/services/messaging/group-service.ts index f70c3771..84631642 100644 --- a/src/services/messaging/group-service.ts +++ b/src/services/messaging/group-service.ts @@ -363,6 +363,10 @@ export class GroupService { // System messages aren't E2E-encrypted; store a tiny JSON marker. encrypted_content: JSON.stringify({ type, ...extra }), initialization_vector: 'system', + // Placeholder — the assign_sequence_number() trigger overwrites this on + // insert (matches the offline path in message-service.ts:335/516). The + // column is NOT NULL so a value must be supplied client-side. + sequence_number: 0, }); } catch (error) { logger.warn('Failed to record group system message', {