Skip to content

Feat/site image links#196

Open
vharmain wants to merge 5 commits into
masterfrom
feat/site-image-links
Open

Feat/site image links#196
vharmain wants to merge 5 commits into
masterfrom
feat/site-image-links

Conversation

@vharmain

Copy link
Copy Markdown
Contributor

Pilot for Loimaa to add externally hosted images to sports sites

vharmain and others added 3 commits June 11, 2026 10:43
Sports sites can now carry an :images collection (url, alt-text, copyright,
description) edited through a new "Kuvat" tab. Images live in an external
image bank; LIPAS stores only the CC BY 4.0 metadata.

Access is gated by a new :site/edit-images privilege. The :images-manager
role grants it without :site/save-api, so its holders can only save revisions
whose diff is limited to :images; enforced in backend/core/check-permissions!
by comparing the incoming doc against the persisted one (ignoring event-date
and search-meta noise).

Initial rollout: Loimaa (city-code 430) assigns the role to designated users.
Later cities either add the privilege to an existing broader role or get a
role assignment. :images surfaces in Public API v2 (not v1, which is frozen).

Elasticsearch mapping declares :images as {:enabled false} — existing strict
indices must be recreated or have the field added before deploy, otherwise
saves with :images populated will fail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…fallback

Per the external image-links requirement spec ("Täydentävä
vaatimusmäärittely"), a bare URL is no longer accepted:

- :alt-text and :copyright (source/owner) are now required with at least
  one non-empty translation; enforced in the malli schema (validates the
  internal save API and surfaces in v2 OpenAPI) and in the edit dialog,
  whose save button stays disabled until URL + alt-text + copyright are
  filled.
- Image URLs must be https:// — http images would be blocked as mixed
  content on the https-served LIPAS anyway. Single source of truth in
  lipas.schema.sports-sites.images/valid-url?, shared by schema and UI.
- Failed image loads (broken links) no longer render the browser's
  broken-image icon: the dialog preview and the hover popper swap in a
  neutral text placeholder via on-error.
- Schema descriptions and docs/site-images.md now spell out the API
  consumer obligations: embed/hotlink images from the source URL, no
  caching or re-hosting, so source-side takedowns (GDPR/copyright)
  propagate immediately. Updating the official API Terms of Use remains a
  legal/process task outside this repo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Previously :site/save-api holders (e.g. city-managers) could write :images
through the regular save endpoint even though the UI never offered it to
them — pilot containment relied on the UI alone. check-permissions! now
enforces both directions of the diff against the persisted revision:

- :images changed (or present on a new site) -> :site/edit-images required
- anything else changed -> :site/save-api required, as before

Saves that merely round-trip unchanged images (the UI posts the full
document) need no images privilege, so regular editing of sites that
already have images keeps working. Rolling the feature out to a new
municipality remains a pure role assignment: grant :images-manager (on top
of existing editor rights) with the municipality's city-code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vharmain vharmain marked this pull request as ready for review June 11, 2026 09:26
vharmain and others added 2 commits June 11, 2026 12:40
Two bugs hid persisted images from the UI even though the API returned
them:

- The ::display-site sub builds its map field-by-field and never included
  :images, so the read-only tab always rendered an empty table — and
  view-images? gating made the tab invisible to users without
  :site/edit-images even when the site had images.
- The images editor caches its table state from :value once at mount
  (r/with-let), so entering edit mode after viewing the empty read-only
  tab kept the stale empty state. Key the component by lipas-id, mode and
  value so state re-derives whenever the source changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The canonical lipas-id schema was [:int {:min 0}] (allowed 0), while the
jobs payload schema required pos-int?. lipas-ids come from a Postgres
sequence and are always >= 1; every save enqueues an analysis job
validated as pos-int?, so a site with lipas-id 0 could never be saved.
The looser front gate let mg/generate produce :lipas-id 0, which passed
the sports-site schema but failed the job schema at enqueue time -> 500
(seed-dependent flake in HTTP-save tests, e.g. the images-manager test).

- Tighten the canonical schema to [:int {:min 1}].
- Repoint the ad-hoc [:lipas-id :int] / [:vector :int] / [:set [:int
  {:min 0}]] usages at the canonical #'sports-sites-schema/lipas-id
  (jobs payload, bulk-operations, ptv handler/workbench, ptv schema,
  users schema, frontend map route) instead of re-spelling it.
- Update lipas-id-test: 0 is now invalid, matching the contract the jobs
  layer always enforced.

Two deliberate exceptions:
- ptv/ai.clj keeps inline :int: it feeds malli.json-schema/transform for
  Gemini structured output, where a var ref becomes a JSON-schema $ref
  that Gemini's schema subset doesn't support (verified).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 53ee6cd)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant