Commit Graph

55 Commits

Author SHA1 Message Date
Jakub Zych
58e6324da8 feat(14-01): beachcomber DropIndex and EnsureIndex for reindex tooling
- optional IndexDropper reports whether a dropped index existed; DropIndex falls back to Flush
- optional IndexEnsurer creates an empty index from its schema; EnsureIndex is a no-op otherwise
- typesense implements both (404 is already absent; ensure reuses collection creation)
2026-10-03 20:01:36 +02:00
Jakub Zych
7241704e93 feat(14-01): sunscreen redacting slog handler installed by every generated main
- Wrap redacts sensitive keys at any depth and scrubs Bearer, sk- and x-api-key shapes
- InstallDefault is the first statement of the generated run; hello main regenerated
- surf test pins that recovered panics echo no credential
- sunscreen README, root modules row and the logging docs page
2026-10-03 20:01:36 +02:00
Jakub Zych
ee0004fb65 feat(14-01): record vendor calls with summer parity:upstream and replay them offline
- WriteUpstream masks vars, hashes long base64 JSON strings and refuses unmasked Authorization/X-Api-Key
- multipart requests recorded as ordered parts; the fake compares parts and hashed payloads
- loopback CONNECT recording proxy with a local ECDSA parity CA, script and forward modes
- parity:upstream command, README and parity docs
2026-10-03 19:55:42 +02:00
Jakub Zych
e6a67134d1 feat(14-01): fetchguard client covers PUT, multipart, bearer and a trusted mode
- TrustedMode (declared after PublicOnlyMode) lifts the scheme, host and dial checks for Client only
- PutJSON, PostMultipart with FormField/FormFile, Bearer
- tests for modes, redirects, multipart order, body cap and the scheme guard
- README, root modules row and outbound HTTP docs describe the client and its test seam
2026-10-03 19:42:37 +02:00
Jakub Zych
2b94dfd2d2 feat(13-01): add the prohibited rule and dated-download and notification masks
- lagoon.ValidateRequest supports Laravel 9 prohibited (!required, not
  implicit); with no catalog line its message is validation.prohibited
- tide compares Content-Disposition with real calendar dates masked on both
  sides; a different name, an invalid date or a one-sided date still diffs
- tide.NormalizePublications masks a Carbon +00:00 $.data.payload.created_at
  and an uncaptured positive integer $.data.payload.id as {{id}}
- the album-date test's outside-album sibling moves off payload.created_at,
  which now has its own mask
- READMEs and docs describe the rule and both masks
2026-10-03 06:32:46 +02:00
Jakub Zych
55a4092019 feat(13-01): queue jobs whose worker ships later while a worker runs
- a kind no plugin registered always inserts through the insert-only River
  client, so Dispatch and Enqueue no longer fail River's unknown-kind check
  while the in-process worker runs
- while a worker runs, such a kind must name a queue no worker serves;
  an empty queue, default, scheduled, a configured queue or a registered
  job's queue is ErrUnregisteredKindQueue and nothing is written
- README and docs/services/jobs.md describe jobs whose worker ships later
2026-10-03 06:28:40 +02:00
Jakub Zych
fbdeb20126 feat(13-01): register overlapping constrained routes in surf
- compile groups routes ServeMux refuses side by side into overlap families
  and registers each under one generated method-less pattern
- the family handler tries members in registration order on literals and
  Where constraints, sets their path values and runs their own wrapped chain
- no match answers the bare 404; a method mismatch answers ServeMux's 405
  and Allow for the same table without the overlap
