diff --git a/app/Http/Controllers/App/PostController.php b/app/Http/Controllers/App/PostController.php index 65576b9c..5dff497b 100644 --- a/app/Http/Controllers/App/PostController.php +++ b/app/Http/Controllers/App/PostController.php @@ -212,7 +212,8 @@ public function show(Request $request, Post $post): Response|RedirectResponse $this->authorize('view', $post); - if (in_array($post->status, [PostStatus::Draft, PostStatus::Scheduled], true)) { + if ($request->user()->can('update', $post) + && in_array($post->status, [PostStatus::Draft, PostStatus::Scheduled], true)) { return redirect()->route('app.posts.edit', $post); } diff --git a/resources/js/pages/posts/Calendar.vue b/resources/js/pages/posts/Calendar.vue index 4e2c04e6..162b45c2 100644 --- a/resources/js/pages/posts/Calendar.vue +++ b/resources/js/pages/posts/Calendar.vue @@ -237,7 +237,7 @@ const getStatusColor = (status: string): string => { const EDITABLE_STATUSES: readonly string[] = [PostStatus.Draft, PostStatus.Scheduled]; const getPostUrl = (post: Post): string => { - return EDITABLE_STATUSES.includes(post.status) + return canCreatePost.value && EDITABLE_STATUSES.includes(post.status) ? editPost.url(post.id) : showPost.url(post.id); }; diff --git a/resources/js/pages/posts/Index.vue b/resources/js/pages/posts/Index.vue index ab7ec15e..26f23204 100644 --- a/resources/js/pages/posts/Index.vue +++ b/resources/js/pages/posts/Index.vue @@ -143,13 +143,13 @@ const DELETABLE_STATUSES: readonly string[] = [PostStatus.Draft, PostStatus.Sche const canEdit = (post: Post): boolean => EDITABLE_STATUSES.includes(post.status); const canDelete = (post: Post): boolean => DELETABLE_STATUSES.includes(post.status); +const { canCreatePost } = useWorkspaceRole(); + const postUrl = (post: Post): string => - canEdit(post) ? editPost.url(post.id) : showPost.url(post.id); + canCreatePost.value && canEdit(post) ? editPost.url(post.id) : showPost.url(post.id); const deleteModal = ref | null>(null); -const { canCreatePost } = useWorkspaceRole(); - const handleDelete = (post: Post) => { deleteModal.value?.open({ url: destroyPost.url(post.id), diff --git a/tests/Feature/Permissions/WorkspaceRolePermissionsTest.php b/tests/Feature/Permissions/WorkspaceRolePermissionsTest.php index 296d7ae6..16165df3 100644 --- a/tests/Feature/Permissions/WorkspaceRolePermissionsTest.php +++ b/tests/Feature/Permissions/WorkspaceRolePermissionsTest.php @@ -60,3 +60,21 @@ 'user_id' => $this->viewer->id, ]); }); + +test('a viewer opening a draft post sees the read-only page instead of the editor', function () { + $this->actingAs($this->viewer) + ->get(route('app.posts.show', $this->post)) + ->assertOk(); +}); + +test('a member opening a draft post is redirected to the editor', function () { + $this->actingAs($this->member) + ->get(route('app.posts.show', $this->post)) + ->assertRedirect(route('app.posts.edit', $this->post)); +}); + +test('a viewer cannot open the post editor directly', function () { + $this->actingAs($this->viewer) + ->get(route('app.posts.edit', $this->post)) + ->assertForbidden(); +});