248 lines
7.5 KiB
PHP
248 lines
7.5 KiB
PHP
<?php
|
|
|
|
namespace App\Actions\ProjectPermissions;
|
|
|
|
use App\Models\Project;
|
|
use App\Models\Team;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
|
|
class ProjectPermissionService
|
|
{
|
|
/**
|
|
* @var array<int, string>
|
|
*/
|
|
public const PERMISSIONS = [
|
|
'create',
|
|
'view',
|
|
'edit',
|
|
'delete',
|
|
];
|
|
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
public static function permissions(): array
|
|
{
|
|
return self::PERMISSIONS;
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array{label: string, value: string}>
|
|
*/
|
|
public function permissionOptions(): array
|
|
{
|
|
return collect(self::PERMISSIONS)
|
|
->map(fn (string $permission): array => [
|
|
'label' => Str::headline($permission),
|
|
'value' => $permission,
|
|
])
|
|
->all();
|
|
}
|
|
|
|
public function grantAllProjectPermissions(Project $project, User $user): void
|
|
{
|
|
$this->syncUserPermissions($project, $user, self::PERMISSIONS);
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $permissions
|
|
*/
|
|
public function syncUserPermissions(Project $project, User $user, array $permissions): void
|
|
{
|
|
$sanitizedPermissions = $this->sanitizePermissions($permissions);
|
|
|
|
DB::transaction(function () use ($project, $user, $sanitizedPermissions): void {
|
|
DB::table('project_user_permissions')
|
|
->where('project_id', $project->id)
|
|
->where('user_id', $user->id)
|
|
->delete();
|
|
|
|
if ($sanitizedPermissions === []) {
|
|
return;
|
|
}
|
|
|
|
DB::table('project_user_permissions')->insert(
|
|
array_map(
|
|
fn (string $permission): array => [
|
|
'project_id' => $project->id,
|
|
'user_id' => $user->id,
|
|
'permission' => $permission,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
],
|
|
$sanitizedPermissions,
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $permissions
|
|
*/
|
|
public function syncTeamPermissions(Project $project, Team $team, array $permissions): void
|
|
{
|
|
$sanitizedPermissions = $this->sanitizePermissions($permissions);
|
|
|
|
DB::transaction(function () use ($project, $team, $sanitizedPermissions): void {
|
|
DB::table('project_team_permissions')
|
|
->where('project_id', $project->id)
|
|
->where('team_id', $team->id)
|
|
->delete();
|
|
|
|
if ($sanitizedPermissions === []) {
|
|
return;
|
|
}
|
|
|
|
DB::table('project_team_permissions')->insert(
|
|
array_map(
|
|
fn (string $permission): array => [
|
|
'project_id' => $project->id,
|
|
'team_id' => $team->id,
|
|
'permission' => $permission,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
],
|
|
$sanitizedPermissions,
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param array<int, int> $userIds
|
|
*/
|
|
public function syncTeamMembers(Team $team, array $userIds): void
|
|
{
|
|
$team->users()->sync($userIds);
|
|
}
|
|
|
|
public function userHasPermission(User $user, Project $project, string $permission): bool
|
|
{
|
|
if ($user->is_admin) {
|
|
return true;
|
|
}
|
|
|
|
$sanitizedPermission = Str::lower(trim($permission));
|
|
|
|
if (! in_array($sanitizedPermission, self::PERMISSIONS, true)) {
|
|
return false;
|
|
}
|
|
|
|
$hasDirectPermission = DB::table('project_user_permissions')
|
|
->where('project_id', $project->id)
|
|
->where('user_id', $user->id)
|
|
->where('permission', $sanitizedPermission)
|
|
->exists();
|
|
|
|
if ($hasDirectPermission) {
|
|
return true;
|
|
}
|
|
|
|
return DB::table('project_team_permissions')
|
|
->join('team_user', 'team_user.team_id', '=', 'project_team_permissions.team_id')
|
|
->where('project_team_permissions.project_id', $project->id)
|
|
->where('team_user.user_id', $user->id)
|
|
->where('project_team_permissions.permission', $sanitizedPermission)
|
|
->exists();
|
|
}
|
|
|
|
public function userCanViewProject(User $user, Project $project): bool
|
|
{
|
|
if ($user->is_admin) {
|
|
return true;
|
|
}
|
|
|
|
return in_array($project->id, $this->viewableProjectIds($user), true);
|
|
}
|
|
|
|
public function userCanManageProjectAccess(User $user, Project $project): bool
|
|
{
|
|
return $this->userHasPermission($user, $project, 'edit');
|
|
}
|
|
|
|
/**
|
|
* @return array<int, int>
|
|
*/
|
|
public function viewableProjectIds(User $user): array
|
|
{
|
|
if ($user->is_admin) {
|
|
return Project::query()->pluck('id')->all();
|
|
}
|
|
|
|
$directProjectIds = DB::table('project_user_permissions')
|
|
->where('user_id', $user->id)
|
|
->pluck('project_id');
|
|
|
|
$teamProjectIds = DB::table('project_team_permissions')
|
|
->join('team_user', 'team_user.team_id', '=', 'project_team_permissions.team_id')
|
|
->where('team_user.user_id', $user->id)
|
|
->pluck('project_team_permissions.project_id');
|
|
|
|
return $directProjectIds
|
|
->merge($teamProjectIds)
|
|
->map(fn (mixed $projectId): int => (int) $projectId)
|
|
->unique()
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
/**
|
|
* @return array<string, array<int, string>>
|
|
*/
|
|
public function userPermissionMap(Project $project): array
|
|
{
|
|
/** @var Collection<int, object{user_id: int, permission: string}> $permissions */
|
|
$permissions = DB::table('project_user_permissions')
|
|
->select(['user_id', 'permission'])
|
|
->where('project_id', $project->id)
|
|
->orderBy('permission')
|
|
->get();
|
|
|
|
return $permissions
|
|
->groupBy(fn (object $permission): int => (int) $permission->user_id)
|
|
->map(fn (Collection $group): array => $group
|
|
->pluck('permission')
|
|
->values()
|
|
->all())
|
|
->all();
|
|
}
|
|
|
|
/**
|
|
* @return array<string, array<int, string>>
|
|
*/
|
|
public function teamPermissionMap(Project $project): array
|
|
{
|
|
/** @var Collection<int, object{team_id: int, permission: string}> $permissions */
|
|
$permissions = DB::table('project_team_permissions')
|
|
->select(['team_id', 'permission'])
|
|
->where('project_id', $project->id)
|
|
->orderBy('permission')
|
|
->get();
|
|
|
|
return $permissions
|
|
->groupBy(fn (object $permission): int => (int) $permission->team_id)
|
|
->map(fn (Collection $group): array => $group
|
|
->pluck('permission')
|
|
->values()
|
|
->all())
|
|
->all();
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $permissions
|
|
* @return array<int, string>
|
|
*/
|
|
private function sanitizePermissions(array $permissions): array
|
|
{
|
|
return collect($permissions)
|
|
->map(fn (mixed $permission): string => Str::lower(trim((string) $permission)))
|
|
->filter(fn (string $permission): bool => in_array($permission, self::PERMISSIONS, true))
|
|
->unique()
|
|
->values()
|
|
->all();
|
|
}
|
|
}
|