Files
KanbanGit/app/Http/Controllers/ProjectRepositorySettingsController.php
T
2026-04-12 19:21:04 +02:00

311 lines
12 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Actions\Git\Data\GitRepository;
use App\Actions\Git\GitProviderManager;
use App\Actions\Permissions\PermissionService;
use App\Enums\Permission;
use App\Http\Requests\StoreRepositoryConnectionRequest;
use App\Http\Requests\UpdateRepositoryConnectionRequest;
use App\Models\Project;
use App\Models\RepositoryConnection;
use App\Models\User;
use App\Models\UserGitConnection;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Validation\ValidationException;
use Inertia\Inertia;
use Inertia\Response;
use Throwable;
class ProjectRepositorySettingsController extends Controller
{
public function __construct(
private readonly GitProviderManager $gitProviderManager,
private readonly PermissionService $permissionService,
) {}
public function show(Request $request, Project $project): Response
{
$user = $request->user();
abort_unless(
$this->permissionService->allows($user, Permission::ProjectAdmin, $project),
403,
);
return Inertia::render('projects/settings/Repositories', [
'availableRepositorySources' => $this->availableRepositorySources($user),
'project' => $project,
'repositoryConnections' => $project->repositoryConnections()
->get()
->map(fn (RepositoryConnection $repositoryConnection): array => [
'base_url' => $repositoryConnection->base_url,
'created_at' => $repositoryConnection->created_at?->toISOString(),
'has_personal_access' => $this->hasPersonalAccess($user, $repositoryConnection),
'id' => $repositoryConnection->id,
'owner_name' => $repositoryConnection->owner_name,
'project_id' => $repositoryConnection->project_id,
'provider' => $repositoryConnection->provider->value,
'provider_label' => $repositoryConnection->provider->label(),
'provider_repository_id' => $repositoryConnection->provider_repository_id,
'repository_full_name' => $repositoryConnection->repository_full_name,
'repository_name' => $repositoryConnection->repository_name,
'repository_url' => $repositoryConnection->repository_url,
'updated_at' => $repositoryConnection->updated_at?->toISOString(),
])
->values(),
'status' => session('status'),
]);
}
public function store(
StoreRepositoryConnectionRequest $request,
Project $project,
): RedirectResponse {
$user = $request->user();
abort_unless(
$this->permissionService->allows($user, Permission::ProjectAdmin, $project),
403,
);
$validated = $request->validated();
$userGitConnectionId = Arr::get($validated, 'user_git_connection_id');
$providerRepositoryId = Arr::get($validated, 'provider_repository_id');
$repositoryAttributes = $this->repositoryAttributesForSelection(
$user,
$userGitConnectionId,
$providerRepositoryId,
);
$this->ensureRepositoryConnectionIsUnique($project, $repositoryAttributes);
$repositoryConnection = $project->repositoryConnections()->create($repositoryAttributes);
return to_route('projects.settings.repositories', $project)
->with('status', "{$repositoryConnection->repository_full_name} was linked to {$project->name}.");
}
public function update(
UpdateRepositoryConnectionRequest $request,
Project $project,
RepositoryConnection $repositoryConnection,
): RedirectResponse {
$user = $request->user();
abort_unless(
$this->permissionService->allows($user, Permission::ProjectAdmin, $project),
403,
);
$validated = $request->validated();
$userGitConnectionId = Arr::get($validated, 'user_git_connection_id');
$providerRepositoryId = Arr::get($validated, 'provider_repository_id');
$repositoryAttributes = $this->repositoryAttributesForSelection(
$user,
$userGitConnectionId,
$providerRepositoryId,
);
$this->ensureRepositoryConnectionIsUnique(
$project,
$repositoryAttributes,
$repositoryConnection,
);
$repositoryConnection->update($repositoryAttributes);
return to_route('projects.settings.repositories', $project)
->with('status', "{$repositoryConnection->fresh()->repository_full_name} connection was updated.");
}
public function destroy(
Request $request,
Project $project,
RepositoryConnection $repositoryConnection,
): RedirectResponse {
abort_unless(
$this->permissionService->allows($request->user(), Permission::ProjectAdmin, $project),
403,
);
$repositoryLabel = $repositoryConnection->repository_full_name;
$repositoryConnection->delete();
return to_route('projects.settings.repositories', $project)
->with('status', "{$repositoryLabel} was removed from the project.");
}
/**
* @return array<int, array{
* account_login: string,
* account_name: string|null,
* base_url: string,
* error: string|null,
* provider: string,
* provider_label: string,
* repositories: array<int, array{
* full_name: string,
* owner_name: string,
* provider_repository_id: string,
* repository_name: string,
* repository_url: string,
* visibility_label: string
* }>,
* user_git_connection_id: int
* }>
*/
private function availableRepositorySources(User $user): array
{
/** @var Collection<int, UserGitConnection> $gitConnections */
$gitConnections = $user->gitConnections()
->get();
return $gitConnections
->map(function (UserGitConnection $gitConnection): array {
try {
$repositories = collect(
$this->gitProviderManager
->client($gitConnection->provider)
->listRepositories($gitConnection->base_url, $gitConnection->api_token),
)
->map(fn (GitRepository $repository): array => [
'full_name' => $repository->fullName,
'owner_name' => $repository->owner,
'provider_repository_id' => $repository->providerRepositoryId,
'repository_name' => $repository->name,
'repository_url' => $repository->htmlUrl,
'visibility_label' => $repository->isPrivate ? 'Private' : 'Public',
])
->sortBy('full_name')
->values()
->all();
$error = null;
} catch (Throwable) {
$repositories = [];
$error = 'KanbanGit could not load repositories from this Git connection right now.';
}
return [
'account_login' => $gitConnection->account_login,
'account_name' => $gitConnection->account_name,
'base_url' => $gitConnection->base_url,
'error' => $error,
'provider' => $gitConnection->provider->value,
'provider_label' => $gitConnection->provider->label(),
'repositories' => $repositories,
'user_git_connection_id' => $gitConnection->id,
];
})
->values()
->all();
}
/**
* @param array{
* base_url: string,
* owner_name: string,
* provider: string,
* provider_repository_id: string,
* repository_full_name: string,
* repository_name: string,
* repository_url: string
* } $repositoryAttributes
*/
private function ensureRepositoryConnectionIsUnique(
Project $project,
array $repositoryAttributes,
?RepositoryConnection $repositoryConnection = null,
): void {
$query = $project->repositoryConnections()
->where('provider', $repositoryAttributes['provider'])
->where('base_url', $repositoryAttributes['base_url'])
->where('provider_repository_id', $repositoryAttributes['provider_repository_id']);
if ($repositoryConnection !== null) {
$query->whereKeyNot($repositoryConnection->id);
}
if ($query->exists()) {
throw ValidationException::withMessages([
'provider_repository_id' => 'This repository is already linked to the project.',
]);
}
}
private function hasPersonalAccess(User $user, RepositoryConnection $repositoryConnection): bool
{
return $user->gitConnections()
->where('provider', $repositoryConnection->provider)
->where('base_url', $repositoryConnection->base_url)
->exists();
}
/**
* @return array{
* base_url: string,
* owner_name: string,
* provider: string,
* provider_repository_id: string,
* repository_full_name: string,
* repository_name: string,
* repository_url: string
* }
*/
private function repositoryAttributesForSelection(
User $user,
int $userGitConnectionId,
string $providerRepositoryId,
): array {
$userGitConnection = $this->ownedUserGitConnection($user, $userGitConnectionId);
$repository = $this->repositoryFromConnection(
$userGitConnection,
$providerRepositoryId,
);
if ($repository === null) {
throw ValidationException::withMessages([
'provider_repository_id' => 'The selected repository is not available through this Git connection.',
]);
}
return [
'base_url' => $userGitConnection->base_url,
'owner_name' => $repository->owner,
'provider' => $userGitConnection->provider,
'provider_repository_id' => $repository->providerRepositoryId,
'repository_full_name' => $repository->fullName,
'repository_name' => $repository->name,
'repository_url' => $repository->htmlUrl,
];
}
private function ownedUserGitConnection(User $user, int $userGitConnectionId): UserGitConnection
{
$userGitConnection = UserGitConnection::query()->findOrFail($userGitConnectionId);
abort_unless($userGitConnection->user_id === $user->id, 403);
return $userGitConnection;
}
private function repositoryFromConnection(
UserGitConnection $userGitConnection,
string $providerRepositoryId,
): ?GitRepository {
return collect(
$this->gitProviderManager
->client($userGitConnection->provider)
->listRepositories($userGitConnection->base_url, $userGitConnection->api_token),
)
->first(fn (GitRepository $repository): bool => $repository->providerRepositoryId === $providerRepositoryId);
}
}