@fayz-ai/plugin-banking-br 0.10.6 → 0.10.7
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.d.ts.map +1 -1
- package/dist/index.js +359 -298
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.d.ts +2 -2
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -49,7 +49,7 @@ function toSyncRun(run) {
|
|
|
49
49
|
periodFrom: stats.period_from ?? void 0,
|
|
50
50
|
periodTo: stats.period_to ?? void 0,
|
|
51
51
|
duplicates: Number(stats.duplicates ?? 0),
|
|
52
|
-
//
|
|
52
|
+
// sync_runs has no created_at: a run is identified by when it STARTED,
|
|
53
53
|
// which is what the old created_at recorded anyway (the row was inserted at
|
|
54
54
|
// the end, so the two disagreed by the length of the import).
|
|
55
55
|
createdAt: run.startedAt ?? ""
|
|
@@ -90,7 +90,7 @@ function createOpenBankingProvider() {
|
|
|
90
90
|
const { data, error } = await supabase.functions.invoke(FUNCTION, {
|
|
91
91
|
body: {
|
|
92
92
|
action: "import_transactions",
|
|
93
|
-
// Still `integrationId` on the wire: it now carries a
|
|
93
|
+
// Still `integrationId` on the wire: it now carries a connections
|
|
94
94
|
// id, and keeping the key means the deployed function needs no
|
|
95
95
|
// coordinated release. The RPC takes the tenant AND the bank account
|
|
96
96
|
// from that row, so neither can be steered from here.
|
|
@@ -329,7 +329,7 @@ var openBankingConnector = {
|
|
|
329
329
|
await provider.saveIntegration();
|
|
330
330
|
},
|
|
331
331
|
disconnect: () => provider.disconnect(),
|
|
332
|
-
// Every import files a `
|
|
332
|
+
// Every import files a `sync_runs` row, and the shared surface renders it.
|
|
333
333
|
// What is connector-specific is not the VIEW, it is the meaning of `stats`:
|
|
334
334
|
// "de 01/08 a 12/08 · 3 duplicadas" is three keys only this connector can
|
|
335
335
|
// read, turned here into the one sentence every run carries. Scoped to the
|
|
@@ -393,121 +393,122 @@ function buildBankingDomainEvents() {
|
|
|
393
393
|
}
|
|
394
394
|
|
|
395
395
|
// src/migrations/index.ts
|
|
396
|
-
var
|
|
397
|
-
-- plugin-banking-br
|
|
398
|
-
-- gets a door it cannot be routed around, and the import becomes a FACT.
|
|
396
|
+
var MIGRATION_000_BASELINE = `-- ============================================================================
|
|
397
|
+
-- plugins/plugin-banking-br/src/migrations/000_baseline.sql \u2014 what installing this provisions, as one file.
|
|
399
398
|
--
|
|
400
|
-
--
|
|
401
|
-
--
|
|
402
|
-
--
|
|
403
|
-
-- \xA74 facts financial.movement.imported
|
|
399
|
+
-- GENERATED by scripts/emit-unit-baselines.mjs (#338). Do not edit by hand:
|
|
400
|
+
-- change the schema, regenerate, and let scripts/check-chain-equivalence.mjs
|
|
401
|
+
-- prove the result still matches.
|
|
404
402
|
--
|
|
405
|
-
--
|
|
406
|
-
--
|
|
403
|
+
-- This replaced 2 migration files. They are kept, unapplied, in
|
|
404
|
+
-- migrations.archive/ \u2014 a year of decisions is worth reading even when it is no
|
|
405
|
+
-- longer worth replaying.
|
|
406
|
+
--
|
|
407
|
+
-- Object order is fixed rather than dependency-sorted: schemas, types,
|
|
408
|
+
-- sequences, tables, defaults, functions, constraints, views, indexes, foreign
|
|
409
|
+
-- keys, triggers, row security, policies, grants. Dependencies BETWEEN units
|
|
410
|
+
-- are carried by the order packages/db/chain.json declares.
|
|
407
411
|
-- ============================================================================
|
|
408
412
|
|
|
409
|
-
--
|
|
410
|
-
--
|
|
411
|
-
--
|
|
412
|
-
--
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
--
|
|
418
|
-
--
|
|
419
|
-
--
|
|
420
|
-
--
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
413
|
+
-- A LANGUAGE sql function binds its body at CREATE time, and 570 functions
|
|
414
|
+
-- sorted by name are not sorted by who calls whom. This is what lets them load
|
|
415
|
+
-- in any order; every body is still compiled on first call, so a genuinely
|
|
416
|
+
-- broken reference surfaces then rather than never. Session-scoped.
|
|
417
|
+
SET check_function_bodies = false;
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
-- \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
|
|
422
|
+
-- Cleared BEFORE anything is created, so every object below gets the ACL the
|
|
423
|
+
-- dump describes rather than the image's defaults on top of it. Restored at
|
|
424
|
+
-- the end of this file.
|
|
425
|
+
|
|
426
|
+
DO $dp$ BEGIN
|
|
427
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;
|
|
428
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
429
|
+
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$;
|
|
430
|
+
END $dp$;
|
|
431
|
+
|
|
432
|
+
DO $dp$ BEGIN
|
|
433
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;
|
|
434
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
435
|
+
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$;
|
|
436
|
+
END $dp$;
|
|
437
|
+
|
|
438
|
+
DO $dp$ BEGIN
|
|
439
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;
|
|
440
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
441
|
+
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$;
|
|
442
|
+
END $dp$;
|
|
443
|
+
|
|
444
|
+
DO $dp$ BEGIN
|
|
445
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;
|
|
446
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
447
|
+
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$;
|
|
448
|
+
END $dp$;
|
|
438
449
|
|
|
439
|
-
DO
|
|
450
|
+
DO $dp$ BEGIN
|
|
451
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;
|
|
452
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
453
|
+
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$;
|
|
454
|
+
END $dp$;
|
|
455
|
+
|
|
456
|
+
DO $dp$ BEGIN
|
|
457
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;
|
|
458
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
459
|
+
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$;
|
|
460
|
+
END $dp$;
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
-- \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
|
|
464
|
+
|
|
465
|
+
CREATE FUNCTION public.plg_bank_emit_movement_imported() RETURNS trigger
|
|
466
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
467
|
+
SET search_path TO 'public'
|
|
468
|
+
AS $$
|
|
469
|
+
DECLARE
|
|
470
|
+
v_payload jsonb;
|
|
440
471
|
BEGIN
|
|
441
|
-
IF
|
|
442
|
-
|
|
443
|
-
ALTER TABLE public.bank_integrations DROP COLUMN IF EXISTS cnpj;
|
|
472
|
+
IF NEW.external_source IS NULL OR NEW.external_id IS NULL THEN
|
|
473
|
+
RETURN NULL;
|
|
444
474
|
END IF;
|
|
445
|
-
END $$;
|
|
446
475
|
|
|
447
|
-
--
|
|
448
|
-
--
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
BEGIN
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
ADD COLUMN IF NOT EXISTS finished_at timestamptz;
|
|
477
|
-
|
|
478
|
-
COMMENT ON COLUMN public.bank_integration_sync_log.trigger_kind IS
|
|
479
|
-
'SyncRun.trigger \u2014 on-write | scheduled | manual | webhook. Named _kind because trigger is reserved.';
|
|
480
|
-
END $$;
|
|
481
|
-
|
|
482
|
-
-- \xA73 \u2500\u2500 import \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
|
|
483
|
-
-- The one door the statement import passes through.
|
|
484
|
-
--
|
|
485
|
-
-- Today the edge function inserts into plg_financial_movements directly with
|
|
486
|
-
-- service_role, and takes \`integrationId\` from the request body without ever
|
|
487
|
-
-- comparing it to the caller \u2014 so any authenticated member of ANY tenant in the
|
|
488
|
-
-- pool can name another tenant's integration and write into that tenant's
|
|
489
|
-
-- ledger. A guard in the function would be a guard the next caller routes
|
|
490
|
-
-- around; the database is the only chokepoint that cannot be.
|
|
491
|
-
--
|
|
492
|
-
-- The tenant is taken from the INTEGRATION ROW, never from the payload, and a
|
|
493
|
-
-- caller presenting a user credential must be a member of it. A caller with no
|
|
494
|
-
-- auth.uid() is the server (service_role, a migration, the future tenant-scoped
|
|
495
|
-
-- token) and is exempt \u2014 the same exemption plg_emit_event makes, for the same
|
|
496
|
-
-- reason: it legitimately writes for any tenant. When FAY-1372 mints the
|
|
497
|
-
-- tenant-scoped credential, that exemption stops applying to connector code and
|
|
498
|
-
-- this function needs no change.
|
|
499
|
-
CREATE OR REPLACE FUNCTION public.plugbank_import_movements(p_payload jsonb)
|
|
500
|
-
RETURNS jsonb
|
|
501
|
-
LANGUAGE plpgsql
|
|
502
|
-
SECURITY DEFINER
|
|
503
|
-
SET search_path = public
|
|
504
|
-
AS $fn$
|
|
476
|
+
-- The contract every consumer reads. Not the row: a whole-row payload would
|
|
477
|
+
-- put the ledger's internal bookkeeping inside every webhook by default.
|
|
478
|
+
v_payload := jsonb_build_object(
|
|
479
|
+
'movement_id', NEW.id::text,
|
|
480
|
+
'provider', NEW.external_source,
|
|
481
|
+
'external_id', NEW.external_id,
|
|
482
|
+
'direction', NEW.direction,
|
|
483
|
+
'amount', NEW.amount,
|
|
484
|
+
'payment_date', NEW.payment_date,
|
|
485
|
+
'description', NEW.notes,
|
|
486
|
+
'bank_account_id', NEW.bank_account_id);
|
|
487
|
+
|
|
488
|
+
BEGIN
|
|
489
|
+
PERFORM public.plg_emit_event(
|
|
490
|
+
'financial.movement.imported', v_payload, 'financial_movement',
|
|
491
|
+
NEW.id::text, NEW.tenant_id, NULL);
|
|
492
|
+
EXCEPTION WHEN OTHERS THEN
|
|
493
|
+
-- Observability must never be able to reject the write that caused it.
|
|
494
|
+
RAISE WARNING '[plg_bank_emit_movement_imported] % on %: %', TG_OP, TG_TABLE_NAME, SQLERRM;
|
|
495
|
+
END;
|
|
496
|
+
|
|
497
|
+
RETURN NULL;
|
|
498
|
+
END;
|
|
499
|
+
$$;
|
|
500
|
+
|
|
501
|
+
CREATE FUNCTION public.plugbank_import_movements(p_payload jsonb) RETURNS jsonb
|
|
502
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
503
|
+
SET search_path TO 'public'
|
|
504
|
+
AS $$
|
|
505
505
|
DECLARE
|
|
506
506
|
v_tenants uuid[];
|
|
507
507
|
v_tenant uuid;
|
|
508
|
-
|
|
508
|
+
v_connection uuid;
|
|
509
|
+
v_instance text;
|
|
509
510
|
v_account uuid;
|
|
510
|
-
|
|
511
|
+
v_connector text;
|
|
511
512
|
v_trigger text;
|
|
512
513
|
v_from date;
|
|
513
514
|
v_to date;
|
|
@@ -522,22 +523,28 @@ DECLARE
|
|
|
522
523
|
v_inserted uuid;
|
|
523
524
|
v_run uuid;
|
|
524
525
|
BEGIN
|
|
525
|
-
|
|
526
|
-
IF
|
|
526
|
+
v_connection := nullif(btrim(p_payload->>'integration_id'), '')::uuid;
|
|
527
|
+
IF v_connection IS NULL THEN
|
|
527
528
|
RAISE EXCEPTION 'plugbank_import_movements: integration_id is required'
|
|
528
529
|
USING ERRCODE = 'invalid_parameter_value';
|
|
529
530
|
END IF;
|
|
530
531
|
|
|
531
|
-
SELECT
|
|
532
|
-
INTO v_tenant,
|
|
533
|
-
FROM public.
|
|
534
|
-
WHERE
|
|
532
|
+
SELECT c.tenant_id, c.instance_key, c.connector_id
|
|
533
|
+
INTO v_tenant, v_instance, v_connector
|
|
534
|
+
FROM public.plg_connections c
|
|
535
|
+
WHERE c.id = v_connection AND c.active;
|
|
535
536
|
|
|
536
537
|
IF v_tenant IS NULL THEN
|
|
537
|
-
RAISE EXCEPTION 'plugbank_import_movements: no active
|
|
538
|
+
RAISE EXCEPTION 'plugbank_import_movements: no active connection %', v_connection
|
|
538
539
|
USING ERRCODE = 'no_data_found';
|
|
539
540
|
END IF;
|
|
540
541
|
|
|
542
|
+
-- The account this connection is for. \`instance_key\` is text because it also
|
|
543
|
+
-- carries things that are not uuids for other connectors (a Bling company, a
|
|
544
|
+
-- gateway instance); here it is the bank account, and '' is the account-less
|
|
545
|
+
-- default that \`bank_account_id IS NULL\` used to mean.
|
|
546
|
+
v_account := nullif(btrim(v_instance), '')::uuid;
|
|
547
|
+
|
|
541
548
|
-- SECURITY DEFINER bypasses RLS, so the membership check the policy would
|
|
542
549
|
-- have made has to happen here.
|
|
543
550
|
SELECT array_agg(t) INTO v_tenants FROM public.user_tenant_ids() AS t;
|
|
@@ -550,12 +557,18 @@ BEGIN
|
|
|
550
557
|
-- another tenant must be told, not silently redirected into this one.
|
|
551
558
|
IF nullif(btrim(p_payload->>'tenant_id'), '') IS NOT NULL
|
|
552
559
|
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
|
|
560
|
+
RAISE EXCEPTION 'plugbank_import_movements: payload names another tenant; the tenant comes from the connection'
|
|
554
561
|
USING ERRCODE = 'insufficient_privilege';
|
|
555
562
|
END IF;
|
|
556
563
|
|
|
557
|
-
|
|
558
|
-
|
|
564
|
+
-- Same rule as the tenant, one level down: the account is the connection's,
|
|
565
|
+
-- and a payload naming a different one is a caller importing into the wrong
|
|
566
|
+
-- ledger account. 002 let the payload override it silently \u2014 with one
|
|
567
|
+
-- connection per account that override is how two accounts get mixed.
|
|
568
|
+
IF nullif(btrim(p_payload->>'bank_account_id'), '') IS NOT NULL
|
|
569
|
+
AND (p_payload->>'bank_account_id')::uuid IS DISTINCT FROM v_account THEN
|
|
570
|
+
RAISE EXCEPTION 'plugbank_import_movements: payload names another bank account; the account comes from the connection'
|
|
571
|
+
USING ERRCODE = 'insufficient_privilege';
|
|
559
572
|
END IF;
|
|
560
573
|
|
|
561
574
|
v_from := nullif(btrim(p_payload->>'period_from'), '')::date;
|
|
@@ -579,9 +592,6 @@ BEGIN
|
|
|
579
592
|
CONTINUE;
|
|
580
593
|
END IF;
|
|
581
594
|
|
|
582
|
-
-- Idempotency is the partial unique index from the financial plugin's
|
|
583
|
-
-- 007_reconciliation.sql, not a SELECT-then-INSERT: two runs of the same
|
|
584
|
-
-- window racing each other would both find nothing and both write.
|
|
585
595
|
INSERT INTO public.plg_financial_movements (
|
|
586
596
|
tenant_id, direction, movement_kind, amount, paid_amount, status,
|
|
587
597
|
due_date, payment_date, bank_account_id, notes, external_id, external_source)
|
|
@@ -591,7 +601,7 @@ BEGIN
|
|
|
591
601
|
'payment', v_amount, v_amount, 'paid',
|
|
592
602
|
v_date, v_date, v_account,
|
|
593
603
|
nullif(btrim(v_line->>'description'), ''),
|
|
594
|
-
v_external, coalesce(
|
|
604
|
+
v_external, coalesce(v_connector, 'plugbank'))
|
|
595
605
|
ON CONFLICT (tenant_id, external_source, external_id)
|
|
596
606
|
WHERE external_id IS NOT NULL AND external_source IS NOT NULL
|
|
597
607
|
DO NOTHING
|
|
@@ -605,17 +615,26 @@ BEGIN
|
|
|
605
615
|
v_inserted := NULL;
|
|
606
616
|
END LOOP;
|
|
607
617
|
|
|
608
|
-
UPDATE public.
|
|
609
|
-
|
|
618
|
+
UPDATE public.plg_connections
|
|
619
|
+
SET last_sync_at = now(), status = 'connected', status_detail = NULL, updated_at = now()
|
|
620
|
+
WHERE id = v_connection;
|
|
610
621
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
622
|
+
-- \`stats\` is where the three facts a manual statement import has and a generic
|
|
623
|
+
-- run does not go: the operator picked a period, a re-import of that period is
|
|
624
|
+
-- mostly duplicates by design, and someone pressed the button. The panel reads
|
|
625
|
+
-- all three back from here.
|
|
626
|
+
INSERT INTO public.plg_sync_runs (
|
|
627
|
+
tenant_id, connection_id, connector_id, direction, trigger_kind, status,
|
|
628
|
+
stream, fetched, written, stats, started_at, finished_at)
|
|
615
629
|
VALUES (
|
|
616
|
-
v_tenant,
|
|
617
|
-
v_fetched, v_imported,
|
|
618
|
-
|
|
630
|
+
v_tenant, v_connection, coalesce(v_connector, 'plugbank'), 'inbound', v_trigger, 'success',
|
|
631
|
+
'statement', v_fetched, v_imported,
|
|
632
|
+
jsonb_strip_nulls(jsonb_build_object(
|
|
633
|
+
'duplicates', v_duplicates,
|
|
634
|
+
'period_from', v_from,
|
|
635
|
+
'period_to', v_to,
|
|
636
|
+
'triggered_by_user_id', auth.uid())),
|
|
637
|
+
v_started, now())
|
|
619
638
|
RETURNING id INTO v_run;
|
|
620
639
|
|
|
621
640
|
RETURN jsonb_build_object(
|
|
@@ -625,154 +644,209 @@ BEGIN
|
|
|
625
644
|
'imported', v_imported,
|
|
626
645
|
'duplicates', v_duplicates);
|
|
627
646
|
END;
|
|
628
|
-
|
|
647
|
+
$$;
|
|
629
648
|
|
|
630
|
-
REVOKE ALL ON FUNCTION public.plugbank_import_movements(jsonb) FROM public, anon;
|
|
631
|
-
GRANT EXECUTE ON FUNCTION public.plugbank_import_movements(jsonb) TO authenticated, service_role;
|
|
632
649
|
|
|
633
|
-
-- \
|
|
634
|
-
-- One trigger on the ledger rather than an emit call inside the importer. The
|
|
635
|
-
-- RPC above, the edge function still inserting directly, a future scheduled
|
|
636
|
-
-- pull and a hand-run backfill all end up in the same row \u2014 so the fact exists
|
|
637
|
-
-- for all four without any of them having to remember to say so. That is the
|
|
638
|
-
-- entire reason plg_emit_event exists (packages/db/migrations/027_domain_events.sql).
|
|
639
|
-
--
|
|
640
|
-
-- It fires only when external_source is set, which is exactly what "imported"
|
|
641
|
-
-- means: a movement that came from outside. An app-native movement typed into
|
|
642
|
-
-- the cash register is not an import and must not wake a bank connector.
|
|
643
|
-
--
|
|
644
|
-
-- Provider-agnostic on purpose. \`provider\` is in the payload, so a second bank
|
|
645
|
-
-- connector importing into the same ledger produces the same fact instead of a
|
|
646
|
-
-- second vocabulary for the same event.
|
|
647
|
-
CREATE OR REPLACE FUNCTION public.plg_bank_emit_movement_imported()
|
|
648
|
-
RETURNS trigger
|
|
649
|
-
LANGUAGE plpgsql
|
|
650
|
-
SECURITY DEFINER
|
|
651
|
-
SET search_path = public
|
|
652
|
-
AS $fn$
|
|
653
|
-
DECLARE
|
|
654
|
-
v_payload jsonb;
|
|
655
|
-
BEGIN
|
|
656
|
-
IF NEW.external_source IS NULL OR NEW.external_id IS NULL THEN
|
|
657
|
-
RETURN NULL;
|
|
658
|
-
END IF;
|
|
650
|
+
-- \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
|
|
659
651
|
|
|
660
|
-
|
|
661
|
-
-- put the ledger's internal bookkeeping inside every webhook by default.
|
|
662
|
-
v_payload := jsonb_build_object(
|
|
663
|
-
'movement_id', NEW.id::text,
|
|
664
|
-
'provider', NEW.external_source,
|
|
665
|
-
'external_id', NEW.external_id,
|
|
666
|
-
'direction', NEW.direction,
|
|
667
|
-
'amount', NEW.amount,
|
|
668
|
-
'payment_date', NEW.payment_date,
|
|
669
|
-
'description', NEW.notes,
|
|
670
|
-
'bank_account_id', NEW.bank_account_id);
|
|
652
|
+
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();
|
|
671
653
|
|
|
672
|
-
BEGIN
|
|
673
|
-
PERFORM public.plg_emit_event(
|
|
674
|
-
'financial.movement.imported', v_payload, 'financial_movement',
|
|
675
|
-
NEW.id::text, NEW.tenant_id, NULL);
|
|
676
|
-
EXCEPTION WHEN OTHERS THEN
|
|
677
|
-
-- Observability must never be able to reject the write that caused it.
|
|
678
|
-
RAISE WARNING '[plg_bank_emit_movement_imported] % on %: %', TG_OP, TG_TABLE_NAME, SQLERRM;
|
|
679
|
-
END;
|
|
680
654
|
|
|
681
|
-
|
|
682
|
-
END;
|
|
683
|
-
$fn$;
|
|
655
|
+
-- \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
|
|
684
656
|
|
|
685
|
-
REVOKE ALL ON FUNCTION public.plg_bank_emit_movement_imported() FROM
|
|
657
|
+
REVOKE ALL ON FUNCTION public.plg_bank_emit_movement_imported() FROM PUBLIC;
|
|
658
|
+
GRANT ALL ON FUNCTION public.plg_bank_emit_movement_imported() TO authenticated;
|
|
659
|
+
GRANT ALL ON FUNCTION public.plg_bank_emit_movement_imported() TO service_role;
|
|
686
660
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
661
|
+
REVOKE ALL ON FUNCTION public.plugbank_import_movements(p_payload jsonb) FROM PUBLIC;
|
|
662
|
+
GRANT ALL ON FUNCTION public.plugbank_import_movements(p_payload jsonb) TO authenticated;
|
|
663
|
+
GRANT ALL ON FUNCTION public.plugbank_import_movements(p_payload jsonb) TO service_role;
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
-- \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
|
|
667
|
+
|
|
668
|
+
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.';
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
-- \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
|
|
672
|
+
-- Put back, so the NEXT thing installed inherits what the chain had.
|
|
673
|
+
|
|
674
|
+
DO $dp$ BEGIN
|
|
675
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;
|
|
676
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
677
|
+
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$;
|
|
678
|
+
END $dp$;
|
|
679
|
+
|
|
680
|
+
DO $dp$ BEGIN
|
|
681
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;
|
|
682
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
683
|
+
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$;
|
|
684
|
+
END $dp$;
|
|
685
|
+
|
|
686
|
+
DO $dp$ BEGIN
|
|
687
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;
|
|
688
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
689
|
+
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$;
|
|
690
|
+
END $dp$;
|
|
691
|
+
|
|
692
|
+
DO $dp$ BEGIN
|
|
693
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;
|
|
694
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
695
|
+
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$;
|
|
696
|
+
END $dp$;
|
|
697
|
+
|
|
698
|
+
DO $dp$ BEGIN
|
|
699
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;
|
|
700
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
701
|
+
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$;
|
|
702
|
+
END $dp$;
|
|
703
|
+
|
|
704
|
+
DO $dp$ BEGIN
|
|
705
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;
|
|
706
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
707
|
+
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$;
|
|
708
|
+
END $dp$;
|
|
709
|
+
|
|
710
|
+
DO $dp$ BEGIN
|
|
711
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;
|
|
712
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
713
|
+
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$;
|
|
714
|
+
END $dp$;
|
|
715
|
+
|
|
716
|
+
DO $dp$ BEGIN
|
|
717
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;
|
|
718
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
719
|
+
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$;
|
|
720
|
+
END $dp$;
|
|
721
|
+
|
|
722
|
+
DO $dp$ BEGIN
|
|
723
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;
|
|
724
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
725
|
+
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$;
|
|
726
|
+
END $dp$;
|
|
727
|
+
|
|
728
|
+
DO $dp$ BEGIN
|
|
729
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;
|
|
730
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
731
|
+
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$;
|
|
732
|
+
END $dp$;
|
|
733
|
+
|
|
734
|
+
DO $dp$ BEGIN
|
|
735
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;
|
|
736
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
737
|
+
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$;
|
|
738
|
+
END $dp$;
|
|
739
|
+
|
|
740
|
+
DO $dp$ BEGIN
|
|
741
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;
|
|
742
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
743
|
+
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$;
|
|
744
|
+
END $dp$;
|
|
745
|
+
|
|
746
|
+
DO $dp$ BEGIN
|
|
747
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO anon$stmt$;
|
|
748
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
749
|
+
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$;
|
|
750
|
+
END $dp$;
|
|
751
|
+
|
|
752
|
+
DO $dp$ BEGIN
|
|
753
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;
|
|
754
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
755
|
+
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$;
|
|
756
|
+
END $dp$;
|
|
757
|
+
|
|
758
|
+
DO $dp$ BEGIN
|
|
759
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;
|
|
760
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
761
|
+
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$;
|
|
762
|
+
END $dp$;
|
|
763
|
+
|
|
764
|
+
DO $dp$ BEGIN
|
|
765
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;
|
|
766
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
767
|
+
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$;
|
|
768
|
+
END $dp$;
|
|
769
|
+
|
|
770
|
+
DO $dp$ BEGIN
|
|
771
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT MAINTAIN ON TABLES TO anon$stmt$;
|
|
772
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
773
|
+
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$;
|
|
774
|
+
END $dp$;
|
|
775
|
+
|
|
776
|
+
DO $dp$ BEGIN
|
|
777
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;
|
|
778
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
779
|
+
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$;
|
|
780
|
+
END $dp$;
|
|
781
|
+
|
|
782
|
+
DO $dp$ BEGIN
|
|
783
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;
|
|
784
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
785
|
+
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$;
|
|
786
|
+
END $dp$;
|
|
787
|
+
|
|
788
|
+
DO $dp$ BEGIN
|
|
789
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;
|
|
790
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
791
|
+
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$;
|
|
792
|
+
END $dp$;
|
|
793
|
+
|
|
794
|
+
DO $dp$ BEGIN
|
|
795
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO anon$stmt$;
|
|
796
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
797
|
+
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$;
|
|
798
|
+
END $dp$;
|
|
799
|
+
|
|
800
|
+
DO $dp$ BEGIN
|
|
801
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;
|
|
802
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
803
|
+
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$;
|
|
804
|
+
END $dp$;
|
|
805
|
+
|
|
806
|
+
DO $dp$ BEGIN
|
|
807
|
+
EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;
|
|
808
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
809
|
+
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$;
|
|
810
|
+
END $dp$;
|
|
811
|
+
|
|
812
|
+
DO $dp$ BEGIN
|
|
813
|
+
EXECUTE $stmt$--
|
|
814
|
+
-- PostgreSQL database dump complete
|
|
815
|
+
--$stmt$;
|
|
816
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
817
|
+
RAISE NOTICE 'baseline: not ours to set \u2014 %', $stmt$--
|
|
818
|
+
-- PostgreSQL database dump complete
|
|
819
|
+
--$stmt$;
|
|
820
|
+
END $dp$;
|
|
704
821
|
`;
|
|
705
|
-
var
|
|
706
|
-
--
|
|
707
|
-
--
|
|
708
|
-
-- Retires public.bank_integrations and public.bank_integration_sync_log. The
|
|
709
|
-
-- connection is now a row of public.plg_connections and each import is a row of
|
|
710
|
-
-- public.plg_sync_runs (packages/db 029). See FAY-1376.
|
|
822
|
+
var MIGRATION_001_THE_IMPORT_RPC_NAMES_REAL_TABLES = `-- ==========================================================================
|
|
823
|
+
-- 001_the_import_rpc_names_real_tables \u2014 plugbank_import_movements
|
|
824
|
+
-- stops reaching through 141's bridge (#352).
|
|
711
825
|
--
|
|
712
|
-
--
|
|
713
|
-
--
|
|
714
|
-
--
|
|
715
|
-
--
|
|
716
|
-
--
|
|
826
|
+
-- 107 renamed the connector spine out of the \`plg_\` prefix \u2014 the prefix means
|
|
827
|
+
-- "a plugin owns this", and the connector framework's tables are the BASE's.
|
|
828
|
+
-- 141 left \`plg_connections\` and \`plg_sync_runs\` answering as views so callers
|
|
829
|
+
-- could move without a synchronised deploy. This function never moved, and it
|
|
830
|
+
-- is not alone: \`plg_connections\` had 13 callers and \`connections\` had none.
|
|
831
|
+
-- The bridge became the road.
|
|
717
832
|
--
|
|
718
|
-
--
|
|
719
|
-
--
|
|
720
|
-
--
|
|
721
|
-
-- which is what \`bank_account_id IS NULL\`
|
|
722
|
-
-- meant before)
|
|
723
|
-
-- bank_integrations.provider \u2192 plg_connections.connector_id
|
|
724
|
-
-- bank_integrations.environment \u2192 plg_connections.settings->>'environment'
|
|
725
|
-
-- bank_integrations.active \u2192 plg_connections.active
|
|
726
|
-
-- bank_integrations.last_sync_at \u2192 plg_connections.last_sync_at
|
|
727
|
-
-- UNIQUE (tenant_id, bank_account_id, provider)
|
|
728
|
-
-- \u2192 UNIQUE (tenant_id, connector_id, instance_key)
|
|
833
|
+
-- The body is 000_baseline's, unchanged except for the three names. It is not
|
|
834
|
+
-- an edit of that file \u2014 an applied migration is immutable and the ledger
|
|
835
|
+
-- treats drift as a hard stop \u2014 it is a CREATE OR REPLACE that lands after it.
|
|
729
836
|
--
|
|
730
|
-
--
|
|
731
|
-
--
|
|
732
|
-
--
|
|
733
|
-
--
|
|
734
|
-
-- sync_log.duplicates \u2192 plg_sync_runs.stats->>'duplicates'
|
|
735
|
-
-- sync_log.period_from / _to \u2192 plg_sync_runs.stats->>'period_from' / '_to'
|
|
736
|
-
-- sync_log.triggered_by_user_id \u2192 plg_sync_runs.stats->>'triggered_by_user_id'
|
|
737
|
-
-- sync_log.error_message \u2192 plg_sync_runs.error
|
|
738
|
-
-- sync_log.bank_account_id \u2192 dropped: it is the connection's
|
|
739
|
-
-- instance_key, and a second copy is a
|
|
740
|
-
-- second thing to disagree
|
|
741
|
-
-- sync_log.provider \u2192 plg_sync_runs.connector_id
|
|
742
|
-
-- sync_log.created_at \u2192 plg_sync_runs.started_at
|
|
743
|
-
-- direction / trigger_kind / status / started_at / finished_at \u2192 same names
|
|
837
|
+
-- Why this plugin has to carry its own: the function is banking-br's, and the
|
|
838
|
+
-- views it stops using are the spine's. The spine runs FIRST, so a spine file
|
|
839
|
+
-- cannot drop a view that a plugin applied later still reads. Each package
|
|
840
|
+
-- moves its own callers; the aliases go once nobody is left.
|
|
744
841
|
--
|
|
745
|
-
--
|
|
746
|
-
--
|
|
747
|
-
--
|
|
748
|
-
-- log at all: it calls plugbank_import_movements, which is the only writer, and
|
|
749
|
-
-- the run is filed here. The choreography 002 was avoiding does not exist.
|
|
750
|
-
--
|
|
751
|
-
-- Nothing is in production, so this is a drop and not a data migration.
|
|
752
|
-
-- ============================================================================
|
|
842
|
+
-- Replay-safe: CREATE OR REPLACE, same signature, same grants (which 000 set
|
|
843
|
+
-- and this does not touch).
|
|
844
|
+
-- ==========================================================================
|
|
753
845
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
--
|
|
759
|
-
-- Everything 002 \xA73 established still holds and for the same reasons: the tenant
|
|
760
|
-
-- comes from the CONNECTION ROW and never from the payload, a caller presenting
|
|
761
|
-
-- a user credential must be a member of it, a caller with no auth.uid() is the
|
|
762
|
-
-- server and is exempt, and idempotency is the partial unique index rather than
|
|
763
|
-
-- a SELECT-then-INSERT that two racing runs would both pass.
|
|
764
|
-
--
|
|
765
|
-
-- One thing is new and is the point of the migration: the run is filed against
|
|
766
|
-
-- \`connection_id\`, so a tenant with two bank accounts gets two histories instead
|
|
767
|
-
-- of one interleaved list. plg_sync_runs carries no INSERT grant for
|
|
768
|
-
-- \`authenticated\` \u2014 this SECURITY DEFINER function is the server-side writer the
|
|
769
|
-
-- table's grants assume.
|
|
770
|
-
CREATE OR REPLACE FUNCTION public.plugbank_import_movements(p_payload jsonb)
|
|
771
|
-
RETURNS jsonb
|
|
772
|
-
LANGUAGE plpgsql
|
|
773
|
-
SECURITY DEFINER
|
|
774
|
-
SET search_path = public
|
|
775
|
-
AS $fn$
|
|
846
|
+
CREATE OR REPLACE FUNCTION public.plugbank_import_movements(p_payload jsonb) RETURNS jsonb
|
|
847
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
848
|
+
SET search_path TO 'public'
|
|
849
|
+
AS $$
|
|
776
850
|
DECLARE
|
|
777
851
|
v_tenants uuid[];
|
|
778
852
|
v_tenant uuid;
|
|
@@ -802,7 +876,7 @@ BEGIN
|
|
|
802
876
|
|
|
803
877
|
SELECT c.tenant_id, c.instance_key, c.connector_id
|
|
804
878
|
INTO v_tenant, v_instance, v_connector
|
|
805
|
-
FROM public.
|
|
879
|
+
FROM public.connections c
|
|
806
880
|
WHERE c.id = v_connection AND c.active;
|
|
807
881
|
|
|
808
882
|
IF v_tenant IS NULL THEN
|
|
@@ -886,7 +960,7 @@ BEGIN
|
|
|
886
960
|
v_inserted := NULL;
|
|
887
961
|
END LOOP;
|
|
888
962
|
|
|
889
|
-
UPDATE public.
|
|
963
|
+
UPDATE public.connections
|
|
890
964
|
SET last_sync_at = now(), status = 'connected', status_detail = NULL, updated_at = now()
|
|
891
965
|
WHERE id = v_connection;
|
|
892
966
|
|
|
@@ -894,7 +968,7 @@ BEGIN
|
|
|
894
968
|
-- run does not go: the operator picked a period, a re-import of that period is
|
|
895
969
|
-- mostly duplicates by design, and someone pressed the button. The panel reads
|
|
896
970
|
-- all three back from here.
|
|
897
|
-
INSERT INTO public.
|
|
971
|
+
INSERT INTO public.sync_runs (
|
|
898
972
|
tenant_id, connection_id, connector_id, direction, trigger_kind, status,
|
|
899
973
|
stream, fetched, written, stats, started_at, finished_at)
|
|
900
974
|
VALUES (
|
|
@@ -915,22 +989,14 @@ BEGIN
|
|
|
915
989
|
'imported', v_imported,
|
|
916
990
|
'duplicates', v_duplicates);
|
|
917
991
|
END;
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
GRANT EXECUTE ON FUNCTION public.plugbank_import_movements(jsonb) TO authenticated, service_role;
|
|
922
|
-
|
|
923
|
-
COMMENT ON FUNCTION public.plugbank_import_movements(jsonb) IS
|
|
924
|
-
'Import bank-statement lines into the financial ledger and file the run in '
|
|
925
|
-
'plg_sync_runs. The tenant AND the bank account come from the plg_connections '
|
|
926
|
-
'row named by integration_id, never from the payload.';
|
|
927
|
-
|
|
928
|
-
-- \xA72 \u2500\u2500 the bespoke tables go \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
|
|
929
|
-
-- After the function above, so a pool is never left with the old door pointing
|
|
930
|
-
-- at a table that no longer exists.
|
|
931
|
-
DROP TABLE IF EXISTS public.bank_integration_sync_log CASCADE;
|
|
932
|
-
DROP TABLE IF EXISTS public.bank_integrations CASCADE;
|
|
992
|
+
$$;
|
|
993
|
+
COMMENT ON FUNCTION public.plugbank_import_movements(p_payload jsonb) IS
|
|
994
|
+
'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.';
|
|
933
995
|
`;
|
|
996
|
+
var MIGRATIONS = [
|
|
997
|
+
{ id: "000_baseline", sql: MIGRATION_000_BASELINE },
|
|
998
|
+
{ id: "001_the_import_rpc_names_real_tables", sql: MIGRATION_001_THE_IMPORT_RPC_NAMES_REAL_TABLES }
|
|
999
|
+
];
|
|
934
1000
|
|
|
935
1001
|
// src/index.ts
|
|
936
1002
|
function createOpenBankingPlugin() {
|
|
@@ -987,27 +1053,22 @@ function createOpenBankingPlugin() {
|
|
|
987
1053
|
// This plugin creates no table. 003 retired the two it had; what is left is
|
|
988
1054
|
// the RPC that is the import's one door, and the trigger that turns an
|
|
989
1055
|
// imported movement into a fact. The connection and its run history are rows
|
|
990
|
-
// of
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
version: "1.0.0",
|
|
1001
|
-
sql: MIGRATION_003_ONTO_CONNECTIONS,
|
|
1002
|
-
description: "Retire bank_integrations + bank_integration_sync_log \u2014 the connection is a plg_connections row keyed by instance_key (the bank account) and each import a plg_sync_runs row; plugbank_import_movements re-pointed at both"
|
|
1003
|
-
}
|
|
1004
|
-
],
|
|
1056
|
+
// of connections / sync_runs, owned by packages/db.
|
|
1057
|
+
// One baseline per unit since #338 — read from the generated barrel
|
|
1058
|
+
// rather than listed here, so regenerating the SQL never needs a
|
|
1059
|
+
// manifest edit to match.
|
|
1060
|
+
migrations: MIGRATIONS.map((entry) => ({
|
|
1061
|
+
id: `openbanking-${entry.id}`,
|
|
1062
|
+
version: "1.0.0",
|
|
1063
|
+
sql: entry.sql,
|
|
1064
|
+
description: "The whole schema this plugin provisions, as one file: every table, view, function, policy and grant, plus the reference rows it seeds. Replaced the per-change chain in the 1.0.0 squash (#338); the files it collapsed are kept, unapplied, in src/migrations.archive/."
|
|
1065
|
+
})),
|
|
1005
1066
|
diagnostics: [
|
|
1006
1067
|
{
|
|
1007
1068
|
id: "openbanking-ledger-ready",
|
|
1008
1069
|
description: "The import writes into the financial ledger and needs its reconciliation columns (plugin-financial 007), the generic connection tables (packages/db 029) and the row-level event emitter.",
|
|
1009
1070
|
requires: {
|
|
1010
|
-
tables: ["
|
|
1071
|
+
tables: ["connections", "sync_runs", "plg_financial_movements"],
|
|
1011
1072
|
rpcs: ["plugbank_import_movements", "plg_emit_event"]
|
|
1012
1073
|
},
|
|
1013
1074
|
level: "error"
|