projectPermissionService->userCanManageProjectAccess($request->user(), $project), 403, ); return Inertia::render('projects/settings/Repositories', [ 'availableRepositorySources' => $this->availableRepositorySources($request->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($request->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 { abort_unless( $this->projectPermissionService->userCanManageProjectAccess($request->user(), $project), 403, ); $repositoryAttributes = $this->repositoryAttributesForSelection( $request->user(), $request->userGitConnectionId(), $request->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 { abort_unless( $this->projectPermissionService->userCanManageProjectAccess($request->user(), $project), 403, ); $repositoryAttributes = $this->repositoryAttributesForSelection( $request->user(), $request->userGitConnectionId(), $request->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, * user_git_connection_id: int * }> */ private function availableRepositorySources(User $user): array { /** @var Collection $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); } }