Files
summercms/.planning/phases/15-journal-plugin/15-03-SUMMARY.md

12 KiB

phase, plan, subsystem, tags, requires, provides, affects, actuals, plan_head_before, plugin_repo_head_after, host_repo_head_after, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status
phase plan subsystem tags requires provides affects actuals plan_head_before plugin_repo_head_after host_repo_head_after tech-stack key-files key-decisions patterns-established requirements-completed coverage duration completed status
15-journal-plugin 03 plugins
journal
api
jwt
surf
beachcomber
rss
typesense
rate-limit
phase provides
15-02 Posts/Categories/Tags admin, FormatHTML, HasPermissions, newPostsEnv postgres harness, host TestBootUserTranslateJournal
Anonymous GET /_journal/api/v1/posts, posts/{slug}, categories, tags, rss with PHP shapes
Backend-audience JWT writes under the same prefix; PHP {error} strings; JOURNAL-005 draft 404
journal-public-api / journal-api buckets Max 120; media upload under journal/; Typesense gate off
15-04
16
tokens tasks commits
32000 3 5
db6243c8c2 bdd1c1be618658374f42e6a643e8d34b6c350c28 716aa44cf58e278251a3cff71f8ae99ed5e316a7
added patterns
Public group throttle:journal-public-api only; never cabana middleware name backend on anonymous GET
Writes authenticate in-handler with bouncer.NewBackendJWTGuard (aud=backend) and PHP {error} JSON, not cabana writeUnauthenticated
Where() applies to the last declared route; register /media/upload before posts/{id} constraints
Host gitlink is a nested clone; bump via file:// fetch with protocol.file.allow=always
created modified
../sm-journal-plugin/routes.go
../sm-journal-plugin/search.go
../sm-journal-plugin/controllers/api/auth.go
../sm-journal-plugin/controllers/api/posts.go
../sm-journal-plugin/controllers/api/posts_helpers.go
../sm-journal-plugin/controllers/api/posts_search.go
../sm-journal-plugin/controllers/api/media.go
../sm-journal-plugin/controllers/api/rss.go
../sm-journal-plugin/models/post_search.go
../sm-journal-plugin/models/search_gate.go
../sm-journal-plugin/journal_public_list_smoke_test.go
../sm-journal-plugin/journal_api_writes_test.go
../sm-journal-plugin/journal_api_task3_test.go
../sm-journal-plugin/plugin.go
../sm-journal-plugin/plugin_test.go
../sm-journal-plugin/README.md
../sm-journal-plugin/posts_admin_smoke_test.go
../sm-grzybyfunkcjonalne-app/boot_test.go
../sm-grzybyfunkcjonalne-app/plugins/golem15/journal
D-14 public prefix is /_journal/api/v1; show is GET posts/{slug} with ctype_digit fallback to id; list per_page default 9 max 30
D-15 writes require cabana backend JWT audience backend; missing Bearer is JSON {error:Authentication required}; frontend-audience tokens fail the same 401
JOURNAL-005 unpublished or future published_at show is 404 with no data key unless owner or access_other_posts; anonymous never 403
D-17 buckets journal-public-api and journal-api Max 120 Decay 1m; 429 body stays surf Too Many Attempts
D-12 search_use_typesense defaults false; ShouldBeSearchable is false when unpublished or the Gate is off; a fresh save makes zero Typesense HTTP
D-16 Journal routes are not added to fonoteka.go tide/parity files
optionalBackendPrincipal on GET (Bearer present only; failure is anonymous); requireBackendPrincipal on writes with PHP-shaped writer
Store/update assign field-by-field; never lagoon.Fill the whole body onto Post; FormatHTML regenerates content_html
beachcomber.From in Plugin.Boot; Gate reads golem15_journal_settings.search_use_typesense for ID=1; read errors count as off
D-12
D-14
D-15
D-16
D-17
id description requirement verification human_judgment
D1 Anonymous GET /_journal/api/v1/posts returns published posts only; buckets Max 120; public group has no cabana backend middleware D-14
kind ref status
integration ../sm-journal-plugin#TestJournalPublicList pass
kind ref status
unit ../sm-journal-plugin#TestJournalBuckets pass
false
id description requirement verification human_judgment
D2 POST without Bearer is 401 Authentication required (not cabana envelope); JOURNAL-005 draft 404; frontend JWT cannot write; publish without access_publish is 403 D-15
kind ref status
integration ../sm-journal-plugin#TestJournalWriteUnauthenticated pass
kind ref status
integration ../sm-journal-plugin#TestJournal005DraftShow pass
false
id description requirement verification human_judgment
D3 Anonymous GET categories and tags return PHP {data} keys; featured-image writes without Bearer are 401; non-editor backend Bearer is 403 D-14
kind ref status
integration ../sm-journal-plugin#TestJournalPublicCategories pass
kind ref status
integration ../sm-journal-plugin#TestJournalPublicTags pass
kind ref status
integration ../sm-journal-plugin#TestJournalFeaturedImageUnauthenticated pass
false
id description requirement verification human_judgment
D4 Media upload without access_posts is 403; with permission path is under journal/; folder .. is 422 D-15
kind ref status
integration ../sm-journal-plugin#TestJournal006MediaUpload pass
false
id description requirement verification human_judgment
D5 Default search_use_typesense is off; a published save with Typesense configured records zero outbound HTTP; unpublished ShouldBeSearchable is false D-12
kind ref status
integration ../sm-journal-plugin#TestSearchGateOff pass
false
id description requirement verification human_judgment
D6 Anonymous GET rss is 200 application/rss+xml, RSS 2.0, rss_title and rss_posts_per_feed honored, unpublished omitted D-14
kind ref status
integration ../sm-journal-plugin#TestJournalRSS pass
false
id description requirement verification human_judgment
D7 Host assembled routes include GET and POST /_journal/api/v1/posts D-14
kind ref status
integration ../sm-grzybyfunkcjonalne-app#TestBootUserTranslateJournal pass
false
80min 2026-10-06 complete