- unsupported shapes (same shape, {name...}, shadowing route) fail at boot
- README and docs/services/routing.md describe the behaviour
2026-10-03 06:22:20 +02:00
Jakub Zych
55314e41f8 fix(admin): datetime popover clock, unsaved confirm, inferred list dates
BM UAT on v0.1.1 showed a date-only calendar for datetime fields, a stuck discard dialog, and raw ISO timestamps when columns.yaml omitted type. The picker now edits time in the popover, confirm sits above the calendar, and omitted time.Time / Date / TimeOfDay columns compile as datetime / date / time.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-03 05:58:24 +02:00
Jakub Zych
516f9c9025 fix(12.2): close code-review blockers on uploads, JSON caps, and pivot fill
Keep form save behind in-flight uploads, make retries idempotent via X-Upload-Id, cap remaining JSON bodies, and surface pending pivot type errors instead of zeroing them.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-02 23:10:48 +02:00
Jakub Zych
ea33296799 feat(12.2-04): add the fileupload field with deferred uploads on the form session key
- sessionKey.ts: one 32-byte base64url key per form mount, sent only in headers
- api/files.ts: FileRoutes over the record and child file routes, XHR upload with progress, 401 refresh and retry
- FileuploadField and FileCaptionModal per UI-SPEC section 3: dropzone, image grid, rows, per-item states, client pre-checks, reorder, protected previews
- FormView provides FORM_SESSION, counts pending changes as dirty and sends X-Session-Key on create and update
- fileupload lang keys in en and pl, admin-spa docs note, deferred smoke test, rebuilt dist
2026-10-02 19:22:22 +02:00
Jakub Zych
fe9e8baaf1 feat(12.2-03): defer relation work on unsaved records and add child file routes
- record id 0 with X-Session-Key manages deferrable relations: create, link, unlink, delete and pivot edits are held in deferred_bindings
- the record's create save applies relation bindings with the file bindings; an ineligible link is a 422 on the relation-manager field
- child forms upload files through .../records/{child}/files/{field} keyed by X-Child-Session-Key; the child save commits them
- boot refuses a deferrable relation with create whose related model no plugin lists in Models()
2026-10-02 19:08:16 +02:00
Jakub Zych
afb05b6ee4 feat(12.2-03): add parent-scoped child show, update, delete and pivot routes
- loadChild finds a child with one query carrying the parent predicate; a foreign child is 404
- GET/PUT .../records/{child} and POST .../delete (all or nothing) per relation kind
- hasMany link adopts NULL-key rows and unlink clears the key; pending created children are never candidates
- link accepts pivot values for one id through the pivot.form whitelist; GET/PUT .../pivot/{child}
- Link and Unlink share linkRelated/unlinkRelated for the deferred commit
2026-10-02 18:44:34 +02:00
Jakub Zych
48a5b8045a feat(12.2-03): add hasMany relation contracts, relation forms and child create
- RelationContract gains Kind (empty is belongsToMany) and ForeignKey, with kind-aware boot checks
- manage.form, view.form and pivot.form compile against the related or pivot model; $/ paths resolve inside the plugin
- view toolbarButtons accept create|update|delete|link|unlink, each the capability of its routes
- POST .../relations/{name}/records creates a child through the manage form; the server sets the hasMany key
- relation schema carries kind, deferrable and the localized forms; 17 new relation message keys in en and pl
2026-10-02 18:37:13 +02:00
Jakub Zych
67d4c7ff13 feat(12.2-02): add the datepicker field with server-side bounds and date list columns
- type: datepicker compiles the D-20 keys; format maps to displayFormat with WinterCMS's momentFormat table
- boot fails when the mode does not match the column's Go type (time.Time, lagoon.Date, lagoon.TimeOfDay)
- datepicker is a writable scalar field; minDate and maxDate are rechecked on save
- columns.yaml accepts type: date and type: time; Scanner/Valuer structs are columns, not relations
- conformance fixture carries date and datetime fields; README, forms and lists docs
2026-10-02 18:19:04 +02:00
Jakub Zych
e54fd257ee feat(12.2-02): add file removal, caption, reorder and protected downloads
- DELETE, PUT and POST reorder under .../{id}/files/{field}, each scoped by one parent query (404 for a foreign file)
- protected download and thumb routes: is_public=false only, nosniff, private no-store, sandbox CSP, inline only for jpeg/png/gif/webp
- the save applies deferred removals, replaces attachOne files and rechecks maxFiles and required
- blobs of deleted files are removed after commit
- swagger2openapi emits binary content for file responses
- admin OpenAPI, TS types, conformance, README and attachments docs
2026-10-02 18:11:56 +02:00
Jakub Zych
044e0450ef feat(12.2-02): add the fileupload field with deferred uploads committed on save
- type: fileupload compiles the D-08 keys and binds to the model's attach.Relation at boot
- X-Session-Key (cabana.SessionKeyHeader) carries the form session key; RecordInput.SessionKey
- GET and POST .../{id}/files/{field}: list with pending uploads, multipart upload into attach.Store
- the create and update save attaches the session's pending files in its transaction
- swagger2openapi folds formData parameters into a multipart requestBody
- admin OpenAPI, TS types, conformance cases, README and forms docs
2026-10-02 18:04:34 +02:00
Jakub Zych
8818d7b023 feat(12.2-01): add deferred:purge, its daily framework schedule and relation child hooks
- deferred:purge [--days] in lagoon.RuntimeCommands (purge_days, default 5)
- lagoon.FrameworkSchedule entry at purge_at (default 03:00, empty disables)
- conga prepends framework entries as summercms.lagoon[i]:<command>
- pact.Relation{Before,After}{Create,Update,Delete} optional hooks
- lagoon, conga and pact READMEs, scheduling and setup docs
2026-10-02 17:45:41 +02:00
Jakub Zych
f48a886f94 feat(12.2-01): add lagoon.Date and lagoon.TimeOfDay with Fill and required support
- Date (DATE) and TimeOfDay (TIME) with Scanner, Valuer, JSON and text forms
- Fill falls back to encoding.TextUnmarshaler for string sources after
  every existing conversion, so time.Time and the new types fill from JSON
