user(); abort_unless( $this->permissionService->allows($user, Permission::ProjectAdmin, $project), 403, ); $repositoryConnections = $project->repositoryConnections() ->get() ->each(function (RepositoryConnection $repositoryConnection) use ($user): void { $repositoryConnection->setAttribute( 'has_personal_access', $this->hasPersonalAccess($user, $repositoryConnection), ); }); return Inertia::render('projects/settings/Repositories', [ 'availableRepositorySources' => AvailableGitRepositorySourceResource::collection( $this->availableRepositorySources($user), )->resolve(), 'project' => ProjectResource::make($project)->resolve(), 'repositoryConnections' => RepositoryConnectionResource::collection($repositoryConnections)->resolve(), '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, * 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 = sprintf('%s could not load repositories from this Git connection right now.', config('app.name')); } 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); } }