feat: complete Epic 1 — team management & permission system

- Story 1.1: Permission enum, config, AuthorizesPermissions & HasWorkspaceScope traits, member→worker migration
- Story 1.2: Team page with member list, invitation system with queued email
- Story 1.3: Role assignment (Manager/Worker) and member removal with activity logging
- Story 1.4: Owner-only permission toggle matrix for Managers (manage team, view logs, configure portal)
- Story 1.5: Role-based access enforcement — Workers see only assigned declarations/clients, sidebar scoping
- Story 1.6: Workspace switcher dropdown for multi-workspace users with session-based switching
- 83 new/modified files, 182 tests passing with zero regressions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-18 00:12:50 +00:00
parent 5dffd2d063
commit c89d1879bf
83 changed files with 5850 additions and 314 deletions

View File

@@ -0,0 +1,71 @@
<?php
namespace App\Http\Requests;
use App\Enums\Permission;
use App\Enums\WorkspaceUserRole;
use Illuminate\Foundation\Http\FormRequest;
class UpdatePermissionsRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
* Only Owners can update permissions Managers with can_manage_team CANNOT.
*/
public function authorize(): bool
{
$workspaceUser = $this->user()->currentWorkspaceUser();
return $workspaceUser->role->is(WorkspaceUserRole::Owner);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'permissions' => ['required', 'array'],
'permissions.*' => ['boolean'],
];
}
/**
* Configure the validator instance.
*/
public function withValidator(\Illuminate\Validation\Validator $validator): void
{
$validator->after(function (\Illuminate\Validation\Validator $validator) {
$permissions = $this->input('permissions', []);
$validKeys = Permission::getValues();
foreach (array_keys($permissions) as $key) {
if (! in_array($key, $validKeys, true)) {
$validator->errors()->add(
'permissions',
"Invalid permission key: {$key}"
);
}
}
// Ensure ALL permission keys are present to prevent silent permission loss
$missingKeys = array_diff($validKeys, array_keys($permissions));
if (! empty($missingKeys)) {
$validator->errors()->add(
'permissions',
'Missing permission keys: '.implode(', ', $missingKeys)
);
}
});
}
/**
* Handle a failed authorization attempt.
*/
protected function failedAuthorization(): void
{
abort(404);
}
}