311 lines
12 KiB
PHP
311 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Actions\Git\Data\GitRepository;
|
|
use App\Actions\Git\GitProviderManager;
|
|
use App\Actions\ProjectPermissions\ProjectPermissionService;
|
|
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 ProjectPermissionService $projectPermissionService,
|
|
) {}
|
|
|
|
public function show(Request $request, Project $project): Response
|
|
{
|
|
$user = $request->user();
|
|
abort_unless(
|
|
$this->projectPermissionService->userCanManageProjectAccess($user, $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->projectPermissionService->userCanManageProjectAccess($user, $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->projectPermissionService->userCanManageProjectAccess($user, $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->projectPermissionService->userCanManageProjectAccess($request->user(), $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);
|
|
}
|
|
}
|