feat(media): per-platform media pivot + workspace-scoped media id validation - #9
Merged
Merged
Conversation
…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>
This was referenced Sep 16, 2026
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.
Summary
Paket A1 (Datenmodell: Medien pro Plattform) combined with the media-id
validation fix TPX-03 (A0b) identified as a blocker.
media_post_platformpivot table: uuid PK (not a plain composite-keypivot, since the same media item can be attached to more than one
platform of the same post),
media_id/post_platform_iduuid FKs tomedias/post_platforms, bothcascadeOnDelete, unique on(media_id, post_platform_id).App\Models\MediaPostPlatform: custom Pivot model usingHasUuidssothe id is generated on
attach()(which only fires model events when acustom pivot class is registered via
->using()).PostPlatform::media(): BelongsToMany+PostPlatform::scopedMediaItems(): Collection<MediaItem>: empty pivot set for a platform means "every mediaitem 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.
App\Support\PostMediaRules::assertHostedMediaExists()closes a cross-tenant IDOR where
CreatePost/UpdatePostwrote aclient-supplied
media.*.id/pathstraight intoposts.mediawith nocheck that it resolved to a real, workspace-owned
mediasrow. Alsofixes an unhandled Postgres 500 (
invalid input syntax for type uuid) ona non-UUID id, now a graceful 422. Wired into all four request classes
(App/Api Store/UpdatePostRequest).
PostMediaExistsValidationTest,PostMediaExistsValidationWebTest), updated 6 existing tests that usedfabricated 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)--filter=Postsuite: 975 passed, 0 regressions--filter=Mcpsuite (media MCP tools): 367 passed, 0 regressionsvendor/bin/pint --dirty --format agentclean🤖 Generated with Claude Code