@fayz-ai/plugin-banking-br 0.10.42 → 0.10.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import { Download, Loader2 } from 'lucide-react';
3
3
  import { DatePicker, Button, Checkbox, toast } from '@fayz-ai/ui';
4
4
  import { connectionStatus, formatCurrency, createConnectionStore, getSupabaseClientOptional } from '@fayz-ai/core';
5
5
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
+ import { MIGRATIONS } from '#migrations/main';
6
7
 
7
8
  // src/connectors/connectorDef.tsx
8
9
  var FUNCTION = "plugbank-sync";
@@ -384,614 +385,6 @@ function buildBankingDomainEvents() {
384
385
  }
385
386
  ];
386
387
  }
387
-
388
- // src/migrations/index.ts
389
- var MIGRATION_000_BASELINE = `-- ============================================================================
390
- -- plugins/plugin-banking-br/src/migrations/000_baseline.sql \u2014 what installing this provisions, as one file.
391
- --
392
- -- GENERATED by scripts/emit-unit-baselines.mjs (#338). Do not edit by hand:
393
- -- change the schema, regenerate, and let scripts/check-chain-equivalence.mjs
394
- -- prove the result still matches.
395
- --
396
- -- This replaced 2 migration files. They are kept, unapplied, in
397
- -- migrations.archive/ \u2014 a year of decisions is worth reading even when it is no
398
- -- longer worth replaying.
399
- --
400
- -- Object order is fixed rather than dependency-sorted: schemas, types,
401
- -- sequences, tables, defaults, functions, constraints, views, indexes, foreign
402
- -- keys, triggers, row security, policies, grants. Dependencies BETWEEN units
403
- -- are carried by the order packages/db/chain.json declares.
404
- -- ============================================================================
405
-
406
- -- A LANGUAGE sql function binds its body at CREATE time, and 570 functions
407
- -- sorted by name are not sorted by who calls whom. This is what lets them load
408
- -- in any order; every body is still compiled on first call, so a genuinely
409
- -- broken reference surfaces then rather than never. Session-scoped.
410
- SET check_function_bodies = false;
411
-
412
-
413
-
414
- -- \u2500\u2500 default privileges: reset \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
415
- -- Cleared BEFORE anything is created, so every object below gets the ACL the
416
- -- dump describes rather than the image's defaults on top of it. Restored at
417
- -- the end of this file.
418
-
419
- DO $dp$ BEGIN
420
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;
421
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
422
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;
423
- END $dp$;
424
-
425
- DO $dp$ BEGIN
426
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;
427
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
428
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;
429
- END $dp$;
430
-
431
- DO $dp$ BEGIN
432
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;
433
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
434
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;
435
- END $dp$;
436
-
437
- DO $dp$ BEGIN
438
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;
439
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
440
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;
441
- END $dp$;
442
-
443
- DO $dp$ BEGIN
444
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;
445
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
446
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;
447
- END $dp$;
448
-
449
- DO $dp$ BEGIN
450
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;
451
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
452
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;
453
- END $dp$;
454
-
455
-
456
- -- \u2500\u2500 function \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
457
-
458
- CREATE FUNCTION public.plg_bank_emit_movement_imported() RETURNS trigger
459
- LANGUAGE plpgsql SECURITY DEFINER
460
- SET search_path TO 'public'
461
- AS $$
462
- DECLARE
463
- v_payload jsonb;
464
- BEGIN
465
- IF NEW.external_source IS NULL OR NEW.external_id IS NULL THEN
466
- RETURN NULL;
467
- END IF;
468
-
469
- -- The contract every consumer reads. Not the row: a whole-row payload would
470
- -- put the ledger's internal bookkeeping inside every webhook by default.
471
- v_payload := jsonb_build_object(
472
- 'movement_id', NEW.id::text,
473
- 'provider', NEW.external_source,
474
- 'external_id', NEW.external_id,
475
- 'direction', NEW.direction,
476
- 'amount', NEW.amount,
477
- 'payment_date', NEW.payment_date,
478
- 'description', NEW.notes,
479
- 'bank_account_id', NEW.bank_account_id);
480
-
481
- BEGIN
482
- PERFORM public.plg_emit_event(
483
- 'financial.movement.imported', v_payload, 'financial_movement',
484
- NEW.id::text, NEW.tenant_id, NULL);
485
- EXCEPTION WHEN OTHERS THEN
486
- -- Observability must never be able to reject the write that caused it.
487
- RAISE WARNING '[plg_bank_emit_movement_imported] % on %: %', TG_OP, TG_TABLE_NAME, SQLERRM;
488
- END;
489
-
490
- RETURN NULL;
491
- END;
492
- $$;
493
-
494
- CREATE FUNCTION public.plugbank_import_movements(p_payload jsonb) RETURNS jsonb
495
- LANGUAGE plpgsql SECURITY DEFINER
496
- SET search_path TO 'public'
497
- AS $$
498
- DECLARE
499
- v_tenants uuid[];
500
- v_tenant uuid;
501
- v_connection uuid;
502
- v_instance text;
503
- v_account uuid;
504
- v_connector text;
505
- v_trigger text;
506
- v_from date;
507
- v_to date;
508
- v_started timestamptz := now();
509
- v_line jsonb;
510
- v_external text;
511
- v_amount numeric(14,2);
512
- v_date date;
513
- v_fetched int := 0;
514
- v_imported int := 0;
515
- v_duplicates int := 0;
516
- v_inserted uuid;
517
- v_run uuid;
518
- BEGIN
519
- v_connection := nullif(btrim(p_payload->>'integration_id'), '')::uuid;
520
- IF v_connection IS NULL THEN
521
- RAISE EXCEPTION 'plugbank_import_movements: integration_id is required'
522
- USING ERRCODE = 'invalid_parameter_value';
523
- END IF;
524
-
525
- SELECT c.tenant_id, c.instance_key, c.connector_id
526
- INTO v_tenant, v_instance, v_connector
527
- FROM public.plg_connections c
528
- WHERE c.id = v_connection AND c.active;
529
-
530
- IF v_tenant IS NULL THEN
531
- RAISE EXCEPTION 'plugbank_import_movements: no active connection %', v_connection
532
- USING ERRCODE = 'no_data_found';
533
- END IF;
534
-
535
- -- The account this connection is for. \`instance_key\` is text because it also
536
- -- carries things that are not uuids for other connectors (a Bling company, a
537
- -- gateway instance); here it is the bank account, and '' is the account-less
538
- -- default that \`bank_account_id IS NULL\` used to mean.
539
- v_account := nullif(btrim(v_instance), '')::uuid;
540
-
541
- -- SECURITY DEFINER bypasses RLS, so the membership check the policy would
542
- -- have made has to happen here.
543
- SELECT array_agg(t) INTO v_tenants FROM public.user_tenant_ids() AS t;
544
- IF auth.uid() IS NOT NULL AND NOT (v_tenant = ANY (COALESCE(v_tenants, '{}'::uuid[]))) THEN
545
- RAISE EXCEPTION 'plugbank_import_movements: caller is not a member of tenant %', v_tenant
546
- USING ERRCODE = 'insufficient_privilege';
547
- END IF;
548
-
549
- -- Refusing beats ignoring: a caller that believes it is importing into
550
- -- another tenant must be told, not silently redirected into this one.
551
- IF nullif(btrim(p_payload->>'tenant_id'), '') IS NOT NULL
552
- AND (p_payload->>'tenant_id')::uuid IS DISTINCT FROM v_tenant THEN
553
- RAISE EXCEPTION 'plugbank_import_movements: payload names another tenant; the tenant comes from the connection'
554
- USING ERRCODE = 'insufficient_privilege';
555
- END IF;
556
-
557
- -- Same rule as the tenant, one level down: the account is the connection's,
558
- -- and a payload naming a different one is a caller importing into the wrong
559
- -- ledger account. 002 let the payload override it silently \u2014 with one
560
- -- connection per account that override is how two accounts get mixed.
561
- IF nullif(btrim(p_payload->>'bank_account_id'), '') IS NOT NULL
562
- AND (p_payload->>'bank_account_id')::uuid IS DISTINCT FROM v_account THEN
563
- RAISE EXCEPTION 'plugbank_import_movements: payload names another bank account; the account comes from the connection'
564
- USING ERRCODE = 'insufficient_privilege';
565
- END IF;
566
-
567
- v_from := nullif(btrim(p_payload->>'period_from'), '')::date;
568
- v_to := nullif(btrim(p_payload->>'period_to'), '')::date;
569
- v_trigger := lower(coalesce(nullif(btrim(p_payload->>'trigger'), ''), 'manual'));
570
- IF v_trigger NOT IN ('on-write', 'scheduled', 'manual', 'webhook') THEN
571
- RAISE EXCEPTION 'plugbank_import_movements: unknown trigger %', v_trigger
572
- USING ERRCODE = 'invalid_parameter_value';
573
- END IF;
574
-
575
- FOR v_line IN SELECT * FROM jsonb_array_elements(coalesce(p_payload->'lines', '[]'::jsonb))
576
- LOOP
577
- v_fetched := v_fetched + 1;
578
- v_external := nullif(btrim(v_line->>'external_id'), '');
579
- v_date := nullif(btrim(v_line->>'date'), '')::date;
580
- v_amount := abs(coalesce((v_line->>'amount')::numeric, 0));
581
-
582
- -- A line with no provider id cannot be recognised on re-delivery, so it
583
- -- would import again on every run. Skipping it is the lesser wrong.
584
- IF v_external IS NULL OR v_date IS NULL OR v_amount = 0 THEN
585
- CONTINUE;
586
- END IF;
587
-
588
- INSERT INTO public.plg_financial_movements (
589
- tenant_id, direction, movement_kind, amount, paid_amount, status,
590
- due_date, payment_date, bank_account_id, notes, external_id, external_source)
591
- VALUES (
592
- v_tenant,
593
- CASE WHEN upper(coalesce(v_line->>'type', 'C')) LIKE 'C%' THEN 'credit' ELSE 'debit' END,
594
- 'payment', v_amount, v_amount, 'paid',
595
- v_date, v_date, v_account,
596
- nullif(btrim(v_line->>'description'), ''),
597
- v_external, coalesce(v_connector, 'plugbank'))
598
- ON CONFLICT (tenant_id, external_source, external_id)
599
- WHERE external_id IS NOT NULL AND external_source IS NOT NULL
600
- DO NOTHING
601
- RETURNING id INTO v_inserted;
602
-
603
- IF v_inserted IS NULL THEN
604
- v_duplicates := v_duplicates + 1;
605
- ELSE
606
- v_imported := v_imported + 1;
607
- END IF;
608
- v_inserted := NULL;
609
- END LOOP;
610
-
611
- UPDATE public.plg_connections
612
- SET last_sync_at = now(), status = 'connected', status_detail = NULL, updated_at = now()
613
- WHERE id = v_connection;
614
-
615
- -- \`stats\` is where the three facts a manual statement import has and a generic
616
- -- run does not go: the operator picked a period, a re-import of that period is
617
- -- mostly duplicates by design, and someone pressed the button. The panel reads
618
- -- all three back from here.
619
- INSERT INTO public.plg_sync_runs (
620
- tenant_id, connection_id, connector_id, direction, trigger_kind, status,
621
- stream, fetched, written, stats, started_at, finished_at)
622
- VALUES (
623
- v_tenant, v_connection, coalesce(v_connector, 'plugbank'), 'inbound', v_trigger, 'success',
624
- 'statement', v_fetched, v_imported,
625
- jsonb_strip_nulls(jsonb_build_object(
626
- 'duplicates', v_duplicates,
627
- 'period_from', v_from,
628
- 'period_to', v_to,
629
- 'triggered_by_user_id', auth.uid())),
630
- v_started, now())
631
- RETURNING id INTO v_run;
632
-
633
- RETURN jsonb_build_object(
634
- 'ok', true,
635
- 'sync_run_id', v_run,
636
- 'fetched', v_fetched,
637
- 'imported', v_imported,
638
- 'duplicates', v_duplicates);
639
- END;
640
- $$;
641
-
642
-
643
- -- \u2500\u2500 trigger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
644
-
645
- CREATE TRIGGER plg_financial_movements_emit_imported AFTER INSERT ON public.plg_financial_movements FOR EACH ROW EXECUTE FUNCTION public.plg_bank_emit_movement_imported();
646
-
647
-
648
- -- \u2500\u2500 acl \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
649
-
650
- REVOKE ALL ON FUNCTION public.plg_bank_emit_movement_imported() FROM PUBLIC;
651
- GRANT ALL ON FUNCTION public.plg_bank_emit_movement_imported() TO authenticated;
652
- GRANT ALL ON FUNCTION public.plg_bank_emit_movement_imported() TO service_role;
653
-
654
- REVOKE ALL ON FUNCTION public.plugbank_import_movements(p_payload jsonb) FROM PUBLIC;
655
- GRANT ALL ON FUNCTION public.plugbank_import_movements(p_payload jsonb) TO authenticated;
656
- GRANT ALL ON FUNCTION public.plugbank_import_movements(p_payload jsonb) TO service_role;
657
-
658
-
659
- -- \u2500\u2500 comment \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
660
-
661
- COMMENT ON FUNCTION public.plugbank_import_movements(p_payload jsonb) IS 'Import bank-statement lines into the financial ledger and file the run in plg_sync_runs. The tenant AND the bank account come from the plg_connections row named by integration_id, never from the payload.';
662
-
663
-
664
- -- \u2500\u2500 default privileges: restore \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
665
- -- Put back, so the NEXT thing installed inherits what the chain had.
666
-
667
- DO $dp$ BEGIN
668
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;
669
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
670
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;
671
- END $dp$;
672
-
673
- DO $dp$ BEGIN
674
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;
675
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
676
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;
677
- END $dp$;
678
-
679
- DO $dp$ BEGIN
680
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;
681
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
682
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;
683
- END $dp$;
684
-
685
- DO $dp$ BEGIN
686
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;
687
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
688
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;
689
- END $dp$;
690
-
691
- DO $dp$ BEGIN
692
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;
693
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
694
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;
695
- END $dp$;
696
-
697
- DO $dp$ BEGIN
698
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;
699
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
700
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;
701
- END $dp$;
702
-
703
- DO $dp$ BEGIN
704
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;
705
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
706
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;
707
- END $dp$;
708
-
709
- DO $dp$ BEGIN
710
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;
711
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
712
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;
713
- END $dp$;
714
-
715
- DO $dp$ BEGIN
716
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;
717
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
718
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;
719
- END $dp$;
720
-
721
- DO $dp$ BEGIN
722
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;
723
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
724
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;
725
- END $dp$;
726
-
727
- DO $dp$ BEGIN
728
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;
729
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
730
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;
731
- END $dp$;
732
-
733
- DO $dp$ BEGIN
734
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;
735
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
736
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;
737
- END $dp$;
738
-
739
- DO $dp$ BEGIN
740
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO anon$stmt$;
741
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
742
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO anon$stmt$;
743
- END $dp$;
744
-
745
- DO $dp$ BEGIN
746
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;
747
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
748
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;
749
- END $dp$;
750
-
751
- DO $dp$ BEGIN
752
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;
753
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
754
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;
755
- END $dp$;
756
-
757
- DO $dp$ BEGIN
758
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;
759
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
760
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;
761
- END $dp$;
762
-
763
- DO $dp$ BEGIN
764
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT MAINTAIN ON TABLES TO anon$stmt$;
765
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
766
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT MAINTAIN ON TABLES TO anon$stmt$;
767
- END $dp$;
768
-
769
- DO $dp$ BEGIN
770
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;
771
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
772
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;
773
- END $dp$;
774
-
775
- DO $dp$ BEGIN
776
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;
777
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
778
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;
779
- END $dp$;
780
-
781
- DO $dp$ BEGIN
782
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;
783
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
784
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;
785
- END $dp$;
786
-
787
- DO $dp$ BEGIN
788
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO anon$stmt$;
789
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
790
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO anon$stmt$;
791
- END $dp$;
792
-
793
- DO $dp$ BEGIN
794
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;
795
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
796
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;
797
- END $dp$;
798
-
799
- DO $dp$ BEGIN
800
- EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;
801
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
802
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;
803
- END $dp$;
804
-
805
- DO $dp$ BEGIN
806
- EXECUTE $stmt$--
807
- -- PostgreSQL database dump complete
808
- --$stmt$;
809
- EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
810
- RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$--
811
- -- PostgreSQL database dump complete
812
- --$stmt$;
813
- END $dp$;
814
- `;
815
- var MIGRATION_001_THE_IMPORT_RPC_NAMES_REAL_TABLES = `-- ==========================================================================
816
- -- 001_the_import_rpc_names_real_tables \u2014 plugbank_import_movements
817
- -- stops reaching through 141's bridge (#352).
818
- --
819
- -- 107 renamed the connector spine out of the \`plg_\` prefix \u2014 the prefix means
820
- -- "a plugin owns this", and the connector framework's tables are the BASE's.
821
- -- 141 left \`plg_connections\` and \`plg_sync_runs\` answering as views so callers
822
- -- could move without a synchronised deploy. This function never moved, and it
823
- -- is not alone: \`plg_connections\` had 13 callers and \`connections\` had none.
824
- -- The bridge became the road.
825
- --
826
- -- The body is 000_baseline's, unchanged except for the three names. It is not
827
- -- an edit of that file \u2014 an applied migration is immutable and the ledger
828
- -- treats drift as a hard stop \u2014 it is a CREATE OR REPLACE that lands after it.
829
- --
830
- -- Why this plugin has to carry its own: the function is banking-br's, and the
831
- -- views it stops using are the spine's. The spine runs FIRST, so a spine file
832
- -- cannot drop a view that a plugin applied later still reads. Each package
833
- -- moves its own callers; the aliases go once nobody is left.
834
- --
835
- -- Replay-safe: CREATE OR REPLACE, same signature, same grants (which 000 set
836
- -- and this does not touch).
837
- -- ==========================================================================
838
-
839
- CREATE OR REPLACE FUNCTION public.plugbank_import_movements(p_payload jsonb) RETURNS jsonb
840
- LANGUAGE plpgsql SECURITY DEFINER
841
- SET search_path TO 'public'
842
- AS $$
843
- DECLARE
844
- v_tenants uuid[];
845
- v_tenant uuid;
846
- v_connection uuid;
847
- v_instance text;
848
- v_account uuid;
849
- v_connector text;
850
- v_trigger text;
851
- v_from date;
852
- v_to date;
853
- v_started timestamptz := now();
854
- v_line jsonb;
855
- v_external text;
856
- v_amount numeric(14,2);
857
- v_date date;
858
- v_fetched int := 0;
859
- v_imported int := 0;
860
- v_duplicates int := 0;
861
- v_inserted uuid;
862
- v_run uuid;
863
- BEGIN
864
- v_connection := nullif(btrim(p_payload->>'integration_id'), '')::uuid;
865
- IF v_connection IS NULL THEN
866
- RAISE EXCEPTION 'plugbank_import_movements: integration_id is required'
867
- USING ERRCODE = 'invalid_parameter_value';
868
- END IF;
869
-
870
- SELECT c.tenant_id, c.instance_key, c.connector_id
871
- INTO v_tenant, v_instance, v_connector
872
- FROM public.connections c
873
- WHERE c.id = v_connection AND c.active;
874
-
875
- IF v_tenant IS NULL THEN
876
- RAISE EXCEPTION 'plugbank_import_movements: no active connection %', v_connection
877
- USING ERRCODE = 'no_data_found';
878
- END IF;
879
-
880
- -- The account this connection is for. \`instance_key\` is text because it also
881
- -- carries things that are not uuids for other connectors (a Bling company, a
882
- -- gateway instance); here it is the bank account, and '' is the account-less
883
- -- default that \`bank_account_id IS NULL\` used to mean.
884
- v_account := nullif(btrim(v_instance), '')::uuid;
885
-
886
- -- SECURITY DEFINER bypasses RLS, so the membership check the policy would
887
- -- have made has to happen here.
888
- SELECT array_agg(t) INTO v_tenants FROM public.user_tenant_ids() AS t;
889
- IF auth.uid() IS NOT NULL AND NOT (v_tenant = ANY (COALESCE(v_tenants, '{}'::uuid[]))) THEN
890
- RAISE EXCEPTION 'plugbank_import_movements: caller is not a member of tenant %', v_tenant
891
- USING ERRCODE = 'insufficient_privilege';
892
- END IF;
893
-
894
- -- Refusing beats ignoring: a caller that believes it is importing into
895
- -- another tenant must be told, not silently redirected into this one.
896
- IF nullif(btrim(p_payload->>'tenant_id'), '') IS NOT NULL
897
- AND (p_payload->>'tenant_id')::uuid IS DISTINCT FROM v_tenant THEN
898
- RAISE EXCEPTION 'plugbank_import_movements: payload names another tenant; the tenant comes from the connection'
899
- USING ERRCODE = 'insufficient_privilege';
900
- END IF;
901
-
902
- -- Same rule as the tenant, one level down: the account is the connection's,
903
- -- and a payload naming a different one is a caller importing into the wrong
904
- -- ledger account. 002 let the payload override it silently \u2014 with one
905
- -- connection per account that override is how two accounts get mixed.
906
- IF nullif(btrim(p_payload->>'bank_account_id'), '') IS NOT NULL
907
- AND (p_payload->>'bank_account_id')::uuid IS DISTINCT FROM v_account THEN
908
- RAISE EXCEPTION 'plugbank_import_movements: payload names another bank account; the account comes from the connection'
909
- USING ERRCODE = 'insufficient_privilege';
910
- END IF;
911
-
912
- v_from := nullif(btrim(p_payload->>'period_from'), '')::date;
913
- v_to := nullif(btrim(p_payload->>'period_to'), '')::date;
914
- v_trigger := lower(coalesce(nullif(btrim(p_payload->>'trigger'), ''), 'manual'));
915
- IF v_trigger NOT IN ('on-write', 'scheduled', 'manual', 'webhook') THEN
916
- RAISE EXCEPTION 'plugbank_import_movements: unknown trigger %', v_trigger
917
- USING ERRCODE = 'invalid_parameter_value';
918
- END IF;
919
-
920
- FOR v_line IN SELECT * FROM jsonb_array_elements(coalesce(p_payload->'lines', '[]'::jsonb))
921
- LOOP
922
- v_fetched := v_fetched + 1;
923
- v_external := nullif(btrim(v_line->>'external_id'), '');
924
- v_date := nullif(btrim(v_line->>'date'), '')::date;
925
- v_amount := abs(coalesce((v_line->>'amount')::numeric, 0));
926
-
927
- -- A line with no provider id cannot be recognised on re-delivery, so it
928
- -- would import again on every run. Skipping it is the lesser wrong.
929
- IF v_external IS NULL OR v_date IS NULL OR v_amount = 0 THEN
930
- CONTINUE;
931
- END IF;
932
-
933
- INSERT INTO public.plg_financial_movements (
934
- tenant_id, direction, movement_kind, amount, paid_amount, status,
935
- due_date, payment_date, bank_account_id, notes, external_id, external_source)
936
- VALUES (
937
- v_tenant,
938
- CASE WHEN upper(coalesce(v_line->>'type', 'C')) LIKE 'C%' THEN 'credit' ELSE 'debit' END,
939
- 'payment', v_amount, v_amount, 'paid',
940
- v_date, v_date, v_account,
941
- nullif(btrim(v_line->>'description'), ''),
942
- v_external, coalesce(v_connector, 'plugbank'))
943
- ON CONFLICT (tenant_id, external_source, external_id)
944
- WHERE external_id IS NOT NULL AND external_source IS NOT NULL
945
- DO NOTHING
946
- RETURNING id INTO v_inserted;
947
-
948
- IF v_inserted IS NULL THEN
949
- v_duplicates := v_duplicates + 1;
950
- ELSE
951
- v_imported := v_imported + 1;
952
- END IF;
953
- v_inserted := NULL;
954
- END LOOP;
955
-
956
- UPDATE public.connections
957
- SET last_sync_at = now(), status = 'connected', status_detail = NULL, updated_at = now()
958
- WHERE id = v_connection;
959
-
960
- -- \`stats\` is where the three facts a manual statement import has and a generic
961
- -- run does not go: the operator picked a period, a re-import of that period is
962
- -- mostly duplicates by design, and someone pressed the button. The panel reads
963
- -- all three back from here.
964
- INSERT INTO public.sync_runs (
965
- tenant_id, connection_id, connector_id, direction, trigger_kind, status,
966
- stream, fetched, written, stats, started_at, finished_at)
967
- VALUES (
968
- v_tenant, v_connection, coalesce(v_connector, 'plugbank'), 'inbound', v_trigger, 'success',
969
- 'statement', v_fetched, v_imported,
970
- jsonb_strip_nulls(jsonb_build_object(
971
- 'duplicates', v_duplicates,
972
- 'period_from', v_from,
973
- 'period_to', v_to,
974
- 'triggered_by_user_id', auth.uid())),
975
- v_started, now())
976
- RETURNING id INTO v_run;
977
-
978
- RETURN jsonb_build_object(
979
- 'ok', true,
980
- 'sync_run_id', v_run,
981
- 'fetched', v_fetched,
982
- 'imported', v_imported,
983
- 'duplicates', v_duplicates);
984
- END;
985
- $$;
986
- COMMENT ON FUNCTION public.plugbank_import_movements(p_payload jsonb) IS
987
- 'Import bank-statement lines into the financial ledger and file the run in public.sync_runs. The tenant AND the bank account come from the public.connections row named by integration_id, never from the payload.';
988
- `;
989
- var MIGRATIONS = [
990
- { id: "000_baseline", sql: MIGRATION_000_BASELINE },
991
- { id: "001_the_import_rpc_names_real_tables", sql: MIGRATION_001_THE_IMPORT_RPC_NAMES_REAL_TABLES }
992
- ];
993
-
994
- // src/index.ts
995
388
  function createOpenBankingPlugin() {
996
389
  return {
997
390
  id: "openbanking",