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
|
@@ -47,10 +47,52 @@ The builder and the runtime cover what APEX offers for its accounts:
|
|
|
47
47
|
The `meta.set_password`/`expire`/`unexpire` functions are owner-only. Grant them to an app role if
|
|
48
48
|
you want to build user administration pages inside an application.
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
A user who forgot their password asks an administrator, who sets a temporary one under
|
|
51
|
+
**Users → account → Set password**, with *Require change of password on first use* ticked (the
|
|
52
|
+
default), or uses **Forgot password?** when the application offers it (below).
|
|
53
|
+
|
|
54
|
+
### Forgot password
|
|
55
|
+
|
|
56
|
+
With **Application settings → Sign-in methods → Offer "Forgot password?"** the sign-in page (the
|
|
57
|
+
built-in one or the application's [own login page](#the-login-page)) has a *Forgot password?* link
|
|
58
|
+
(APEX apps don't have one out of the box). It needs an enabled [mail service](06-processing.md#sending-e-mail)
|
|
59
|
+
with a From address for the application; without one the link isn't shown. It is for passwords
|
|
60
|
+
of the user directory only: not for LDAP, single sign-on, database accounts or custom
|
|
61
|
+
authentication.
|
|
62
|
+
|
|
63
|
+
1. The user types their user name or e-mail address. The answer is always the same, whether or
|
|
64
|
+
not an account matched, so the page doesn't tell who has an account.
|
|
65
|
+
2. An active account with a password and an e-mail address that may use the application gets an
|
|
66
|
+
e-mail (in the user's language) with a link to `/a/<alias>/password/reset?token=…`, built from
|
|
67
|
+
`PUBLIC_URL`, never from the request's `Host` header.
|
|
68
|
+
3. The link works **once**, for **30 minutes**, in this application only. Opening it moves the
|
|
69
|
+
token into the session and out of the address bar; the user chooses a new password (the
|
|
70
|
+
[password policy](#passwords-and-my-account) applies).
|
|
71
|
+
4. The new password ends every session and *Keep me signed in* token of the account, and makes
|
|
72
|
+
the account's other open links useless.
|
|
73
|
+
|
|
74
|
+
Only a SHA-256 hash of each token is stored (`meta.password_reset`, owner only). Requests are
|
|
75
|
+
limited to 5 per address in 15 minutes and 3 mails per account per hour; they count towards the
|
|
76
|
+
application's daily mail limit and show in the activity log (`password_reset_request`,
|
|
77
|
+
`password_reset`).
|
|
78
|
+
|
|
79
|
+
### The login page
|
|
80
|
+
|
|
81
|
+
`/a/<alias>/login` shows pgkiln's sign-in page: the application's name, the sign-in methods and,
|
|
82
|
+
when the application has several languages, a language picker. To design your own (APEX: the
|
|
83
|
+
login page, usually page 9999), choose **Application settings → Sign-in methods → Create a login
|
|
84
|
+
page**: it adds page 9999 (or the next free number) that doesn't require authentication, with
|
|
85
|
+
page template *Minimal (no navigation)* and a **Login** region, and makes it the application's
|
|
86
|
+
**Login page**. From then on `/a/<alias>/login` (and every page that needs a signed-in user)
|
|
87
|
+
sends visitors there.
|
|
88
|
+
|
|
89
|
+
Edit it like any page: add static content, images, lists of links or any other region around the
|
|
90
|
+
Login region, which is the sign-in form (identity providers, user name and password, *Keep me
|
|
91
|
+
signed in*, *Forgot password?*). The form still posts to `/a/<alias>/login`, so throttling, the
|
|
92
|
+
CSRF check and every sign-in method work as on the built-in page; a failed sign-in comes back to
|
|
93
|
+
the login page with its message and the user name. A login page that requires authentication,
|
|
94
|
+
or doesn't exist, is ignored (the built-in page shows) and the Advisor reports it; so does a
|
|
95
|
+
login page without a Login region.
|
|
54
96
|
|
|
55
97
|
With single sign-on, password changes happen at the identity provider; My account says so.
|
|
56
98
|
|
|
@@ -327,6 +369,16 @@ like for other users (an account of the same name, if there is one), or from the
|
|
|
327
369
|
in authorization schemes. The login page has the user name and password form only: *Keep me signed
|
|
328
370
|
in*, LDAP, single sign-on and the password-change form are not used by these apps.
|
|
329
371
|
|
|
372
|
+
### Session timeout warning
|
|
373
|
+
|
|
374
|
+
Sessions end after the idle time (`SESSION_IDLE_MINUTES`, or Instance settings) and at their maximum
|
|
375
|
+
length (`SESSION_MAX_HOURS`). Signed-in users are warned before (Settings → *Errors and sessions* →
|
|
376
|
+
*Session timeout warning*, default 120 seconds; 0: no warning; APEX: Session Timeout Warning): a
|
|
377
|
+
dialog says when the session ends and offers **Stay signed in**, which marks the session active
|
|
378
|
+
(POST `/a/<alias>/session/extend`, with the session's CSRF token). At the maximum length the user can
|
|
379
|
+
only save their work; when the session has ended the dialog offers to sign in again and comes back
|
|
380
|
+
to the page.
|
|
381
|
+
|
|
330
382
|
### Keep me signed in
|
|
331
383
|
|
|
332
384
|
Under **Settings → Sign-in methods**, *"Keep me signed in" for (days)* (1–365) adds a checkbox to the
|
|
@@ -13,6 +13,11 @@ triggers, your own functions).
|
|
|
13
13
|
| `meta.jwt_claims()` | jsonb | The verified JWT claims of a PostgREST request, or NULL |
|
|
14
14
|
| `meta.app_language()` | text | The language of the request (`nl`, `en-GB`, …) |
|
|
15
15
|
| `meta.message(name, variadic params)` | text | A text message in the current language, `%0`…`%9` replaced; falls back to the base and primary language |
|
|
16
|
+
| `meta.lang(text, language default null)` | text | A [dynamic translation](14-globalization.md#dynamic-translations) of a value from a table in the session's (or the given) language, else the text (APEX: `APEX_LANG.LANG`) |
|
|
17
|
+
| `meta.get_app_setting(name)`, `meta.set_app_setting(name, value)` | text / void | An [application setting](06-processing.md#application-settings) of the current application (APEX: `APEX_APP_SETTING`); application code only |
|
|
18
|
+
| `meta.get_preference(name, user default null)`, `meta.set_preference(name, value, user default null)`, `meta.remove_preference(name, user default null)` | text / void | A [user preference](06-processing.md#user-preferences) of the signed-in (or the given) user in the current application (APEX: `APEX_UTIL.*_PREFERENCE`) |
|
|
19
|
+
| `meta.data_export(query, format default 'csv', options jsonb default '{}')` | bytea | A query's rows as a CSV, JSON, XML or HTML file, run with the caller's privileges ([data export](06-processing.md#data-export-from-sql); APEX: `APEX_DATA_EXPORT`) |
|
|
20
|
+
| `meta.csv_field(value, separator default ',', is_text default true)` | text | One CSV field: quoted when needed, a formula neutralised |
|
|
16
21
|
| `meta.password_days_left(username)` | int | Days until the password expires (0: must change now, NULL: never) |
|
|
17
22
|
| `meta.v(name)` | text | The session-state value of an item (use it inside functions and `DO` blocks) |
|
|
18
23
|
| `meta.set_tenant(tenant)` | void | Sets (or with NULL or `''` clears) the current session's tenant (APEX: `apex_session.set_tenant_id`): at once and for the session's next requests. Workflows and tasks then carry it and show only to that tenant ([chapter 6](06-processing.md#tenants)) |
|
|
@@ -93,6 +98,22 @@ select meta.web_request_source('EXCHANGE', '{"currency": "EUR"}');
|
|
|
93
98
|
- Application roles can't read `meta.web_request_log`; the owner can, e.g. in the SQL Workshop:
|
|
94
99
|
`select id, status, status_code, url, message from meta.web_request_log order by id desc`.
|
|
95
100
|
|
|
101
|
+
### E-mail from SQL
|
|
102
|
+
|
|
103
|
+
[Sending e-mail](06-processing.md#sending-e-mail) (APEX: `APEX_MAIL`). The application needs a mail
|
|
104
|
+
service (an administrator chooses it under Workspace utilities → Mail services).
|
|
105
|
+
|
|
106
|
+
| Function | Returns |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `meta.send_mail(p_to, p_subject, p_body, p_body_html, p_from, p_cc, p_bcc, p_reply_to)` | Queues a mail and returns its id (APEX_MAIL.SEND). `p_to`, `p_cc`, `p_bcc`: addresses separated by commas or semicolons, at most 50 together (plain addresses, no display names); `p_subject`: one line, at most 998 characters; `p_body` (plain text) and/or `p_body_html`, each at most 1 MB; `p_from`: an address the service may send as (default: the application's); `p_reply_to`: one address |
|
|
109
|
+
| `meta.send_mail_template(p_template, p_placeholders jsonb, p_to, p_cc, p_bcc, p_from, p_reply_to)` | The same with an [e-mail template](06-processing.md#e-mail-templates) and its placeholder values (APEX_MAIL.SEND with `p_template_static_id`) |
|
|
110
|
+
| `meta.add_mail_attachment(p_mail_id, p_content bytea, p_filename, p_mime)` | Adds a file to a mail queued **in the same transaction** (APEX_MAIL.ADD_ATTACHMENT); at most 20 files and 20 MB per mail, 10 MB per file |
|
|
111
|
+
| `meta.mail_status(p_mail_id)` | `queued`, `sending`, `sent`, `error` or `cancelled`; NULL for another application's mail |
|
|
112
|
+
|
|
113
|
+
The mail is sent by the server **after the transaction commits**. Over the application's limit per
|
|
114
|
+
day `meta.send_mail` raises an error. Application roles can't read the queue; the owner can:
|
|
115
|
+
`select id, app_id, to_addresses, subject, status, attempts, message from meta.mail_queue order by id desc`.
|
|
116
|
+
|
|
96
117
|
### Push notifications from SQL
|
|
97
118
|
|
|
98
119
|
APEX_PWA push notifications ([chapter 17](17-mobile.md#push-notifications)); the application needs
|
|
@@ -47,12 +47,17 @@ What's missing is listed in the [feature parity matrix](../apex-feature-parity.m
|
|
|
47
47
|
| Rich Text Editor, Markdown Editor, Star Rating, Combobox, QR Code | Item types `richtext` (sanitised HTML), `markdown`, `rating`, `combobox` (colon-separated), `qrcode`; plus `daterange` (`from:to`) and `{"reveal": true}` on password items ([chapter 5](05-items.md)) |
|
|
48
48
|
| Data Generator (sample data for development, 26.1) | SQL Workshop → **Sample Data**: generators proposed per column, foreign keys to existing parent rows, a seed, preview, insert in one transaction (parents first), SQL/CSV download, saved definitions ([chapter 16](16-files.md#sql-workshop--sample-data)) |
|
|
49
49
|
| Data Workshop (load and unload: CSV, Excel, JSON, XML) / Data Load Definition, *Execute Data Load* process | SQL Workshop → Load Data and **Unload Data** (CSV, JSON, Excel, XML; [chapter 16](16-files.md#sql-workshop--unload-data)); Shared Components → Data load definitions; process type `data_load` with `"definition"` ([chapter 16](16-files.md#data-loading)) |
|
|
50
|
+
| SQL Workshop → Utilities → Generate DDL, Schema Comparison | SQL Workshop → **DDL** and **Compare**: a schema's DDL in an order that runs; two schemas compared by their DDL with a change script; also `pgkiln ddl` and `pgkiln compare` ([chapter 3](03-builder.md#sql-workshop)) |
|
|
50
51
|
| SQL Workshop → SQL Scripts, Quick SQL, Query Builder | The same names in the SQL Workshop: saved scripts with a result per statement and a run history, shorthand → PostgreSQL DDL, a SELECT built on a canvas of tables, joined by foreign keys or columns you connect, with column functions and grouping ([chapter 3](03-builder.md#sql-workshop)) |
|
|
51
52
|
| Interactive report *Download → PDF*, printing | Actions → Download PDF, Print ([chapter 16](16-files.md#printing)) |
|
|
52
53
|
| Pagination *Row Ranges X to Y*, *Maximum Row Count*, region *Lazy Loading*, *Server Cache* | `"pagination": "range"`, `max_rows`, `"lazy": true`, `"cache": {"scope", "seconds"}` ([large tables](04-pages-and-regions.md#large-tables)) |
|
|
53
54
|
| `APEX_UTIL.CHANGE_CURRENT_USER_PW`, `RESET_PASSWORD`, `EXPIRE_END_USER_ACCOUNT` | My account page; `meta.set_password()`, `meta.expire_password()` ([chapter 8](08-security.md#passwords-and-my-account)) |
|
|
54
55
|
| Progressive Web App push notifications: `APEX_PWA.SEND_PUSH_NOTIFICATION`, `HAS_PUSH_SUBSCRIPTION`, *Send Push Notification* process, the subscription settings page | `meta.send_push(user, title, body, page, items)`, `meta.has_push_subscription(user)`, process type `send_push`, *My account → Notifications* and the dynamic action `push_subscribe` ([chapter 17](17-mobile.md#push-notifications)) |
|
|
55
|
-
| `APEX_MAIL`, Send E-Mail process, e-mail templates |
|
|
56
|
+
| `APEX_MAIL`, Send E-Mail process, e-mail templates | `meta.send_mail`, `meta.send_mail_template`, `meta.add_mail_attachment`, the `send_email` process and Shared Components → E-mail templates (`#NAME#` placeholders) ([chapter 6](06-processing.md#sending-e-mail)). Instead of the instance's SMTP settings: mail services with SMTP (password or OAuth2), Microsoft Graph, Amazon SES, Resend, Postmark, SendGrid or Mailgun. `APEX_MAIL.PUSH_QUEUE` is not needed: mail goes out right after the commit |
|
|
57
|
+
| `APEX_APP_SETTING`, `APEX_UTIL.GET/SET/REMOVE_PREFERENCE`, *Maintain session state: Per User* | Shared Components → Application settings and `meta.get_app_setting`/`meta.set_app_setting`; `meta.get_preference`/`set_preference`/`remove_preference`; `{"persist": "user"}` on an item ([chapter 6](06-processing.md#application-settings)) |
|
|
58
|
+
| `APEX_DATA_EXPORT.EXPORT` | `meta.data_export(query, format, options)` (CSV, JSON, XML, HTML) and the download process's `{"export": "xlsx"}` (also PDF) ([chapter 6](06-processing.md#data-export-from-sql)) |
|
|
59
|
+
| `APEX_LANG.LANG`, dynamic translations | Shared Components → Dynamic translations and `meta.lang(text)` ([chapter 14](14-globalization.md#dynamic-translations)) |
|
|
60
|
+
| Display Image, Geocoded Address items | Item types `image` and `address` (geocoder: `GEOCODER_URL`) ([chapter 5](05-items.md#display-image-and-geocoded-address)) |
|
|
56
61
|
| Translated applications (XLIFF), `APEX_LANG.MESSAGE`, `&APP_TEXT$NAME.` | Translations in the app (XLIFF/CSV import and export), `meta.message()`, `&APP_TEXT$NAME.` ([chapter 14](14-globalization.md)) |
|
|
57
62
|
| Application date format mask | Settings → Globalization → Date format (Oracle-style masks) |
|
|
58
63
|
| Number format masks (`FML999G999G990D00`) on columns and items | `{"formats": {...}}` on report, grid and cards columns, `{"format_mask": "..."}` on charts and number/display items ([chapter 14](14-globalization.md#number-formats)) |
|
|
@@ -93,7 +98,9 @@ in to a second app is then silent via the identity provider's session. See
|
|
|
93
98
|
Active Directory** passwords work the same way (groups → roles), and *Keep me signed in* matches
|
|
94
99
|
APEX's persistent authentication. **Database accounts** sign in with PostgreSQL login roles, and
|
|
95
100
|
**custom authentication** calls your own PL/pgSQL function (APEX: a custom PL/SQL scheme), see
|
|
96
|
-
[chapter 8](08-security.md#custom-authentication-a-plpgsql-function).
|
|
101
|
+
[chapter 8](08-security.md#custom-authentication-a-plpgsql-function). The **login page** (APEX: page 9999 with
|
|
102
|
+
the Login region template) is a page you design in the Page Designer around a **Login** region, and
|
|
103
|
+
**Forgot password?** is built in (APEX apps build their own), see [chapter 8](08-security.md#the-login-page).
|
|
97
104
|
|
|
98
105
|
## ORDS and PostgREST
|
|
99
106
|
|
|
@@ -34,6 +34,8 @@ src/
|
|
|
34
34
|
app.ts / server.ts Fastify setup / entry point
|
|
35
35
|
db.ts the two pools, appTx() (SET LOCAL ROLE + pgkiln.* settings, NOTICEs to the debug log), savepoints
|
|
36
36
|
security.ts URL checksums, password policy, security headers (CSP nonce), throttling limits
|
|
37
|
+
ddl.ts Generate DDL and Schema Comparison: readSchema (catalog → one DDL per object, unqualified), ddlScript (order), compare, changeScript
|
|
38
|
+
static-version.ts the version of app.css, app.js, icons.svg and sw.js (?v= in their URLs, the service worker version): content hash, looked at again when a file changes
|
|
37
39
|
session.ts sessions (hashed tokens), activity log, login throttling
|
|
38
40
|
sso.ts OpenID Connect: discovery, sign-in flow, ID token checks, account linking
|
|
39
41
|
saml.ts SAML 2.0 sign-in (node-saml): AuthnRequest, response checks, SP metadata
|
|
@@ -68,6 +70,7 @@ src/
|
|
|
68
70
|
css.ts PageCss: data-dependent styles as classes in the page's nonce'd <style> (CSP)
|
|
69
71
|
metadata.ts types + loaders for apps and pages (components of excluded build options are left out here)
|
|
70
72
|
maptiles.ts map tile server URL, attribution and CSP origin
|
|
73
|
+
geocode.ts the installation's geocoder for address items (GEOCODER_URL, Nominatim search API)
|
|
71
74
|
mvt.ts Mapbox Vector Tile encoding for map layers served as tiles
|
|
72
75
|
yamltext.ts the text style of the directory export: a strict YAML subset, written and read
|
|
73
76
|
webclient.ts outgoing HTTP to web services: allow-list, address checks at connect time (SSRF), redirects, limits
|
|
@@ -80,6 +83,11 @@ src/
|
|
|
80
83
|
webrequests.ts web requests from SQL (meta.web_request, migration 052): runPending() after each sql page process
|
|
81
84
|
(same transaction), webRequestTick() for committed ones (automations scheduler), retention purge;
|
|
82
85
|
calls go through websources.ts call()/invoke() (allow-list, SSRF checks, credentials)
|
|
86
|
+
mail/ e-mail (migration 081): providers.ts (one deliver() per provider: SMTP through nodemailer with a password
|
|
87
|
+
or XOAUTH2, Microsoft Graph sendMail, Amazon SES v2 raw MIME with signV4, Resend, Postmark, SendGrid,
|
|
88
|
+
Mailgun, log; MailError says whether a retry may help; client-credentials token cache; settingsProblems),
|
|
89
|
+
mail.ts (sendWith(): service enabled, allowed sender, secret decrypted; mailTick(): claim with SKIP LOCKED,
|
|
90
|
+
send, retry after 1/4/16/64 minutes, purge after 30 days; the NOTIFY pgkiln_mail listener)
|
|
83
91
|
ai/ AI services (migration 060): types.ts (the provider interface, AiError kinds),
|
|
84
92
|
anthropic.ts (Claude through @anthropic-ai/sdk: streamed, effort, structured outputs via
|
|
85
93
|
output_config.format, refusal fallbacks, stop_reason checks, typed SDK errors), openai.ts (the openai
|
|
@@ -93,6 +101,8 @@ src/
|
|
|
93
101
|
icons.ts icon helper (sprite in public/icons.svg)
|
|
94
102
|
runtime/
|
|
95
103
|
routes.ts HTTP handlers: show, submit, dynamic actions, cascading lists, login
|
|
104
|
+
signin.ts the sign-in form (built-in page and the login region of an application's own login page), safeNext, passwordResetAvailable
|
|
105
|
+
password-reset.ts Forgot password: request (same answer either way), the link (token into the session), the new password
|
|
96
106
|
context.ts PageContext, dbg()/timed() debug helpers, bind values, substitutions, public error messages, writeOut (streamed responses with back pressure)
|
|
97
107
|
authz.ts authorization schemes, conditions, visibility (menu requests count as buttons)
|
|
98
108
|
engine.ts form fetch, validations, processes (conditions, execution chains, queueing background chains; web requests queued by an sql process are made right after it), application processes
|
|
@@ -103,7 +113,7 @@ src/
|
|
|
103
113
|
style (user choice, default), themeCss() (only hex values and constants reach the CSS)
|
|
104
114
|
template-options.ts template options: the fixed CSS class list per region and button, templateClasses() (unknown values ignored)
|
|
105
115
|
regions.ts region shell + chart (drill-down links, gauge settings)/cards/dynamic dispatch with row limits, lazy placeholder and cache, buttons (menu buttons, badges)
|
|
106
|
-
report.ts, report-views.ts (group by, pivot, chart), compute.ts (computed column expressions), grid.ts (aggregates, row actions, Actions menu, layoutFromForm), grid-layout.ts (column layouts: clean, arrange, per user), master-detail.ts (signed master row selection, details), facets.ts, items.ts
|
|
116
|
+
report.ts, report-views.ts (group by, pivot, chart), compute.ts (computed column expressions), grid.ts (aggregates, row actions, Actions menu, single row view, layoutFromForm), grid-layout.ts (column layouts: clean, arrange, per user), master-detail.ts (signed master row selection, details), facets.ts, items.ts
|
|
107
117
|
(report.ts: paging with row ranges and max_rows, keyset paging (keysetPlan, seekCondition, signed r<id>_k), pagerNav, streamed CSV/Excel downloads with a cursor;
|
|
108
118
|
items.ts: lovOptions, searchLov/lovLookup for popup LOVs, served by POST /a/:alias/:page/lov/:item/search in routes.ts)
|
|
109
119
|
region-cache.ts region caching (keys per scope, CSRF placeholder, invalidation on submit) and lazy regions (GET …/region/:id is in routes.ts)
|
|
@@ -142,6 +152,21 @@ src/
|
|
|
142
152
|
session, formatAnswer (escaped), send/clear routes
|
|
143
153
|
ai-filter.ts natural-language filters on a report ("ai_filter"): columns → structured output → checked → r<id>_f/q/s/d
|
|
144
154
|
pdf.ts report PDFs with report layouts (pdfkit); rows from a cursor in batches (tablePdf takes batches)
|
|
155
|
+
search.ts search region and search configurations (migration 080): words as ILIKE parameters, table/sql/list
|
|
156
|
+
sources as the app role in savepoints, marked results, configColumns/searchConfigProblems for the Advisor
|
|
157
|
+
url-region.ts URL region (migration 078): literal origins only (urlTarget), &ITEM. URL-encoded, sandbox and height,
|
|
158
|
+
frameOrigins() for the CSP's frame-src (security.ts)
|
|
159
|
+
ajax.ts Ajax callbacks (migration 085: POST …/ajax/:name, point "ajax" processes, x01 … x10 binds) and the
|
|
160
|
+
download dynamic action (POST …/da/:id/download)
|
|
161
|
+
availability.ts availabilityRefusal(): who may use an application (migration 083); loadContext, the login page
|
|
162
|
+
and REST modules ask it (PostgREST: meta.api_check)
|
|
163
|
+
feedback.ts users' feedback page and the navigation bar entry (migration 084)
|
|
164
|
+
data-items.ts display image and address items (migration 086): imageUrl (https origins into the CSP's img-src),
|
|
165
|
+
GET …/image/:item (a BLOB from the item's query), POST …/geocode/:item, and items kept per user
|
|
166
|
+
(loadPreferences / savePreferences through meta.get_preference / set_preference)
|
|
167
|
+
data-export.ts the download process's {"export": …}: meta.data_export for csv/json/xml/html, Excel and PDF here
|
|
168
|
+
devbar.ts developer toolbar and session state viewer (migration 079): the pgkiln_devbar token of a builder
|
|
169
|
+
session (ensureDevbar/devbarOf), the toolbar markup, the viewer, clear and per-session debug routes
|
|
145
170
|
builder/
|
|
146
171
|
components.ts property spec of every component (drives the property editor)
|
|
147
172
|
ui.ts IDE shell (icon rail, toolbar, breadcrumb, status bar), builder theme, form helpers, CSRF check, app tabs
|
|
@@ -158,6 +183,14 @@ src/
|
|
|
158
183
|
appwizard.ts Create → From pasted data (kept as a temp file, then the From a file steps) and From existing tables
|
|
159
184
|
(a schema's tables/views → report+form or report pages, navigation, dashboard)
|
|
160
185
|
forms.ts generic component property form (lookups, render, save)
|
|
186
|
+
backups.ts Application → Backups (migration 082): list, back up now, download, restore (replaceApp), keep N;
|
|
187
|
+
backupTick() for the scheduler's daily backups of changed applications
|
|
188
|
+
availability.ts Application → Availability (migration 083): status, message, users, build status (Run only is
|
|
189
|
+
enforced by locks.ts blockingLock); availabilityBadge() in the application header
|
|
190
|
+
feedback.ts Application → Feedback (migration 084): the setting, the list with filters, status and answers
|
|
191
|
+
region-help.ts the Attributes (JSON) help per region type, with links to the guide
|
|
192
|
+
mail.ts Workspace utilities → Mail services (administrators: providers, write-only secret, allowed senders,
|
|
193
|
+
applications with From and daily limit, test) and Applications → Activity → Mail (retry, cancel)
|
|
161
194
|
shared.ts Shared Components and access control
|
|
162
195
|
designer.ts page designer: component tree (with computations and branches), layout canvas and gallery, property editor, toolbar
|
|
163
196
|
arrange.ts page designer layout changes: move, column span, create from the gallery, undo / redo
|
|
@@ -177,6 +210,8 @@ src/
|
|
|
177
210
|
region-settings.ts page designer: settings forms for grid, chart (gauge, drill-down), cards, calendar (views, create, drag and drop), facets, smart filters, display selector, list
|
|
178
211
|
search.ts app search, "where used" (appEntries, search, whereUsed, usedInPanel)
|
|
179
212
|
advisor.ts Advisor: EXPLAIN every SQL fragment, reference checks, plpgsql_check
|
|
213
|
+
ddl-tools.ts SQL Workshop → DDL and Compare (src/ddl.ts)
|
|
214
|
+
login-page.ts Application settings → Login page and Forgot password; Create a login page (page 9999)
|
|
180
215
|
top-sql.ts Top SQL per app role from pg_stat_statements
|
|
181
216
|
diagnostics.ts Activity → Debug messages (level, list, one request's entries, purge); Workspace utilities →
|
|
182
217
|
Installation (version, install/upgrade runs, applied and missing migrations; administrators)
|
|
@@ -377,5 +412,8 @@ browser enhancements at the end of `public/app.js` (the item must work without t
|
|
|
377
412
|
1. Update `CHANGELOG.md` and the version in `package.json`.
|
|
378
413
|
2. Merge to `main`; CI must be green (every job). The `main` branch should be protected on
|
|
379
414
|
GitHub (Settings → Branches → rule for `main`: require the CI checks).
|
|
380
|
-
3. Tag: `git tag -a vX.Y.Z -m vX.Y.Z && git push origin vX.Y.Z`.
|
|
415
|
+
3. Tag: `git tag -a vX.Y.Z -m vX.Y.Z && git push origin vX.Y.Z`. The tag publishes the Docker image
|
|
416
|
+
(`docker-publish.yml`: `pgkiln/pgkiln` and `ghcr.io/pgkiln/pgkiln`, amd64 and arm64) and the npm package
|
|
417
|
+
(`npm-publish.yml`, trusted publishing with provenance; it refuses a tag that doesn't match
|
|
418
|
+
`package.json`). Then `gh release create vX.Y.Z` with the changelog section as notes.
|
|
381
419
|
4. Add the new tag to the `upgrade` job's matrix in `.github/workflows/ci.yml` (see above).
|
|
@@ -98,6 +98,21 @@ raise exception '%', meta.message('LEAVE_OVERLAP', to_char(p_start, 'DD-MM-YYYY'
|
|
|
98
98
|
|
|
99
99
|
The HR sample's dashboard uses them for its key figures (`examples/hr/hr_04_i18n.sql`).
|
|
100
100
|
|
|
101
|
+
### Dynamic translations
|
|
102
|
+
|
|
103
|
+
For **values from tables** (department names, statuses, list of values entries), use dynamic
|
|
104
|
+
translations (APEX: *Dynamic Translations*, `APEX_LANG.LANG`): **Shared Components → Dynamic
|
|
105
|
+
translations** holds a language, the text as stored and its translation, and `meta.lang` returns
|
|
106
|
+
the text in the session's language:
|
|
107
|
+
|
|
108
|
+
```sql
|
|
109
|
+
select meta.lang(dname) as d, deptno as r from hr.dept order by 1 -- a list of values
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
An exact language match wins, then the base language (`nl-BE` → `nl`), else the text as it is.
|
|
113
|
+
`meta.lang(text, 'de')` asks for another language. Dynamic translations are exported with the
|
|
114
|
+
application (`globalization/dynamic-translations.yaml` in an application's files).
|
|
115
|
+
|
|
101
116
|
### Date formats
|
|
102
117
|
|
|
103
118
|
Dates and timestamps in reports, grids and display items are formatted with a **mask**:
|
|
@@ -109,7 +109,7 @@ Audit triggers record such changes as made by `nobody`, which is what you want f
|
|
|
109
109
|
| **[pgmq](https://github.com/pgmq/pgmq)** | A message queue in Postgres for background work handled by a worker service | Like AWS SQS, transactional |
|
|
110
110
|
| **pg_partman** | Partition big append-only tables such as `meta.activity_log` by month | Only needed at high volume |
|
|
111
111
|
| **[plv8](https://github.com/plv8/plv8)** | Server-side JavaScript functions | Like APEX's MLE JavaScript. Maintained upstream, but some hosts (e.g. Supabase on PostgreSQL 17) no longer offer it, so check before depending on it |
|
|
112
|
-
| **[pg_smtp_client](https://github.com/brianpursley/pg_smtp_client)** | Sending e-mail from SQL,
|
|
112
|
+
| **[pg_smtp_client](https://github.com/brianpursley/pg_smtp_client)** | Sending e-mail synchronously from SQL, outside an application | pgkiln itself sends mail queued with `meta.send_mail` after the commit, through SMTP, Microsoft Graph, Amazon SES or an HTTP API ([chapter 6](06-processing.md#sending-e-mail)); this extension talks SMTP from the database itself |
|
|
113
113
|
|
|
114
114
|
## Security notes
|
|
115
115
|
|
package/docs/guide/17-mobile.md
CHANGED
|
@@ -27,7 +27,9 @@ Everything is per application, under its own address: `/a/<alias>/manifest.webma
|
|
|
27
27
|
## Offline
|
|
28
28
|
|
|
29
29
|
- **The app shell** (pgkiln's CSS, script and icons) and an offline page are stored when the app is
|
|
30
|
-
installed, so the app always opens.
|
|
30
|
+
installed, so the app always opens. Pages ask for these files with a version in the URL
|
|
31
|
+
(`/static/app.css?v=…`, from the files' contents); when pgkiln is upgraded, or a file changes,
|
|
32
|
+
devices fetch the new one at the next page load.
|
|
31
33
|
- **Pages** are fetched from the network first. Without a connection, a page the user opened before
|
|
32
34
|
comes from the device (when *Keep visited pages* is on); otherwise the offline page appears,
|
|
33
35
|
listing the pages that are on the device.
|
package/docs/guide/18-cli.md
CHANGED
|
@@ -35,8 +35,10 @@ any (see [what is not exported](03-builder.md#export-format)).
|
|
|
35
35
|
| `pgkiln migrate [--example <name>]` | Applies the migrations that were not applied yet, then optionally `examples/<name>/` (same as `npm run db:migrate` / `npm run example:hr`; `--root` and `--seed` as in `scripts/migrate.ts`) |
|
|
36
36
|
| `pgkiln apps [--json]` | Lists the applications: alias, number of pages, name |
|
|
37
37
|
| `pgkiln export <alias> [--format json\|dir\|text] [--out <path>]` | `json` (default): the `pgkiln/2` document with sorted keys, to `--out` or standard output. `dir`: a directory (default `./<alias>`), see below. `text`: the same directory in YAML with the code inline ([text files](#text-files-apexlang)) |
|
|
38
|
-
| `pgkiln import <path> [--alias <alias>] [--replace]` | Imports a JSON export, an application directory or a `.zip` of one. `--alias` gives the copy another alias. `--replace` updates the application with that alias in place |
|
|
38
|
+
| `pgkiln import <path> [--alias <alias>] [--replace] [--check [--force] \| --dry-run]` | Imports a JSON export, an application directory or a `.zip` of one. `--alias` gives the copy another alias. `--replace` updates the application with that alias in place (after a [backup](03-builder.md#backups)). `--check` checks the application before it commits, `--dry-run` only checks ([below](#checking-before-an-import)) |
|
|
39
39
|
| `pgkiln diff <alias> <path> [--name-only \| --quiet]` | What differs between the application in the database and a directory (or JSON file, or zip) |
|
|
40
|
+
| `pgkiln ddl <schema> [--no-comments] [--no-grants] [--out <file>]` | The DDL of a schema's objects, in an order that runs (as SQL Workshop → DDL, [chapter 3](03-builder.md#sql-workshop)) |
|
|
41
|
+
| `pgkiln compare <source> <target> [--script]` | Compares two schemas: `+` only in the source, `-` only in the target, `~` different; exit code 1 when they differ. `--script` writes the script that makes the target like the source (drops commented out); nothing is run |
|
|
40
42
|
| `pgkiln mcp` | An MCP server on standard input/output for AI coding agents, see [chapter 20](20-ai-agents.md) |
|
|
41
43
|
| `pgkiln plugin build <dir> [-o <file>]` | Builds a plug-in file (`pgkiln-plugin/2`) from a source directory, see [plug-ins](04-pages-and-regions.md#plug-ins-with-their-own-code) |
|
|
42
44
|
| `pgkiln plugin install <file\|dir> --app <alias> [--replace]` | Adds a plug-in (file or source directory) to an application; its install SQL is not run |
|
|
@@ -187,6 +189,9 @@ the files any more are removed. What belongs to this installation stays:
|
|
|
187
189
|
[chapter 19](19-rest-data-sources.md#web-credentials)).
|
|
188
190
|
- the **style variant each user chose** ([chapter 14](14-globalization.md#style-variants-theme-roller));
|
|
189
191
|
a style that the file no longer has falls back to the default.
|
|
192
|
+
- its **availability and build status** ([chapter 3](03-builder.md#availability-and-build-status)): exports
|
|
193
|
+
leave them out, so a deployment doesn't open an application you closed, or close one;
|
|
194
|
+
- its **backups**: the version the import replaces is the newest one ([chapter 3](03-builder.md#backups)).
|
|
190
195
|
|
|
191
196
|
It all happens in one transaction; any error leaves the application unchanged. Without an
|
|
192
197
|
application with that alias, `--replace` simply imports, so the same command deploys the first
|
|
@@ -201,6 +206,32 @@ pgkiln diff hr apps/hr # on the target: what would cha
|
|
|
201
206
|
pgkiln import apps/hr --replace # deploy
|
|
202
207
|
```
|
|
203
208
|
|
|
209
|
+
### Checking before an import
|
|
210
|
+
|
|
211
|
+
An import doesn't need the application's tables, columns and functions to exist (neither does APEX's):
|
|
212
|
+
a page whose SQL names a missing table imports fine and fails when it runs. `--check` asks the
|
|
213
|
+
[Advisor](03-builder.md#advisor) first, inside the import's transaction, before anything is committed:
|
|
214
|
+
every SQL fragment is planned as the application's database role (unknown tables, columns,
|
|
215
|
+
functions and types, missing grants), PL/pgSQL blocks are compiled, and references to pages,
|
|
216
|
+
items, lists of values, schemes and search configurations are checked.
|
|
217
|
+
|
|
218
|
+
```sh
|
|
219
|
+
pgkiln import apps/hr --replace --check # errors: nothing changes, exit code 1
|
|
220
|
+
pgkiln import apps/hr --replace --dry-run # only check (exit code 0 without errors)
|
|
221
|
+
pgkiln import apps/hr --replace --check --force # report, import anyway
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
```text
|
|
225
|
+
ERROR page 2, region Employees (Source): relation "hr.employee" does not exist
|
|
226
|
+
WARNING page 3, item P3_SAL (Default value): …
|
|
227
|
+
Check: 1 error(s), 1 warning(s), 0 note(s).
|
|
228
|
+
Not imported: the check found 1 error(s). Fix them, or use --force.
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Deploy the database changes (your migration scripts) before the application, so the check sees
|
|
232
|
+
the tables the new version needs. The MCP server's `import_app` checks by default; the builder's
|
|
233
|
+
Import page has the check ticked.
|
|
234
|
+
|
|
204
235
|
`export --format dir` into an existing directory removes the files of deleted components and
|
|
205
236
|
leaves dot files (`.git`, `.gitattributes`) alone; it refuses a non-empty directory without a
|
|
206
237
|
`pgkiln.json`, so a typo can't empty the wrong folder.
|
|
@@ -48,12 +48,14 @@ directory the agent started the server in (the project).
|
|
|
48
48
|
| `get_page` | every component of one page as YAML, SQL inline | no |
|
|
49
49
|
| `read_app_files` | component files by path or prefix (`shared/lovs/`, `navigation.yaml`, …) | no |
|
|
50
50
|
| `describe_schema` | schemas with tables, views and functions; or one table with columns, constraints, indexes, RLS policies and triggers | no |
|
|
51
|
+
| `generate_ddl` | the DDL of a schema's objects, in an order that runs: a start for a migration script | no |
|
|
52
|
+
| `compare_schemas` | the differences between two schemas and a script that makes the target like the source (not run) | no |
|
|
51
53
|
| `run_query` | one SQL statement, read-only, rows as JSON | no |
|
|
52
54
|
| `search_docs` | searches this user guide, the parity matrix and SECURITY.md | no |
|
|
53
55
|
| `recent_errors` | the latest errors and refused requests from the activity log | no |
|
|
54
56
|
| `export_app` | writes an application to a directory (default `apps/<alias>`, format `text`) | files on disk |
|
|
55
57
|
| `diff_app` | what an import of a directory would change (A/D/M with unified diffs); also reports files it can't read | no |
|
|
56
|
-
| `import_app` | imports a directory, zip or JSON file; `replace: true` updates the application in place | the database |
|
|
58
|
+
| `import_app` | imports a directory, zip or JSON file; `replace: true` updates the application in place. It checks the application first (the Advisor, in the import's transaction): errors refuse the import and are listed; `force: true` imports anyway, `dry_run: true` only checks | the database |
|
|
57
59
|
|
|
58
60
|
The files are the [directory format](18-cli.md#the-directory-format) of the command line, in the
|
|
59
61
|
[text style](18-cli.md#text-files-apexlang): one YAML file per component, with SQL, PL/pgSQL and
|
|
@@ -67,7 +69,8 @@ users, sessions and saved reports kept, supporting objects and plug-in install S
|
|
|
67
69
|
`describe_schema` for the tables; `search_docs` before guessing a property or region type.
|
|
68
70
|
2. `export_app` to `apps/<alias>`, then edit the YAML files there.
|
|
69
71
|
3. `diff_app`, so you (and it) see exactly what will change.
|
|
70
|
-
4. `import_app` with `replace: true`.
|
|
72
|
+
4. `import_app` with `replace: true`. When it names errors (a missing column, a typo in SQL), the agent fixes
|
|
73
|
+
the files and imports again; nothing was changed. Open the page in the browser to check.
|
|
71
74
|
|
|
72
75
|
Tables, views and functions are ordinary PostgreSQL objects: an agent changes them in your
|
|
73
76
|
migration scripts, not in the application files (`run_query` can't change them anyway).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- HR example, part 50: search configurations and the search region
|
|
3
|
+
-- (migration 080; docs/guide/04-pages-and-regions.md, search region)
|
|
4
|
+
--
|
|
5
|
+
-- Page 42 "Search": one field searches three sources as you type:
|
|
6
|
+
-- - EMPLOYEES: a query (employees with their department), each result
|
|
7
|
+
-- opens the employee's form (page 3);
|
|
8
|
+
-- - DEPARTMENTS: the table hr.dept, each result opens page 5;
|
|
9
|
+
-- - SHORTCUTS: the entries of the list HR_SHORTCUTS the user may see.
|
|
10
|
+
-- =====================================================================
|
|
11
|
+
|
|
12
|
+
insert into meta.search_config (app_id, name, label, seq, source_type, source, searchable_columns,
|
|
13
|
+
title_column, subtitle_column, badge_column, icon, link)
|
|
14
|
+
select a.id, c.name, c.label, c.seq, c.source_type, c.source, c.searchable, c.title, c.subtitle, c.badge, c.icon, c.link::jsonb
|
|
15
|
+
from meta.app a, (values
|
|
16
|
+
('EMPLOYEES', 'Employees', 10, 'sql',
|
|
17
|
+
$q$select e.empno, initcap(e.ename) as name, initcap(e.job) as job, initcap(d.dname) as department
|
|
18
|
+
from hr.emp e left join hr.dept d on d.deptno = e.deptno$q$,
|
|
19
|
+
'{name,job,department}'::text[], 'name', 'job', 'department', 'user', '{"page": 3, "items": {"P3_EMPNO": "#EMPNO#"}}'),
|
|
20
|
+
('DEPARTMENTS', 'Departments', 20, 'table', 'hr.dept', '{dname,loc}'::text[], 'dname', 'loc', null, 'building',
|
|
21
|
+
'{"page": 5, "items": {"P5_DEPTNO": "#DEPTNO#"}}'),
|
|
22
|
+
('SHORTCUTS', 'Shortcuts', 30, 'list', 'HR_SHORTCUTS', '{}'::text[], null, null, null, 'link', null)
|
|
23
|
+
) as c (name, label, seq, source_type, source, searchable, title, subtitle, badge, icon, link)
|
|
24
|
+
where a.alias = 'hr';
|
|
25
|
+
|
|
26
|
+
insert into meta.page (app_id, page_no, name, title, parent_page)
|
|
27
|
+
select id, 42, 'Search', 'Search', 1 from meta.app where alias = 'hr';
|
|
28
|
+
|
|
29
|
+
insert into meta.region (page_id, seq, title, type, columns, template, config)
|
|
30
|
+
select p.id, 10, 'Search', 'search', 12, 'standard',
|
|
31
|
+
'{"configs": ["EMPLOYEES", "DEPARTMENTS", "SHORTCUTS"], "placeholder": "Search employees, departments and shortcuts…"}'
|
|
32
|
+
from meta.page p join meta.app a on a.id = p.app_id where a.alias = 'hr' and p.page_no = 42;
|
|
33
|
+
|
|
34
|
+
insert into meta.nav_entry (app_id, seq, label, icon, target_page)
|
|
35
|
+
select id, 2, 'Search', 'search', 42 from meta.app where alias = 'hr';
|
|
36
|
+
|
|
37
|
+
insert into meta.translation (app_id, language, source, target)
|
|
38
|
+
select a.id, 'nl', t.source, t.target
|
|
39
|
+
from meta.app a, (values
|
|
40
|
+
('Search', 'Zoeken'),
|
|
41
|
+
('Employees', 'Medewerkers'),
|
|
42
|
+
('Departments', 'Afdelingen'),
|
|
43
|
+
('Shortcuts', 'Snelkoppelingen'),
|
|
44
|
+
('Search employees, departments and shortcuts…', 'Zoek medewerkers, afdelingen en snelkoppelingen…')
|
|
45
|
+
) as t (source, target)
|
|
46
|
+
where a.alias = 'hr'
|
|
47
|
+
on conflict do nothing;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- HR example, part 51: e-mail (migration 081; docs/guide/06-processing.md,
|
|
3
|
+
-- "Sending e-mail")
|
|
4
|
+
--
|
|
5
|
+
-- The example sends through a mail service of provider "log": the server
|
|
6
|
+
-- writes each mail to its log and sends nothing, so the example works on
|
|
7
|
+
-- any installation. An administrator points HR at a real service under
|
|
8
|
+
-- Workspace utilities → Mail services (SMTP, Microsoft 365, Amazon SES,
|
|
9
|
+
-- Resend, Postmark, SendGrid or Mailgun).
|
|
10
|
+
--
|
|
11
|
+
-- The template LEAVE_DECIDED tells an employee that a leave request was
|
|
12
|
+
-- approved or rejected; a trigger queues it from the running application
|
|
13
|
+
-- when the status changes (the decision's transaction: a rollback sends
|
|
14
|
+
-- nothing). A mail that can't be queued never stops the decision.
|
|
15
|
+
-- =====================================================================
|
|
16
|
+
|
|
17
|
+
insert into meta.mail_service (name, description, provider, senders, settings)
|
|
18
|
+
values ('DEVELOPMENT_LOG', 'Writes mail to the server log; sends nothing', 'log', '{@example.com}', '{"default_from": "hr@example.com"}')
|
|
19
|
+
on conflict (name) do nothing;
|
|
20
|
+
|
|
21
|
+
insert into meta.app_mail_service (app_id, service_id, from_address, max_per_day)
|
|
22
|
+
select a.id, s.id, 'hr@example.com', 500
|
|
23
|
+
from meta.app a, meta.mail_service s
|
|
24
|
+
where a.alias = 'hr' and s.name = 'DEVELOPMENT_LOG'
|
|
25
|
+
on conflict (app_id) do nothing;
|
|
26
|
+
|
|
27
|
+
insert into meta.email_template (app_id, name, subject, html_body, text_body)
|
|
28
|
+
select id, 'LEAVE_DECIDED', 'Your leave request was #STATUS#',
|
|
29
|
+
$h$<p>Dear #NAME#,</p>
|
|
30
|
+
<p>Your leave request from <b>#START#</b> to <b>#END#</b> was <b>#STATUS#</b> by #DECIDED_BY#.</p>
|
|
31
|
+
<p>Kind regards,<br>HR</p>$h$,
|
|
32
|
+
$t$Dear #NAME#,
|
|
33
|
+
|
|
34
|
+
Your leave request from #START# to #END# was #STATUS# by #DECIDED_BY#.
|
|
35
|
+
|
|
36
|
+
Kind regards,
|
|
37
|
+
HR$t$
|
|
38
|
+
from meta.app where alias = 'hr';
|
|
39
|
+
|
|
40
|
+
create function hr.mail_leave_decided() returns trigger
|
|
41
|
+
language plpgsql security definer set search_path = hr, pg_catalog as $$
|
|
42
|
+
declare
|
|
43
|
+
v_email text;
|
|
44
|
+
v_name text;
|
|
45
|
+
begin
|
|
46
|
+
if new.status not in ('APPROVED', 'REJECTED') or meta.app_id() is null then
|
|
47
|
+
return new;
|
|
48
|
+
end if;
|
|
49
|
+
select a.email, initcap(e.ename) into v_email, v_name
|
|
50
|
+
from hr.emp e join meta.account a on lower(a.username) = lower(e.username)
|
|
51
|
+
where e.empno = new.empno;
|
|
52
|
+
if v_email is null then
|
|
53
|
+
return new;
|
|
54
|
+
end if;
|
|
55
|
+
perform meta.send_mail_template('LEAVE_DECIDED', jsonb_build_object(
|
|
56
|
+
'NAME', v_name,
|
|
57
|
+
'STATUS', lower(new.status),
|
|
58
|
+
'START', to_char(new.start_date, 'DD Mon YYYY'),
|
|
59
|
+
'END', to_char(new.end_date, 'DD Mon YYYY'),
|
|
60
|
+
'DECIDED_BY', coalesce(new.decided_by, 'HR')), v_email);
|
|
61
|
+
return new;
|
|
62
|
+
exception when others then
|
|
63
|
+
raise warning 'hr.mail_leave_decided: %', sqlerrm;
|
|
64
|
+
return new;
|
|
65
|
+
end
|
|
66
|
+
$$;
|
|
67
|
+
|
|
68
|
+
create trigger leave_decided_mail after update of status on hr.leave_request
|
|
69
|
+
for each row when (old.status is distinct from new.status)
|
|
70
|
+
execute function hr.mail_leave_decided();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- HR example, part 52: feedback (migration 084; docs/guide/03-builder.md,
|
|
3
|
+
-- "Feedback")
|
|
4
|
+
--
|
|
5
|
+
-- Users send feedback about a page from the navigation bar's Feedback link;
|
|
6
|
+
-- developers answer under Application → Activity → Feedback. One answered
|
|
7
|
+
-- example from the user king.
|
|
8
|
+
-- =====================================================================
|
|
9
|
+
|
|
10
|
+
update meta.app set feedback = true where alias = 'hr';
|
|
11
|
+
|
|
12
|
+
insert into meta.feedback (app_id, page_no, username, kind, rating, body, status, response, responded_by, responded_at)
|
|
13
|
+
select id, 2, 'king', 'idea', 4, 'Could the employee list show the hire date as well?', 'done',
|
|
14
|
+
'Added in the column list: open Actions → Columns and show "Hiredate".', 'admin', now()
|
|
15
|
+
from meta.app where alias = 'hr';
|