pgkiln 0.35.0 → 0.36.0
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/db/migrations/087_login_page.sql +175 -0
- package/db/migrations/088_region_templates.sql +18 -0
- package/db/migrations/089_page_groups.sql +330 -0
- package/docs/apex-feature-parity.md +22 -20
- package/docs/guide/03-builder.md +36 -2
- package/docs/guide/04-pages-and-regions.md +42 -3
- package/docs/guide/08-security.md +46 -5
- package/docs/guide/11-from-apex.md +4 -1
- package/docs/guide/12-development.md +7 -1
- package/docs/guide/17-mobile.md +3 -1
- package/docs/guide/18-cli.md +2 -0
- package/docs/guide/20-ai-agents.md +2 -0
- package/examples/hr/hr_54_region_templates.sql +67 -0
- package/examples/hr/hr_55_page_groups.sql +24 -0
- package/package.json +1 -1
- package/public/app.css +60 -3
- package/public/app.js +89 -3
- package/public/builder.css +11 -0
- package/public/builder.js +50 -0
- package/public/code-editor.css +3 -3
- package/public/sw.js +5 -3
- package/src/app.ts +2 -0
- package/src/appfiles.ts +1 -0
- package/src/builder/advisor.ts +14 -0
- package/src/builder/components.ts +15 -2
- package/src/builder/ddl-tools.ts +114 -0
- package/src/builder/designer.ts +29 -3
- package/src/builder/forms.ts +6 -4
- package/src/builder/login-page.ts +82 -0
- package/src/builder/region-help.ts +4 -2
- package/src/builder/region-settings.ts +19 -3
- package/src/builder/routes.ts +16 -4
- package/src/builder/shared.ts +1 -1
- package/src/builder/sql.ts +3 -0
- package/src/builder/ui.ts +2 -2
- package/src/cli/main.ts +45 -0
- package/src/cli/mcp.ts +42 -0
- package/src/cli/replace.ts +4 -0
- package/src/ddl.ts +509 -0
- package/src/i18n/ar.ts +17 -0
- package/src/i18n/cs.ts +17 -0
- package/src/i18n/da.ts +17 -0
- package/src/i18n/de.ts +17 -0
- package/src/i18n/el.ts +17 -0
- package/src/i18n/es.ts +17 -0
- package/src/i18n/fi.ts +17 -0
- package/src/i18n/fr.ts +17 -0
- package/src/i18n/he.ts +17 -0
- package/src/i18n/it.ts +17 -0
- package/src/i18n/ja.ts +17 -0
- package/src/i18n/ko.ts +17 -0
- package/src/i18n/nb.ts +17 -0
- package/src/i18n/pl.ts +17 -0
- package/src/i18n/pt.ts +17 -0
- package/src/i18n/ru.ts +17 -0
- package/src/i18n/sv.ts +17 -0
- package/src/i18n/tr.ts +17 -0
- package/src/i18n/uk.ts +17 -0
- package/src/i18n/zh.ts +17 -0
- package/src/i18n.ts +34 -0
- package/src/icons.ts +2 -1
- package/src/layout.ts +3 -2
- package/src/metadata.ts +10 -4
- package/src/runtime/devbar.ts +3 -1
- package/src/runtime/display-selector.ts +12 -4
- package/src/runtime/grid.ts +170 -26
- package/src/runtime/lists.ts +11 -1
- package/src/runtime/password-reset.ts +171 -0
- package/src/runtime/pwa.ts +7 -10
- package/src/runtime/regions.ts +33 -0
- package/src/runtime/render.ts +15 -0
- package/src/runtime/report-views.ts +4 -2
- package/src/runtime/report.ts +36 -22
- package/src/runtime/routes.ts +35 -32
- package/src/runtime/signin.ts +80 -0
- package/src/static-version.ts +34 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- 087: an editable login page and "Forgot password?" for app users
|
|
3
|
+
--
|
|
4
|
+
-- meta.app.login_page: the page that replaces the built-in sign-in page
|
|
5
|
+
-- (APEX: the login page, usually 9999). /a/<alias>/login sends visitors
|
|
6
|
+
-- there; it must not require authentication. Its "login" region is the
|
|
7
|
+
-- sign-in form, which posts to /a/<alias>/login as before.
|
|
8
|
+
-- meta.page.page_template: 'minimal' shows a page without the header and
|
|
9
|
+
-- navigation (APEX: Minimal (No Navigation)); login pages use it.
|
|
10
|
+
-- meta.app.password_reset: the sign-in page offers "Forgot password?" to
|
|
11
|
+
-- accounts with a password in the user directory, when the application
|
|
12
|
+
-- has an enabled mail service. A reset link is single-use and expires
|
|
13
|
+
-- after 30 minutes; only a SHA-256 hash of its token is stored.
|
|
14
|
+
-- meta.password_reset: the tokens (owner only; the functions below are
|
|
15
|
+
-- the runtime's way in).
|
|
16
|
+
-- =====================================================================
|
|
17
|
+
alter table meta.app add column login_page int check (login_page > 0);
|
|
18
|
+
alter table meta.app add column password_reset boolean not null default false;
|
|
19
|
+
alter table meta.page add column page_template text not null default 'standard' check (page_template in ('standard', 'minimal'));
|
|
20
|
+
|
|
21
|
+
-- an export made before 087 has neither: NULL becomes the default (like 077)
|
|
22
|
+
create function meta.login_page_defaults() returns trigger
|
|
23
|
+
language plpgsql set search_path = pg_catalog as $$
|
|
24
|
+
begin
|
|
25
|
+
if tg_table_name = 'app' then
|
|
26
|
+
new.password_reset := coalesce(new.password_reset, false);
|
|
27
|
+
else
|
|
28
|
+
new.page_template := coalesce(new.page_template, 'standard');
|
|
29
|
+
end if;
|
|
30
|
+
return new;
|
|
31
|
+
end
|
|
32
|
+
$$;
|
|
33
|
+
create trigger app_login_page_defaults before insert or update on meta.app
|
|
34
|
+
for each row execute function meta.login_page_defaults();
|
|
35
|
+
create trigger page_login_page_defaults before insert or update on meta.page
|
|
36
|
+
for each row execute function meta.login_page_defaults();
|
|
37
|
+
|
|
38
|
+
alter table meta.region drop constraint region_type_check;
|
|
39
|
+
alter table meta.region add constraint region_type_check
|
|
40
|
+
check (type in ('report', 'form', 'chart', 'cards', 'static', 'grid', 'calendar', 'dynamic', 'facets', 'tasks', 'workflows', 'map', 'tree',
|
|
41
|
+
'template_component', 'smart_filters', 'display_selector', 'list', 'data_reporter', 'ai_assistant', 'plugin', 'url', 'search',
|
|
42
|
+
'login'));
|
|
43
|
+
|
|
44
|
+
create table meta.password_reset (
|
|
45
|
+
id bigint generated always as identity primary key,
|
|
46
|
+
app_id int not null references meta.app on delete cascade,
|
|
47
|
+
account_id int not null references meta.account on delete cascade,
|
|
48
|
+
token_hash text not null unique check (token_hash ~ '^[0-9a-f]{64}$'),
|
|
49
|
+
ip text,
|
|
50
|
+
created_at timestamptz not null default now(),
|
|
51
|
+
expires_at timestamptz not null default now() + interval '30 minutes',
|
|
52
|
+
used_at timestamptz
|
|
53
|
+
);
|
|
54
|
+
create index password_reset_account on meta.password_reset (account_id, created_at);
|
|
55
|
+
revoke all on meta.password_reset from public;
|
|
56
|
+
|
|
57
|
+
-- Whether the application offers "Forgot password?": turned on, app users with local passwords,
|
|
58
|
+
-- and an enabled mail service with a From address.
|
|
59
|
+
create function meta.password_reset_available(p_app_id int) returns boolean
|
|
60
|
+
language sql stable security definer set search_path = meta, pg_catalog as $$
|
|
61
|
+
select exists (
|
|
62
|
+
select 1
|
|
63
|
+
from meta.app a
|
|
64
|
+
join meta.app_mail_service ams on ams.app_id = a.id
|
|
65
|
+
join meta.mail_service s on s.id = ams.service_id
|
|
66
|
+
where a.id = p_app_id and a.password_reset and a.authentication = 'app_users' and a.local_login
|
|
67
|
+
and s.enabled and nullif(btrim(ams.from_address), '') is not null)
|
|
68
|
+
$$;
|
|
69
|
+
revoke all on function meta.password_reset_available(int) from public;
|
|
70
|
+
grant execute on function meta.password_reset_available(int) to pgkiln_runtime;
|
|
71
|
+
|
|
72
|
+
-- A reset request for a user name or e-mail address. When it matches one active account with a
|
|
73
|
+
-- password and an e-mail address that may use the application, keeps the token's hash and queues
|
|
74
|
+
-- the mail (#USER# in the subject and bodies becomes the user name; the caller puts the link in).
|
|
75
|
+
-- Says nothing about whether an account matched: the caller shows the same answer either way.
|
|
76
|
+
-- At most 3 requests per account per hour.
|
|
77
|
+
create function meta.request_password_reset(p_app_id int, p_login text, p_token_hash text, p_ip text,
|
|
78
|
+
p_subject text, p_body text, p_body_html text) returns void
|
|
79
|
+
language plpgsql volatile security definer set search_path = meta, public, pg_catalog as $$
|
|
80
|
+
declare
|
|
81
|
+
v_acc meta.account;
|
|
82
|
+
v_n int;
|
|
83
|
+
v_from text;
|
|
84
|
+
v_send text[];
|
|
85
|
+
v_max int;
|
|
86
|
+
v_vals jsonb;
|
|
87
|
+
v_id bigint;
|
|
88
|
+
begin
|
|
89
|
+
if not meta.password_reset_available(p_app_id) or p_token_hash !~ '^[0-9a-f]{64}$' or btrim(coalesce(p_login, '')) = '' then
|
|
90
|
+
return;
|
|
91
|
+
end if;
|
|
92
|
+
-- a user name, else an e-mail address that only one active account has
|
|
93
|
+
select * into v_acc from meta.account where lower(username) = lower(btrim(p_login)) and active;
|
|
94
|
+
if not found then
|
|
95
|
+
select count(*) into v_n from meta.account where lower(email) = lower(btrim(p_login)) and active;
|
|
96
|
+
if v_n <> 1 then
|
|
97
|
+
return;
|
|
98
|
+
end if;
|
|
99
|
+
select * into v_acc from meta.account where lower(email) = lower(btrim(p_login)) and active;
|
|
100
|
+
end if;
|
|
101
|
+
if v_acc.password_hash is null or nullif(btrim(v_acc.email), '') is null then
|
|
102
|
+
return;
|
|
103
|
+
end if;
|
|
104
|
+
if not exists (select 1 from meta.app a where a.id = p_app_id and a.access_control = 'any_user')
|
|
105
|
+
and not exists (select 1 from meta.app_access where app_id = p_app_id and account_id = v_acc.id) then
|
|
106
|
+
return;
|
|
107
|
+
end if;
|
|
108
|
+
select count(*) into v_n from meta.password_reset where account_id = v_acc.id and created_at > now() - interval '1 hour';
|
|
109
|
+
if v_n >= 3 then
|
|
110
|
+
return;
|
|
111
|
+
end if;
|
|
112
|
+
select ams.from_address, s.senders, ams.max_per_day into v_from, v_send, v_max
|
|
113
|
+
from meta.app_mail_service ams join meta.mail_service s on s.id = ams.service_id
|
|
114
|
+
where ams.app_id = p_app_id;
|
|
115
|
+
if not meta.mail_sender_allowed(v_send, v_from) then
|
|
116
|
+
raise warning 'password reset for app %: the mail service may not send as %', p_app_id, v_from;
|
|
117
|
+
return;
|
|
118
|
+
end if;
|
|
119
|
+
if v_max is not null and (select count(*) from meta.mail_queue
|
|
120
|
+
where app_id = p_app_id and created_at >= date_trunc('day', now() at time zone 'UTC') at time zone 'UTC'
|
|
121
|
+
and status <> 'cancelled') >= v_max then
|
|
122
|
+
raise warning 'password reset for app %: the daily mail limit is reached', p_app_id;
|
|
123
|
+
return;
|
|
124
|
+
end if;
|
|
125
|
+
delete from meta.password_reset where created_at < now() - interval '7 days';
|
|
126
|
+
insert into meta.password_reset (app_id, account_id, token_hash, ip) values (p_app_id, v_acc.id, p_token_hash, left(p_ip, 100));
|
|
127
|
+
v_vals := jsonb_build_object('USER', v_acc.username);
|
|
128
|
+
insert into meta.mail_queue (app_id, username, from_address, to_addresses, subject, body_text, body_html, template)
|
|
129
|
+
values (p_app_id, v_acc.username, v_from, array[v_acc.email], left(replace(replace(meta.mail_fill(p_subject, v_vals, false), E'\r', ''), E'\n', ' '), 998),
|
|
130
|
+
meta.mail_fill(p_body, v_vals, false), meta.mail_fill(p_body_html, v_vals, true), 'PASSWORD_RESET')
|
|
131
|
+
returning id into v_id;
|
|
132
|
+
perform pg_notify('pgkiln_mail', v_id::text);
|
|
133
|
+
end
|
|
134
|
+
$$;
|
|
135
|
+
revoke all on function meta.request_password_reset(int, text, text, text, text, text, text) from public;
|
|
136
|
+
grant execute on function meta.request_password_reset(int, text, text, text, text, text, text) to pgkiln_runtime;
|
|
137
|
+
|
|
138
|
+
-- The user name a valid (unused, unexpired) token is for, or null.
|
|
139
|
+
create function meta.password_reset_user(p_app_id int, p_token_hash text) returns text
|
|
140
|
+
language sql stable security definer set search_path = meta, pg_catalog as $$
|
|
141
|
+
select ac.username
|
|
142
|
+
from meta.password_reset r join meta.account ac on ac.id = r.account_id
|
|
143
|
+
where r.app_id = p_app_id and r.token_hash = p_token_hash and r.used_at is null and r.expires_at > now() and ac.active
|
|
144
|
+
$$;
|
|
145
|
+
revoke all on function meta.password_reset_user(int, text) from public;
|
|
146
|
+
grant execute on function meta.password_reset_user(int, text) to pgkiln_runtime;
|
|
147
|
+
|
|
148
|
+
-- Set the new password with a valid token: the token and the account's other open requests are used
|
|
149
|
+
-- up, every session and "keep me signed in" token of the account ends. Returns the user name, or
|
|
150
|
+
-- null when the token is not valid (any more).
|
|
151
|
+
create function meta.reset_password(p_app_id int, p_token_hash text, p_new text) returns text
|
|
152
|
+
language plpgsql volatile security definer set search_path = meta, public, pg_catalog as $$
|
|
153
|
+
declare
|
|
154
|
+
v_reset meta.password_reset;
|
|
155
|
+
v_acc meta.account;
|
|
156
|
+
begin
|
|
157
|
+
select * into v_reset from meta.password_reset
|
|
158
|
+
where app_id = p_app_id and token_hash = p_token_hash and used_at is null and expires_at > now()
|
|
159
|
+
for update;
|
|
160
|
+
if not found or coalesce(p_new, '') = '' then
|
|
161
|
+
return null;
|
|
162
|
+
end if;
|
|
163
|
+
select * into v_acc from meta.account where id = v_reset.account_id and active;
|
|
164
|
+
if not found then
|
|
165
|
+
return null;
|
|
166
|
+
end if;
|
|
167
|
+
update meta.account set password_hash = crypt(p_new, gen_salt('bf', 10)), must_change_password = false where id = v_acc.id;
|
|
168
|
+
update meta.password_reset set used_at = now() where account_id = v_acc.id and used_at is null;
|
|
169
|
+
delete from meta.session where app_id is not null and lower(username) = lower(v_acc.username);
|
|
170
|
+
delete from meta.persistent_login where account_id = v_acc.id;
|
|
171
|
+
return v_acc.username;
|
|
172
|
+
end
|
|
173
|
+
$$;
|
|
174
|
+
revoke all on function meta.reset_password(int, text, text) from public;
|
|
175
|
+
grant execute on function meta.reset_password(int, text, text) to pgkiln_runtime;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- 088: more region templates (APEX: Hero, Title Bar, Buttons Container,
|
|
3
|
+
-- Inline Dialog, Inline Drawer, Wizard Container)
|
|
4
|
+
--
|
|
5
|
+
-- hero a large banner: title, text, an icon, buttons
|
|
6
|
+
-- title_bar the title and buttons on one line, without a card
|
|
7
|
+
-- buttons_container the buttons only (and any text), without a header
|
|
8
|
+
-- inline_dialog hidden until opened (dynamic action "Open region", or
|
|
9
|
+
-- a link to #R<id>); a dialog over the page
|
|
10
|
+
-- inline_drawer the same, sliding in from the side
|
|
11
|
+
-- wizard a narrow card for one step of a wizard (with the list
|
|
12
|
+
-- template "wizard_progress" above it)
|
|
13
|
+
-- The carousel is a style of the display selector region ({"style":
|
|
14
|
+
-- "carousel"}); it needs no column.
|
|
15
|
+
-- =====================================================================
|
|
16
|
+
alter table meta.region drop constraint region_template_check;
|
|
17
|
+
alter table meta.region add constraint region_template_check
|
|
18
|
+
check (template in ('standard', 'plain', 'collapsible', 'hero', 'title_bar', 'buttons_container', 'inline_dialog', 'inline_drawer', 'wizard'));
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- 089: page groups (APEX: Shared Components → Page Groups)
|
|
3
|
+
--
|
|
4
|
+
-- meta.page_group: an application's named groups of pages, to organise
|
|
5
|
+
-- them in the App Builder (the page list by group, a filter). A page's
|
|
6
|
+
-- group is meta.page.page_group (the group's name; renaming a group in
|
|
7
|
+
-- the builder renames it on its pages). They change nothing at run time.
|
|
8
|
+
-- export_app / import_app are redefined from 086 to carry page_groups.
|
|
9
|
+
-- =====================================================================
|
|
10
|
+
create table meta.page_group (
|
|
11
|
+
id serial primary key,
|
|
12
|
+
app_id int not null references meta.app on delete cascade,
|
|
13
|
+
name text not null check (name ~ '^[A-Za-z0-9][A-Za-z0-9 _.-]{0,59}$'),
|
|
14
|
+
description text,
|
|
15
|
+
unique (app_id, name)
|
|
16
|
+
);
|
|
17
|
+
revoke all on meta.page_group from public;
|
|
18
|
+
grant select on meta.page_group to pgkiln_runtime;
|
|
19
|
+
comment on table meta.page_group is 'Page groups (089): named groups of an application''s pages, for the App Builder';
|
|
20
|
+
|
|
21
|
+
alter table meta.page add column page_group text;
|
|
22
|
+
|
|
23
|
+
-- renaming a group renames it on its pages; deleting it takes the pages out of it
|
|
24
|
+
create function meta.page_group_follow() returns trigger
|
|
25
|
+
language plpgsql set search_path = meta, pg_catalog as $$
|
|
26
|
+
begin
|
|
27
|
+
if tg_op = 'DELETE' then
|
|
28
|
+
update meta.page set page_group = null where app_id = old.app_id and page_group = old.name;
|
|
29
|
+
return old;
|
|
30
|
+
end if;
|
|
31
|
+
if new.name is distinct from old.name then
|
|
32
|
+
update meta.page set page_group = new.name where app_id = new.app_id and page_group = old.name;
|
|
33
|
+
end if;
|
|
34
|
+
return new;
|
|
35
|
+
end
|
|
36
|
+
$$;
|
|
37
|
+
create trigger page_group_follow after update or delete on meta.page_group
|
|
38
|
+
for each row execute function meta.page_group_follow();
|
|
39
|
+
|
|
40
|
+
create or replace function meta.export_app(p_alias text) returns jsonb
|
|
41
|
+
language sql stable set search_path = meta, pg_catalog as $$
|
|
42
|
+
select jsonb_build_object(
|
|
43
|
+
'format', 'pgkiln/2',
|
|
44
|
+
-- (083) availability and build status belong to the installation
|
|
45
|
+
'app', to_jsonb(a) - 'id' - 'created_at' - 'updated_at' - 'availability' - 'availability_message' - 'availability_users' - 'build_status',
|
|
46
|
+
'authz_schemes', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.authz_scheme x where x.app_id = a.id), '[]'),
|
|
47
|
+
'app_items', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.app_item x where x.app_id = a.id), '[]'),
|
|
48
|
+
'app_processes', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.seq, x.id) from meta.app_process x where x.app_id = a.id), '[]'),
|
|
49
|
+
'lovs', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.lov x where x.app_id = a.id), '[]'),
|
|
50
|
+
'group_roles', coalesce((select jsonb_agg(to_jsonb(x) - 'app_id' order by x.group_name, x.role) from meta.app_group_role x where x.app_id = a.id), '[]'),
|
|
51
|
+
'text_messages', coalesce((select jsonb_agg(to_jsonb(x) - 'app_id' order by x.name, x.language) from meta.text_message x where x.app_id = a.id), '[]'),
|
|
52
|
+
'translations', coalesce((select jsonb_agg(to_jsonb(x) - 'app_id' order by x.language, x.source) from meta.translation x where x.app_id = a.id), '[]'),
|
|
53
|
+
'report_layouts', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' - 'logo' || jsonb_build_object('logo', encode(x.logo, 'base64')) order by x.name)
|
|
54
|
+
from meta.report_layout x where x.app_id = a.id), '[]'),
|
|
55
|
+
'automations', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' - 'next_run_at' - 'last_run_at' - 'last_status' - 'code' order by x.name)
|
|
56
|
+
from meta.automation x where x.app_id = a.id), '[]'),
|
|
57
|
+
-- (044) the actions of the automations, by automation name
|
|
58
|
+
'automation_actions', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.automation_name collate "C", x.seq, x.name collate "C")
|
|
59
|
+
from meta.automation_action x where x.app_id = a.id), '[]'),
|
|
60
|
+
'document_templates', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.document_template x where x.app_id = a.id), '[]'),
|
|
61
|
+
'task_definitions', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.task_definition x where x.app_id = a.id), '[]'),
|
|
62
|
+
'workflow_definitions', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.workflow_definition x where x.app_id = a.id), '[]'),
|
|
63
|
+
'rest_modules', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.rest_module x where x.app_id = a.id), '[]'),
|
|
64
|
+
'template_components', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.static_id) from meta.template_component x where x.app_id = a.id), '[]'),
|
|
65
|
+
'build_options', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.build_option x where x.app_id = a.id), '[]'),
|
|
66
|
+
-- (030) secrets never leave the installation: they are entered again after an import
|
|
67
|
+
-- (050) nor do the password and refresh token of the OAuth2 flows, or the token state
|
|
68
|
+
'web_credentials', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' - 'secret_enc' - 'password_enc' - 'refresh_token_enc' - 'token_refreshed_at' order by x.name)
|
|
69
|
+
from meta.web_credential x where x.app_id = a.id), '[]'),
|
|
70
|
+
-- (050) the synchronisation's state (next and last run) belongs to the installation
|
|
71
|
+
'rest_sources', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' - 'sync_next_at' - 'sync_last_at' - 'sync_last_status' order by x.name)
|
|
72
|
+
from meta.rest_source x where x.app_id = a.id), '[]'),
|
|
73
|
+
-- (041) data load definitions
|
|
74
|
+
'data_load_definitions', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.data_load_def x where x.app_id = a.id), '[]'),
|
|
75
|
+
-- (042) lists; entries keep their ids so parents can be remapped on import
|
|
76
|
+
'lists', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.list x where x.app_id = a.id), '[]'),
|
|
77
|
+
'list_entries', coalesce((select jsonb_agg(to_jsonb(x) - 'app_id' order by x.list_name, x.parent_id nulls first, x.seq, x.id) from meta.list_entry x where x.app_id = a.id), '[]'),
|
|
78
|
+
-- (042) supporting objects: install, upgrade and deinstall scripts (never run on import)
|
|
79
|
+
'supporting_scripts', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.supporting_script x where x.app_id = a.id), '[]'),
|
|
80
|
+
-- (069) plug-ins (their files are static files, their templates template components)
|
|
81
|
+
'plugins', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.plugin x where x.app_id = a.id), '[]'),
|
|
82
|
+
-- (080) search configurations
|
|
83
|
+
'search_configs', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.search_config x where x.app_id = a.id), '[]'),
|
|
84
|
+
-- (081) e-mail templates
|
|
85
|
+
'email_templates', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.email_template x where x.app_id = a.id), '[]'),
|
|
86
|
+
-- (086) application settings (with their values) and dynamic translations
|
|
87
|
+
'app_settings', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.app_setting x where x.app_id = a.id), '[]'),
|
|
88
|
+
'page_groups', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.page_group x where x.app_id = a.id), '[]'),
|
|
89
|
+
'dynamic_translations', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.language, x.source collate "C") from meta.dynamic_translation x where x.app_id = a.id), '[]'),
|
|
90
|
+
-- (068) static application files (JavaScript, CSS, images, fonts) as base64
|
|
91
|
+
'static_files', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' - 'content' - 'updated_at' || jsonb_build_object('content', translate(encode(x.content, 'base64'), E'\n', '')) order by x.name)
|
|
92
|
+
from meta.static_file x where x.app_id = a.id), '[]'),
|
|
93
|
+
-- nav entries and regions keep their ids, so parents and references can be remapped on import
|
|
94
|
+
'nav', coalesce((select jsonb_agg(to_jsonb(x) - 'app_id' order by x.parent_id nulls first, x.seq, x.id) from meta.nav_entry x where x.app_id = a.id), '[]'),
|
|
95
|
+
'pages', coalesce((
|
|
96
|
+
select jsonb_agg(to_jsonb(p) - 'id' - 'app_id' || jsonb_build_object(
|
|
97
|
+
'regions', coalesce((select jsonb_agg(to_jsonb(r) - 'page_id' order by r.seq, r.id) from meta.region r where r.page_id = p.id), '[]'),
|
|
98
|
+
'items', coalesce((select jsonb_agg(to_jsonb(i) - 'id' - 'page_id' order by i.seq, i.id) from meta.item i where i.page_id = p.id), '[]'),
|
|
99
|
+
'buttons', coalesce((select jsonb_agg(to_jsonb(b) - 'id' - 'page_id' order by b.seq, b.id) from meta.button b where b.page_id = p.id), '[]'),
|
|
100
|
+
'dynamic_actions', coalesce((select jsonb_agg(to_jsonb(d) - 'id' - 'page_id' order by d.seq, d.id) from meta.dynamic_action d where d.page_id = p.id), '[]'),
|
|
101
|
+
'validations', coalesce((select jsonb_agg(to_jsonb(v) - 'id' - 'page_id' order by v.seq, v.id) from meta.validation v where v.page_id = p.id), '[]'),
|
|
102
|
+
'processes', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'page_id' order by x.seq, x.id) from meta.process x where x.page_id = p.id), '[]'),
|
|
103
|
+
'computations', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'page_id' order by x.seq, x.id) from meta.computation x where x.page_id = p.id), '[]'),
|
|
104
|
+
'branches', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'page_id' order by x.seq, x.id) from meta.branch x where x.page_id = p.id), '[]')
|
|
105
|
+
) order by p.page_no)
|
|
106
|
+
from meta.page p where p.app_id = a.id), '[]'))
|
|
107
|
+
from meta.app a
|
|
108
|
+
where a.alias = p_alias
|
|
109
|
+
$$;
|
|
110
|
+
|
|
111
|
+
-- ---------------------------------------------------------------- import
|
|
112
|
+
-- Same as 081, plus app_settings and dynamic_translations.
|
|
113
|
+
|
|
114
|
+
create or replace function meta.import_app(p_doc jsonb, p_alias text default null) returns int
|
|
115
|
+
language plpgsql set search_path = meta, pg_catalog as $$
|
|
116
|
+
declare
|
|
117
|
+
v_app_id int;
|
|
118
|
+
v_page_id int;
|
|
119
|
+
v_page jsonb;
|
|
120
|
+
v_e jsonb;
|
|
121
|
+
v_rmap jsonb;
|
|
122
|
+
v_nmap jsonb := '{}';
|
|
123
|
+
v_lmap jsonb := '{}';
|
|
124
|
+
v_new_id int;
|
|
125
|
+
begin
|
|
126
|
+
if p_doc->>'format' is distinct from 'pgkiln/2' then
|
|
127
|
+
raise exception 'unsupported export format %', coalesce(p_doc->>'format', '(none)');
|
|
128
|
+
end if;
|
|
129
|
+
|
|
130
|
+
insert into meta.app
|
|
131
|
+
select (jsonb_populate_record(null::meta.app, p_doc->'app' || jsonb_build_object(
|
|
132
|
+
'id', nextval('meta.app_id_seq'),
|
|
133
|
+
'alias', coalesce(p_alias, p_doc->'app'->>'alias'),
|
|
134
|
+
'created_at', now(), 'updated_at', now()))).*
|
|
135
|
+
returning id into v_app_id;
|
|
136
|
+
|
|
137
|
+
insert into meta.authz_scheme
|
|
138
|
+
select (jsonb_populate_record(null::meta.authz_scheme, e || jsonb_build_object('id', nextval('meta.authz_scheme_id_seq'), 'app_id', v_app_id))).*
|
|
139
|
+
from jsonb_array_elements(coalesce(p_doc->'authz_schemes', '[]')) e;
|
|
140
|
+
insert into meta.app_item
|
|
141
|
+
select (jsonb_populate_record(null::meta.app_item, e || jsonb_build_object('id', nextval('meta.app_item_id_seq'), 'app_id', v_app_id))).*
|
|
142
|
+
from jsonb_array_elements(coalesce(p_doc->'app_items', '[]')) e;
|
|
143
|
+
insert into meta.app_process
|
|
144
|
+
select (jsonb_populate_record(null::meta.app_process, e || jsonb_build_object('id', nextval('meta.app_process_id_seq'), 'app_id', v_app_id))).*
|
|
145
|
+
from jsonb_array_elements(coalesce(p_doc->'app_processes', '[]')) e;
|
|
146
|
+
insert into meta.lov
|
|
147
|
+
select (jsonb_populate_record(null::meta.lov, e || jsonb_build_object('id', nextval('meta.lov_id_seq'), 'app_id', v_app_id))).*
|
|
148
|
+
from jsonb_array_elements(coalesce(p_doc->'lovs', '[]')) e;
|
|
149
|
+
insert into meta.app_group_role
|
|
150
|
+
select (jsonb_populate_record(null::meta.app_group_role, e || jsonb_build_object('app_id', v_app_id))).*
|
|
151
|
+
from jsonb_array_elements(coalesce(p_doc->'group_roles', '[]')) e;
|
|
152
|
+
insert into meta.text_message
|
|
153
|
+
select (jsonb_populate_record(null::meta.text_message, e || jsonb_build_object('app_id', v_app_id))).*
|
|
154
|
+
from jsonb_array_elements(coalesce(p_doc->'text_messages', '[]')) e;
|
|
155
|
+
insert into meta.translation
|
|
156
|
+
select (jsonb_populate_record(null::meta.translation, e || jsonb_build_object('app_id', v_app_id))).*
|
|
157
|
+
from jsonb_array_elements(coalesce(p_doc->'translations', '[]')) e;
|
|
158
|
+
insert into meta.report_layout
|
|
159
|
+
select (jsonb_populate_record(null::meta.report_layout, (e - 'logo') || jsonb_build_object(
|
|
160
|
+
'id', nextval('meta.report_layout_id_seq'), 'app_id', v_app_id,
|
|
161
|
+
'logo', null))).*
|
|
162
|
+
from jsonb_array_elements(coalesce(p_doc->'report_layouts', '[]')) e;
|
|
163
|
+
update meta.report_layout l
|
|
164
|
+
set logo = decode(e->>'logo', 'base64')
|
|
165
|
+
from jsonb_array_elements(coalesce(p_doc->'report_layouts', '[]')) e
|
|
166
|
+
where l.app_id = v_app_id and l.name = e->>'name' and e->>'logo' is not null;
|
|
167
|
+
-- imported automations start switched off: the copy must not run the original's jobs unasked.
|
|
168
|
+
-- (044) a file with actions: the automations' code is ignored; an older file: code becomes the single action
|
|
169
|
+
insert into meta.automation
|
|
170
|
+
select (jsonb_populate_record(null::meta.automation, '{"error_handling": "stop"}'::jsonb
|
|
171
|
+
|| jsonb_strip_nulls(case when p_doc ? 'automation_actions' then e - 'code' else e end)
|
|
172
|
+
|| jsonb_build_object('id', nextval('meta.automation_id_seq'), 'app_id', v_app_id, 'enabled', false))).*
|
|
173
|
+
from jsonb_array_elements(coalesce(p_doc->'automations', '[]')) e;
|
|
174
|
+
insert into meta.automation_action
|
|
175
|
+
select (jsonb_populate_record(null::meta.automation_action, '{"seq": 10}'::jsonb || jsonb_strip_nulls(e)
|
|
176
|
+
|| jsonb_build_object('id', nextval('meta.automation_action_id_seq'), 'app_id', v_app_id))).*
|
|
177
|
+
from jsonb_array_elements(coalesce(p_doc->'automation_actions', '[]')) e;
|
|
178
|
+
insert into meta.document_template
|
|
179
|
+
select (jsonb_populate_record(null::meta.document_template, e || jsonb_build_object('id', nextval('meta.document_template_id_seq'), 'app_id', v_app_id))).*
|
|
180
|
+
from jsonb_array_elements(coalesce(p_doc->'document_templates', '[]')) e;
|
|
181
|
+
insert into meta.task_definition
|
|
182
|
+
select (jsonb_populate_record(null::meta.task_definition, e || jsonb_build_object('id', nextval('meta.task_definition_id_seq'), 'app_id', v_app_id))).*
|
|
183
|
+
from jsonb_array_elements(coalesce(p_doc->'task_definitions', '[]')) e;
|
|
184
|
+
insert into meta.workflow_definition
|
|
185
|
+
select (jsonb_populate_record(null::meta.workflow_definition, e || jsonb_build_object('id', nextval('meta.workflow_definition_id_seq'), 'app_id', v_app_id))).*
|
|
186
|
+
from jsonb_array_elements(coalesce(p_doc->'workflow_definitions', '[]')) e;
|
|
187
|
+
insert into meta.rest_module
|
|
188
|
+
select (jsonb_populate_record(null::meta.rest_module, e || jsonb_build_object('id', nextval('meta.rest_module_id_seq'), 'app_id', v_app_id))).*
|
|
189
|
+
from jsonb_array_elements(coalesce(p_doc->'rest_modules', '[]')) e;
|
|
190
|
+
-- (028) template components: regions and report columns refer to them by static id
|
|
191
|
+
insert into meta.template_component
|
|
192
|
+
select (jsonb_populate_record(null::meta.template_component, e || jsonb_build_object('id', nextval('meta.template_component_id_seq'), 'app_id', v_app_id))).*
|
|
193
|
+
from jsonb_array_elements(coalesce(p_doc->'template_components', '[]')) e;
|
|
194
|
+
-- (029) build options (components name them by name)
|
|
195
|
+
insert into meta.build_option
|
|
196
|
+
select (jsonb_populate_record(null::meta.build_option, e || jsonb_build_object('id', nextval('meta.build_option_id_seq'), 'app_id', v_app_id))).*
|
|
197
|
+
from jsonb_array_elements(coalesce(p_doc->'build_options', '[]')) e;
|
|
198
|
+
-- (030) web credentials arrive without a secret, whatever the document holds
|
|
199
|
+
-- (050) nor a password or refresh token
|
|
200
|
+
insert into meta.web_credential
|
|
201
|
+
select (jsonb_populate_record(null::meta.web_credential, '{"type": "basic", "valid_for": [], "grant_type": "client_credentials"}'::jsonb
|
|
202
|
+
|| jsonb_strip_nulls(e - 'secret_enc' - 'password_enc' - 'refresh_token_enc' - 'token_refreshed_at')
|
|
203
|
+
|| jsonb_build_object('id', nextval('meta.web_credential_id_seq'), 'app_id', v_app_id))).*
|
|
204
|
+
from jsonb_array_elements(coalesce(p_doc->'web_credentials', '[]')) e;
|
|
205
|
+
-- (050) write-back operations and synchronisation; an imported synchronisation starts switched off
|
|
206
|
+
insert into meta.rest_source
|
|
207
|
+
select (jsonb_populate_record(null::meta.rest_source, '{"method": "GET", "headers": {}, "params": [], "columns": [], "cache_seconds": 0, "timeout_s": 10, "max_rows": 1000,
|
|
208
|
+
"operations": {}, "key_columns": [], "sync_mode": "merge", "sync_delete": false, "sync_time_zone": "UTC"}'::jsonb
|
|
209
|
+
|| jsonb_strip_nulls(e - 'sync_next_at' - 'sync_last_at' - 'sync_last_status')
|
|
210
|
+
|| jsonb_build_object('id', nextval('meta.rest_source_id_seq'), 'app_id', v_app_id, 'sync_enabled', false))).*
|
|
211
|
+
from jsonb_array_elements(coalesce(p_doc->'rest_sources', '[]')) e;
|
|
212
|
+
|
|
213
|
+
-- (041) data load definitions (the data_load process names them)
|
|
214
|
+
insert into meta.data_load_def
|
|
215
|
+
select (jsonb_populate_record(null::meta.data_load_def, '{"format": "auto", "headers": true, "mode": "append", "skip_errors": false, "columns": []}'::jsonb
|
|
216
|
+
|| jsonb_strip_nulls(e) || jsonb_build_object('id', nextval('meta.data_load_def_id_seq'), 'app_id', v_app_id))).*
|
|
217
|
+
from jsonb_array_elements(coalesce(p_doc->'data_load_definitions', '[]')) e;
|
|
218
|
+
|
|
219
|
+
-- (042) lists and their entries, supporting scripts. Entries are inserted
|
|
220
|
+
-- without a parent first and the parents set afterwards, so the order of
|
|
221
|
+
-- the entries in the document doesn't matter (a moved entry may have a
|
|
222
|
+
-- higher id than its children).
|
|
223
|
+
insert into meta.list
|
|
224
|
+
select (jsonb_populate_record(null::meta.list, '{"type": "static"}'::jsonb || jsonb_strip_nulls(e) || jsonb_build_object('id', nextval('meta.list_id_seq'), 'app_id', v_app_id))).*
|
|
225
|
+
from jsonb_array_elements(coalesce(p_doc->'lists', '[]')) e;
|
|
226
|
+
for v_e in select * from jsonb_array_elements(coalesce(p_doc->'list_entries', '[]')) loop
|
|
227
|
+
insert into meta.list_entry
|
|
228
|
+
select (jsonb_populate_record(null::meta.list_entry, '{"target_items": {}}'::jsonb || jsonb_strip_nulls(v_e) || jsonb_build_object(
|
|
229
|
+
'id', nextval('meta.list_entry_id_seq'), 'app_id', v_app_id, 'parent_id', null))).*
|
|
230
|
+
returning id into v_new_id;
|
|
231
|
+
if v_e->>'id' is not null then
|
|
232
|
+
v_lmap := v_lmap || jsonb_build_object(v_e->>'id', v_new_id);
|
|
233
|
+
end if;
|
|
234
|
+
end loop;
|
|
235
|
+
update meta.list_entry x
|
|
236
|
+
set parent_id = (v_lmap->>(e->>'parent_id'))::int
|
|
237
|
+
from jsonb_array_elements(coalesce(p_doc->'list_entries', '[]')) e
|
|
238
|
+
where e->>'parent_id' is not null and v_lmap ? (e->>'parent_id') and v_lmap ? (e->>'id')
|
|
239
|
+
and x.id = (v_lmap->>(e->>'id'))::int;
|
|
240
|
+
insert into meta.supporting_script
|
|
241
|
+
select (jsonb_populate_record(null::meta.supporting_script, '{"kind": "install", "seq": 10, "script": ""}'::jsonb || jsonb_strip_nulls(e)
|
|
242
|
+
|| jsonb_build_object('id', nextval('meta.supporting_script_id_seq'), 'app_id', v_app_id))).*
|
|
243
|
+
from jsonb_array_elements(coalesce(p_doc->'supporting_scripts', '[]')) e;
|
|
244
|
+
|
|
245
|
+
-- (069) plug-ins
|
|
246
|
+
insert into meta.plugin
|
|
247
|
+
select (jsonb_populate_record(null::meta.plugin, e || jsonb_build_object('id', nextval('meta.plugin_id_seq'), 'app_id', v_app_id))).*
|
|
248
|
+
from jsonb_array_elements(coalesce(p_doc->'plugins', '[]')) e;
|
|
249
|
+
|
|
250
|
+
-- (080) search configurations (search regions name them)
|
|
251
|
+
insert into meta.search_config
|
|
252
|
+
select (jsonb_populate_record(null::meta.search_config, '{"seq": 10, "source_type": "table", "searchable_columns": [], "max_results": 10}'::jsonb
|
|
253
|
+
|| jsonb_strip_nulls(e) || jsonb_build_object('id', nextval('meta.search_config_id_seq'), 'app_id', v_app_id))).*
|
|
254
|
+
from jsonb_array_elements(coalesce(p_doc->'search_configs', '[]')) e;
|
|
255
|
+
|
|
256
|
+
-- (081) e-mail templates (meta.send_mail_template and the send_email process name them)
|
|
257
|
+
insert into meta.email_template
|
|
258
|
+
select (jsonb_populate_record(null::meta.email_template, e || jsonb_build_object('id', nextval('meta.email_template_id_seq'), 'app_id', v_app_id))).*
|
|
259
|
+
from jsonb_array_elements(coalesce(p_doc->'email_templates', '[]')) e;
|
|
260
|
+
|
|
261
|
+
-- (089) page groups
|
|
262
|
+
insert into meta.page_group
|
|
263
|
+
select (jsonb_populate_record(null::meta.page_group, e || jsonb_build_object('id', nextval('meta.page_group_id_seq'), 'app_id', v_app_id))).*
|
|
264
|
+
from jsonb_array_elements(coalesce(p_doc->'page_groups', '[]')) e;
|
|
265
|
+
|
|
266
|
+
-- (086) application settings and dynamic translations
|
|
267
|
+
insert into meta.app_setting
|
|
268
|
+
select (jsonb_populate_record(null::meta.app_setting, e || jsonb_build_object('id', nextval('meta.app_setting_id_seq'), 'app_id', v_app_id))).*
|
|
269
|
+
from jsonb_array_elements(coalesce(p_doc->'app_settings', '[]')) e;
|
|
270
|
+
insert into meta.dynamic_translation
|
|
271
|
+
select (jsonb_populate_record(null::meta.dynamic_translation, e || jsonb_build_object('id', nextval('meta.dynamic_translation_id_seq'), 'app_id', v_app_id))).*
|
|
272
|
+
from jsonb_array_elements(coalesce(p_doc->'dynamic_translations', '[]')) e;
|
|
273
|
+
|
|
274
|
+
-- (068) static application files
|
|
275
|
+
insert into meta.static_file (app_id, name, mime, content, updated_at)
|
|
276
|
+
select v_app_id, e->>'name', e->>'mime', decode(coalesce(e->>'content', ''), 'base64'), now()
|
|
277
|
+
from jsonb_array_elements(coalesce(p_doc->'static_files', '[]')) e;
|
|
278
|
+
|
|
279
|
+
for v_e in select * from jsonb_array_elements(coalesce(p_doc->'nav', '[]')) loop
|
|
280
|
+
insert into meta.nav_entry
|
|
281
|
+
select (jsonb_populate_record(null::meta.nav_entry, v_e || jsonb_build_object(
|
|
282
|
+
'id', nextval('meta.nav_entry_id_seq'), 'app_id', v_app_id,
|
|
283
|
+
'parent_id', v_nmap->>(v_e->>'parent_id')))).*
|
|
284
|
+
returning id into v_new_id;
|
|
285
|
+
v_nmap := v_nmap || jsonb_build_object(v_e->>'id', v_new_id);
|
|
286
|
+
end loop;
|
|
287
|
+
|
|
288
|
+
for v_page in select * from jsonb_array_elements(coalesce(p_doc->'pages', '[]')) loop
|
|
289
|
+
insert into meta.page
|
|
290
|
+
select (jsonb_populate_record(null::meta.page, v_page || jsonb_build_object('id', nextval('meta.page_id_seq'), 'app_id', v_app_id))).*
|
|
291
|
+
returning id into v_page_id;
|
|
292
|
+
|
|
293
|
+
v_rmap := '{}';
|
|
294
|
+
for v_e in select * from jsonb_array_elements(coalesce(v_page->'regions', '[]')) loop
|
|
295
|
+
insert into meta.region
|
|
296
|
+
select (jsonb_populate_record(null::meta.region, v_e || jsonb_build_object('id', nextval('meta.region_id_seq'), 'page_id', v_page_id))).*
|
|
297
|
+
returning id into v_new_id;
|
|
298
|
+
v_rmap := v_rmap || jsonb_build_object(v_e->>'id', v_new_id);
|
|
299
|
+
end loop;
|
|
300
|
+
-- facet and map regions point at their report region by id
|
|
301
|
+
update meta.region r
|
|
302
|
+
set config = jsonb_set(r.config, '{report}', to_jsonb((v_rmap->>(r.config->>'report'))::int))
|
|
303
|
+
where r.page_id = v_page_id and r.type in ('facets', 'smart_filters', 'map') and v_rmap ? (r.config->>'report');
|
|
304
|
+
|
|
305
|
+
insert into meta.item
|
|
306
|
+
select (jsonb_populate_record(null::meta.item, e || jsonb_build_object('id', nextval('meta.item_id_seq'), 'page_id', v_page_id, 'region_id', v_rmap->>(e->>'region_id')))).*
|
|
307
|
+
from jsonb_array_elements(coalesce(v_page->'items', '[]')) e;
|
|
308
|
+
insert into meta.button
|
|
309
|
+
select (jsonb_populate_record(null::meta.button, e || jsonb_build_object('id', nextval('meta.button_id_seq'), 'page_id', v_page_id, 'region_id', v_rmap->>(e->>'region_id')))).*
|
|
310
|
+
from jsonb_array_elements(coalesce(v_page->'buttons', '[]')) e;
|
|
311
|
+
insert into meta.dynamic_action
|
|
312
|
+
select (jsonb_populate_record(null::meta.dynamic_action, e || jsonb_build_object('id', nextval('meta.dynamic_action_id_seq'), 'page_id', v_page_id, 'affected_region_id', v_rmap->>(e->>'affected_region_id')))).*
|
|
313
|
+
from jsonb_array_elements(coalesce(v_page->'dynamic_actions', '[]')) e;
|
|
314
|
+
insert into meta.validation
|
|
315
|
+
select (jsonb_populate_record(null::meta.validation, e || jsonb_build_object('id', nextval('meta.validation_id_seq'), 'page_id', v_page_id))).*
|
|
316
|
+
from jsonb_array_elements(coalesce(v_page->'validations', '[]')) e;
|
|
317
|
+
insert into meta.process
|
|
318
|
+
select (jsonb_populate_record(null::meta.process, e || jsonb_build_object('id', nextval('meta.process_id_seq'), 'page_id', v_page_id, 'region_id', v_rmap->>(e->>'region_id')))).*
|
|
319
|
+
from jsonb_array_elements(coalesce(v_page->'processes', '[]')) e;
|
|
320
|
+
insert into meta.computation
|
|
321
|
+
select (jsonb_populate_record(null::meta.computation, e || jsonb_build_object('id', nextval('meta.computation_id_seq'), 'page_id', v_page_id))).*
|
|
322
|
+
from jsonb_array_elements(coalesce(v_page->'computations', '[]')) e;
|
|
323
|
+
insert into meta.branch
|
|
324
|
+
select (jsonb_populate_record(null::meta.branch, e || jsonb_build_object('id', nextval('meta.branch_id_seq'), 'page_id', v_page_id))).*
|
|
325
|
+
from jsonb_array_elements(coalesce(v_page->'branches', '[]')) e;
|
|
326
|
+
end loop;
|
|
327
|
+
|
|
328
|
+
return v_app_id;
|
|
329
|
+
end
|
|
330
|
+
$$;
|