Commit Graph

17 Commits

Author SHA1 Message Date
da7a0732c3 test(16.2-06): full unit coverage of the Journal Blog Editor field types
- Decode: clean values byte for byte, nil, non-string refusal, the 35
  unsafe payloads stripped with notices exactly when changed
- notices order, ten-name cut, plural forms in lang/en and lang/pl
- LooksLikeHTML, options, ListValue, Upload sniffing and safe names
- StoreMedia layout, suffixes, no overwrite, size and folder rules
- editor corpus and policy cross-check extended in place: more negatives,
  notices only when stripped, stable second pass
- TestPostsEditorHooks: create and update, default and non-default
  writes, no writes, no writer
- TestCategoriesAdminBlogEditor (Docker host): untouched saves keep
  imported markup and NULL, clean edit, strip and report, blank to NULL,
  editor upload
2026-10-10 15:55:00 +02:00
2384a61e38 feat(classes): allow the Blog Editor's own markup in the HTML policy
- figure data-embed ^youtube$ and data-id ^[A-Za-z0-9_-]{11}$ (the embed
  placeholder), img alt with any text, ol start ^[0-9]+$; nothing else
- TestEditorOutputPassesPolicy checks every editor-corpus.json entry byte
  for byte with an empty report, plus the negative cases;
  TestSanitizeHTMLReport pins the removal report
- CheckHTML documented as a predicate no write path refuses with; README
  lists the four additions and the report API
2026-10-10 12:11:29 +02:00
cb1c688213 feat(admin): category content on the Blog Editor and editor image uploads
- category content is the blogeditor type (uploadFolder categories): it is
  stripped and reported like post content; the 422 CheckHTML gate is
  retired and blank content still becomes NULL
- blogeditor and mlblogeditor Upload: magic-byte check for JPEG, PNG, GIF
  and WebP matching the extension, safe lowercase names, storage through
  classes.StoreMedia in the field's uploadFolder
- TestBlogEditorUploadSmoke, TestCategoriesAdminContent asserts the strip,
  README describes the field types, admin saves and editor uploads
2026-10-10 12:08:51 +02:00
15fcad7a93 refactor(api): share media storage with the editor upload
- classes.StoreMedia holds the media upload route's storage: extension
  allowlist, 10240 KiB cap, /journal/<folder>/<name> keys with the unique
  suffix, bucket write and public URL
- UploadMedia keeps authentication, permissions, the folder and form checks
  and answers with the same 201 body and messages
- uniqueMediaKey fails instead of overwriting when every numbered name is
  taken; the media path tests move to classes
2026-10-10 12:08:42 +02:00
fbd1c85168 feat(admin): post content on the Blog Editor field type
- blogeditor and mlblogeditor field types (classes.BlogEditorTypes) on the
  plugin; post content is mlblogeditor with htmlColumn content_html
- Decode stores SanitizeHTMLReport output per locale with notices naming
  what was removed; clean HTML is stored byte for byte, nothing is refused
  because of markup
- Load serves HTML as stored and markdown posts as their content_html
- the admin hooks copy written content to content_html per locale without
  goldmark; untouched content leaves both columns as they are
- editor lang keys (en, pl plural notices), tracer smoke test, updated
  Postgres admin tests
2026-10-10 12:02:55 +02:00
0d00ec8e0d fix(api): sanitize translated content_html on every write
A translated content_html sent through the Journal API (syncTranslations
formatted only when content_html was empty) or through
journal:import-posts (no check) was stored verbatim and served by the
anonymous GET /_journal/api/v1/posts and /posts/{slug}. A translation
keyed on the default locale also wrote the host content_html column
past FormatHTML.

classes.FormatTranslation now sets content_html for every translated
locale before it is stored: rendered from a non-blank content with
FormatHTML (host-column parity; a supplied content_html is replaced),
otherwise a supplied content_html is sanitized with the new
classes.SanitizeHTML, which FormatHTML reuses. Both write paths call it.

PHP parity note: Winter's PostApiController::syncTranslations called
Post::formatHtml only when content_html was empty and otherwise stored
the supplied content_html verbatim; ImportPosts::syncTranslations stored
it verbatim; the MLJournalMarkdown admin widget always derived it from
content via formatHtml. The Go port now follows the admin widget on
every path. Response shapes and status codes for valid input are
unchanged; only the stored content_html of unsafe or content-mismatched
translations differs.
2026-10-10 00:09:47 +02:00
f22d08902e feat(admin): edit category content in the admin form behind the HTML gate
- add a content textarea (size huge) after description, with en and pl phrases
- add content to Category.Fillable
- export classes.CheckHTML and run it from FormBeforeCreate and FormBeforeUpdate:
  unsafe category content answers 422 on content, blank content is stored as NULL
2026-10-09 16:26:12 +02:00
740dcd086d feat(api): serve the category content field on GET /_journal/api/v1/categories
- add the nullable TEXT column golem15_journal_categories.content (migration 202610090001)
- map it as models.Category.Content and emit category content on every categories row
- document the categories row shape and the stored-as-is HTML semantics
2026-10-09 16:24:20 +02:00
57a347c97b feat(api): list an author's published posts at /authors/{author}/posts
- AuthorPosts reuses the /posts published predicate, per_page clamp, sort allow-list and row serializer
- Bearer tokens are ignored; drafts and scheduled posts never appear
- Both author routes share one resolve-or-respond helper for identical 404/500 paths
- Document query keys, envelope and 404 in README
2026-10-09 13:20:39 +02:00
908ade9c78 feat(api): anonymous author profile at /_journal/api/v1/authors/{author}
- Resolve {author} by golem15_bloghub_author_slug, then by login for users with a published post
- Project only id, names, login and author slug from backend_users; payload is id, slug, first_name, last_name
- JSON 404 {"error":"Author not found"} for unknown, soft-deleted and malformed values
- Document the route in README
2026-10-09 13:19:35 +02:00
Jakub Zych
d09edde080 test(15-04): complete PHPUnit map, migrations, FormatHTML, and search-gate tests
Named JOURNAL-001/002 fillable, FormatHTML unsafe tags, rollback/remigrate, RSS/categories/tags/media, and TestSearchGateOff with zero Typesense HTTP.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 19:18:53 +02:00
Jakub Zych
bdd1c1be61 feat(15-03): add journal media upload, RSS, and off-by-default Typesense gate
- POST /_journal/api/v1/media/upload requires access_posts and stays under journal/
- Anonymous GET rss is RSS 2.0 honoring rss_* settings; unpublished posts are omitted
- beachcomber Gate reads search_use_typesense (default false) so a fresh save makes no Typesense HTTP

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 19:03:35 +02:00
Jakub Zych
3a1dda45ec feat(15-03): enforce JOURNAL-005 draft 404 and backend-Bearer writes
- Show unpublished or future posts as 404 without a data key unless owner or access_other_posts
- POST/PUT/DELETE posts and featured-images require a backend-audience JWT and PHP {error} strings
- Anonymous GET categories and tags return the PHP list keys; frontend-audience tokens cannot write

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 18:59:12 +02:00
Jakub Zych
f2e5b3d072 feat(15-03): serve anonymous GET /_journal/api/v1/posts as a published list
- Public and write groups under /_journal/api/v1 with journal-public-api and journal-api buckets (Max 120)
- Index omits drafts and future published_at; per_page default 9 max 30
- Optional backend JWT on GET; missing Bearer on writes returns PHP {error:Authentication required}

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 18:54:41 +02:00
Jakub Zych
4ff98f7de8 feat(15-02): register journal export/import commands and Posts toolbar
JSON CLI matching PHP ExportPosts/ImportPosts ships as HasCommands, and Posts toolbar actions reuse that logic behind access_import_export.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 18:47:53 +02:00
Jakub Zych
afd0a2cafc feat(15-02): add Categories, Tags, and journal settings admin
Register the remaining YAML admin screens and the settings singleton
so a host can boot Posts, Categories, Tags, and Code journal. Posts
update and delete refuse other authors without access_other_posts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 18:42:29 +02:00
Jakub Zych
dea0934950 feat(15-02): add Posts admin with mlmarkdown and FormatHTML
YAML-driven Posts screen stores translatable markdown and regenerates
content_html in the plugin. Publish writes without access_publish are
refused; list and form queries stay on the principal without
access_other_posts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 18:41:10 +02:00