fix(permissions): address code-review findings
- store(): members without connected accounts no longer get redirected into
the now-admin-only /accounts (403); non-managers go to the calendar with
the same flash, admins still go to /accounts
- cover the SyncPostPlatforms can('update') gate (viewer creates no platform
rows; member does) and the store redirect split, in WorkspaceRolePermissions
- cover PostPolicy::duplicate viewer-denied
- docs sidebar link uses the canonical https://docs.trypost.it
- drop orphaned sidebar.support.{discord,last_updates} keys in all locales
- remove the explanatory isLocked comment in Edit.vue
This commit is contained in:
parent
d2ede9350f
commit
dc29d4dd48
8 changed files with 45 additions and 11 deletions
|
|
@ -180,7 +180,9 @@ public function store(StorePostRequest $request): RedirectResponse|\Symfony\Comp
|
|||
session()->flash('flash.banner', __('posts.flash.connect_first'));
|
||||
session()->flash('flash.bannerStyle', 'danger');
|
||||
|
||||
return redirect()->route('app.accounts');
|
||||
return $request->user()->can('manageAccounts', $workspace)
|
||||
? redirect()->route('app.accounts')
|
||||
: redirect()->route('app.calendar');
|
||||
}
|
||||
|
||||
$post = CreatePost::execute($workspace, $request->user(), [
|
||||
|
|
|
|||
|
|
@ -54,8 +54,6 @@
|
|||
'no_notifications' => 'No notifications',
|
||||
|
||||
'support' => [
|
||||
'discord' => 'Discord',
|
||||
'last_updates' => 'Last Updates',
|
||||
'docs' => 'Documentation',
|
||||
'referral' => 'Earn 30% referral',
|
||||
'stay_updated' => 'Stay updated',
|
||||
|
|
|
|||
|
|
@ -54,8 +54,6 @@
|
|||
'no_notifications' => 'Sin notificaciones',
|
||||
|
||||
'support' => [
|
||||
'discord' => 'Discord',
|
||||
'last_updates' => 'Últimas actualizaciones',
|
||||
'docs' => 'Documentación',
|
||||
'referral' => 'Gana 30% de comisión',
|
||||
'stay_updated' => 'Mantente al día',
|
||||
|
|
|
|||
|
|
@ -54,8 +54,6 @@
|
|||
'no_notifications' => 'Sem notificações',
|
||||
|
||||
'support' => [
|
||||
'discord' => 'Discord',
|
||||
'last_updates' => 'Últimas Atualizações',
|
||||
'docs' => 'Documentação',
|
||||
'referral' => 'Ganhe 30% de indicação',
|
||||
'stay_updated' => 'Fique por dentro',
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ const supportNavItems = computed(() => [
|
|||
},
|
||||
{
|
||||
title: trans('sidebar.support.docs'),
|
||||
href: 'https://trypost.it/docs',
|
||||
href: 'https://docs.trypost.it',
|
||||
icon: IconLifebuoy,
|
||||
},
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -103,8 +103,6 @@ const READONLY_STATUSES: readonly string[] = [
|
|||
const isReadOnly = computed(() => READONLY_STATUSES.includes(post.value.status));
|
||||
const isPublishing = computed(() => post.value.status === PostStatus.Publishing);
|
||||
const isScheduled = computed(() => post.value.status === PostStatus.Scheduled);
|
||||
// Locked states — terminal + scheduled, plus viewers who can only comment.
|
||||
// Field edits and auto-save are suppressed.
|
||||
const isLocked = computed(() => isReadOnly.value || isScheduled.value || !canCreatePost.value);
|
||||
|
||||
// Content
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
use App\Enums\Post\Status;
|
||||
use App\Enums\UserWorkspace\Role;
|
||||
use App\Models\Post;
|
||||
use App\Models\SocialAccount;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
|
||||
|
|
@ -95,3 +96,41 @@
|
|||
'member' => ['member', false],
|
||||
'viewer' => ['viewer', false],
|
||||
]);
|
||||
|
||||
test('opening the editor does not create platform rows for a viewer', function () {
|
||||
SocialAccount::factory()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$this->actingAs($this->viewer)
|
||||
->get(route('app.posts.edit', $this->post))
|
||||
->assertOk();
|
||||
|
||||
expect($this->post->postPlatforms()->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('opening the editor syncs platform rows for a member', function () {
|
||||
SocialAccount::factory()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$this->actingAs($this->member)
|
||||
->get(route('app.posts.edit', $this->post))
|
||||
->assertOk();
|
||||
|
||||
expect($this->post->postPlatforms()->count())->toBe(1);
|
||||
});
|
||||
|
||||
test('a member without connected accounts is redirected away from the admin-only accounts screen when creating a post', function () {
|
||||
$this->actingAs($this->member)
|
||||
->post(route('app.posts.store'))
|
||||
->assertRedirect(route('app.calendar'));
|
||||
});
|
||||
|
||||
test('an admin without connected accounts is sent to the accounts screen when creating a post', function () {
|
||||
$this->actingAs($this->admin)
|
||||
->post(route('app.posts.store'))
|
||||
->assertRedirect(route('app.accounts'));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -43,11 +43,12 @@ function postPolicyActor(string $role): array
|
|||
expect($this->policy->view($actor, $post))->toBeTrue();
|
||||
})->with(['owner', 'admin', 'member', 'viewer']);
|
||||
|
||||
test('post update/delete is allowed for member+ and denied for viewer', function (string $role, bool $allowed) {
|
||||
test('post update/delete/duplicate is allowed for member+ and denied for viewer', function (string $role, bool $allowed) {
|
||||
[$actor, $post] = postPolicyActor($role);
|
||||
|
||||
expect($this->policy->update($actor, $post))->toBe($allowed);
|
||||
expect($this->policy->delete($actor, $post))->toBe($allowed);
|
||||
expect($this->policy->duplicate($actor, $post))->toBe($allowed);
|
||||
})->with([
|
||||
'owner' => ['owner', true],
|
||||
'admin' => ['admin', true],
|
||||
|
|
|
|||
Loading…
Reference in a new issue