* fix: BankID graceful fallback, bank import fixes, and account deletion - BankID: surface service_unavailable state in login/register with password fallback messaging; structured error codes in tic extension; poll failure counter in BankIdAuth avoids infinite retry when TIC API is down. - Transactions: allow deleting unbooked bank-synced and imported transactions (only posted entries remain protected); detect reconnect duplicates by also checking unbooked bank-synced rows in content-based dedup. - Enable Banking: key external_id by account iban/uid instead of connection id so reconnects don't create duplicates. - Bank import: detect SEB privatbanken CSV variant (Bokföringsdatum / Valutadatum headers) via regex. - Banking settings: replace full-screen sync loader with toast notifications. - delete_user_account: raise statement_timeout, pre-clear NO ACTION FK references, and disable audit/immutability triggers during CASCADE. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Greptile review — ingest dedup scoping and migration EXCEPTION handler - ingest: split buildExistingTransactionMap into two maps. Booked rows (any source) remain consumed by any incoming raw transaction, but unbooked enable_banking slots are only consumed when the incoming raw transaction is also enable_banking. This preserves reconnect dedup while preventing false positives where a pending bank-synced row silently blocks a legitimately separate CSV row with the same date/amount. - delete_user_account: add EXCEPTION WHEN OTHERS handler that re-enables every legally required enforcement trigger before re-raising. Postgres transactional DDL already rolls back on abort, but the explicit guard makes the intent unambiguous and covers sub-transaction edge cases so enforcement triggers are never left disabled. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
117 lines
6.4 KiB
PL/PgSQL
117 lines
6.4 KiB
PL/PgSQL
-- Fix delete_user_account: timeout + cascade blockers.
|
|
--
|
|
-- Problem 1: ALTER TABLE DDL requires ACCESS EXCLUSIVE locks. With the
|
|
-- authenticated role's 8s statement_timeout, any concurrent read causes
|
|
-- error 57014. Fixed via function-level SET statement_timeout = '60s'.
|
|
--
|
|
-- Problem 2: Several NO ACTION FKs block the CASCADE chain from
|
|
-- auth.users → companies → children:
|
|
-- audit_log.company_id → companies (NO ACTION)
|
|
-- fiscal_periods.previous_period_id → fiscal_periods (NO ACTION, self-ref)
|
|
-- fiscal_periods.closing_entry_id → journal_entries (NO ACTION)
|
|
-- fiscal_periods.opening_balance_entry_id → journal_entries (NO ACTION)
|
|
-- Fixed by explicitly clearing these before the CASCADE fires.
|
|
|
|
CREATE OR REPLACE FUNCTION public.delete_user_account(target_user_id uuid)
|
|
RETURNS void
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = public
|
|
SET statement_timeout = '60s'
|
|
SET lock_timeout = '10s'
|
|
AS $$
|
|
DECLARE
|
|
v_company_ids uuid[];
|
|
BEGIN
|
|
-- Only allow users to delete their own account
|
|
IF auth.uid() IS DISTINCT FROM target_user_id THEN
|
|
RAISE EXCEPTION 'Can only delete your own account';
|
|
END IF;
|
|
|
|
-- Collect companies owned by this user (CASCADE will delete these)
|
|
SELECT array_agg(id) INTO v_company_ids
|
|
FROM public.companies
|
|
WHERE created_by = target_user_id;
|
|
|
|
-- Clear active_company_id to avoid FK conflicts during CASCADE
|
|
DELETE FROM public.user_preferences WHERE user_id = target_user_id;
|
|
|
|
-- Explicitly delete from extension_data (missing DELETE RLS policy
|
|
-- causes CASCADE from auth.users to fail even with ON DELETE CASCADE)
|
|
DELETE FROM public.extension_data WHERE user_id = target_user_id;
|
|
|
|
-- Disable BEFORE DELETE triggers that block deletion
|
|
ALTER TABLE audit_log DISABLE TRIGGER audit_log_no_delete;
|
|
ALTER TABLE payment_match_log DISABLE TRIGGER payment_match_log_no_delete;
|
|
ALTER TABLE document_attachments DISABLE TRIGGER block_document_deletion;
|
|
ALTER TABLE journal_entries DISABLE TRIGGER enforce_journal_entry_immutability;
|
|
ALTER TABLE journal_entries DISABLE TRIGGER enforce_retention_journal_entries;
|
|
ALTER TABLE journal_entry_lines DISABLE TRIGGER enforce_journal_entry_line_immutability;
|
|
|
|
-- Disable AFTER DELETE audit triggers (they INSERT into audit_log during
|
|
-- CASCADE, which would create orphaned rows after the user is gone)
|
|
ALTER TABLE api_keys DISABLE TRIGGER audit_api_keys;
|
|
ALTER TABLE chart_of_accounts DISABLE TRIGGER audit_chart_of_accounts;
|
|
ALTER TABLE company_settings DISABLE TRIGGER audit_company_settings;
|
|
ALTER TABLE document_attachments DISABLE TRIGGER audit_document_attachments;
|
|
ALTER TABLE extension_data DISABLE TRIGGER audit_extension_data;
|
|
ALTER TABLE fiscal_periods DISABLE TRIGGER audit_fiscal_periods;
|
|
ALTER TABLE journal_entries DISABLE TRIGGER audit_journal_entries;
|
|
ALTER TABLE supplier_invoices DISABLE TRIGGER audit_supplier_invoices;
|
|
|
|
-- Clear NO ACTION FK references that block the CASCADE chain
|
|
IF v_company_ids IS NOT NULL THEN
|
|
-- audit_log.company_id → companies (NO ACTION)
|
|
DELETE FROM public.audit_log
|
|
WHERE company_id = ANY(v_company_ids);
|
|
|
|
-- fiscal_periods self-ref and cross-refs to journal_entries (NO ACTION)
|
|
UPDATE public.fiscal_periods
|
|
SET previous_period_id = NULL,
|
|
closing_entry_id = NULL,
|
|
opening_balance_entry_id = NULL
|
|
WHERE company_id = ANY(v_company_ids);
|
|
END IF;
|
|
|
|
-- Delete from auth.users — ON DELETE CASCADE handles all public tables
|
|
DELETE FROM auth.users WHERE id = target_user_id;
|
|
|
|
-- Re-enable all triggers
|
|
ALTER TABLE audit_log ENABLE TRIGGER audit_log_no_delete;
|
|
ALTER TABLE payment_match_log ENABLE TRIGGER payment_match_log_no_delete;
|
|
ALTER TABLE document_attachments ENABLE TRIGGER block_document_deletion;
|
|
ALTER TABLE journal_entries ENABLE TRIGGER enforce_journal_entry_immutability;
|
|
ALTER TABLE journal_entries ENABLE TRIGGER enforce_retention_journal_entries;
|
|
ALTER TABLE journal_entry_lines ENABLE TRIGGER enforce_journal_entry_line_immutability;
|
|
ALTER TABLE api_keys ENABLE TRIGGER audit_api_keys;
|
|
ALTER TABLE chart_of_accounts ENABLE TRIGGER audit_chart_of_accounts;
|
|
ALTER TABLE company_settings ENABLE TRIGGER audit_company_settings;
|
|
ALTER TABLE document_attachments ENABLE TRIGGER audit_document_attachments;
|
|
ALTER TABLE extension_data ENABLE TRIGGER audit_extension_data;
|
|
ALTER TABLE fiscal_periods ENABLE TRIGGER audit_fiscal_periods;
|
|
ALTER TABLE journal_entries ENABLE TRIGGER audit_journal_entries;
|
|
ALTER TABLE supplier_invoices ENABLE TRIGGER audit_supplier_invoices;
|
|
|
|
EXCEPTION WHEN OTHERS THEN
|
|
-- PostgreSQL transactional DDL already rolls back the DISABLE TRIGGER
|
|
-- statements if the function aborts, but re-enable explicitly as a
|
|
-- defensive guard against sub-transaction edge cases so enforcement
|
|
-- triggers are never left disabled on the live tables.
|
|
BEGIN ALTER TABLE audit_log ENABLE TRIGGER audit_log_no_delete; EXCEPTION WHEN OTHERS THEN NULL; END;
|
|
BEGIN ALTER TABLE payment_match_log ENABLE TRIGGER payment_match_log_no_delete; EXCEPTION WHEN OTHERS THEN NULL; END;
|
|
BEGIN ALTER TABLE document_attachments ENABLE TRIGGER block_document_deletion; EXCEPTION WHEN OTHERS THEN NULL; END;
|
|
BEGIN ALTER TABLE journal_entries ENABLE TRIGGER enforce_journal_entry_immutability; EXCEPTION WHEN OTHERS THEN NULL; END;
|
|
BEGIN ALTER TABLE journal_entries ENABLE TRIGGER enforce_retention_journal_entries; EXCEPTION WHEN OTHERS THEN NULL; END;
|
|
BEGIN ALTER TABLE journal_entry_lines ENABLE TRIGGER enforce_journal_entry_line_immutability; EXCEPTION WHEN OTHERS THEN NULL; END;
|
|
BEGIN ALTER TABLE api_keys ENABLE TRIGGER audit_api_keys; EXCEPTION WHEN OTHERS THEN NULL; END;
|
|
BEGIN ALTER TABLE chart_of_accounts ENABLE TRIGGER audit_chart_of_accounts; EXCEPTION WHEN OTHERS THEN NULL; END;
|
|
BEGIN ALTER TABLE company_settings ENABLE TRIGGER audit_company_settings; EXCEPTION WHEN OTHERS THEN NULL; END;
|
|
BEGIN ALTER TABLE document_attachments ENABLE TRIGGER audit_document_attachments; EXCEPTION WHEN OTHERS THEN NULL; END;
|
|
BEGIN ALTER TABLE extension_data ENABLE TRIGGER audit_extension_data; EXCEPTION WHEN OTHERS THEN NULL; END;
|
|
BEGIN ALTER TABLE fiscal_periods ENABLE TRIGGER audit_fiscal_periods; EXCEPTION WHEN OTHERS THEN NULL; END;
|
|
BEGIN ALTER TABLE journal_entries ENABLE TRIGGER audit_journal_entries; EXCEPTION WHEN OTHERS THEN NULL; END;
|
|
BEGIN ALTER TABLE supplier_invoices ENABLE TRIGGER audit_supplier_invoices; EXCEPTION WHEN OTHERS THEN NULL; END;
|
|
RAISE;
|
|
END;
|
|
$$;
|