- required treats a zero time.Time, Date or TimeOfDay as empty
- lagoon README, models and casts-and-validation docs
2026-10-02 17:40:48 +02:00
Jakub Zych
19f4cf8232 feat(12.2-01): add deferred bindings, guarded upload store and purge
- deferred_bindings migration set under summercms.deferred with backend_user_id
- lagoon.DeferredBind/Unbind/Bindings/Forget/Slaves scoped by DeferredKey
- lagoon.PurgeDeferred with SKIP LOCKED batches and after-commit blob deletes
- attach.Store with the ported image guard, extension and MIME limits
- attach.Relation, attach.HasRelations, attach.BlobKeys, File.ThumbKey
- lagoon README and attachments docs
2026-10-02 17:36:43 +02:00
Jakub Zych
1307060e15 fix(12-05): store WinterCMS's broken-image thumbnail for an unusable original (T-12-16)
A photo whose original is missing, does not decode or declares more than
4096x4096 pixels made attach.File.Thumb return an error, and every listing
that shows the photo answered 500 from then on: one 100-byte PNG uploaded
by any household member broke GET collections and the album for everyone.

Thumb now follows WinterCMS's File::makeThumb catch branch: it logs the
reason at warn level, stores WinterCMS's BrokenImage picture (exported as
attach.BrokenImagePNG) under the thumbnail key and returns its URL. Invalid
arguments, storage errors and encode failures are still errors.
2026-10-02 15:15:26 +02:00
Jakub Zych
3ae49bb6f4 feat(12-01): report the search engine's found count and field weights
- optional beachcomber.PageSearcher returns a page of candidate ids plus
  the engine's found count; beachcomber.SearchPage falls back to
  SearchIDs for engines without it, so Engine is unchanged
- Query.QueryByWeights is sent to Typesense as query_by_weights; a
  mismatched weight list or a page above typesense.MaxPerPage (250) is
  refused before any request
2026-10-02 11:37:01 +02:00
Jakub Zych
e06e0cc8bf feat(12-01): record multipart uploads and match Winter upload URLs
- attach.PublicURL and (*File).URL build Winter File::getPath() URLs; the
  thumbnailer decodes webp via golang.org/x/image v0.46.0 and checks the
  image size from the header before decoding
- tide requests carry multipart parts (files beside the fixture pinned by
  sha256) encoded with the fixed MultipartBoundary, so PHP and Go receive
  byte-identical bodies
- tide masks the random partition, disk name and file id of url/thumb_url
  upload URLs while still diffing prefix, size, mode and extension, and
  NormalizePublications masks Carbon dates in the published album
2026-10-02 11:33:42 +02:00
Jakub Zych
f9b7f2ea33 feat(12-01): add Laravel request validation to lagoon
- lagoon.ValidateRequest ports Laravel 9 request validation: wildcard
  expansion, implicit-rule stop, bail, sometimes/nullable/blank skipping,
  size messages split by type and character-counted string lengths
- ParseRules, In, CustomRule, UploadedFile and ErrorKeys for rule tables
- pl/en lagoon::validation catalogs ported verbatim from WinterCMS
- lagoon.Validate answers a numeric range failure with the bound that
  failed (min, max or numeric between) instead of always max
