pgkiln 0.31.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/README.md +2 -2
- package/SECURITY.md +10 -0
- package/db/migrations/076_form_lost_update.sql +50 -0
- package/db/migrations/077_warn_unsaved.sql +41 -0
- package/db/migrations/078_url_region.sql +12 -0
- package/db/migrations/079_developer_toolbar.sql +53 -0
- package/db/migrations/080_search.sql +326 -0
- package/db/migrations/081_mail.sql +621 -0
- package/db/migrations/082_app_backups.sql +73 -0
- package/db/migrations/083_availability.sql +156 -0
- package/db/migrations/084_feedback.sql +46 -0
- package/db/migrations/085_page_behaviour.sql +65 -0
- package/db/migrations/086_data_settings.sql +599 -0
- 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 +61 -39
- package/docs/guide/01-installation.md +1 -0
- package/docs/guide/03-builder.md +154 -4
- package/docs/guide/04-pages-and-regions.md +157 -3
- package/docs/guide/05-items.md +47 -0
- package/docs/guide/06-processing.md +210 -2
- package/docs/guide/07-dynamic-actions.md +60 -2
- package/docs/guide/08-security.md +56 -4
- package/docs/guide/09-reference.md +21 -0
- package/docs/guide/11-from-apex.md +9 -2
- package/docs/guide/12-development.md +40 -2
- package/docs/guide/14-globalization.md +15 -0
- package/docs/guide/15-extensions.md +1 -1
- package/docs/guide/17-mobile.md +3 -1
- package/docs/guide/18-cli.md +32 -1
- package/docs/guide/20-ai-agents.md +5 -2
- package/examples/hr/hr_50_search.sql +47 -0
- package/examples/hr/hr_51_mail.sql +70 -0
- package/examples/hr/hr_52_feedback.sql +15 -0
- package/examples/hr/hr_53_data_settings.sql +108 -0
- package/examples/hr/hr_54_region_templates.sql +67 -0
- package/examples/hr/hr_55_page_groups.sql +24 -0
- package/package.json +3 -1
- package/public/app.css +122 -3
- package/public/app.js +496 -13
- package/public/builder.css +22 -0
- package/public/builder.js +77 -0
- package/public/code-editor.css +3 -3
- package/public/sw.js +5 -3
- package/src/app.ts +10 -0
- package/src/appfiles.ts +13 -0
- package/src/automations.ts +12 -0
- package/src/builder/advisor.ts +68 -14
- package/src/builder/arrange.ts +2 -2
- package/src/builder/availability.ts +74 -0
- package/src/builder/backups.ts +134 -0
- package/src/builder/components.ts +123 -16
- package/src/builder/ddl-tools.ts +114 -0
- package/src/builder/designer.ts +35 -4
- package/src/builder/feedback.ts +82 -0
- package/src/builder/forms.ts +10 -5
- package/src/builder/home.ts +3 -0
- package/src/builder/locks.ts +15 -5
- package/src/builder/login-page.ts +82 -0
- package/src/builder/mail.ts +425 -0
- package/src/builder/newapp.ts +1 -1
- package/src/builder/region-help.ts +73 -0
- package/src/builder/region-settings.ts +19 -3
- package/src/builder/routes.ts +60 -5
- package/src/builder/search.ts +4 -4
- package/src/builder/shared.ts +3 -2
- package/src/builder/sql.ts +3 -0
- package/src/builder/ui.ts +9 -3
- package/src/cli/apps.ts +46 -5
- package/src/cli/main.ts +66 -5
- package/src/cli/mcp.ts +58 -4
- package/src/cli/replace.ts +40 -4
- package/src/ddl.ts +509 -0
- package/src/geocode.ts +44 -0
- package/src/i18n/ar.ts +56 -0
- package/src/i18n/cs.ts +56 -0
- package/src/i18n/da.ts +56 -0
- package/src/i18n/de.ts +56 -0
- package/src/i18n/el.ts +56 -0
- package/src/i18n/es.ts +56 -0
- package/src/i18n/fi.ts +56 -0
- package/src/i18n/fr.ts +56 -0
- package/src/i18n/he.ts +56 -0
- package/src/i18n/it.ts +56 -0
- package/src/i18n/ja.ts +56 -0
- package/src/i18n/ko.ts +56 -0
- package/src/i18n/nb.ts +56 -0
- package/src/i18n/pl.ts +56 -0
- package/src/i18n/pt.ts +56 -0
- package/src/i18n/ru.ts +56 -0
- package/src/i18n/sv.ts +56 -0
- package/src/i18n/tr.ts +56 -0
- package/src/i18n/uk.ts +56 -0
- package/src/i18n/zh.ts +56 -0
- package/src/i18n.ts +112 -0
- package/src/icons.ts +2 -1
- package/src/layout.ts +3 -2
- package/src/mail/mail.ts +148 -0
- package/src/mail/providers.ts +444 -0
- package/src/metadata.ts +34 -10
- package/src/runtime/ajax.ts +97 -0
- package/src/runtime/authz.ts +1 -1
- package/src/runtime/availability.ts +37 -0
- package/src/runtime/context.ts +46 -6
- package/src/runtime/data-export.ts +94 -0
- package/src/runtime/data-items.ts +194 -0
- package/src/runtime/devbar.ts +184 -0
- package/src/runtime/display-selector.ts +12 -4
- package/src/runtime/engine.ts +26 -1
- package/src/runtime/feedback.ts +128 -0
- package/src/runtime/grid.ts +170 -26
- package/src/runtime/items.ts +8 -0
- package/src/runtime/lists.ts +11 -1
- package/src/runtime/password-reset.ts +171 -0
- package/src/runtime/processes.ts +83 -2
- package/src/runtime/pwa.ts +7 -10
- package/src/runtime/regions.ts +41 -0
- package/src/runtime/render.ts +48 -7
- package/src/runtime/report-views.ts +4 -2
- package/src/runtime/report.ts +36 -22
- package/src/runtime/rest.ts +4 -0
- package/src/runtime/routes.ts +94 -37
- package/src/runtime/search.ts +252 -0
- package/src/runtime/signin.ts +80 -0
- package/src/runtime/url-region.ts +93 -0
- package/src/security.ts +11 -1
- package/src/server.ts +2 -0
- package/src/session.ts +8 -2
- package/src/static-version.ts +34 -0
- package/src/workingcopy.ts +2 -2
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- 081: sending e-mail (APEX: APEX_MAIL, Email Templates, the "Send E-Mail"
|
|
3
|
+
-- process), through SMTP (password or OAuth2 / XOAUTH2), Microsoft Graph,
|
|
4
|
+
-- Amazon SES or an HTTP API (Resend, Postmark, SendGrid, Mailgun)
|
|
5
|
+
--
|
|
6
|
+
-- This replaces the decision of 008 (no e-mail): the project owner asked
|
|
7
|
+
-- for it on 2026-10-12.
|
|
8
|
+
--
|
|
9
|
+
-- meta.mail_service: an instance-level mail service (administrators,
|
|
10
|
+
-- Workspace utilities → Mail services). Provider, settings without
|
|
11
|
+
-- secrets, the secret (password, client secret or API key) encrypted by
|
|
12
|
+
-- the server with PGKILN_SECRET_KEY (write-only, never exported, never
|
|
13
|
+
-- readable by applications), the From addresses it may send as.
|
|
14
|
+
-- meta.app_mail_service: the service an application sends through, its
|
|
15
|
+
-- From address and a daily limit. Installation data: not exported.
|
|
16
|
+
-- meta.mail_queue, meta.mail_attachment: the outbox. meta.send_mail(),
|
|
17
|
+
-- meta.send_mail_template() and meta.add_mail_attachment() write it in
|
|
18
|
+
-- the caller's transaction (a rollback sends nothing) and NOTIFY
|
|
19
|
+
-- pgkiln_mail; the server sends after the commit (src/mail/), retries
|
|
20
|
+
-- with growing pauses, and keeps the log 30 days.
|
|
21
|
+
-- meta.email_template: an application's e-mail templates (subject, HTML
|
|
22
|
+
-- and plain text with #PLACEHOLDERS#), exported with the application.
|
|
23
|
+
--
|
|
24
|
+
-- The functions run for the current application (meta.app_id()) only when
|
|
25
|
+
-- the caller runs as that application's database role, so application code
|
|
26
|
+
-- can't send in another application's name.
|
|
27
|
+
--
|
|
28
|
+
-- export_app / import_app are redefined from 080 to carry email_templates.
|
|
29
|
+
-- =====================================================================
|
|
30
|
+
|
|
31
|
+
create table meta.mail_service (
|
|
32
|
+
id serial primary key,
|
|
33
|
+
name text not null unique check (name ~ '^[A-Z][A-Z0-9_]{0,59}$'),
|
|
34
|
+
description text,
|
|
35
|
+
provider text not null check (provider in ('smtp', 'graph', 'ses', 'resend', 'postmark', 'sendgrid', 'mailgun', 'log')),
|
|
36
|
+
-- host, port, security, auth, username, token_url, client_id, scope, tenant_id, access_key_id,
|
|
37
|
+
-- region, domain, message_stream, save_to_sent_items, base_url (administrators only); no secrets
|
|
38
|
+
settings jsonb not null default '{}' check (jsonb_typeof(settings) = 'object'),
|
|
39
|
+
-- "v1:" + base64(iv, tag, ciphertext); written by the server only (src/secrets.ts)
|
|
40
|
+
secret_enc text,
|
|
41
|
+
-- the From addresses it may send as: user@example.com, or @example.com for a whole domain
|
|
42
|
+
senders text[] not null default '{}',
|
|
43
|
+
enabled boolean not null default true,
|
|
44
|
+
created_at timestamptz not null default now(),
|
|
45
|
+
updated_at timestamptz not null default now()
|
|
46
|
+
);
|
|
47
|
+
revoke all on meta.mail_service from public;
|
|
48
|
+
comment on table meta.mail_service is 'Mail services of this installation (081); the secret is encrypted and never readable by applications';
|
|
49
|
+
|
|
50
|
+
create table meta.app_mail_service (
|
|
51
|
+
app_id int primary key references meta.app on delete cascade,
|
|
52
|
+
service_id int not null references meta.mail_service on delete cascade,
|
|
53
|
+
-- the application's From address when the caller gives none
|
|
54
|
+
from_address text,
|
|
55
|
+
-- per day (UTC); null = no limit
|
|
56
|
+
max_per_day int check (max_per_day >= 0)
|
|
57
|
+
);
|
|
58
|
+
revoke all on meta.app_mail_service from public;
|
|
59
|
+
|
|
60
|
+
create table meta.mail_queue (
|
|
61
|
+
id bigint generated always as identity primary key,
|
|
62
|
+
app_id int not null references meta.app on delete cascade,
|
|
63
|
+
username text,
|
|
64
|
+
from_address text,
|
|
65
|
+
reply_to text,
|
|
66
|
+
to_addresses text[] not null default '{}',
|
|
67
|
+
cc_addresses text[] not null default '{}',
|
|
68
|
+
bcc_addresses text[] not null default '{}',
|
|
69
|
+
subject text not null,
|
|
70
|
+
body_text text,
|
|
71
|
+
body_html text,
|
|
72
|
+
template text,
|
|
73
|
+
status text not null default 'queued' check (status in ('queued', 'sending', 'sent', 'error', 'cancelled')),
|
|
74
|
+
attempts int not null default 0,
|
|
75
|
+
not_before timestamptz not null default now(),
|
|
76
|
+
-- the last problem, or what the provider said
|
|
77
|
+
message text,
|
|
78
|
+
-- the provider's id of the message, the service that sent it
|
|
79
|
+
provider_id text,
|
|
80
|
+
service text,
|
|
81
|
+
created_at timestamptz not null default now(),
|
|
82
|
+
-- attachments may be added in the transaction that queued the mail only
|
|
83
|
+
created_xact bigint not null default txid_current(),
|
|
84
|
+
sent_at timestamptz,
|
|
85
|
+
check (cardinality(to_addresses) + cardinality(cc_addresses) + cardinality(bcc_addresses) between 1 and 50)
|
|
86
|
+
);
|
|
87
|
+
create index mail_queue_queued on meta.mail_queue (not_before) where status = 'queued';
|
|
88
|
+
create index mail_queue_app on meta.mail_queue (app_id, created_at);
|
|
89
|
+
revoke all on meta.mail_queue from public;
|
|
90
|
+
|
|
91
|
+
create table meta.mail_attachment (
|
|
92
|
+
id bigint generated always as identity primary key,
|
|
93
|
+
mail_id bigint not null references meta.mail_queue on delete cascade,
|
|
94
|
+
filename text not null check (filename ~ '^[^/\\\r\n"<>:|?*]{1,200}$'),
|
|
95
|
+
mime text not null check (mime ~ '^[a-z]+/[a-z0-9.+-]{1,100}$'),
|
|
96
|
+
content bytea not null check (octet_length(content) <= 10 * 1024 * 1024)
|
|
97
|
+
);
|
|
98
|
+
create index mail_attachment_mail on meta.mail_attachment (mail_id);
|
|
99
|
+
revoke all on meta.mail_attachment from public;
|
|
100
|
+
|
|
101
|
+
create table meta.email_template (
|
|
102
|
+
id serial primary key,
|
|
103
|
+
app_id int not null references meta.app on delete cascade,
|
|
104
|
+
name text not null check (name ~ '^[A-Z][A-Z0-9_]{0,59}$'),
|
|
105
|
+
subject text not null check (length(subject) between 1 and 998 and subject !~ '[\r\n]'),
|
|
106
|
+
html_body text,
|
|
107
|
+
text_body text,
|
|
108
|
+
unique (app_id, name)
|
|
109
|
+
);
|
|
110
|
+
revoke all on meta.email_template from public;
|
|
111
|
+
comment on table meta.email_template is 'E-mail templates (081): subject, HTML and plain text with #PLACEHOLDERS#; exported with the application';
|
|
112
|
+
|
|
113
|
+
alter table meta.process drop constraint process_type_check;
|
|
114
|
+
alter table meta.process add constraint process_type_check check (type in (
|
|
115
|
+
'ai_generate', 'chain', 'data_load', 'download', 'form_dml', 'grid_dml', 'invoke_api', 'sql', 'workflow', 'plugin', 'send_push', 'send_email'));
|
|
116
|
+
|
|
117
|
+
-- ---------------------------------------------------------------- SQL API
|
|
118
|
+
|
|
119
|
+
-- The addresses of a list (comma or semicolon separated, or one per element),
|
|
120
|
+
-- checked: plain addresses only (no names, no line breaks), each at most 254
|
|
121
|
+
-- characters. Raises with p_what in the message.
|
|
122
|
+
create function meta.mail_addresses(p_list text, p_what text) returns text[]
|
|
123
|
+
language plpgsql immutable set search_path = pg_catalog as $$
|
|
124
|
+
declare
|
|
125
|
+
v_out text[] := '{}';
|
|
126
|
+
v text;
|
|
127
|
+
begin
|
|
128
|
+
if p_list is null or btrim(p_list) = '' then
|
|
129
|
+
return v_out;
|
|
130
|
+
end if;
|
|
131
|
+
foreach v in array regexp_split_to_array(p_list, '\s*[,;]\s*') loop
|
|
132
|
+
v := btrim(v);
|
|
133
|
+
continue when v = '';
|
|
134
|
+
if length(v) > 254 or v !~ '^[A-Za-z0-9.!#$%&''*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$' then
|
|
135
|
+
raise exception 'meta.send_mail: "%" is not an e-mail address (%)', left(v, 100), p_what;
|
|
136
|
+
end if;
|
|
137
|
+
if not v = any (v_out) then
|
|
138
|
+
v_out := v_out || v;
|
|
139
|
+
end if;
|
|
140
|
+
end loop;
|
|
141
|
+
return v_out;
|
|
142
|
+
end
|
|
143
|
+
$$;
|
|
144
|
+
|
|
145
|
+
-- The calling application and its mail service: the current application, called as its own
|
|
146
|
+
-- database role, with a mail service an administrator gave it.
|
|
147
|
+
create function meta.mail_caller(out app_id int, out service_id int, out from_address text, out max_per_day int, out senders text[])
|
|
148
|
+
language plpgsql stable security definer set search_path = meta, pg_catalog as $$
|
|
149
|
+
declare
|
|
150
|
+
v_role text;
|
|
151
|
+
begin
|
|
152
|
+
app_id := meta.app_id();
|
|
153
|
+
if app_id is null then
|
|
154
|
+
raise exception 'meta.send_mail: no current application (call it from application code)';
|
|
155
|
+
end if;
|
|
156
|
+
select a.db_role into v_role from meta.app a where a.id = app_id;
|
|
157
|
+
if not found then
|
|
158
|
+
raise exception 'meta.send_mail: no current application (call it from application code)';
|
|
159
|
+
end if;
|
|
160
|
+
if v_role is not null and current_setting('role') is distinct from v_role then
|
|
161
|
+
raise exception 'meta.send_mail: called outside the application''s own database role';
|
|
162
|
+
end if;
|
|
163
|
+
select x.service_id, coalesce(x.from_address, nullif(s.settings->>'default_from', '')), x.max_per_day, s.senders
|
|
164
|
+
into service_id, from_address, max_per_day, senders
|
|
165
|
+
from meta.app_mail_service x join meta.mail_service s on s.id = x.service_id
|
|
166
|
+
where x.app_id = mail_caller.app_id and s.enabled;
|
|
167
|
+
if not found then
|
|
168
|
+
raise exception 'meta.send_mail: this application may not send e-mail (an administrator chooses its mail service under Workspace utilities → Mail services)';
|
|
169
|
+
end if;
|
|
170
|
+
end
|
|
171
|
+
$$;
|
|
172
|
+
revoke all on function meta.mail_caller() from public;
|
|
173
|
+
|
|
174
|
+
-- May the service send as this address? (user@example.com, or @example.com for the whole domain)
|
|
175
|
+
create function meta.mail_sender_allowed(p_senders text[], p_from text) returns boolean
|
|
176
|
+
language sql immutable set search_path = pg_catalog as $$
|
|
177
|
+
select exists (select 1 from unnest(p_senders) s
|
|
178
|
+
where lower(s) = lower(p_from) or (left(s, 1) = '@' and lower(p_from) like '%' || lower(s)))
|
|
179
|
+
$$;
|
|
180
|
+
|
|
181
|
+
-- Queue one mail (the functions below check their arguments first).
|
|
182
|
+
create function meta.mail_enqueue(p_to text, p_subject text, p_body text, p_body_html text, p_from text, p_cc text, p_bcc text, p_reply_to text, p_template text)
|
|
183
|
+
returns bigint
|
|
184
|
+
language plpgsql volatile security definer set search_path = meta, pg_catalog as $$
|
|
185
|
+
declare
|
|
186
|
+
c record;
|
|
187
|
+
v_to text[] := meta.mail_addresses(p_to, 'to');
|
|
188
|
+
v_cc text[] := meta.mail_addresses(p_cc, 'cc');
|
|
189
|
+
v_bcc text[] := meta.mail_addresses(p_bcc, 'bcc');
|
|
190
|
+
v_reply text[] := meta.mail_addresses(p_reply_to, 'reply to');
|
|
191
|
+
v_from text[];
|
|
192
|
+
v_count int;
|
|
193
|
+
v_id bigint;
|
|
194
|
+
begin
|
|
195
|
+
select * into c from meta.mail_caller();
|
|
196
|
+
if cardinality(v_to) + cardinality(v_cc) + cardinality(v_bcc) = 0 then
|
|
197
|
+
raise exception 'meta.send_mail: no recipient';
|
|
198
|
+
end if;
|
|
199
|
+
if cardinality(v_to) + cardinality(v_cc) + cardinality(v_bcc) > 50 then
|
|
200
|
+
raise exception 'meta.send_mail: at most 50 recipients per mail (to, cc and bcc together)';
|
|
201
|
+
end if;
|
|
202
|
+
if cardinality(v_reply) > 1 then
|
|
203
|
+
raise exception 'meta.send_mail: one reply-to address';
|
|
204
|
+
end if;
|
|
205
|
+
if p_subject is null or length(p_subject) not between 1 and 998 or p_subject ~ '[\r\n]' then
|
|
206
|
+
raise exception 'meta.send_mail: the subject is required, on one line (at most 998 characters)';
|
|
207
|
+
end if;
|
|
208
|
+
if p_body is null and p_body_html is null then
|
|
209
|
+
raise exception 'meta.send_mail: a plain text or HTML body is required';
|
|
210
|
+
end if;
|
|
211
|
+
if coalesce(octet_length(p_body), 0) > 1048576 or coalesce(octet_length(p_body_html), 0) > 1048576 then
|
|
212
|
+
raise exception 'meta.send_mail: each body is at most 1 MB';
|
|
213
|
+
end if;
|
|
214
|
+
v_from := meta.mail_addresses(coalesce(nullif(btrim(p_from), ''), c.from_address), 'from');
|
|
215
|
+
if cardinality(v_from) <> 1 then
|
|
216
|
+
raise exception 'meta.send_mail: no From address (give one, or an administrator sets the application''s)';
|
|
217
|
+
end if;
|
|
218
|
+
if not meta.mail_sender_allowed(c.senders, v_from[1]) then
|
|
219
|
+
raise exception 'meta.send_mail: the mail service may not send as % (its allowed senders are set by an administrator)', v_from[1];
|
|
220
|
+
end if;
|
|
221
|
+
if c.max_per_day is not null then
|
|
222
|
+
select count(*) into v_count from meta.mail_queue
|
|
223
|
+
where app_id = c.app_id and created_at >= date_trunc('day', now() at time zone 'UTC') at time zone 'UTC' and status <> 'cancelled';
|
|
224
|
+
if v_count >= c.max_per_day then
|
|
225
|
+
raise exception 'meta.send_mail: this application''s limit of % mails per day is reached', c.max_per_day;
|
|
226
|
+
end if;
|
|
227
|
+
end if;
|
|
228
|
+
insert into meta.mail_queue (app_id, username, from_address, reply_to, to_addresses, cc_addresses, bcc_addresses, subject, body_text, body_html, template)
|
|
229
|
+
values (c.app_id, nullif(current_setting('pgkiln.app_user', true), ''), v_from[1], v_reply[1], v_to, v_cc, v_bcc, p_subject, p_body, p_body_html, p_template)
|
|
230
|
+
returning id into v_id;
|
|
231
|
+
perform pg_notify('pgkiln_mail', v_id::text);
|
|
232
|
+
return v_id;
|
|
233
|
+
end
|
|
234
|
+
$$;
|
|
235
|
+
revoke all on function meta.mail_enqueue(text, text, text, text, text, text, text, text, text) from public;
|
|
236
|
+
|
|
237
|
+
-- APEX_MAIL.SEND: queue a mail; it is sent after the transaction commits.
|
|
238
|
+
create function meta.send_mail(
|
|
239
|
+
p_to text,
|
|
240
|
+
p_subject text,
|
|
241
|
+
p_body text default null,
|
|
242
|
+
p_body_html text default null,
|
|
243
|
+
p_from text default null,
|
|
244
|
+
p_cc text default null,
|
|
245
|
+
p_bcc text default null,
|
|
246
|
+
p_reply_to text default null
|
|
247
|
+
) returns bigint
|
|
248
|
+
language sql volatile security definer set search_path = meta, pg_catalog as $$
|
|
249
|
+
select meta.mail_enqueue(p_to, p_subject, p_body, p_body_html, p_from, p_cc, p_bcc, p_reply_to, null)
|
|
250
|
+
$$;
|
|
251
|
+
grant execute on function meta.send_mail(text, text, text, text, text, text, text, text) to public;
|
|
252
|
+
|
|
253
|
+
-- A template with its placeholders filled, in one pass (a value is never searched for
|
|
254
|
+
-- placeholders itself): #NAME# is the value (HTML-escaped in the HTML body), #NAME!RAW#
|
|
255
|
+
-- the value as it is (trusted HTML). Names are case-insensitive; unknown ones stay.
|
|
256
|
+
create function meta.mail_fill(p_text text, p_values jsonb, p_html boolean) returns text
|
|
257
|
+
language plpgsql immutable set search_path = meta, pg_catalog as $$
|
|
258
|
+
declare
|
|
259
|
+
c_pat constant text := '#([A-Za-z][A-Za-z0-9_]{0,59})(!RAW)?#';
|
|
260
|
+
v_vals jsonb;
|
|
261
|
+
v_out text := '';
|
|
262
|
+
v_rest text := p_text;
|
|
263
|
+
v_pos int;
|
|
264
|
+
v_tok text;
|
|
265
|
+
v_m text[];
|
|
266
|
+
v_val text;
|
|
267
|
+
begin
|
|
268
|
+
if p_text is null then
|
|
269
|
+
return null;
|
|
270
|
+
end if;
|
|
271
|
+
select coalesce(jsonb_object_agg(upper(key), value), '{}') into v_vals
|
|
272
|
+
from jsonb_each(case when jsonb_typeof(p_values) = 'object' then p_values else '{}' end);
|
|
273
|
+
loop
|
|
274
|
+
v_pos := regexp_instr(v_rest, c_pat);
|
|
275
|
+
exit when v_pos = 0;
|
|
276
|
+
v_tok := regexp_substr(v_rest, c_pat);
|
|
277
|
+
v_m := regexp_match(v_tok, '^' || c_pat || '$');
|
|
278
|
+
v_out := v_out || left(v_rest, v_pos - 1);
|
|
279
|
+
if v_vals ? upper(v_m[1]) then
|
|
280
|
+
v_val := case jsonb_typeof(v_vals->upper(v_m[1])) when 'string' then v_vals->>upper(v_m[1]) when 'null' then '' else (v_vals->upper(v_m[1]))::text end;
|
|
281
|
+
v_out := v_out || case when p_html and v_m[2] is null then meta.html_escape(v_val) else v_val end;
|
|
282
|
+
else
|
|
283
|
+
v_out := v_out || v_tok;
|
|
284
|
+
end if;
|
|
285
|
+
v_rest := substr(v_rest, v_pos + length(v_tok));
|
|
286
|
+
end loop;
|
|
287
|
+
return v_out || v_rest;
|
|
288
|
+
end
|
|
289
|
+
$$;
|
|
290
|
+
|
|
291
|
+
-- APEX_MAIL.SEND with p_template_static_id and p_placeholders.
|
|
292
|
+
create function meta.send_mail_template(
|
|
293
|
+
p_template text,
|
|
294
|
+
p_placeholders jsonb,
|
|
295
|
+
p_to text,
|
|
296
|
+
p_cc text default null,
|
|
297
|
+
p_bcc text default null,
|
|
298
|
+
p_from text default null,
|
|
299
|
+
p_reply_to text default null
|
|
300
|
+
) returns bigint
|
|
301
|
+
language plpgsql volatile security definer set search_path = meta, pg_catalog as $$
|
|
302
|
+
declare
|
|
303
|
+
t record;
|
|
304
|
+
begin
|
|
305
|
+
select * into t from meta.email_template where app_id = meta.app_id() and name = upper(p_template);
|
|
306
|
+
if not found then
|
|
307
|
+
raise exception 'meta.send_mail_template: no e-mail template %', p_template;
|
|
308
|
+
end if;
|
|
309
|
+
return meta.mail_enqueue(p_to, regexp_replace(meta.mail_fill(t.subject, p_placeholders, false), '[\r\n]+', ' ', 'g'),
|
|
310
|
+
meta.mail_fill(t.text_body, p_placeholders, false), meta.mail_fill(t.html_body, p_placeholders, true),
|
|
311
|
+
p_from, p_cc, p_bcc, p_reply_to, t.name);
|
|
312
|
+
end
|
|
313
|
+
$$;
|
|
314
|
+
grant execute on function meta.send_mail_template(text, jsonb, text, text, text, text, text) to public;
|
|
315
|
+
|
|
316
|
+
-- APEX_MAIL.ADD_ATTACHMENT: a file for a mail queued in this transaction.
|
|
317
|
+
create function meta.add_mail_attachment(p_mail_id bigint, p_content bytea, p_filename text, p_mime text default 'application/octet-stream')
|
|
318
|
+
returns void
|
|
319
|
+
language plpgsql volatile security definer set search_path = meta, pg_catalog as $$
|
|
320
|
+
declare
|
|
321
|
+
c record;
|
|
322
|
+
v_count int;
|
|
323
|
+
v_size bigint;
|
|
324
|
+
begin
|
|
325
|
+
select * into c from meta.mail_caller();
|
|
326
|
+
if not exists (select 1 from meta.mail_queue where id = p_mail_id and app_id = c.app_id and status = 'queued' and created_xact = txid_current()) then
|
|
327
|
+
raise exception 'meta.add_mail_attachment: mail % was not queued in this transaction', p_mail_id;
|
|
328
|
+
end if;
|
|
329
|
+
if p_content is null then
|
|
330
|
+
raise exception 'meta.add_mail_attachment: no content';
|
|
331
|
+
end if;
|
|
332
|
+
select count(*), coalesce(sum(octet_length(content)), 0) into v_count, v_size from meta.mail_attachment where mail_id = p_mail_id;
|
|
333
|
+
if v_count >= 20 or v_size + octet_length(p_content) > 20 * 1024 * 1024 then
|
|
334
|
+
raise exception 'meta.add_mail_attachment: at most 20 attachments and 20 MB per mail';
|
|
335
|
+
end if;
|
|
336
|
+
insert into meta.mail_attachment (mail_id, filename, mime, content)
|
|
337
|
+
values (p_mail_id, regexp_replace(coalesce(p_filename, 'attachment'), '[/\\\r\n"<>:|?*]', '_', 'g'), lower(coalesce(p_mime, 'application/octet-stream')), p_content);
|
|
338
|
+
end
|
|
339
|
+
$$;
|
|
340
|
+
grant execute on function meta.add_mail_attachment(bigint, bytea, text, text) to public;
|
|
341
|
+
|
|
342
|
+
-- The status of a mail of the current application: queued, sending, sent, error or cancelled.
|
|
343
|
+
create function meta.mail_status(p_mail_id bigint) returns text
|
|
344
|
+
language sql stable security definer set search_path = meta, pg_catalog as $$
|
|
345
|
+
select status from meta.mail_queue where id = p_mail_id and app_id = meta.app_id()
|
|
346
|
+
$$;
|
|
347
|
+
grant execute on function meta.mail_status(bigint) to public;
|
|
348
|
+
|
|
349
|
+
-- ---------------------------------------------------------------- export / import
|
|
350
|
+
-- Same as 080, plus email_templates. Mail services, the application's service, the queue
|
|
351
|
+
-- and attachments belong to the installation: not exported.
|
|
352
|
+
create or replace function meta.export_app(p_alias text) returns jsonb
|
|
353
|
+
language sql stable set search_path = meta, pg_catalog as $$
|
|
354
|
+
select jsonb_build_object(
|
|
355
|
+
'format', 'pgkiln/2',
|
|
356
|
+
'app', to_jsonb(a) - 'id' - 'created_at' - 'updated_at',
|
|
357
|
+
'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), '[]'),
|
|
358
|
+
'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), '[]'),
|
|
359
|
+
'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), '[]'),
|
|
360
|
+
'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), '[]'),
|
|
361
|
+
'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), '[]'),
|
|
362
|
+
'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), '[]'),
|
|
363
|
+
'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), '[]'),
|
|
364
|
+
'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)
|
|
365
|
+
from meta.report_layout x where x.app_id = a.id), '[]'),
|
|
366
|
+
'automations', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' - 'next_run_at' - 'last_run_at' - 'last_status' - 'code' order by x.name)
|
|
367
|
+
from meta.automation x where x.app_id = a.id), '[]'),
|
|
368
|
+
-- (044) the actions of the automations, by automation name
|
|
369
|
+
'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")
|
|
370
|
+
from meta.automation_action x where x.app_id = a.id), '[]'),
|
|
371
|
+
'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), '[]'),
|
|
372
|
+
'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), '[]'),
|
|
373
|
+
'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), '[]'),
|
|
374
|
+
'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), '[]'),
|
|
375
|
+
'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), '[]'),
|
|
376
|
+
'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), '[]'),
|
|
377
|
+
-- (030) secrets never leave the installation: they are entered again after an import
|
|
378
|
+
-- (050) nor do the password and refresh token of the OAuth2 flows, or the token state
|
|
379
|
+
'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)
|
|
380
|
+
from meta.web_credential x where x.app_id = a.id), '[]'),
|
|
381
|
+
-- (050) the synchronisation's state (next and last run) belongs to the installation
|
|
382
|
+
'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)
|
|
383
|
+
from meta.rest_source x where x.app_id = a.id), '[]'),
|
|
384
|
+
-- (041) data load definitions
|
|
385
|
+
'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), '[]'),
|
|
386
|
+
-- (042) lists; entries keep their ids so parents can be remapped on import
|
|
387
|
+
'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), '[]'),
|
|
388
|
+
'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), '[]'),
|
|
389
|
+
-- (042) supporting objects: install, upgrade and deinstall scripts (never run on import)
|
|
390
|
+
'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), '[]'),
|
|
391
|
+
-- (069) plug-ins (their files are static files, their templates template components)
|
|
392
|
+
'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), '[]'),
|
|
393
|
+
-- (080) search configurations
|
|
394
|
+
'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), '[]'),
|
|
395
|
+
-- (081) e-mail templates
|
|
396
|
+
'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), '[]'),
|
|
397
|
+
-- (068) static application files (JavaScript, CSS, images, fonts) as base64
|
|
398
|
+
'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)
|
|
399
|
+
from meta.static_file x where x.app_id = a.id), '[]'),
|
|
400
|
+
-- nav entries and regions keep their ids, so parents and references can be remapped on import
|
|
401
|
+
'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), '[]'),
|
|
402
|
+
'pages', coalesce((
|
|
403
|
+
select jsonb_agg(to_jsonb(p) - 'id' - 'app_id' || jsonb_build_object(
|
|
404
|
+
'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), '[]'),
|
|
405
|
+
'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), '[]'),
|
|
406
|
+
'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), '[]'),
|
|
407
|
+
'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), '[]'),
|
|
408
|
+
'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), '[]'),
|
|
409
|
+
'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), '[]'),
|
|
410
|
+
'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), '[]'),
|
|
411
|
+
'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), '[]')
|
|
412
|
+
) order by p.page_no)
|
|
413
|
+
from meta.page p where p.app_id = a.id), '[]'))
|
|
414
|
+
from meta.app a
|
|
415
|
+
where a.alias = p_alias
|
|
416
|
+
$$;
|
|
417
|
+
|
|
418
|
+
create or replace function meta.import_app(p_doc jsonb, p_alias text default null) returns int
|
|
419
|
+
language plpgsql set search_path = meta, pg_catalog as $$
|
|
420
|
+
declare
|
|
421
|
+
v_app_id int;
|
|
422
|
+
v_page_id int;
|
|
423
|
+
v_page jsonb;
|
|
424
|
+
v_e jsonb;
|
|
425
|
+
v_rmap jsonb;
|
|
426
|
+
v_nmap jsonb := '{}';
|
|
427
|
+
v_lmap jsonb := '{}';
|
|
428
|
+
v_new_id int;
|
|
429
|
+
begin
|
|
430
|
+
if p_doc->>'format' is distinct from 'pgkiln/2' then
|
|
431
|
+
raise exception 'unsupported export format %', coalesce(p_doc->>'format', '(none)');
|
|
432
|
+
end if;
|
|
433
|
+
|
|
434
|
+
insert into meta.app
|
|
435
|
+
select (jsonb_populate_record(null::meta.app, p_doc->'app' || jsonb_build_object(
|
|
436
|
+
'id', nextval('meta.app_id_seq'),
|
|
437
|
+
'alias', coalesce(p_alias, p_doc->'app'->>'alias'),
|
|
438
|
+
'created_at', now(), 'updated_at', now()))).*
|
|
439
|
+
returning id into v_app_id;
|
|
440
|
+
|
|
441
|
+
insert into meta.authz_scheme
|
|
442
|
+
select (jsonb_populate_record(null::meta.authz_scheme, e || jsonb_build_object('id', nextval('meta.authz_scheme_id_seq'), 'app_id', v_app_id))).*
|
|
443
|
+
from jsonb_array_elements(coalesce(p_doc->'authz_schemes', '[]')) e;
|
|
444
|
+
insert into meta.app_item
|
|
445
|
+
select (jsonb_populate_record(null::meta.app_item, e || jsonb_build_object('id', nextval('meta.app_item_id_seq'), 'app_id', v_app_id))).*
|
|
446
|
+
from jsonb_array_elements(coalesce(p_doc->'app_items', '[]')) e;
|
|
447
|
+
insert into meta.app_process
|
|
448
|
+
select (jsonb_populate_record(null::meta.app_process, e || jsonb_build_object('id', nextval('meta.app_process_id_seq'), 'app_id', v_app_id))).*
|
|
449
|
+
from jsonb_array_elements(coalesce(p_doc->'app_processes', '[]')) e;
|
|
450
|
+
insert into meta.lov
|
|
451
|
+
select (jsonb_populate_record(null::meta.lov, e || jsonb_build_object('id', nextval('meta.lov_id_seq'), 'app_id', v_app_id))).*
|
|
452
|
+
from jsonb_array_elements(coalesce(p_doc->'lovs', '[]')) e;
|
|
453
|
+
insert into meta.app_group_role
|
|
454
|
+
select (jsonb_populate_record(null::meta.app_group_role, e || jsonb_build_object('app_id', v_app_id))).*
|
|
455
|
+
from jsonb_array_elements(coalesce(p_doc->'group_roles', '[]')) e;
|
|
456
|
+
insert into meta.text_message
|
|
457
|
+
select (jsonb_populate_record(null::meta.text_message, e || jsonb_build_object('app_id', v_app_id))).*
|
|
458
|
+
from jsonb_array_elements(coalesce(p_doc->'text_messages', '[]')) e;
|
|
459
|
+
insert into meta.translation
|
|
460
|
+
select (jsonb_populate_record(null::meta.translation, e || jsonb_build_object('app_id', v_app_id))).*
|
|
461
|
+
from jsonb_array_elements(coalesce(p_doc->'translations', '[]')) e;
|
|
462
|
+
insert into meta.report_layout
|
|
463
|
+
select (jsonb_populate_record(null::meta.report_layout, (e - 'logo') || jsonb_build_object(
|
|
464
|
+
'id', nextval('meta.report_layout_id_seq'), 'app_id', v_app_id,
|
|
465
|
+
'logo', null))).*
|
|
466
|
+
from jsonb_array_elements(coalesce(p_doc->'report_layouts', '[]')) e;
|
|
467
|
+
update meta.report_layout l
|
|
468
|
+
set logo = decode(e->>'logo', 'base64')
|
|
469
|
+
from jsonb_array_elements(coalesce(p_doc->'report_layouts', '[]')) e
|
|
470
|
+
where l.app_id = v_app_id and l.name = e->>'name' and e->>'logo' is not null;
|
|
471
|
+
-- imported automations start switched off: the copy must not run the original's jobs unasked.
|
|
472
|
+
-- (044) a file with actions: the automations' code is ignored; an older file: code becomes the single action
|
|
473
|
+
insert into meta.automation
|
|
474
|
+
select (jsonb_populate_record(null::meta.automation, '{"error_handling": "stop"}'::jsonb
|
|
475
|
+
|| jsonb_strip_nulls(case when p_doc ? 'automation_actions' then e - 'code' else e end)
|
|
476
|
+
|| jsonb_build_object('id', nextval('meta.automation_id_seq'), 'app_id', v_app_id, 'enabled', false))).*
|
|
477
|
+
from jsonb_array_elements(coalesce(p_doc->'automations', '[]')) e;
|
|
478
|
+
insert into meta.automation_action
|
|
479
|
+
select (jsonb_populate_record(null::meta.automation_action, '{"seq": 10}'::jsonb || jsonb_strip_nulls(e)
|
|
480
|
+
|| jsonb_build_object('id', nextval('meta.automation_action_id_seq'), 'app_id', v_app_id))).*
|
|
481
|
+
from jsonb_array_elements(coalesce(p_doc->'automation_actions', '[]')) e;
|
|
482
|
+
insert into meta.document_template
|
|
483
|
+
select (jsonb_populate_record(null::meta.document_template, e || jsonb_build_object('id', nextval('meta.document_template_id_seq'), 'app_id', v_app_id))).*
|
|
484
|
+
from jsonb_array_elements(coalesce(p_doc->'document_templates', '[]')) e;
|
|
485
|
+
insert into meta.task_definition
|
|
486
|
+
select (jsonb_populate_record(null::meta.task_definition, e || jsonb_build_object('id', nextval('meta.task_definition_id_seq'), 'app_id', v_app_id))).*
|
|
487
|
+
from jsonb_array_elements(coalesce(p_doc->'task_definitions', '[]')) e;
|
|
488
|
+
insert into meta.workflow_definition
|
|
489
|
+
select (jsonb_populate_record(null::meta.workflow_definition, e || jsonb_build_object('id', nextval('meta.workflow_definition_id_seq'), 'app_id', v_app_id))).*
|
|
490
|
+
from jsonb_array_elements(coalesce(p_doc->'workflow_definitions', '[]')) e;
|
|
491
|
+
insert into meta.rest_module
|
|
492
|
+
select (jsonb_populate_record(null::meta.rest_module, e || jsonb_build_object('id', nextval('meta.rest_module_id_seq'), 'app_id', v_app_id))).*
|
|
493
|
+
from jsonb_array_elements(coalesce(p_doc->'rest_modules', '[]')) e;
|
|
494
|
+
-- (028) template components: regions and report columns refer to them by static id
|
|
495
|
+
insert into meta.template_component
|
|
496
|
+
select (jsonb_populate_record(null::meta.template_component, e || jsonb_build_object('id', nextval('meta.template_component_id_seq'), 'app_id', v_app_id))).*
|
|
497
|
+
from jsonb_array_elements(coalesce(p_doc->'template_components', '[]')) e;
|
|
498
|
+
-- (029) build options (components name them by name)
|
|
499
|
+
insert into meta.build_option
|
|
500
|
+
select (jsonb_populate_record(null::meta.build_option, e || jsonb_build_object('id', nextval('meta.build_option_id_seq'), 'app_id', v_app_id))).*
|
|
501
|
+
from jsonb_array_elements(coalesce(p_doc->'build_options', '[]')) e;
|
|
502
|
+
-- (030) web credentials arrive without a secret, whatever the document holds
|
|
503
|
+
-- (050) nor a password or refresh token
|
|
504
|
+
insert into meta.web_credential
|
|
505
|
+
select (jsonb_populate_record(null::meta.web_credential, '{"type": "basic", "valid_for": [], "grant_type": "client_credentials"}'::jsonb
|
|
506
|
+
|| jsonb_strip_nulls(e - 'secret_enc' - 'password_enc' - 'refresh_token_enc' - 'token_refreshed_at')
|
|
507
|
+
|| jsonb_build_object('id', nextval('meta.web_credential_id_seq'), 'app_id', v_app_id))).*
|
|
508
|
+
from jsonb_array_elements(coalesce(p_doc->'web_credentials', '[]')) e;
|
|
509
|
+
-- (050) write-back operations and synchronisation; an imported synchronisation starts switched off
|
|
510
|
+
insert into meta.rest_source
|
|
511
|
+
select (jsonb_populate_record(null::meta.rest_source, '{"method": "GET", "headers": {}, "params": [], "columns": [], "cache_seconds": 0, "timeout_s": 10, "max_rows": 1000,
|
|
512
|
+
"operations": {}, "key_columns": [], "sync_mode": "merge", "sync_delete": false, "sync_time_zone": "UTC"}'::jsonb
|
|
513
|
+
|| jsonb_strip_nulls(e - 'sync_next_at' - 'sync_last_at' - 'sync_last_status')
|
|
514
|
+
|| jsonb_build_object('id', nextval('meta.rest_source_id_seq'), 'app_id', v_app_id, 'sync_enabled', false))).*
|
|
515
|
+
from jsonb_array_elements(coalesce(p_doc->'rest_sources', '[]')) e;
|
|
516
|
+
|
|
517
|
+
-- (041) data load definitions (the data_load process names them)
|
|
518
|
+
insert into meta.data_load_def
|
|
519
|
+
select (jsonb_populate_record(null::meta.data_load_def, '{"format": "auto", "headers": true, "mode": "append", "skip_errors": false, "columns": []}'::jsonb
|
|
520
|
+
|| jsonb_strip_nulls(e) || jsonb_build_object('id', nextval('meta.data_load_def_id_seq'), 'app_id', v_app_id))).*
|
|
521
|
+
from jsonb_array_elements(coalesce(p_doc->'data_load_definitions', '[]')) e;
|
|
522
|
+
|
|
523
|
+
-- (042) lists and their entries, supporting scripts. Entries are inserted
|
|
524
|
+
-- without a parent first and the parents set afterwards, so the order of
|
|
525
|
+
-- the entries in the document doesn't matter (a moved entry may have a
|
|
526
|
+
-- higher id than its children).
|
|
527
|
+
insert into meta.list
|
|
528
|
+
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))).*
|
|
529
|
+
from jsonb_array_elements(coalesce(p_doc->'lists', '[]')) e;
|
|
530
|
+
for v_e in select * from jsonb_array_elements(coalesce(p_doc->'list_entries', '[]')) loop
|
|
531
|
+
insert into meta.list_entry
|
|
532
|
+
select (jsonb_populate_record(null::meta.list_entry, '{"target_items": {}}'::jsonb || jsonb_strip_nulls(v_e) || jsonb_build_object(
|
|
533
|
+
'id', nextval('meta.list_entry_id_seq'), 'app_id', v_app_id, 'parent_id', null))).*
|
|
534
|
+
returning id into v_new_id;
|
|
535
|
+
if v_e->>'id' is not null then
|
|
536
|
+
v_lmap := v_lmap || jsonb_build_object(v_e->>'id', v_new_id);
|
|
537
|
+
end if;
|
|
538
|
+
end loop;
|
|
539
|
+
update meta.list_entry x
|
|
540
|
+
set parent_id = (v_lmap->>(e->>'parent_id'))::int
|
|
541
|
+
from jsonb_array_elements(coalesce(p_doc->'list_entries', '[]')) e
|
|
542
|
+
where e->>'parent_id' is not null and v_lmap ? (e->>'parent_id') and v_lmap ? (e->>'id')
|
|
543
|
+
and x.id = (v_lmap->>(e->>'id'))::int;
|
|
544
|
+
insert into meta.supporting_script
|
|
545
|
+
select (jsonb_populate_record(null::meta.supporting_script, '{"kind": "install", "seq": 10, "script": ""}'::jsonb || jsonb_strip_nulls(e)
|
|
546
|
+
|| jsonb_build_object('id', nextval('meta.supporting_script_id_seq'), 'app_id', v_app_id))).*
|
|
547
|
+
from jsonb_array_elements(coalesce(p_doc->'supporting_scripts', '[]')) e;
|
|
548
|
+
|
|
549
|
+
-- (069) plug-ins
|
|
550
|
+
insert into meta.plugin
|
|
551
|
+
select (jsonb_populate_record(null::meta.plugin, e || jsonb_build_object('id', nextval('meta.plugin_id_seq'), 'app_id', v_app_id))).*
|
|
552
|
+
from jsonb_array_elements(coalesce(p_doc->'plugins', '[]')) e;
|
|
553
|
+
|
|
554
|
+
-- (080) search configurations (search regions name them)
|
|
555
|
+
insert into meta.search_config
|
|
556
|
+
select (jsonb_populate_record(null::meta.search_config, '{"seq": 10, "source_type": "table", "searchable_columns": [], "max_results": 10}'::jsonb
|
|
557
|
+
|| jsonb_strip_nulls(e) || jsonb_build_object('id', nextval('meta.search_config_id_seq'), 'app_id', v_app_id))).*
|
|
558
|
+
from jsonb_array_elements(coalesce(p_doc->'search_configs', '[]')) e;
|
|
559
|
+
|
|
560
|
+
-- (081) e-mail templates (meta.send_mail_template and the send_email process name them)
|
|
561
|
+
insert into meta.email_template
|
|
562
|
+
select (jsonb_populate_record(null::meta.email_template, e || jsonb_build_object('id', nextval('meta.email_template_id_seq'), 'app_id', v_app_id))).*
|
|
563
|
+
from jsonb_array_elements(coalesce(p_doc->'email_templates', '[]')) e;
|
|
564
|
+
|
|
565
|
+
-- (068) static application files
|
|
566
|
+
insert into meta.static_file (app_id, name, mime, content, updated_at)
|
|
567
|
+
select v_app_id, e->>'name', e->>'mime', decode(coalesce(e->>'content', ''), 'base64'), now()
|
|
568
|
+
from jsonb_array_elements(coalesce(p_doc->'static_files', '[]')) e;
|
|
569
|
+
|
|
570
|
+
for v_e in select * from jsonb_array_elements(coalesce(p_doc->'nav', '[]')) loop
|
|
571
|
+
insert into meta.nav_entry
|
|
572
|
+
select (jsonb_populate_record(null::meta.nav_entry, v_e || jsonb_build_object(
|
|
573
|
+
'id', nextval('meta.nav_entry_id_seq'), 'app_id', v_app_id,
|
|
574
|
+
'parent_id', v_nmap->>(v_e->>'parent_id')))).*
|
|
575
|
+
returning id into v_new_id;
|
|
576
|
+
v_nmap := v_nmap || jsonb_build_object(v_e->>'id', v_new_id);
|
|
577
|
+
end loop;
|
|
578
|
+
|
|
579
|
+
for v_page in select * from jsonb_array_elements(coalesce(p_doc->'pages', '[]')) loop
|
|
580
|
+
insert into meta.page
|
|
581
|
+
select (jsonb_populate_record(null::meta.page, v_page || jsonb_build_object('id', nextval('meta.page_id_seq'), 'app_id', v_app_id))).*
|
|
582
|
+
returning id into v_page_id;
|
|
583
|
+
|
|
584
|
+
v_rmap := '{}';
|
|
585
|
+
for v_e in select * from jsonb_array_elements(coalesce(v_page->'regions', '[]')) loop
|
|
586
|
+
insert into meta.region
|
|
587
|
+
select (jsonb_populate_record(null::meta.region, v_e || jsonb_build_object('id', nextval('meta.region_id_seq'), 'page_id', v_page_id))).*
|
|
588
|
+
returning id into v_new_id;
|
|
589
|
+
v_rmap := v_rmap || jsonb_build_object(v_e->>'id', v_new_id);
|
|
590
|
+
end loop;
|
|
591
|
+
-- facet and map regions point at their report region by id
|
|
592
|
+
update meta.region r
|
|
593
|
+
set config = jsonb_set(r.config, '{report}', to_jsonb((v_rmap->>(r.config->>'report'))::int))
|
|
594
|
+
where r.page_id = v_page_id and r.type in ('facets', 'smart_filters', 'map') and v_rmap ? (r.config->>'report');
|
|
595
|
+
|
|
596
|
+
insert into meta.item
|
|
597
|
+
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')))).*
|
|
598
|
+
from jsonb_array_elements(coalesce(v_page->'items', '[]')) e;
|
|
599
|
+
insert into meta.button
|
|
600
|
+
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')))).*
|
|
601
|
+
from jsonb_array_elements(coalesce(v_page->'buttons', '[]')) e;
|
|
602
|
+
insert into meta.dynamic_action
|
|
603
|
+
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')))).*
|
|
604
|
+
from jsonb_array_elements(coalesce(v_page->'dynamic_actions', '[]')) e;
|
|
605
|
+
insert into meta.validation
|
|
606
|
+
select (jsonb_populate_record(null::meta.validation, e || jsonb_build_object('id', nextval('meta.validation_id_seq'), 'page_id', v_page_id))).*
|
|
607
|
+
from jsonb_array_elements(coalesce(v_page->'validations', '[]')) e;
|
|
608
|
+
insert into meta.process
|
|
609
|
+
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')))).*
|
|
610
|
+
from jsonb_array_elements(coalesce(v_page->'processes', '[]')) e;
|
|
611
|
+
insert into meta.computation
|
|
612
|
+
select (jsonb_populate_record(null::meta.computation, e || jsonb_build_object('id', nextval('meta.computation_id_seq'), 'page_id', v_page_id))).*
|
|
613
|
+
from jsonb_array_elements(coalesce(v_page->'computations', '[]')) e;
|
|
614
|
+
insert into meta.branch
|
|
615
|
+
select (jsonb_populate_record(null::meta.branch, e || jsonb_build_object('id', nextval('meta.branch_id_seq'), 'page_id', v_page_id))).*
|
|
616
|
+
from jsonb_array_elements(coalesce(v_page->'branches', '[]')) e;
|
|
617
|
+
end loop;
|
|
618
|
+
|
|
619
|
+
return v_app_id;
|
|
620
|
+
end
|
|
621
|
+
$$;
|