mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-14 19:22:14 +01:00
Add roles for project members; Restrict access to sensitive project values
This commit is contained in:
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Enums\ProjectMemberRole;
|
||||
use App\Exceptions\Api\EntityStillInUseApiException;
|
||||
use App\Http\Requests\V1\Project\ProjectIndexRequest;
|
||||
use App\Http\Requests\V1\Project\ProjectStoreRequest;
|
||||
@@ -15,6 +16,8 @@ use App\Models\Project;
|
||||
use App\Models\ProjectMember;
|
||||
use App\Service\BillableRateService;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Carbon;
|
||||
@@ -50,6 +53,12 @@ class ProjectController extends Controller
|
||||
|
||||
if (! $canViewAllProjects) {
|
||||
$projectsQuery->visibleByEmployee($user);
|
||||
$projectsQuery->with([
|
||||
'members' => function (HasMany $query): void {
|
||||
/** @var Builder<ProjectMember> $query */
|
||||
$query->whereBelongsTo($this->user(), 'user');
|
||||
},
|
||||
]);
|
||||
}
|
||||
$filterArchived = $request->getFilterArchived();
|
||||
if ($filterArchived === 'true') {
|
||||
@@ -60,6 +69,14 @@ class ProjectController extends Controller
|
||||
|
||||
$projects = $projectsQuery->paginate(config('app.pagination_per_page_default'));
|
||||
|
||||
foreach ($projects->items() as $project) {
|
||||
if ($canViewAllProjects) {
|
||||
$project->setAttribute('limited_visibility', false);
|
||||
} else {
|
||||
$project->setAttribute('limited_visibility', $project->members->firstWhere('user_id', $this->user()->id)?->role !== ProjectMemberRole::Manager);
|
||||
}
|
||||
}
|
||||
|
||||
return new ProjectCollection($projects);
|
||||
}
|
||||
|
||||
@@ -73,6 +90,26 @@ class ProjectController extends Controller
|
||||
public function show(Organization $organization, Project $project): JsonResource
|
||||
{
|
||||
$this->checkPermission($organization, 'projects:view', $project);
|
||||
$canViewAllProjects = $this->hasPermission($organization, 'projects:view:all');
|
||||
|
||||
$project->load([
|
||||
'members' => function (HasMany $query): void {
|
||||
/** @var Builder<ProjectMember> $query */
|
||||
$query->whereBelongsTo($this->user(), 'user');
|
||||
},
|
||||
]);
|
||||
|
||||
if (! $canViewAllProjects) {
|
||||
if (! $project->is_public && $project->members->firstWhere('user_id', '=', $this->user()->id) === null) {
|
||||
throw new AuthorizationException('No access to project');
|
||||
}
|
||||
}
|
||||
|
||||
if ($canViewAllProjects) {
|
||||
$project->setAttribute('limited_visibility', false);
|
||||
} else {
|
||||
$project->setAttribute('limited_visibility', $project->members->firstWhere('user_id', $this->user()->id)?->role !== ProjectMemberRole::Manager);
|
||||
}
|
||||
|
||||
$project->load('organization');
|
||||
|
||||
@@ -101,6 +138,8 @@ class ProjectController extends Controller
|
||||
$project->organization()->associate($organization);
|
||||
$project->save();
|
||||
|
||||
$project->setAttribute('limited_visibility', false);
|
||||
|
||||
return new ProjectResource($project);
|
||||
}
|
||||
|
||||
@@ -132,6 +171,8 @@ class ProjectController extends Controller
|
||||
$billableRateService->updateTimeEntriesBillableRateForProject($project);
|
||||
}
|
||||
|
||||
$project->setAttribute('limited_visibility', false);
|
||||
|
||||
return new ProjectResource($project);
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ class ProjectMemberController extends Controller
|
||||
}
|
||||
|
||||
$projectMember = new ProjectMember;
|
||||
$projectMember->role = $request->getRole();
|
||||
$projectMember->billable_rate = $request->getBillableRate();
|
||||
$projectMember->member()->associate($member);
|
||||
$projectMember->user()->associate($member->user);
|
||||
@@ -95,11 +96,17 @@ class ProjectMemberController extends Controller
|
||||
public function update(Organization $organization, ProjectMember $projectMember, ProjectMemberUpdateRequest $request, BillableRateService $billableRateService): JsonResource
|
||||
{
|
||||
$this->checkPermission($organization, 'project-members:update', projectMember: $projectMember);
|
||||
$oldBillableRate = $projectMember->billable_rate;
|
||||
$projectMember->billable_rate = $request->getBillableRate();
|
||||
$hasBillableRate = $request->has('billable_rate');
|
||||
if ($hasBillableRate) {
|
||||
$oldBillableRate = $projectMember->billable_rate;
|
||||
$projectMember->billable_rate = $request->getBillableRate();
|
||||
}
|
||||
if ($request->getRole() !== null) {
|
||||
$projectMember->role = $request->getRole();
|
||||
}
|
||||
$projectMember->save();
|
||||
|
||||
if ($oldBillableRate !== $request->getBillableRate()) {
|
||||
if ($hasBillableRate && $oldBillableRate !== $request->getBillableRate()) {
|
||||
$billableRateService->updateTimeEntriesBillableRateForProjectMember($projectMember);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\V1\ProjectMember;
|
||||
|
||||
use App\Enums\ProjectMemberRole;
|
||||
use App\Models\Member;
|
||||
use App\Models\Organization;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Korridor\LaravelModelValidationRules\Rules\ExistsEloquent;
|
||||
|
||||
/**
|
||||
@@ -19,7 +21,7 @@ class ProjectMemberStoreRequest extends FormRequest
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string|ValidationRule>>
|
||||
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
@@ -37,6 +39,11 @@ class ProjectMemberStoreRequest extends FormRequest
|
||||
'integer',
|
||||
'min:0',
|
||||
],
|
||||
'role' => [
|
||||
'required',
|
||||
'string',
|
||||
Rule::enum(ProjectMemberRole::class),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -46,4 +53,9 @@ class ProjectMemberStoreRequest extends FormRequest
|
||||
|
||||
return $input !== null && $input !== 0 ? (int) $this->input('billable_rate') : null;
|
||||
}
|
||||
|
||||
public function getRole(): ProjectMemberRole
|
||||
{
|
||||
return ProjectMemberRole::from($this->validated('role'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\V1\ProjectMember;
|
||||
|
||||
use App\Enums\ProjectMemberRole;
|
||||
use App\Models\Organization;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* @property Organization $organization Organization from model binding
|
||||
@@ -16,7 +18,7 @@ class ProjectMemberUpdateRequest extends FormRequest
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string|ValidationRule>>
|
||||
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
@@ -26,6 +28,10 @@ class ProjectMemberUpdateRequest extends FormRequest
|
||||
'integer',
|
||||
'min:0',
|
||||
],
|
||||
'role' => [
|
||||
'string',
|
||||
Rule::enum(ProjectMemberRole::class),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -33,6 +39,11 @@ class ProjectMemberUpdateRequest extends FormRequest
|
||||
{
|
||||
$input = $this->input('billable_rate');
|
||||
|
||||
return $input !== null && $input !== 0 ? (int) $this->input('billable_rate') : null;
|
||||
return $input !== null && ((int) $input) !== 0 ? (int) $this->validated('billable_rate') : null;
|
||||
}
|
||||
|
||||
public function getRole(): ?ProjectMemberRole
|
||||
{
|
||||
return $this->has('role') ? ProjectMemberRole::from($this->validated('role')) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ class ProjectResource extends BaseResource
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
$limitedVisibility = is_bool($this->resource->getAttributeValue('limited_visibility')) ? $this->resource->getAttributeValue('limited_visibility') : true;
|
||||
|
||||
return [
|
||||
/** @var string $id ID of project */
|
||||
'id' => $this->resource->id,
|
||||
@@ -32,13 +34,15 @@ class ProjectResource extends BaseResource
|
||||
/** @var bool $is_archived Whether the client is archived */
|
||||
'is_archived' => $this->resource->is_archived,
|
||||
/** @var int|null $billable_rate Billable rate in cents per hour */
|
||||
'billable_rate' => $this->resource->billable_rate,
|
||||
'billable_rate' => $limitedVisibility ? null : $this->resource->billable_rate,
|
||||
/** @var bool $is_billable Project time entries billable default */
|
||||
'is_billable' => $this->resource->is_billable,
|
||||
/** @var int|null $estimated_time Estimated time in seconds */
|
||||
'estimated_time' => $this->resource->estimated_time,
|
||||
'estimated_time' => $limitedVisibility ? null : $this->resource->estimated_time,
|
||||
/** @var int $spent_time Spent time on this project in seconds (sum of the duration of all associated time entries, excl. still running time entries) */
|
||||
'spent_time' => $this->resource->spent_time,
|
||||
'spent_time' => $limitedVisibility ? null : $this->resource->spent_time,
|
||||
/** @var bool $limited_visibility */
|
||||
'limited_visibility' => $limitedVisibility,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user