2026-10-02 11:25:36 +02:00
Jakub Zych
2da8112dbb fix(09): WR-11 enforce case-insensitive unique backend user emails
Add a backend admin migration that creates a unique index on
lower(backend_users.email). Rows copied from WinterCMS may hold emails
that differ only in case, so the migration refuses to run and names the
clashing logins instead of choosing an account to drop.
2026-10-01 23:12:29 +02:00
Jakub Zych
4ae272e3cc fix(09): WR-19 expose the write transaction to hooks and scopes through TxFromContext 2026-10-01 21:37:13 +02:00
Jakub Zych
8479defe53 fix(09): WR-17 merge an admin's own permissions over the role's, honouring denies 2026-10-01 21:33:07 +02:00
Jakub Zych
629fac4d29 fix(09): WR-16 resolve model columns through embedded structs and explicit column tags 2026-10-01 21:31:46 +02:00
Jakub Zych
299d220b51 fix(09): WR-14 let logout revoke an expired token that is still refreshable and always clear the cookie 2026-10-01 21:23:42 +02:00
Jakub Zych
c9bb14944a fix(09): WR-13 read admin passwords from a prompt or stdin and deprecate the --password flag 2026-10-01 21:20:20 +02:00
Jakub Zych
331351a73c fix(09): WR-11 reject ambiguous admin logins and cross-field login or email collisions 2026-10-01 21:17:50 +02:00
Jakub Zych
3f476164f9 fix(09): WR-10 fail boot when another plugin already owns the backend guard 2026-10-01 21:15:05 +02:00
Jakub Zych
20a79c5df4 fix(09): WR-09 scaffold admin controllers with a required permission and a record source placeholder 2026-10-01 21:13:40 +02:00
Jakub Zych
9bca815b1b fix(09): WR-05 refuse relation link and unlink the panel does not declare 2026-10-01 21:07:11 +02:00
Jakub Zych
f2ab93f291 fix(09): WR-03 refuse writes that the compiled list and form do not declare 2026-10-01 21:04:31 +02:00
Jakub Zych
28aa073de0 fix(09): WR-02 drop a denied main menu item and never link it to a controller the admin cannot open 2026-10-01 20:59:26 +02:00
Jakub Zych
b4b8b5df64 fix(09): WR-01 match wildcard required permissions and treat several codes as any, like Winter 2026-10-01 20:58:16 +02:00
Jakub Zych
4103d95a1a docs(architecture): add performance and scaling page compared to PHP-FPM 2026-10-01 19:20:46 +02:00
Jakub Zych
bd2f7e7cb5 docs(setup): explain adding the Go bin directory to PATH after go install 2026-10-01 17:12:53 +02:00
Jakub Zych
a494375db7 feat(docsite): optional site_url and site_label link back to the main site
- site.yaml keys site_url and site_label, validated: http(s) URL with a host
  or a path starting with a single /; a label needs a URL
- docs:build and docs:serve flags --site-url and --site-label override them
  the way --base-url overrides base_url
- every page header, the 404 page included, links back with the explicit
  label, else the URL host, else Home; unset output is unchanged
- docs/console/utilities.md documents the keys and flags
2026-10-01 16:09:40 +02:00
Jakub Zych
7936234e8c docs: require PostgreSQL 15 or newer (verified on postgres:15)
The database suites (lagoon, lagoon/attach, cabana, beachcomber,
lighthouse, bouncer, conga, docs/examples/blog) pass against postgres:15
from a HEAD export with the test image retargeted.
2026-10-01 16:07:36 +02:00
Jakub Zych
037dc53030 feat(lagoon): per-query collation for OrderBy, drop the database locale check
- lagoon.OrderBy takes variadic lagoon.OrderOption values; lagoon.Collate(name)
  emits a validated, double-quoted COLLATE clause (e.g. "pl-x-icu")
- remove the exported CheckLocale and the ICU pl-PL check from Open and Use
- framework test containers and per-test databases are plain PostgreSQL
- lagoon README, root README and docs pages drop the locale requirement;
  queries-and-pagination gains a "Sorting with a collation" section
  backed by ExampleCollate
2026-10-01 09:51:03 +02:00
Jakub Zych
efc3161c3f fix(11.1-07): require built, run src= code; case-sensitive go doc; parse command forms
- .go src= targets must be in the default build and reached from a Test or an Example with output
- go doc -c makes the identifier fallback case-sensitive
- commandWord parses env prefixes, flags, go run and bin/ forms
2026-10-01 08:37:38 +02:00
Jakub Zych
73c72af244 fix(11.1-07): check go fences, README src= and shell fences from the AST
- goLang follows the highlighter's chroma lookup, so golang and main.go need src=
- a src= fence in a module README is refused and never captioned
- shell fences inside callouts and lists are command-checked
- a fence with four or more leading spaces is an indented code block
2026-10-01 08:30:19 +02:00
Jakub Zych
63290c66d3 feat(11.1-05): pin the walkthrough to the scaffolder and link it from the concept map
- TestScaffoldLayout runs make:plugin, make:model, make:migration,
  make:admin-controller and make:command for acme.blog in a copy of
  examples/hello and compares the file set with docs/examples/blog