Phase 15: Journal plugin — Plan 03 Summary

Anonymous /_journal/api/v1 list/show/categories/tags/rss with PHP shapes, backend-Bearer writes, JOURNAL-005 draft 404, media under journal/, and Typesense gated off by default

Performance

  • Duration: 80 min
  • Started: 2026-10-06T16:51:02Z
  • Completed: 2026-10-06T17:12:00Z
  • Tasks: 3
  • Files modified: 22

Accomplishments

  • Public GETs under /_journal/api/v1 work without Authorization; list omits drafts; per_page default 9 max 30.
  • Writes require a cabana backend JWT (aud=backend) and return PHP {error} strings, not the cabana admin envelope.
  • Unpublished show is 404 with no data key unless the caller is the owner or holds access_other_posts.
  • Media upload stays under journal/; RSS is well-formed 2.0; search_use_typesense default false makes zero Typesense HTTP on a fresh save.

Task Commits

Each task was committed atomically:

  1. Task 1: Serve anonymous GET /_journal/api/v1/posts as a published list — f2e5b3d072d21a7865d30cd504330bcc5164e0a1 (feat, sm-journal-plugin)
  2. Task 2: Slug show, draft 404, and backend-Bearer writes — 3a1dda45ec32c47438f5a159d57194a5bb783ce6 (feat, sm-journal-plugin)
  3. Task 3: Media upload, RSS, Typesense gate, and host route assertion — bdd1c1be618658374f42e6a643e8d34b6c350c28 (feat, sm-journal-plugin) and 716aa44cf58e278251a3cff71f8ae99ed5e316a7 (feat, sm-grzybyfunkcjonalne-app gitlink + boot_test)

Plan metadata: (this commit)

