pgkiln 0.31.0 → 0.35.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/docs/apex-feature-parity.md +55 -35
- package/docs/guide/01-installation.md +1 -0
- package/docs/guide/03-builder.md +118 -2
- package/docs/guide/04-pages-and-regions.md +115 -0
- 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 +13 -2
- package/docs/guide/09-reference.md +21 -0
- package/docs/guide/11-from-apex.md +5 -1
- package/docs/guide/12-development.md +33 -1
- package/docs/guide/14-globalization.md +15 -0
- package/docs/guide/15-extensions.md +1 -1
- package/docs/guide/18-cli.md +30 -1
- package/docs/guide/20-ai-agents.md +3 -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/package.json +3 -1
- package/public/app.css +62 -0
- package/public/app.js +408 -11
- package/public/builder.css +11 -0
- package/public/builder.js +27 -0
- package/src/app.ts +8 -0
- package/src/appfiles.ts +12 -0
- package/src/automations.ts +12 -0
- package/src/builder/advisor.ts +54 -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 +109 -15
- package/src/builder/designer.ts +8 -3
- package/src/builder/feedback.ts +82 -0
- package/src/builder/forms.ts +4 -1
- package/src/builder/home.ts +3 -0
- package/src/builder/locks.ts +15 -5
- package/src/builder/mail.ts +425 -0
- package/src/builder/newapp.ts +1 -1
- package/src/builder/region-help.ts +71 -0
- package/src/builder/routes.ts +44 -1
- package/src/builder/search.ts +4 -4
- package/src/builder/shared.ts +3 -2
- package/src/builder/ui.ts +7 -1
- package/src/cli/apps.ts +46 -5
- package/src/cli/main.ts +21 -5
- package/src/cli/mcp.ts +16 -4
- package/src/cli/replace.ts +36 -4
- package/src/geocode.ts +44 -0
- package/src/i18n/ar.ts +39 -0
- package/src/i18n/cs.ts +39 -0
- package/src/i18n/da.ts +39 -0
- package/src/i18n/de.ts +39 -0
- package/src/i18n/el.ts +39 -0
- package/src/i18n/es.ts +39 -0
- package/src/i18n/fi.ts +39 -0
- package/src/i18n/fr.ts +39 -0
- package/src/i18n/he.ts +39 -0
- package/src/i18n/it.ts +39 -0
- package/src/i18n/ja.ts +39 -0
- package/src/i18n/ko.ts +39 -0
- package/src/i18n/nb.ts +39 -0
- package/src/i18n/pl.ts +39 -0
- package/src/i18n/pt.ts +39 -0
- package/src/i18n/ru.ts +39 -0
- package/src/i18n/sv.ts +39 -0
- package/src/i18n/tr.ts +39 -0
- package/src/i18n/uk.ts +39 -0
- package/src/i18n/zh.ts +39 -0
- package/src/i18n.ts +78 -0
- package/src/mail/mail.ts +148 -0
- package/src/mail/providers.ts +444 -0
- package/src/metadata.ts +26 -8
- 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 +182 -0
- package/src/runtime/engine.ts +26 -1
- package/src/runtime/feedback.ts +128 -0
- package/src/runtime/items.ts +8 -0
- package/src/runtime/processes.ts +83 -2
- package/src/runtime/regions.ts +8 -0
- package/src/runtime/render.ts +33 -7
- package/src/runtime/rest.ts +4 -0
- package/src/runtime/routes.ts +59 -5
- package/src/runtime/search.ts +252 -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/workingcopy.ts +2 -2
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- 086: data and settings (sprint 53)
|
|
3
|
+
--
|
|
4
|
+
-- meta.app_setting (APEX: Application Settings, APEX_APP_SETTING): named
|
|
5
|
+
-- values of an application that an administrator changes after
|
|
6
|
+
-- deployment, read and written from SQL with meta.get_app_setting(name) and
|
|
7
|
+
-- meta.set_app_setting(name, value). Exported with the application; an
|
|
8
|
+
-- import over an existing application keeps the installed values of the
|
|
9
|
+
-- settings marked keep_on_upgrade.
|
|
10
|
+
--
|
|
11
|
+
-- meta.user_preference (APEX: APEX_UTIL.GET/SET/REMOVE_PREFERENCE): values
|
|
12
|
+
-- kept per user and application across sessions. Items with
|
|
13
|
+
-- {"persist": "user"} (APEX: Maintain session state: Per User) are read
|
|
14
|
+
-- from and saved to them by the runtime. Installation data: not exported.
|
|
15
|
+
--
|
|
16
|
+
-- meta.dynamic_translation (APEX: Dynamic Translations, APEX_LANG.LANG):
|
|
17
|
+
-- translations of values from tables (e.g. list of values entries):
|
|
18
|
+
-- meta.lang(text) returns the text in the session's language.
|
|
19
|
+
--
|
|
20
|
+
-- meta.data_export(query, format, options) (APEX: APEX_DATA_EXPORT): a
|
|
21
|
+
-- query's rows as a CSV, JSON, XML or HTML file (bytea), run with the
|
|
22
|
+
-- caller's privileges. The download process makes Excel and PDF too.
|
|
23
|
+
--
|
|
24
|
+
-- Item types image (APEX: Display Image: a URL, or a BLOB from a query) and
|
|
25
|
+
-- address (APEX: Geocoded Address: an address checked against the
|
|
26
|
+
-- installation's geocoder, its position into another item).
|
|
27
|
+
--
|
|
28
|
+
-- export_app / import_app are redefined from 083 / 081 to carry
|
|
29
|
+
-- app_settings and dynamic_translations.
|
|
30
|
+
-- =====================================================================
|
|
31
|
+
|
|
32
|
+
-- The current application, when called as its own database role (application code).
|
|
33
|
+
create function meta.caller_app(p_fn text) returns int
|
|
34
|
+
language plpgsql stable security definer set search_path = meta, pg_catalog as $$
|
|
35
|
+
declare
|
|
36
|
+
v_app int := meta.app_id();
|
|
37
|
+
v_role text;
|
|
38
|
+
begin
|
|
39
|
+
if v_app is null then
|
|
40
|
+
raise exception '%: no current application (call it from application code)', p_fn;
|
|
41
|
+
end if;
|
|
42
|
+
select a.db_role into v_role from meta.app a where a.id = v_app;
|
|
43
|
+
if not found then
|
|
44
|
+
raise exception '%: no current application (call it from application code)', p_fn;
|
|
45
|
+
end if;
|
|
46
|
+
-- an application without a role of its own runs as the runtime's login role (no SET ROLE):
|
|
47
|
+
-- another application's role can't claim it
|
|
48
|
+
if current_setting('role') is distinct from coalesce(v_role, 'none') then
|
|
49
|
+
raise exception '%: called outside the application''s own database role', p_fn;
|
|
50
|
+
end if;
|
|
51
|
+
return v_app;
|
|
52
|
+
end
|
|
53
|
+
$$;
|
|
54
|
+
revoke all on function meta.caller_app(text) from public;
|
|
55
|
+
|
|
56
|
+
-- ---------------------------------------------------------------- application settings
|
|
57
|
+
create table meta.app_setting (
|
|
58
|
+
id serial primary key,
|
|
59
|
+
app_id int not null references meta.app on delete cascade,
|
|
60
|
+
name text not null check (name ~ '^[A-Z][A-Z0-9_]{0,62}$'),
|
|
61
|
+
value text check (octet_length(value) <= 4000),
|
|
62
|
+
description text,
|
|
63
|
+
-- a value is required (an empty one is refused)
|
|
64
|
+
required boolean not null default false,
|
|
65
|
+
-- the values allowed (null: any)
|
|
66
|
+
valid_values text[] check (valid_values is null or cardinality(valid_values) between 1 and 100),
|
|
67
|
+
-- an import over an installed application keeps the installed value
|
|
68
|
+
keep_on_upgrade boolean not null default true,
|
|
69
|
+
unique (app_id, name),
|
|
70
|
+
constraint app_setting_required check (not required or coalesce(value, '') <> ''),
|
|
71
|
+
constraint app_setting_valid check (value is null or valid_values is null or value = any(valid_values))
|
|
72
|
+
);
|
|
73
|
+
revoke all on meta.app_setting from public;
|
|
74
|
+
comment on table meta.app_setting is 'Application settings (086): named values changed after deployment; meta.get_app_setting / meta.set_app_setting';
|
|
75
|
+
|
|
76
|
+
-- an export made before 086 (or a document written by hand) may leave the flags out
|
|
77
|
+
create function meta.app_setting_defaults() returns trigger
|
|
78
|
+
language plpgsql set search_path = pg_catalog as $$
|
|
79
|
+
begin
|
|
80
|
+
new.required := coalesce(new.required, false);
|
|
81
|
+
new.keep_on_upgrade := coalesce(new.keep_on_upgrade, true);
|
|
82
|
+
return new;
|
|
83
|
+
end $$;
|
|
84
|
+
create trigger app_setting_defaults before insert on meta.app_setting
|
|
85
|
+
for each row execute function meta.app_setting_defaults();
|
|
86
|
+
|
|
87
|
+
-- APEX_APP_SETTING.GET_VALUE
|
|
88
|
+
create function meta.get_app_setting(p_name text) returns text
|
|
89
|
+
language plpgsql stable security definer set search_path = meta, pg_catalog as $$
|
|
90
|
+
declare
|
|
91
|
+
v_app int := meta.caller_app('meta.get_app_setting');
|
|
92
|
+
v meta.app_setting;
|
|
93
|
+
begin
|
|
94
|
+
select * into v from meta.app_setting where app_id = v_app and name = upper(p_name);
|
|
95
|
+
if not found then
|
|
96
|
+
raise exception 'meta.get_app_setting: the application has no setting %', upper(p_name);
|
|
97
|
+
end if;
|
|
98
|
+
return v.value;
|
|
99
|
+
end
|
|
100
|
+
$$;
|
|
101
|
+
grant execute on function meta.get_app_setting(text) to public;
|
|
102
|
+
|
|
103
|
+
-- APEX_APP_SETTING.SET_VALUE
|
|
104
|
+
create function meta.set_app_setting(p_name text, p_value text) returns void
|
|
105
|
+
language plpgsql volatile security definer set search_path = meta, pg_catalog as $$
|
|
106
|
+
declare
|
|
107
|
+
v_app int := meta.caller_app('meta.set_app_setting');
|
|
108
|
+
v meta.app_setting;
|
|
109
|
+
begin
|
|
110
|
+
select * into v from meta.app_setting where app_id = v_app and name = upper(p_name) for update;
|
|
111
|
+
if not found then
|
|
112
|
+
raise exception 'meta.set_app_setting: the application has no setting %', upper(p_name);
|
|
113
|
+
end if;
|
|
114
|
+
if v.required and coalesce(p_value, '') = '' then
|
|
115
|
+
raise exception 'meta.set_app_setting: % needs a value', v.name;
|
|
116
|
+
end if;
|
|
117
|
+
if p_value is not null and v.valid_values is not null and not p_value = any(v.valid_values) then
|
|
118
|
+
raise exception 'meta.set_app_setting: % is one of %', v.name, array_to_string(v.valid_values, ', ');
|
|
119
|
+
end if;
|
|
120
|
+
update meta.app_setting set value = p_value where id = v.id;
|
|
121
|
+
end
|
|
122
|
+
$$;
|
|
123
|
+
grant execute on function meta.set_app_setting(text, text) to public;
|
|
124
|
+
|
|
125
|
+
-- ---------------------------------------------------------------- user preferences
|
|
126
|
+
create table meta.user_preference (
|
|
127
|
+
app_id int not null references meta.app on delete cascade,
|
|
128
|
+
username text not null,
|
|
129
|
+
name text not null check (name ~ '^[A-Za-z][A-Za-z0-9_.-]{0,99}$'),
|
|
130
|
+
value text check (octet_length(value) <= 32000),
|
|
131
|
+
updated_at timestamptz not null default now(),
|
|
132
|
+
primary key (app_id, username, name)
|
|
133
|
+
);
|
|
134
|
+
revoke all on meta.user_preference from public;
|
|
135
|
+
comment on table meta.user_preference is 'User preferences (086): values kept per user across sessions; meta.get_preference / set_preference / remove_preference';
|
|
136
|
+
|
|
137
|
+
-- The user a preference belongs to: the given one, or the signed-in user.
|
|
138
|
+
create function meta.preference_user(p_fn text, p_user text) returns text
|
|
139
|
+
language plpgsql stable set search_path = meta, pg_catalog as $$
|
|
140
|
+
declare
|
|
141
|
+
v_user text := coalesce(nullif(btrim(p_user), ''), nullif(current_setting('pgkiln.app_user', true), ''));
|
|
142
|
+
begin
|
|
143
|
+
if v_user is null or v_user = 'nobody' then
|
|
144
|
+
raise exception '%: no signed-in user', p_fn;
|
|
145
|
+
end if;
|
|
146
|
+
return v_user;
|
|
147
|
+
end
|
|
148
|
+
$$;
|
|
149
|
+
revoke all on function meta.preference_user(text, text) from public;
|
|
150
|
+
|
|
151
|
+
-- APEX_UTIL.GET_PREFERENCE: null when the user has none
|
|
152
|
+
create function meta.get_preference(p_name text, p_user text default null) returns text
|
|
153
|
+
language plpgsql stable security definer set search_path = meta, pg_catalog as $$
|
|
154
|
+
declare
|
|
155
|
+
v_app int := meta.caller_app('meta.get_preference');
|
|
156
|
+
v_user text := coalesce(nullif(btrim(p_user), ''), nullif(current_setting('pgkiln.app_user', true), ''));
|
|
157
|
+
begin
|
|
158
|
+
if v_user is null or v_user = 'nobody' then
|
|
159
|
+
return null;
|
|
160
|
+
end if;
|
|
161
|
+
return (select value from meta.user_preference where app_id = v_app and username = v_user and name = p_name);
|
|
162
|
+
end
|
|
163
|
+
$$;
|
|
164
|
+
grant execute on function meta.get_preference(text, text) to public;
|
|
165
|
+
|
|
166
|
+
-- APEX_UTIL.SET_PREFERENCE: at most 500 preferences per user and application
|
|
167
|
+
create function meta.set_preference(p_name text, p_value text, p_user text default null) returns void
|
|
168
|
+
language plpgsql volatile security definer set search_path = meta, pg_catalog as $$
|
|
169
|
+
declare
|
|
170
|
+
v_app int := meta.caller_app('meta.set_preference');
|
|
171
|
+
v_user text := meta.preference_user('meta.set_preference', p_user);
|
|
172
|
+
begin
|
|
173
|
+
if p_name is null or p_name !~ '^[A-Za-z][A-Za-z0-9_.-]{0,99}$' then
|
|
174
|
+
raise exception 'meta.set_preference: a name of letters, digits, _ . and - (at most 100)';
|
|
175
|
+
end if;
|
|
176
|
+
if not exists (select 1 from meta.user_preference where app_id = v_app and username = v_user and name = p_name)
|
|
177
|
+
and (select count(*) from meta.user_preference where app_id = v_app and username = v_user) >= 500 then
|
|
178
|
+
raise exception 'meta.set_preference: at most 500 preferences per user';
|
|
179
|
+
end if;
|
|
180
|
+
insert into meta.user_preference (app_id, username, name, value) values (v_app, v_user, p_name, p_value)
|
|
181
|
+
on conflict (app_id, username, name) do update set value = excluded.value, updated_at = now();
|
|
182
|
+
end
|
|
183
|
+
$$;
|
|
184
|
+
grant execute on function meta.set_preference(text, text, text) to public;
|
|
185
|
+
|
|
186
|
+
-- APEX_UTIL.REMOVE_PREFERENCE
|
|
187
|
+
create function meta.remove_preference(p_name text, p_user text default null) returns void
|
|
188
|
+
language plpgsql volatile security definer set search_path = meta, pg_catalog as $$
|
|
189
|
+
declare
|
|
190
|
+
v_app int := meta.caller_app('meta.remove_preference');
|
|
191
|
+
v_user text := meta.preference_user('meta.remove_preference', p_user);
|
|
192
|
+
begin
|
|
193
|
+
delete from meta.user_preference where app_id = v_app and username = v_user and name = p_name;
|
|
194
|
+
end
|
|
195
|
+
$$;
|
|
196
|
+
grant execute on function meta.remove_preference(text, text) to public;
|
|
197
|
+
|
|
198
|
+
-- ---------------------------------------------------------------- dynamic translations
|
|
199
|
+
create table meta.dynamic_translation (
|
|
200
|
+
id serial primary key,
|
|
201
|
+
app_id int not null references meta.app on delete cascade,
|
|
202
|
+
language text not null check (language ~ '^[a-z]{2,3}(-[A-Za-z0-9]{2,8}){0,2}$'),
|
|
203
|
+
source text not null check (length(source) between 1 and 4000),
|
|
204
|
+
target text not null check (length(target) <= 4000),
|
|
205
|
+
unique (app_id, language, source)
|
|
206
|
+
);
|
|
207
|
+
revoke all on meta.dynamic_translation from public;
|
|
208
|
+
comment on table meta.dynamic_translation is 'Dynamic translations (086): values from tables in other languages; meta.lang(text)';
|
|
209
|
+
|
|
210
|
+
-- APEX_LANG.LANG: the text in the session's language (or p_language): an exact
|
|
211
|
+
-- match, then the base language (nl for nl-BE), else the text as it is.
|
|
212
|
+
create function meta.lang(p_text text, p_language text default null) returns text
|
|
213
|
+
language plpgsql stable security definer set search_path = meta, pg_catalog as $$
|
|
214
|
+
declare
|
|
215
|
+
v_app int := meta.app_id();
|
|
216
|
+
v_lang text := lower(coalesce(nullif(btrim(p_language), ''), meta.app_language()));
|
|
217
|
+
v_out text;
|
|
218
|
+
begin
|
|
219
|
+
if p_text is null or v_app is null then
|
|
220
|
+
return p_text;
|
|
221
|
+
end if;
|
|
222
|
+
select t.target into v_out from meta.dynamic_translation t
|
|
223
|
+
where t.app_id = v_app and t.source = p_text and t.language in (v_lang, split_part(v_lang, '-', 1))
|
|
224
|
+
order by t.language = v_lang desc
|
|
225
|
+
limit 1;
|
|
226
|
+
return coalesce(v_out, p_text);
|
|
227
|
+
end
|
|
228
|
+
$$;
|
|
229
|
+
grant execute on function meta.lang(text, text) to public;
|
|
230
|
+
|
|
231
|
+
-- ---------------------------------------------------------------- data export
|
|
232
|
+
-- A CSV field, as the report download writes it: quoted when it holds the
|
|
233
|
+
-- separator, a quote or a line break; text that looks like a formula
|
|
234
|
+
-- (= + - @ tab CR) gets a leading apostrophe so a spreadsheet doesn't run it.
|
|
235
|
+
create function meta.csv_field(p_value text, p_sep text default ',', p_text boolean default true) returns text
|
|
236
|
+
language sql immutable set search_path = pg_catalog as $$
|
|
237
|
+
select case
|
|
238
|
+
when v is null then ''
|
|
239
|
+
when strpos(v, p_sep) > 0 or v ~ '["\r\n]' then '"' || replace(v, '"', '""') || '"'
|
|
240
|
+
else v
|
|
241
|
+
end
|
|
242
|
+
from (select case when p_text and p_value ~ '^[=+\-@\t\r]' then '''' || p_value else p_value end as v) x
|
|
243
|
+
$$;
|
|
244
|
+
grant execute on function meta.csv_field(text, text, boolean) to public;
|
|
245
|
+
|
|
246
|
+
-- APEX_DATA_EXPORT.EXPORT: the rows of a query as a file. Formats: csv,
|
|
247
|
+
-- json (an array of objects), xml (query_to_xml) and html (a table).
|
|
248
|
+
-- Options: {"separator": ";", "header": false, "bom": false, "title": "…"}.
|
|
249
|
+
-- Runs with the caller's privileges (security invoker): row level security
|
|
250
|
+
-- and grants apply as to the query itself.
|
|
251
|
+
create function meta.data_export(p_query text, p_format text default 'csv', p_options jsonb default '{}') returns bytea
|
|
252
|
+
language plpgsql volatile as $$
|
|
253
|
+
declare
|
|
254
|
+
v_format text := lower(coalesce(nullif(btrim(p_format), ''), 'csv'));
|
|
255
|
+
v_opts jsonb := coalesce(p_options, '{}');
|
|
256
|
+
v_query text := regexp_replace(coalesce(p_query, ''), '[\s;]+$', '');
|
|
257
|
+
v_sep text := coalesce(v_opts->>'separator', ',');
|
|
258
|
+
v_header boolean := coalesce((v_opts->>'header')::boolean, true);
|
|
259
|
+
v_cols text[];
|
|
260
|
+
v_rows text;
|
|
261
|
+
v_title text := v_opts->>'title';
|
|
262
|
+
begin
|
|
263
|
+
if btrim(v_query) = '' then
|
|
264
|
+
raise exception 'meta.data_export: a query is required';
|
|
265
|
+
end if;
|
|
266
|
+
if v_format not in ('csv', 'json', 'xml', 'html') then
|
|
267
|
+
raise exception 'meta.data_export: the format is csv, json, xml or html (Excel and PDF: the download process)';
|
|
268
|
+
end if;
|
|
269
|
+
if length(v_sep) <> 1 or v_sep ~ '["\r\n]' then
|
|
270
|
+
raise exception 'meta.data_export: the separator is one character';
|
|
271
|
+
end if;
|
|
272
|
+
if v_format = 'json' then
|
|
273
|
+
execute format(E'select coalesce(json_agg(q), ''[]'')::text from (\n%s\n) q', v_query) into v_rows;
|
|
274
|
+
return convert_to(v_rows, 'UTF8');
|
|
275
|
+
end if;
|
|
276
|
+
if v_format = 'xml' then
|
|
277
|
+
return convert_to(query_to_xml(v_query, true, false, '')::text, 'UTF8');
|
|
278
|
+
end if;
|
|
279
|
+
-- the column names, in order, also when there are no rows (the outer join on false reads no row)
|
|
280
|
+
execute format(E'select array(select json_object_keys(row_to_json(x))) from (select q.* from (select 1) d left join (\n%s\n) q on false) x', v_query)
|
|
281
|
+
into v_cols;
|
|
282
|
+
if v_format = 'csv' then
|
|
283
|
+
execute format(E'select string_agg((select string_agg(meta.csv_field(e.value #>> ''{}'', %L, json_typeof(e.value) = ''string''), %L order by e.n) from json_each(row_to_json(q)) with ordinality e(k, value, n)), E''\\r\\n'') from (\n%s\n) q',
|
|
284
|
+
v_sep, v_sep, v_query)
|
|
285
|
+
into v_rows;
|
|
286
|
+
return convert_to(
|
|
287
|
+
case when coalesce((v_opts->>'bom')::boolean, true) then chr(65279) else '' end
|
|
288
|
+
|| case when v_header then (select string_agg(meta.csv_field(c, v_sep), v_sep order by n) from unnest(v_cols) with ordinality u(c, n)) || E'\r\n' else '' end
|
|
289
|
+
|| coalesce(v_rows || E'\r\n', ''),
|
|
290
|
+
'UTF8');
|
|
291
|
+
end if;
|
|
292
|
+
-- html
|
|
293
|
+
execute format(E'select string_agg(''<tr>'' || (select string_agg(''<td>'' || coalesce(meta.html_escape(e.value), '''') || ''</td>'', '''' order by e.n) from json_each_text(row_to_json(q)) with ordinality e(k, value, n)) || ''</tr>'', E''\\n'') from (\n%s\n) q',
|
|
294
|
+
v_query)
|
|
295
|
+
into v_rows;
|
|
296
|
+
return convert_to(
|
|
297
|
+
'<!doctype html>' || E'\n' || '<html><head><meta charset="utf-8"><title>' || coalesce(meta.html_escape(v_title), 'Export') || '</title></head><body>' || E'\n'
|
|
298
|
+
|| case when v_title is not null then '<h1>' || meta.html_escape(v_title) || '</h1>' || E'\n' else '' end
|
|
299
|
+
|| '<table>' || E'\n'
|
|
300
|
+
|| case when v_header then '<thead><tr>' || coalesce((select string_agg('<th>' || meta.html_escape(c) || '</th>', '' order by n) from unnest(v_cols) with ordinality u(c, n)), '') || '</tr></thead>' || E'\n' else '' end
|
|
301
|
+
|| '<tbody>' || E'\n' || coalesce(v_rows || E'\n', '') || '</tbody></table>' || E'\n' || '</body></html>' || E'\n',
|
|
302
|
+
'UTF8');
|
|
303
|
+
end
|
|
304
|
+
$$;
|
|
305
|
+
grant execute on function meta.data_export(text, text, jsonb) to public;
|
|
306
|
+
|
|
307
|
+
-- ---------------------------------------------------------------- items
|
|
308
|
+
alter table meta.item drop constraint item_type_check;
|
|
309
|
+
alter table meta.item add constraint item_type_check check (type in (
|
|
310
|
+
'text', 'textarea', 'number', 'date', 'datetime', 'select', 'radio', 'checkbox', 'switch', 'hidden', 'display', 'password',
|
|
311
|
+
'checkbox_group', 'multiselect', 'popup_lov', 'email', 'tel', 'url', 'color', 'file', 'location', 'richtext', 'markdown',
|
|
312
|
+
'rating', 'combobox', 'daterange', 'qrcode', 'plugin', 'image', 'address'));
|
|
313
|
+
|
|
314
|
+
-- ---------------------------------------------------------------- export
|
|
315
|
+
-- Same as 083, plus app_settings and dynamic_translations.
|
|
316
|
+
create or replace function meta.export_app(p_alias text) returns jsonb
|
|
317
|
+
language sql stable set search_path = meta, pg_catalog as $$
|
|
318
|
+
select jsonb_build_object(
|
|
319
|
+
'format', 'pgkiln/2',
|
|
320
|
+
-- (083) availability and build status belong to the installation
|
|
321
|
+
'app', to_jsonb(a) - 'id' - 'created_at' - 'updated_at' - 'availability' - 'availability_message' - 'availability_users' - 'build_status',
|
|
322
|
+
'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), '[]'),
|
|
323
|
+
'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), '[]'),
|
|
324
|
+
'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), '[]'),
|
|
325
|
+
'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), '[]'),
|
|
326
|
+
'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), '[]'),
|
|
327
|
+
'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), '[]'),
|
|
328
|
+
'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), '[]'),
|
|
329
|
+
'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)
|
|
330
|
+
from meta.report_layout x where x.app_id = a.id), '[]'),
|
|
331
|
+
'automations', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' - 'next_run_at' - 'last_run_at' - 'last_status' - 'code' order by x.name)
|
|
332
|
+
from meta.automation x where x.app_id = a.id), '[]'),
|
|
333
|
+
-- (044) the actions of the automations, by automation name
|
|
334
|
+
'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")
|
|
335
|
+
from meta.automation_action x where x.app_id = a.id), '[]'),
|
|
336
|
+
'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), '[]'),
|
|
337
|
+
'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), '[]'),
|
|
338
|
+
'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), '[]'),
|
|
339
|
+
'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), '[]'),
|
|
340
|
+
'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), '[]'),
|
|
341
|
+
'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), '[]'),
|
|
342
|
+
-- (030) secrets never leave the installation: they are entered again after an import
|
|
343
|
+
-- (050) nor do the password and refresh token of the OAuth2 flows, or the token state
|
|
344
|
+
'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)
|
|
345
|
+
from meta.web_credential x where x.app_id = a.id), '[]'),
|
|
346
|
+
-- (050) the synchronisation's state (next and last run) belongs to the installation
|
|
347
|
+
'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)
|
|
348
|
+
from meta.rest_source x where x.app_id = a.id), '[]'),
|
|
349
|
+
-- (041) data load definitions
|
|
350
|
+
'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), '[]'),
|
|
351
|
+
-- (042) lists; entries keep their ids so parents can be remapped on import
|
|
352
|
+
'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), '[]'),
|
|
353
|
+
'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), '[]'),
|
|
354
|
+
-- (042) supporting objects: install, upgrade and deinstall scripts (never run on import)
|
|
355
|
+
'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), '[]'),
|
|
356
|
+
-- (069) plug-ins (their files are static files, their templates template components)
|
|
357
|
+
'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), '[]'),
|
|
358
|
+
-- (080) search configurations
|
|
359
|
+
'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), '[]'),
|
|
360
|
+
-- (081) e-mail templates
|
|
361
|
+
'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), '[]'),
|
|
362
|
+
-- (086) application settings (with their values) and dynamic translations
|
|
363
|
+
'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), '[]'),
|
|
364
|
+
'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), '[]'),
|
|
365
|
+
-- (068) static application files (JavaScript, CSS, images, fonts) as base64
|
|
366
|
+
'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)
|
|
367
|
+
from meta.static_file x where x.app_id = a.id), '[]'),
|
|
368
|
+
-- nav entries and regions keep their ids, so parents and references can be remapped on import
|
|
369
|
+
'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), '[]'),
|
|
370
|
+
'pages', coalesce((
|
|
371
|
+
select jsonb_agg(to_jsonb(p) - 'id' - 'app_id' || jsonb_build_object(
|
|
372
|
+
'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), '[]'),
|
|
373
|
+
'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), '[]'),
|
|
374
|
+
'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), '[]'),
|
|
375
|
+
'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), '[]'),
|
|
376
|
+
'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), '[]'),
|
|
377
|
+
'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), '[]'),
|
|
378
|
+
'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), '[]'),
|
|
379
|
+
'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), '[]')
|
|
380
|
+
) order by p.page_no)
|
|
381
|
+
from meta.page p where p.app_id = a.id), '[]'))
|
|
382
|
+
from meta.app a
|
|
383
|
+
where a.alias = p_alias
|
|
384
|
+
$$;
|
|
385
|
+
|
|
386
|
+
-- ---------------------------------------------------------------- import
|
|
387
|
+
-- Same as 081, plus app_settings and dynamic_translations.
|
|
388
|
+
create or replace function meta.import_app(p_doc jsonb, p_alias text default null) returns int
|
|
389
|
+
language plpgsql set search_path = meta, pg_catalog as $$
|
|
390
|
+
declare
|
|
391
|
+
v_app_id int;
|
|
392
|
+
v_page_id int;
|
|
393
|
+
v_page jsonb;
|
|
394
|
+
v_e jsonb;
|
|
395
|
+
v_rmap jsonb;
|
|
396
|
+
v_nmap jsonb := '{}';
|
|
397
|
+
v_lmap jsonb := '{}';
|
|
398
|
+
v_new_id int;
|
|
399
|
+
begin
|
|
400
|
+
if p_doc->>'format' is distinct from 'pgkiln/2' then
|
|
401
|
+
raise exception 'unsupported export format %', coalesce(p_doc->>'format', '(none)');
|
|
402
|
+
end if;
|
|
403
|
+
|
|
404
|
+
insert into meta.app
|
|
405
|
+
select (jsonb_populate_record(null::meta.app, p_doc->'app' || jsonb_build_object(
|
|
406
|
+
'id', nextval('meta.app_id_seq'),
|
|
407
|
+
'alias', coalesce(p_alias, p_doc->'app'->>'alias'),
|
|
408
|
+
'created_at', now(), 'updated_at', now()))).*
|
|
409
|
+
returning id into v_app_id;
|
|
410
|
+
|
|
411
|
+
insert into meta.authz_scheme
|
|
412
|
+
select (jsonb_populate_record(null::meta.authz_scheme, e || jsonb_build_object('id', nextval('meta.authz_scheme_id_seq'), 'app_id', v_app_id))).*
|
|
413
|
+
from jsonb_array_elements(coalesce(p_doc->'authz_schemes', '[]')) e;
|
|
414
|
+
insert into meta.app_item
|
|
415
|
+
select (jsonb_populate_record(null::meta.app_item, e || jsonb_build_object('id', nextval('meta.app_item_id_seq'), 'app_id', v_app_id))).*
|
|
416
|
+
from jsonb_array_elements(coalesce(p_doc->'app_items', '[]')) e;
|
|
417
|
+
insert into meta.app_process
|
|
418
|
+
select (jsonb_populate_record(null::meta.app_process, e || jsonb_build_object('id', nextval('meta.app_process_id_seq'), 'app_id', v_app_id))).*
|
|
419
|
+
from jsonb_array_elements(coalesce(p_doc->'app_processes', '[]')) e;
|
|
420
|
+
insert into meta.lov
|
|
421
|
+
select (jsonb_populate_record(null::meta.lov, e || jsonb_build_object('id', nextval('meta.lov_id_seq'), 'app_id', v_app_id))).*
|
|
422
|
+
from jsonb_array_elements(coalesce(p_doc->'lovs', '[]')) e;
|
|
423
|
+
insert into meta.app_group_role
|
|
424
|
+
select (jsonb_populate_record(null::meta.app_group_role, e || jsonb_build_object('app_id', v_app_id))).*
|
|
425
|
+
from jsonb_array_elements(coalesce(p_doc->'group_roles', '[]')) e;
|
|
426
|
+
insert into meta.text_message
|
|
427
|
+
select (jsonb_populate_record(null::meta.text_message, e || jsonb_build_object('app_id', v_app_id))).*
|
|
428
|
+
from jsonb_array_elements(coalesce(p_doc->'text_messages', '[]')) e;
|
|
429
|
+
insert into meta.translation
|
|
430
|
+
select (jsonb_populate_record(null::meta.translation, e || jsonb_build_object('app_id', v_app_id))).*
|
|
431
|
+
from jsonb_array_elements(coalesce(p_doc->'translations', '[]')) e;
|
|
432
|
+
insert into meta.report_layout
|
|
433
|
+
select (jsonb_populate_record(null::meta.report_layout, (e - 'logo') || jsonb_build_object(
|
|
434
|
+
'id', nextval('meta.report_layout_id_seq'), 'app_id', v_app_id,
|
|
435
|
+
'logo', null))).*
|
|
436
|
+
from jsonb_array_elements(coalesce(p_doc->'report_layouts', '[]')) e;
|
|
437
|
+
update meta.report_layout l
|
|
438
|
+
set logo = decode(e->>'logo', 'base64')
|
|
439
|
+
from jsonb_array_elements(coalesce(p_doc->'report_layouts', '[]')) e
|
|
440
|
+
where l.app_id = v_app_id and l.name = e->>'name' and e->>'logo' is not null;
|
|
441
|
+
-- imported automations start switched off: the copy must not run the original's jobs unasked.
|
|
442
|
+
-- (044) a file with actions: the automations' code is ignored; an older file: code becomes the single action
|
|
443
|
+
insert into meta.automation
|
|
444
|
+
select (jsonb_populate_record(null::meta.automation, '{"error_handling": "stop"}'::jsonb
|
|
445
|
+
|| jsonb_strip_nulls(case when p_doc ? 'automation_actions' then e - 'code' else e end)
|
|
446
|
+
|| jsonb_build_object('id', nextval('meta.automation_id_seq'), 'app_id', v_app_id, 'enabled', false))).*
|
|
447
|
+
from jsonb_array_elements(coalesce(p_doc->'automations', '[]')) e;
|
|
448
|
+
insert into meta.automation_action
|
|
449
|
+
select (jsonb_populate_record(null::meta.automation_action, '{"seq": 10}'::jsonb || jsonb_strip_nulls(e)
|
|
450
|
+
|| jsonb_build_object('id', nextval('meta.automation_action_id_seq'), 'app_id', v_app_id))).*
|
|
451
|
+
from jsonb_array_elements(coalesce(p_doc->'automation_actions', '[]')) e;
|
|
452
|
+
insert into meta.document_template
|
|
453
|
+
select (jsonb_populate_record(null::meta.document_template, e || jsonb_build_object('id', nextval('meta.document_template_id_seq'), 'app_id', v_app_id))).*
|
|
454
|
+
from jsonb_array_elements(coalesce(p_doc->'document_templates', '[]')) e;
|
|
455
|
+
insert into meta.task_definition
|
|
456
|
+
select (jsonb_populate_record(null::meta.task_definition, e || jsonb_build_object('id', nextval('meta.task_definition_id_seq'), 'app_id', v_app_id))).*
|
|
457
|
+
from jsonb_array_elements(coalesce(p_doc->'task_definitions', '[]')) e;
|
|
458
|
+
insert into meta.workflow_definition
|
|
459
|
+
select (jsonb_populate_record(null::meta.workflow_definition, e || jsonb_build_object('id', nextval('meta.workflow_definition_id_seq'), 'app_id', v_app_id))).*
|
|
460
|
+
from jsonb_array_elements(coalesce(p_doc->'workflow_definitions', '[]')) e;
|
|
461
|
+
insert into meta.rest_module
|
|
462
|
+
select (jsonb_populate_record(null::meta.rest_module, e || jsonb_build_object('id', nextval('meta.rest_module_id_seq'), 'app_id', v_app_id))).*
|
|
463
|
+
from jsonb_array_elements(coalesce(p_doc->'rest_modules', '[]')) e;
|
|
464
|
+
-- (028) template components: regions and report columns refer to them by static id
|
|
465
|
+
insert into meta.template_component
|
|
466
|
+
select (jsonb_populate_record(null::meta.template_component, e || jsonb_build_object('id', nextval('meta.template_component_id_seq'), 'app_id', v_app_id))).*
|
|
467
|
+
from jsonb_array_elements(coalesce(p_doc->'template_components', '[]')) e;
|
|
468
|
+
-- (029) build options (components name them by name)
|
|
469
|
+
insert into meta.build_option
|
|
470
|
+
select (jsonb_populate_record(null::meta.build_option, e || jsonb_build_object('id', nextval('meta.build_option_id_seq'), 'app_id', v_app_id))).*
|
|
471
|
+
from jsonb_array_elements(coalesce(p_doc->'build_options', '[]')) e;
|
|
472
|
+
-- (030) web credentials arrive without a secret, whatever the document holds
|
|
473
|
+
-- (050) nor a password or refresh token
|
|
474
|
+
insert into meta.web_credential
|
|
475
|
+
select (jsonb_populate_record(null::meta.web_credential, '{"type": "basic", "valid_for": [], "grant_type": "client_credentials"}'::jsonb
|
|
476
|
+
|| jsonb_strip_nulls(e - 'secret_enc' - 'password_enc' - 'refresh_token_enc' - 'token_refreshed_at')
|
|
477
|
+
|| jsonb_build_object('id', nextval('meta.web_credential_id_seq'), 'app_id', v_app_id))).*
|
|
478
|
+
from jsonb_array_elements(coalesce(p_doc->'web_credentials', '[]')) e;
|
|
479
|
+
-- (050) write-back operations and synchronisation; an imported synchronisation starts switched off
|
|
480
|
+
insert into meta.rest_source
|
|
481
|
+
select (jsonb_populate_record(null::meta.rest_source, '{"method": "GET", "headers": {}, "params": [], "columns": [], "cache_seconds": 0, "timeout_s": 10, "max_rows": 1000,
|
|
482
|
+
"operations": {}, "key_columns": [], "sync_mode": "merge", "sync_delete": false, "sync_time_zone": "UTC"}'::jsonb
|
|
483
|
+
|| jsonb_strip_nulls(e - 'sync_next_at' - 'sync_last_at' - 'sync_last_status')
|
|
484
|
+
|| jsonb_build_object('id', nextval('meta.rest_source_id_seq'), 'app_id', v_app_id, 'sync_enabled', false))).*
|
|
485
|
+
from jsonb_array_elements(coalesce(p_doc->'rest_sources', '[]')) e;
|
|
486
|
+
|
|
487
|
+
-- (041) data load definitions (the data_load process names them)
|
|
488
|
+
insert into meta.data_load_def
|
|
489
|
+
select (jsonb_populate_record(null::meta.data_load_def, '{"format": "auto", "headers": true, "mode": "append", "skip_errors": false, "columns": []}'::jsonb
|
|
490
|
+
|| jsonb_strip_nulls(e) || jsonb_build_object('id', nextval('meta.data_load_def_id_seq'), 'app_id', v_app_id))).*
|
|
491
|
+
from jsonb_array_elements(coalesce(p_doc->'data_load_definitions', '[]')) e;
|
|
492
|
+
|
|
493
|
+
-- (042) lists and their entries, supporting scripts. Entries are inserted
|
|
494
|
+
-- without a parent first and the parents set afterwards, so the order of
|
|
495
|
+
-- the entries in the document doesn't matter (a moved entry may have a
|
|
496
|
+
-- higher id than its children).
|
|
497
|
+
insert into meta.list
|
|
498
|
+
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))).*
|
|
499
|
+
from jsonb_array_elements(coalesce(p_doc->'lists', '[]')) e;
|
|
500
|
+
for v_e in select * from jsonb_array_elements(coalesce(p_doc->'list_entries', '[]')) loop
|
|
501
|
+
insert into meta.list_entry
|
|
502
|
+
select (jsonb_populate_record(null::meta.list_entry, '{"target_items": {}}'::jsonb || jsonb_strip_nulls(v_e) || jsonb_build_object(
|
|
503
|
+
'id', nextval('meta.list_entry_id_seq'), 'app_id', v_app_id, 'parent_id', null))).*
|
|
504
|
+
returning id into v_new_id;
|
|
505
|
+
if v_e->>'id' is not null then
|
|
506
|
+
v_lmap := v_lmap || jsonb_build_object(v_e->>'id', v_new_id);
|
|
507
|
+
end if;
|
|
508
|
+
end loop;
|
|
509
|
+
update meta.list_entry x
|
|
510
|
+
set parent_id = (v_lmap->>(e->>'parent_id'))::int
|
|
511
|
+
from jsonb_array_elements(coalesce(p_doc->'list_entries', '[]')) e
|
|
512
|
+
where e->>'parent_id' is not null and v_lmap ? (e->>'parent_id') and v_lmap ? (e->>'id')
|
|
513
|
+
and x.id = (v_lmap->>(e->>'id'))::int;
|
|
514
|
+
insert into meta.supporting_script
|
|
515
|
+
select (jsonb_populate_record(null::meta.supporting_script, '{"kind": "install", "seq": 10, "script": ""}'::jsonb || jsonb_strip_nulls(e)
|
|
516
|
+
|| jsonb_build_object('id', nextval('meta.supporting_script_id_seq'), 'app_id', v_app_id))).*
|
|
517
|
+
from jsonb_array_elements(coalesce(p_doc->'supporting_scripts', '[]')) e;
|
|
518
|
+
|
|
519
|
+
-- (069) plug-ins
|
|
520
|
+
insert into meta.plugin
|
|
521
|
+
select (jsonb_populate_record(null::meta.plugin, e || jsonb_build_object('id', nextval('meta.plugin_id_seq'), 'app_id', v_app_id))).*
|
|
522
|
+
from jsonb_array_elements(coalesce(p_doc->'plugins', '[]')) e;
|
|
523
|
+
|
|
524
|
+
-- (080) search configurations (search regions name them)
|
|
525
|
+
insert into meta.search_config
|
|
526
|
+
select (jsonb_populate_record(null::meta.search_config, '{"seq": 10, "source_type": "table", "searchable_columns": [], "max_results": 10}'::jsonb
|
|
527
|
+
|| jsonb_strip_nulls(e) || jsonb_build_object('id', nextval('meta.search_config_id_seq'), 'app_id', v_app_id))).*
|
|
528
|
+
from jsonb_array_elements(coalesce(p_doc->'search_configs', '[]')) e;
|
|
529
|
+
|
|
530
|
+
-- (081) e-mail templates (meta.send_mail_template and the send_email process name them)
|
|
531
|
+
insert into meta.email_template
|
|
532
|
+
select (jsonb_populate_record(null::meta.email_template, e || jsonb_build_object('id', nextval('meta.email_template_id_seq'), 'app_id', v_app_id))).*
|
|
533
|
+
from jsonb_array_elements(coalesce(p_doc->'email_templates', '[]')) e;
|
|
534
|
+
|
|
535
|
+
-- (086) application settings and dynamic translations
|
|
536
|
+
insert into meta.app_setting
|
|
537
|
+
select (jsonb_populate_record(null::meta.app_setting, e || jsonb_build_object('id', nextval('meta.app_setting_id_seq'), 'app_id', v_app_id))).*
|
|
538
|
+
from jsonb_array_elements(coalesce(p_doc->'app_settings', '[]')) e;
|
|
539
|
+
insert into meta.dynamic_translation
|
|
540
|
+
select (jsonb_populate_record(null::meta.dynamic_translation, e || jsonb_build_object('id', nextval('meta.dynamic_translation_id_seq'), 'app_id', v_app_id))).*
|
|
541
|
+
from jsonb_array_elements(coalesce(p_doc->'dynamic_translations', '[]')) e;
|
|
542
|
+
|
|
543
|
+
-- (068) static application files
|
|
544
|
+
insert into meta.static_file (app_id, name, mime, content, updated_at)
|
|
545
|
+
select v_app_id, e->>'name', e->>'mime', decode(coalesce(e->>'content', ''), 'base64'), now()
|
|
546
|
+
from jsonb_array_elements(coalesce(p_doc->'static_files', '[]')) e;
|
|
547
|
+
|
|
548
|
+
for v_e in select * from jsonb_array_elements(coalesce(p_doc->'nav', '[]')) loop
|
|
549
|
+
insert into meta.nav_entry
|
|
550
|
+
select (jsonb_populate_record(null::meta.nav_entry, v_e || jsonb_build_object(
|
|
551
|
+
'id', nextval('meta.nav_entry_id_seq'), 'app_id', v_app_id,
|
|
552
|
+
'parent_id', v_nmap->>(v_e->>'parent_id')))).*
|
|
553
|
+
returning id into v_new_id;
|
|
554
|
+
v_nmap := v_nmap || jsonb_build_object(v_e->>'id', v_new_id);
|
|
555
|
+
end loop;
|
|
556
|
+
|
|
557
|
+
for v_page in select * from jsonb_array_elements(coalesce(p_doc->'pages', '[]')) loop
|
|
558
|
+
insert into meta.page
|
|
559
|
+
select (jsonb_populate_record(null::meta.page, v_page || jsonb_build_object('id', nextval('meta.page_id_seq'), 'app_id', v_app_id))).*
|
|
560
|
+
returning id into v_page_id;
|
|
561
|
+
|
|
562
|
+
v_rmap := '{}';
|
|
563
|
+
for v_e in select * from jsonb_array_elements(coalesce(v_page->'regions', '[]')) loop
|
|
564
|
+
insert into meta.region
|
|
565
|
+
select (jsonb_populate_record(null::meta.region, v_e || jsonb_build_object('id', nextval('meta.region_id_seq'), 'page_id', v_page_id))).*
|
|
566
|
+
returning id into v_new_id;
|
|
567
|
+
v_rmap := v_rmap || jsonb_build_object(v_e->>'id', v_new_id);
|
|
568
|
+
end loop;
|
|
569
|
+
-- facet and map regions point at their report region by id
|
|
570
|
+
update meta.region r
|
|
571
|
+
set config = jsonb_set(r.config, '{report}', to_jsonb((v_rmap->>(r.config->>'report'))::int))
|
|
572
|
+
where r.page_id = v_page_id and r.type in ('facets', 'smart_filters', 'map') and v_rmap ? (r.config->>'report');
|
|
573
|
+
|
|
574
|
+
insert into meta.item
|
|
575
|
+
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')))).*
|
|
576
|
+
from jsonb_array_elements(coalesce(v_page->'items', '[]')) e;
|
|
577
|
+
insert into meta.button
|
|
578
|
+
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')))).*
|
|
579
|
+
from jsonb_array_elements(coalesce(v_page->'buttons', '[]')) e;
|
|
580
|
+
insert into meta.dynamic_action
|
|
581
|
+
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')))).*
|
|
582
|
+
from jsonb_array_elements(coalesce(v_page->'dynamic_actions', '[]')) e;
|
|
583
|
+
insert into meta.validation
|
|
584
|
+
select (jsonb_populate_record(null::meta.validation, e || jsonb_build_object('id', nextval('meta.validation_id_seq'), 'page_id', v_page_id))).*
|
|
585
|
+
from jsonb_array_elements(coalesce(v_page->'validations', '[]')) e;
|
|
586
|
+
insert into meta.process
|
|
587
|
+
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')))).*
|
|
588
|
+
from jsonb_array_elements(coalesce(v_page->'processes', '[]')) e;
|
|
589
|
+
insert into meta.computation
|
|
590
|
+
select (jsonb_populate_record(null::meta.computation, e || jsonb_build_object('id', nextval('meta.computation_id_seq'), 'page_id', v_page_id))).*
|
|
591
|
+
from jsonb_array_elements(coalesce(v_page->'computations', '[]')) e;
|
|
592
|
+
insert into meta.branch
|
|
593
|
+
select (jsonb_populate_record(null::meta.branch, e || jsonb_build_object('id', nextval('meta.branch_id_seq'), 'page_id', v_page_id))).*
|
|
594
|
+
from jsonb_array_elements(coalesce(v_page->'branches', '[]')) e;
|
|
595
|
+
end loop;
|
|
596
|
+
|
|
597
|
+
return v_app_id;
|
|
598
|
+
end
|
|
599
|
+
$$;
|