- the page lists the exact make commands, the go.mod a scaffolded plugin
  gets, what the scaffolder leaves to the developer and a checklist
- scaffolding.md no longer claims same-second migrations get consecutive
  timestamps; only same-name ones do
- coming-from-wintercms.md and index.md link the walkthrough
2026-09-30 23:41:00 +02:00
Jakub Zych
dd82b8a2ad feat(11.1-05): add the walkthrough's admin controller, publish command and published_at migration
- make:admin-controller, make:command and make:migration output, finished:
  the Posts controller serves models.Post behind acme.blog.access_posts,
  WinterCMS-style form and list YAML embedded through pact.AdminAssets,
  blog:publish sets published_at by slug with a bound parameter
- the posts route lists published posts only, newest first
- Docker tests migrate an ICU pl-PL database, roll back published_at, serve
  the route and run blog:publish with a published and an opened database
- the page gains the admin controller, console command and added-column
  sections
2026-09-30 23:35:44 +02:00
Jakub Zych
41a3190956 feat(11.1-05): add the acme.blog walkthrough plugin with its model, migration and posts route
- docs/examples/blog: scaffolder output for acme.blog (make:plugin, make:model)
  in the root module, with a Post model, a fill allow-list and NewPost, the
  create migration and GET /api/blog/posts paginated through lagoon
- short tests activate the plugin, check the route with surf, the fill
  allow-list and the migration order
- docs/setup/porting-a-plugin.md: registration, model, migrations and routes
  sections with src= copies of the plugin
- TestDocsRequiredPages requires setup/porting-a-plugin
2026-09-30 23:28:41 +02:00
Jakub Zych
44bd1446f5 feat(11.1-04): add the Backend section, the remaining Services pages and the concept map links
- docs/backend: admin controllers, forms, lists and filters, relation
  manager, users and permissions, settings, partials and widgets, admin SPA
- docs/services: storage, outbound HTTP, realtime, Web Push, search, parity
  testing and the Frontend and AJAX (not provided) page
- Examples for cabana (with testdata/docs YAML), fetchguard, lighthouse and
  its centrifugo driver, flare, beachcomber and typesense, tide; lighthouse
  and beachcomber TestDocs* regions run on their Postgres harnesses
- concept map rows link their guide pages and the not-provided rows the
  Frontend and AJAX page; index lists Backend, Database and Services
- TestDocsRequiredPages asserts the D-08 section order
2026-09-30 23:18:35 +02:00
Jakub Zych
efb35a2d35 feat(11.1-04): add the Database section and the core Services pages
- docs/database: models, migrations, queries and pagination, relations,
  casts and validation, attachments and transactions (lagoon.Transaction,
  lagoon.AfterCommit, nested savepoints, lagoon.OnDatabase)
- docs/services: configuration, events, routing with auth groups, rate
  limiting, authentication, the OAuth server, mail and localization
- runnable Examples for lagoon, attach, compass, surf, wire, bouncer,
  wristband, postcard, phrasebook and festival; lagoon TestDocs* regions
  run on the package's Postgres harness through DocsDB
- 15 new required pages
2026-09-30 22:59:25 +02:00
Jakub Zych
9d37d56486 feat(11.1-04): add the Services section with a verified Queued jobs page
- docs/services/jobs.md: declaring, registering and dispatching jobs, the
  summer_jobs record, progress, cancellation and workers
- conga ExampleJob plus dispatch and status regions run by TestDocsDispatch
  on the package's Postgres harness (DocsApp in export_docs_test.go)
- concept map links the queued jobs row; services/jobs is a required page
2026-09-30 22:35:22 +02:00
Jakub Zych
a896f3ff81 feat(11.1-03): add the Setup and Console docs sections
- setup: introduction, installation rewritten from install to serve,
  configuration with the keys an application sets
- console: introduction, setup and maintenance, scaffolding, writing
  commands, utilities; every command name is checker-verified
- bonfire ExampleCatalog shows arguments, bare and repeatable flags
- index links the section introductions; TestDocsRequiredPages lists
  the seven new pages
2026-09-30 22:16:36 +02:00