Skip to content

feat(media): per-platform media pivot + workspace-scoped media id validation - #9

Merged
Cryptoom merged 2 commits into
mainfrom
claude/tpx-04-a1-migration
Sep 16, 2026
Merged

Cryptoom merged 2 commits into
mainfrom
claude/tpx-04-a1-migration

Conversation

@Cryptoom

Copy link
Copy Markdown
Owner

Summary

Paket A1 (Datenmodell: Medien pro Plattform) combined with the media-id
validation fix TPX-03 (A0b) identified as a blocker.

  • New media_post_platform pivot table: uuid PK (not a plain composite-key
    pivot, since the same media item can be attached to more than one
    platform of the same post), media_id/post_platform_id uuid FKs to
    medias/post_platforms, both cascadeOnDelete, unique on
    (media_id, post_platform_id).
  • App\Models\MediaPostPlatform: custom Pivot model using HasUuids so
    the id is generated on attach() (which only fires model events when a
    custom pivot class is registered via ->using()).
  • PostPlatform::media(): BelongsToMany + PostPlatform::scopedMediaItems(): Collection<MediaItem>: empty pivot set for a platform means "every media
    item on the post" (today's behavior, no special-casing needed anywhere
    reading it). This is the single place a later publisher rollout (A3) and
    any validator will read per-platform media selection from.
  • Security fix (ported from TPX-03/PR DO NOT MERGE: TPX-03 reference fix, media id existence check for A1 #8, adapted): App\Support\PostMediaRules::assertHostedMediaExists()
    closes a cross-tenant IDOR where CreatePost/UpdatePost wrote a
    client-supplied media.*.id/path straight into posts.media with no
    check that it resolved to a real, workspace-owned medias row. Also
    fixes an unhandled Postgres 500 (invalid input syntax for type uuid) on
    a non-UUID id, now a graceful 422. Wired into all four request classes
    (App/Api Store/UpdatePostRequest).
  • Ported the 11 tests from TPX-03 reference PR DO NOT MERGE: TPX-03 reference fix, media id existence check for A1 #8 (PostMediaExistsValidationTest,
    PostMediaExistsValidationWebTest), updated 6 existing tests that used
    fabricated ids as a fixture shortcut, and added 8 new tests covering the
    pivot table, the model relation, uuid generation, the unique constraint,
    and cascade deletes on both sides.

Test plan

  • tests/Feature/PostPlatformMediaTest.php (8 passed, pivot/relation)
  • tests/Feature/Api/PostMediaExistsValidationTest.php + tests/Feature/PostMediaExistsValidationWebTest.php (11 passed, ported from PR DO NOT MERGE: TPX-03 reference fix, media id existence check for A1 #8)
  • Full --filter=Post suite: 975 passed, 0 regressions
  • --filter=Mcp suite (media MCP tools): 367 passed, 0 regressions
  • vendor/bin/pint --dirty --format agent clean

🤖 Generated with Claude Code

Cryptoom and others added 2 commits September 17, 2026 00:56
…idation

Adds media_post_platform (uuid PK, unique on media_id+post_platform_id,
cascadeOnDelete on both FKs) and PostPlatform::media()/scopedMediaItems(),
the single place publishers and validators will read per-platform media
selection from once the publisher rollout (A3) lands. An empty pivot set
for a platform means "all of the post's media", today's behavior, so
nothing else needs a special case.

Combines this with the media-id validation fix identified in TPX-03
(A0b): posts.media accepted a client-supplied id/path with no check that
it resolved to a real medias row owned by the caller's workspace, a
cross-tenant IDOR, plus an unhandled 500 on a non-UUID id. Both request
classes for create/update (web + API) now validate via
PostMediaRules::assertHostedMediaExists. Ported the 11 tests from
PR #8 (TPX-03 reference branch) plus 8 new tests for the pivot/relation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… test

Review round 1 feedback (2 independent code-reviewer passes): the
composite-key pivot post_workspace_label has the identical many-to-many
shape and works fine with a composite PK, so "no natural single key"
overstated the uuid-PK justification. Rewrote both docblocks around the
actual reason (row addressability for debugging), the unique index still
does the uniqueness enforcement.

Also closes a minor coverage gap: no test asserted the collection ===
'assets' boundary in assertHostedMediaExists (a real workspace-owned
medias row in the wrong collection, e.g. a logo, must still 422).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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