Shared plumbing for the admin HTTP layer.
Leaf namespace requiring no handler or route namespaces. Provides the
error mappings, query/form parsing, and handler helpers used by the handler
namespaces and the route definitions in wagoe.admin.shell.http.
Shared plumbing for the admin HTTP layer. Leaf namespace requiring no handler or route namespaces. Provides the error mappings, query/form parsing, and handler helpers used by the handler namespaces and the route definitions in `wagoe.admin.shell.http`.
(attempt f)Call f: [result nil], or [nil e] when it throws a refusal or a rejection.
Call `f`: [result nil], or [nil e] when it throws a refusal or a rejection.
(build-entity-detail-opts admin-service
schema-provider
config
entity-name
entity-config
record
request)Builds opts for entity-detail-page and the surrounding admin-layout. Shared between entity-detail-handler and update-entity-handler.
Returns a map with: :entity-configs - map of entity-name -> entity-config :page-opts - opts map for entity-detail-page (related-records, return-to, parent-context, sibling-nav, display, field-options, workflow)
Builds opts for entity-detail-page and the surrounding admin-layout.
Shared between entity-detail-handler and update-entity-handler.
Returns a map with:
:entity-configs - map of entity-name -> entity-config
:page-opts - opts map for entity-detail-page
(related-records, return-to, parent-context, sibling-nav,
display, field-options, workflow)The :type -> status table admin errors are answered with. Admin throws the platform's own types, so the platform's status and this one agree (BOU-586).
The :type -> status table admin errors are answered with. Admin throws the platform's own types, so the platform's status and this one agree (BOU-586).
(conflict-errors e)Field errors for a key over several columns the database refused: each of its fields marked, since no one of them is wrong alone (BOU-712).
Field errors for a key over several columns the database refused: each of its fields marked, since no one of them is wrong alone (BOU-712).
(create-config-error-response request
config
schema-provider
user
entity-name
entity-config)The admin page saying why entity-name cannot be created, or nil when it
can. A create form that could never be saved is refused up front (BOU-494).
A 500: the config is at fault, not the request.
The admin page saying why `entity-name` cannot be created, or nil when it can. A create form that could never be saved is refused up front (BOU-494). A 500: the config is at fault, not the request.
(create-disabled-response request user entity-name entity-config)The admin 403 page for an entity with :permissions {:create false}, or
nil when create is allowed. Names :create-hint when the config has one
(BOU-534).
The admin 403 page for an entity with `:permissions {:create false}`, or
nil when create is allowed. Names `:create-hint` when the config has one
(BOU-534).The zone timestamps are shown and entered in when neither the browser nor
:time-zone in :wagoe/settings names one. A presentation default only:
storage is zone-aware and the JVM runs in UTC (BOU-431), so this changes
what people see, never what is stored.
The zone timestamps are shown and entered in when neither the browser nor `:time-zone` in :wagoe/settings names one. A presentation default only: storage is zone-aware and the JVM runs in UTC (BOU-431), so this changes what people see, never what is stored.
(display-options config request)Zones, locale and date patterns for rendering stored timestamps in the admin UI.
Two zones (BOU-523). :server-zone-id is the JVM zone — the one the
database reads a zone-less timestamp in, so the one such a value is read
back in. :zone-id is the zone timestamps are shown and entered in: the
browser's, from the wagoe_tz cookie; else the application's configured
:time-zone; else default-time-zone, Europe/Amsterdam. Not the server's:
that is UTC by design (BOU-431) and says nothing about the people using it.
Read here because wagoe.admin.core.ui.base may not: check:fcis bans
ZoneId/systemDefault in a core namespace. The patterns come from the
application's :wagoe/settings (BOU-382).
Zones, locale and date patterns for rendering stored timestamps in the admin UI. Two zones (BOU-523). `:server-zone-id` is the JVM zone — the one the database reads a zone-less timestamp in, so the one such a value is read back in. `:zone-id` is the zone timestamps are shown and entered in: the browser's, from the `wagoe_tz` cookie; else the application's configured `:time-zone`; else `default-time-zone`, Europe/Amsterdam. Not the server's: that is UTC by design (BOU-431) and says nothing about the people using it. Read here because `wagoe.admin.core.ui.base` may not: check:fcis bans `ZoneId/systemDefault` in a core namespace. The patterns come from the application's `:wagoe/settings` (BOU-382).
(display-with-overview admin-service config request entity-name options records)The display options, with the overview of records.
The display options, with the overview of `records`.
(entity-label admin-service entity-name entity-cfg id)What row id of entity-name is called: its label, or the id when it
cannot be read.
What row `id` of `entity-name` is called: its label, or the id when it cannot be read.
(field-options admin-service schema-provider config entity-name field values)The choices field of entity-name is rendered with, as
foreign-key-options finds them, or nil when it is neither a foreign key
nor a timezone. Reads only the parent of that field.
The choices `field` of `entity-name` is rendered with, as `foreign-key-options` finds them, or nil when it is neither a foreign key nor a timezone. Reads only the parent of that field.
(filter-field-options admin-service
schema-provider
config
user
entity-name
filters)The choices of each filterable foreign key of entity-name, as
foreign-key-options finds them, for the parents user may view: a
filter lists them as the form does. filters are the current ones, whose
values the comboboxes show by label.
The choices of each filterable foreign key of `entity-name`, as `foreign-key-options` finds them, for the parents `user` may view: a filter lists them as the form does. `filters` are the current ones, whose values the comboboxes show by label.
(foreign-key-options admin-service config entity-configs entity-name values)(foreign-key-options admin-service
config
entity-configs
entity-name
fields
values)The choices for each editable foreign key of entity-name, and the zone
combobox of each timezone field (timezone-field?). A foreign key offers the parent's
rows as [id label], where the label is the parent's :title-field — what
its relation cells show. Only when every parent fits in the maximum page
size: a select capped there would silently offer a subset, so past it the
key is a combobox, {:url :label}, searching the label — or a text input
when the label is not text. An optional key can be left
empty. fields limits it to those keys; the form's editable fields by
default.
The choices for each editable foreign key of `entity-name`, and the zone
combobox of each timezone field (`timezone-field?`). A foreign key offers the parent's
rows as [id label], where the label is the parent's `:title-field` — what
its relation cells show. Only when every parent fits in the maximum page
size: a select capped there would silently offer a subset, so past it the
key is a combobox, {:url :label}, searching the label — or a text input
when the label is not text. An optional key can be left
empty. `fields` limits it to those keys; the form's editable fields by
default.(foreign-key-prefill entity-name entity-configs query-params)Starting values for a create form of entity-name, from the query string
a parent's "New" link builds. Only a field some entity names as the
:foreign-key of a has-many on entity-name is taken, and only when its
value is a UUID.
Starting values for a create form of `entity-name`, from the query string a parent's "New" link builds. Only a field some entity names as the `:foreign-key` of a has-many on `entity-name` is taken, and only when its value is a UUID.
(form-zone-options config request params)The zones to parse a submitted form's :instant fields in, and params
without the __zone field that carried them.
The form says which zone it was rendered in, and parsing uses exactly that:
deriving it from the cookie again would shift every value on the first
visit, when the page was rendered before the cookie existed. A missing or
unknown __zone falls back to the same resolution the page used.
The zones to parse a submitted form's :instant fields in, and `params` without the `__zone` field that carried them. The form says which zone it was rendered in, and parsing uses exactly that: deriving it from the cookie again would shift every value on the first visit, when the page was rendered before the cookie existed. A missing or unknown `__zone` falls back to the same resolution the page used.
(get-current-user request)Extract authenticated user from request.
The authentication middleware sets [:user {...}] with the full user entity (including :id, :role, :email, :name, etc.).
Args: request: Ring request map
Returns: Full user entity map, or nil if not authenticated
Extract authenticated user from request.
The authentication middleware sets [:user {...}] with the full user entity
(including :id, :role, :email, :name, etc.).
Args:
request: Ring request map
Returns:
Full user entity map, or nil if not authenticated(get-entity-id request)The record id in the path, a UUID; :not-found when it is not one.
The record id in the path, a UUID; :not-found when it is not one.
(get-entity-name request)Extract entity name from path parameters.
Args: request: Ring request map
Returns: Entity name as keyword
Extract entity name from path parameters. Args: request: Ring request map Returns: Entity name as keyword
(html-response request html)Create HTML response with standard headers, resolving [:t ...] i18n markers.
Args: request: Ring request map (used to extract :i18n/t translation function) html: Hiccup data structure or HTML string
Returns: Ring response map
Create HTML response with standard headers, resolving [:t ...] i18n markers. Args: request: Ring request map (used to extract :i18n/t translation function) html: Hiccup data structure or HTML string Returns: Ring response map
(htmx-fragment-response request html)(htmx-fragment-response request html oob-fragments)Create HTMX fragment response, resolving [:t ...] i18n markers.
Args: request: Ring request map html: Hiccup data or HTML string oob-fragments: (optional) elements swapped out of band, by id — a badge counter or a toast beside the main fragment
Returns: Ring response map with HTMX headers
Create HTMX fragment response, resolving [:t ...] i18n markers.
Args:
request: Ring request map
html: Hiccup data or HTML string
oob-fragments: (optional) elements swapped out of band, by id — a badge
counter or a toast beside the main fragment
Returns:
Ring response map with HTMX headers(isolated part f)(f), or nil when it throws: logged, and the page shows the part as missing
instead of failing. In a savepoint, so a failed query in a tenant request's
transaction does not abort the parts after it on PostgreSQL.
`(f)`, or nil when it throws: logged, and the page shows the part as missing instead of failing. In a savepoint, so a failed query in a tenant request's transaction does not abort the parts after it on PostgreSQL.
(link-fields admin-service config entity-configs entity-name record-id links)How each many-to-many relation of entity-name is edited:
{key {:rel :chosen [[id label] ...] :options [[id label] ...] | :url}}.
Checkboxes (:options) while the linked entity has at most
checkbox-limit records, else a multiple combobox. The chosen ids come
from links ({key [id ...]}, a form shown again) for a key it has, else
from the record record-id. A linked entity whose label is not text
cannot be searched: up to the maximum page size it is checkboxes too,
past it left off the form. So is one that cannot be read. Nil for a
service without links.
How each many-to-many relation of `entity-name` is edited:
{key {:rel :chosen [[id label] ...] :options [[id label] ...] | :url}}.
Checkboxes (`:options`) while the linked entity has at most
`checkbox-limit` records, else a multiple combobox. The chosen ids come
from `links` ({key [id ...]}, a form shown again) for a key it has, else
from the record `record-id`. A linked entity whose label is not text
cannot be searched: up to the maximum page size it is checkboxes too,
past it left off the form. So is one that cannot be read. Nil for a
service without links.(nested-relationships schema-provider entity-config)The has-many entries entity-config is created with, as
forms/nested-relationships finds them (BOU-570).
The has-many entries `entity-config` is created with, as `forms/nested-relationships` finds them (BOU-570).
(options-url entity-name field)Where the combobox of foreign key field of entity-name reads its options.
Where the combobox of foreign key `field` of `entity-name` reads its options.
(overview admin-service entity-name options records)What the cells of records need (ADR-040), or nil when the service does
not provide it.
What the cells of `records` need (ADR-040), or nil when the service does not provide it.
(parent-trail admin-service
schema-provider
entity-name
entity-config
{:keys [filters return-to]})The record a page of entity-name hangs under, for its breadcrumbs: the
one return-to opens, or the one an eq filter on a belongs-to names —
in filters, or in the list return-to points at. {:label :href :record
:entity-config :title-href :list-href}, :list-href being this entity's list
of that record's children; nil when there is none.
The record a page of `entity-name` hangs under, for its breadcrumbs: the
one `return-to` opens, or the one an `eq` filter on a belongs-to names —
in `filters`, or in the list `return-to` points at. {:label :href :record
:entity-config :title-href :list-href}, :list-href being this entity's list
of that record's children; nil when there is none.(parse-form-params params entity-config)(parse-form-params params
entity-config
{:keys [input-zone server-zone offsets]})Parse form parameters into entity data map.
Converts string form values to appropriate types based on field config.
Args: params: Ring form-params map (all string values) entity-config: Entity configuration with field metadata
Returns: Entity data map with typed values
Examples: (parse-form-params {name John active true} entity-config) => {:name John :active true}
(parse-form-params {price 19.99 quantity 5} entity-config) => {:price 19.99 :quantity 5}
Parse form parameters into entity data map.
Converts string form values to appropriate types based on field config.
Args:
params: Ring form-params map (all string values)
entity-config: Entity configuration with field metadata
Returns:
Entity data map with typed values
Examples:
(parse-form-params {name John active true} entity-config)
=> {:name John :active true}
(parse-form-params {price 19.99 quantity 5} entity-config)
=> {:price 19.99 :quantity 5}(parse-form-params-checked params entity-config)(parse-form-params-checked params entity-config zones)Like parse-form-params, but returns [data field-errors] instead of
throwing on a value that cannot be read as its field's type.
parse-form-params throws a :validation-error on the first bad field, and
the create and update handlers call it outside their error handling, so an
invalid integer, decimal, UUID or date answered 500 rather than re-rendering
the form (BOU-521). Parsing field by field reports every bad field at once,
in the {field [message]} shape the form already renders, and keeps what the
user typed in data so the re-rendered form shows it. A stored file the
form removes (forms/remove-file-param) is cleared.
Like `parse-form-params`, but returns `[data field-errors]` instead of
throwing on a value that cannot be read as its field's type.
`parse-form-params` throws a :validation-error on the first bad field, and
the create and update handlers call it outside their error handling, so an
invalid integer, decimal, UUID or date answered 500 rather than re-rendering
the form (BOU-521). Parsing field by field reports every bad field at once,
in the {field [message]} shape the form already renders, and keeps what the
user typed in `data` so the re-rendered form shows it. A stored file the
form removes (`forms/remove-file-param`) is cleared.(parse-id s)s as a record's UUID. One that is not a UUID names no record: :not-found.
`s` as a record's UUID. One that is not a UUID names no record: :not-found.
(parse-query-params params)Admin service options from a Ring query-params map: the table state
(table-ui/parse-table-params, :dir as :sort-dir), :limit/:offset, and
filters in both the filters[f][op] and the plain role=admin format.
Blank filters are kept, so the filter drawer can re-render them.
add_filter_field / remove_filter add or drop one.
Admin service options from a Ring query-params map: the table state (`table-ui/parse-table-params`, :dir as :sort-dir), :limit/:offset, and filters in both the `filters[f][op]` and the plain `role=admin` format. Blank filters are kept, so the filter drawer can re-render them. `add_filter_field` / `remove_filter` add or drop one.
(refusal-message request e)What to tell the user about refusal e: its :message-key translated, a
unique key's translation, or its message.
What to tell the user about refusal `e`: its :message-key translated, a unique key's translation, or its message.
(refusal? e)Whether e is a write refused by a rule: a :conflict, shown to the user
rather than raised.
Whether `e` is a write refused by a rule: a :conflict, shown to the user rather than raised.
(rejection e)The field errors of e, a :validation-error naming its fields, such as a
value the database refused as taken; nil otherwise.
The field errors of `e`, a :validation-error naming its fields, such as a value the database refused as taken; nil otherwise.
(require-admin-user! request schema-provider)Assert current user may use the admin, throw if not. Under :enforce and
:report that reads schema-provider's entities.
Args: request: Ring request map schema-provider: the admin's ISchemaProvider
Returns: User entity map if admin
Throws: ExceptionInfo with :type :forbidden if not admin
Assert current user may use the admin, throw if not. Under :enforce and :report that reads `schema-provider`'s entities. Args: request: Ring request map schema-provider: the admin's ISchemaProvider Returns: User entity map if admin Throws: ExceptionInfo with :type :forbidden if not admin
(safe-return-to request)The request's return_to, or nil unless it is a path inside the admin, so
it cannot redirect off-site.
The request's `return_to`, or nil unless it is a path inside the admin, so it cannot redirect off-site.
(search-entity-options admin-service entity-cfg entity-name q)The first rows of entity-name by label whose label contains the typed
q, as [id label], for a combobox choosing one of them. Nil when its label
is not text.
The first rows of `entity-name` by label whose label contains the typed `q`, as [id label], for a combobox choosing one of them. Nil when its label is not text.
(search-foreign-key-options admin-service schema-provider entity-name field q)What the combobox of foreign key field of entity-name offers for the
typed q: {:parent :parent-cfg :options}, where the options are the first
parent rows, by label, whose label contains q, as [id label]. A
many-to-many key searches the entity it links (BOU-778). Nil when field
is neither, or its parent's label is not text.
What the combobox of foreign key `field` of `entity-name` offers for the
typed `q`: {:parent :parent-cfg :options}, where the options are the first
parent rows, by label, whose label contains `q`, as [id label]. A
many-to-many key searches the entity it links (BOU-778). Nil when `field`
is neither, or its parent's label is not text.(search-timezones q)The first zones whose id contains the typed q, as [id id], for the
combobox of a field with :options-source :timezones.
The first zones whose id contains the typed `q`, as [id id], for the combobox of a field with `:options-source :timezones`.
(t-fn request)The request's translation function; without i18n on the request, one that answers the key's name.
The request's translation function; without i18n on the request, one that answers the key's name.
(timezone-field? entity-config field)Whether field of entity-config is chosen from the IANA zones.
Whether `field` of `entity-config` is chosen from the IANA zones.
(valid-date-pattern pattern)The pattern, or nil with a warning when DateTimeFormatter rejects it.
Dropping it means one bad character in config.edn costs a column its
formatting rather than the whole page — the renderer falls back to its
default pattern.
Called once from the module wiring, not per request: a misconfigured application should say so at boot, rather than log the same warning on every admin page load and every HTMX table refresh for the life of the process.
The pattern, or nil with a warning when `DateTimeFormatter` rejects it. Dropping it means one bad character in `config.edn` costs a column its formatting rather than the whole page — the renderer falls back to its default pattern. Called once from the module wiring, not per request: a misconfigured application should say so at boot, rather than log the same warning on every admin page load and every HTMX table refresh for the life of the process.
(viewable-entity! schema-provider request)[user entity-name entity-config] for the entity in the path, which the signed-in admin may view. Throws :not-found or :forbidden.
[user entity-name entity-config] for the entity in the path, which the signed-in admin may view. Throws :not-found or :forbidden.
(with-filter-value-labels admin-service
schema-provider
user
entity-name
entity-config
filters)entity-config with each active foreign-key filter's parent label under
[:fields field :value-labels], so its chip names the parent rather than
its id.
`entity-config` with each active foreign-key filter's parent label under `[:fields field :value-labels]`, so its chip names the parent rather than its id.
(with-link-options admin-service schema-provider config entity-config keep?)entity-config with, on each many-to-many relation keep? accepts, the
records its filter offers, while they fit in the maximum page size. Past
it the filter takes an id.
`entity-config` with, on each many-to-many relation `keep?` accepts, the records its filter offers, while they fit in the maximum page size. Past it the filter takes an id.
(with-workflow-states config entity-config records)records, each with :admin/workflow {:instance-id :state} when it has a
workflow instance. Only for an entity with a :workflow config, and only
when the workflow module is on.
`records`, each with `:admin/workflow` {:instance-id :state} when it has a
workflow instance. Only for an entity with a `:workflow` config, and only
when the workflow module is on.Set by init.js to the browser's IANA zone (Intl…resolvedOptions().timeZone).
Set by init.js to the browser's IANA zone (`Intl…resolvedOptions().timeZone`).
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |