Syndication slice 1: Syndicator + Dev.to adapter + storage + capability - #1
Merged
Merged
Conversation
…capability The shared core for cross-posting a blog post to external dev platforms, from both the admin and MCP later (slice 2). This slice is the engine, unit-tested with a faked HTTP client, no user-facing surface yet. - Syndicator: reads a published post (published-only content reader), computes the canonical (the post's canonical_url if it is itself a copy, else the self /blog URL), calls a target adapter, and records the result so a repeat push updates instead of duplicating. One method both surfaces will call. - SyndicationTarget adapter interface + DevToTarget (Forem API: POST create / PUT update by id, api-key header, canonical + sanitised tags [lowercase alphanumeric, max 4]); a non-2xx becomes a clear SyndicationError. - HttpClient seam (CurlHttpClient default, faked in tests) so no network in tests. - SyndicationStore interface + SyndicationRepository over the plugin's first table blog_syndication (entry_id+target -> external id/url, upsert = update-not-duplicate). - New capability nimbuscms.blog:syndicate (wildcard-immune, fine-grained) declared; migration 001_syndication registered. Secrets (DEVTO_API_KEY) read server-side. Design + reviews in docs/DESIGN-syndication.md. 27 tests green, PHPStan L6 + cs-fixer clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The engine for cross-posting a blog post to external dev platforms (Dev.to first), driven from both the admin and MCP once slice 2 wires the surfaces. This slice is the shared core, unit-tested with a faked HTTP client, no user-facing surface yet.
What
Syndicator— reads a published post (published-only content reader), computes the canonical (the post's owncanonical_urlif it's a copy, else the self/blog/{slug}URL), calls a target adapter, and records the result so a repeat push updates, not duplicates. The one method both surfaces will call.SyndicationTargetadapter interface +DevToTarget(Forem:POSTcreate /PUTupdate by id,api-keyheader, canonical + tags sanitised to Dev.to's rules; non-2xx →SyndicationError). New targets are new adapters.HttpClientseam (CurlHttpClientdefault, faked in tests) so nothing touches the network in tests.SyndicationRepositoryover the plugin's first tableblog_syndication(entry_id + target → external id/url, upsert = update-not-duplicate), behind aSyndicationStoreinterface so the service is DB-free testable.nimbuscms.blog:syndicate(wildcard-immune, fine-grained) declared; migration001_syndicationregistered. Secrets (DEVTO_API_KEY) read server-side, never in the DB/MCP/logs.Reviews
Design + platform + security review in
docs/DESIGN-syndication.md. Fixed outbound hosts (no SSRF), secrets server-side, capability opt-in on both surfaces, idempotent by external id.Tests
DevToTargetTest(create/update request shape, tags, non-2xx),SyndicatorTest(canonical self vs declared, create vs update, unknown target, missing post, failure records error),SyndicationRepositoryTest(insert-then-update-in-place, per-target listing). 27 tests green, PHPStan L6 + cs-fixer clean.Next: slice 2 wires the Syndication admin page + the
syndicate_post/syndication_statusMCP tools onto this, both gatedblog:syndicate.🤖 Generated with Claude Code