Files Created/Modified

  • ../sm-journal-plugin/plugin.go — HasRoutes, BucketProvider, wireSearch in Boot
  • ../sm-journal-plugin/routes.go — public and write groups under /_journal/api/v1
  • ../sm-journal-plugin/controllers/api/auth.go — PHP {error} writer, optional/require backend principal
  • ../sm-journal-plugin/controllers/api/posts.go — Index/Show/Store/Update/Destroy/featured-images
  • ../sm-journal-plugin/controllers/api/posts_helpers.go — JOURNAL-005, categories/tags trees, field-by-field writes
  • ../sm-journal-plugin/controllers/api/media.go — JOURNAL-006 media upload
  • ../sm-journal-plugin/controllers/api/rss.go — RSS 2.0
  • ../sm-journal-plugin/search.go — beachcomber Gate on search_use_typesense
  • ../sm-journal-plugin/models/post_search.go — SearchableAs / ShouldBeSearchable / ToSearchableArray
  • ../sm-journal-plugin/README.md — public prefix with blog examples
  • ../sm-grzybyfunkcjonalne-app/boot_test.go — assembled GET and POST /_journal/api/v1/posts
  • ../sm-grzybyfunkcjonalne-app/plugins/golem15/journal — gitlink bdd1c1b

Decisions Made

  • Followed D-14/D-15/D-16/D-17/D-12 as specified. Show is slug-or-numeric-id. Writes are backend audience only; Apparatus personal tokens are not parsed.
  • ShouldBeSearchable is false when unpublished or the Gate is off (plan, not PHP's index-drafts-when-enabled).
  • RSS rss_enabled false still returns well-formed XML.

Deviations from Plan

Auto-fixed Issues

1. [Rule 2 - Blocking] Where("id") after /media/upload failed Assemble

  • Found during: Task 3 (TestJournal006MediaUpload env boot)
  • Issue: surf Where applies to the last declared route; /media/upload has no {id} parameter.
  • Fix: Register POST /media/upload before the {id} / {fileId} routes so Where still targets featured-image delete.
  • Files modified: ../sm-journal-plugin/routes.go
  • Verification: Task 3 named tests PASS
  • Committed in: bdd1c1b (Task 3)

2. [Rule 3 - Test layout] Task 2 tests live next to newPostsEnv, not controllers/api/posts_test.go

  • Found during: Task 2
  • Issue: newPostsEnv and TestMain are package journal_test at the plugin root; controllers/api cannot share that harness without a second Postgres TestMain.
  • Fix: Named tests in journal_api_writes_test.go (same 15-01/15-02 smoke layout). Verify -run still finds them via ./....
  • Files modified: ../sm-journal-plugin/journal_api_writes_test.go
  • Verification: All five Task 2 named tests PASS
  • Committed in: 3a1dda4 (Task 2)

Total deviations: 2 auto-fixed (1 assemble, 1 test-file location) Impact on plan: Required for boot and harness reuse. No scope creep. Contract tests are the plan names.

Issues Encountered

  • Plan verify go test ./... -run '^(…)$' prints [no tests to run] / [no test files] for models, updates, classes, console, controllers, controllers/api. Named tests still --- PASS. Same leaf-package shape as 15-01/15-02; stubs were not added.
  • Host go test must run with GOWORK unset so it does not inherit the plugin workspace.
  • Nested host gitlink is a separate clone from sibling sm-journal-plugin; bump with git -c protocol.file.allow=always fetch file://….
  • Journal remote still has no refs; gitlink records local SHA bdd1c1b. Do not push unless asked.
  • fonoteka.go tide/parity files were not modified (D-16).

User Setup Required

None - no external service configuration required.

Next Phase Readiness

Ready for 15-04 (unit/integration tests last, PHPUnit map, phase gate, security review). Public and write HTTP surface is in. Typesense query path exists behind the Gate; a fresh install never dials Typesense.


Phase: 15-journal-plugin Completed: 2026-10-06