Add application API and OpenAPI docs
linter / quality (pull_request) Successful in 1m18s
tests / ci (8.4) (pull_request) Successful in 1m28s

This commit is contained in:
2026-04-20 13:22:56 +02:00
parent 70d760e3a6
commit 8b25421e7c
80 changed files with 6908 additions and 1277 deletions
@@ -0,0 +1,120 @@
<?php
namespace App\Actions\Repositories;
use App\Actions\Git\Data\GitRepository;
use App\Actions\Git\GitProviderManager;
use App\Http\Resources\AvailableGitRepositorySourceResource;
use App\Http\Resources\ProjectResource;
use App\Http\Resources\RepositoryConnectionResource;
use App\Models\Project;
use App\Models\RepositoryConnection;
use App\Models\User;
use App\Models\UserGitConnection;
use Illuminate\Support\Collection;
use Throwable;
class BuildProjectRepositorySettingsPayload
{
public function __construct(
private readonly GitProviderManager $gitProviderManager,
) {}
/**
* @return array<string, mixed>
*/
public function handle(Project $project, User $user, mixed $status = null): array
{
$repositoryConnections = $project->repositoryConnections()
->get()
->each(function (RepositoryConnection $repositoryConnection) use ($user): void {
$repositoryConnection->setAttribute(
'has_personal_access',
$this->hasPersonalAccess($user, $repositoryConnection),
);
});
return [
'availableRepositorySources' => AvailableGitRepositorySourceResource::collection(
$this->availableRepositorySources($user),
)->resolve(),
'project' => ProjectResource::make($project)->resolve(),
'repositoryConnections' => RepositoryConnectionResource::collection($repositoryConnections)->resolve(),
'status' => $status,
];
}
/**
* @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
* }>
*/
public 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 = 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();
}
public function hasPersonalAccess(User $user, RepositoryConnection $repositoryConnection): bool
{
return $user->gitConnections()
->where('provider', $repositoryConnection->provider)
->where('base_url', $repositoryConnection->base_url)
->exists();
}
}
@@ -0,0 +1,17 @@
<?php
namespace App\Actions\Repositories;
use App\Models\RepositoryConnection;
class DeleteProjectRepositoryConnection
{
public function handle(RepositoryConnection $repositoryConnection): string
{
$repositoryLabel = $repositoryConnection->repository_full_name;
$repositoryConnection->delete();
return $repositoryLabel;
}
}
@@ -0,0 +1,70 @@
<?php
namespace App\Actions\Repositories;
use App\Actions\Git\Data\GitRepository;
use App\Actions\Git\GitProviderManager;
use App\Models\User;
use App\Models\UserGitConnection;
use Illuminate\Validation\ValidationException;
class ResolveRepositorySelectionAttributes
{
public function __construct(
private readonly GitProviderManager $gitProviderManager,
) {}
/**
* @return array{
* base_url: string,
* owner_name: string,
* provider: mixed,
* provider_repository_id: string,
* repository_full_name: string,
* repository_name: string,
* repository_url: string
* }
*/
public function handle(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);
}
}
@@ -0,0 +1,59 @@
<?php
namespace App\Actions\Repositories;
use App\Models\Project;
use App\Models\RepositoryConnection;
use App\Models\User;
use Illuminate\Support\Arr;
use Illuminate\Validation\ValidationException;
class StoreProjectRepositoryConnection
{
public function __construct(
private readonly ResolveRepositorySelectionAttributes $resolveRepositorySelectionAttributes,
) {}
/**
* @param array{provider_repository_id: string, user_git_connection_id: int} $validated
*/
public function handle(Project $project, User $user, array $validated): RepositoryConnection
{
$repositoryAttributes = $this->resolveRepositorySelectionAttributes->handle(
$user,
(int) Arr::get($validated, 'user_git_connection_id'),
(string) Arr::get($validated, 'provider_repository_id'),
);
$this->ensureRepositoryConnectionIsUnique($project, $repositoryAttributes);
return $project->repositoryConnections()->create($repositoryAttributes);
}
/**
* @param array{
* base_url: string,
* owner_name: string,
* provider: mixed,
* provider_repository_id: string,
* repository_full_name: string,
* repository_name: string,
* repository_url: string
* } $repositoryAttributes
*/
private function ensureRepositoryConnectionIsUnique(
Project $project,
array $repositoryAttributes,
): void {
$query = $project->repositoryConnections()
->where('provider', $repositoryAttributes['provider'])
->where('base_url', $repositoryAttributes['base_url'])
->where('provider_repository_id', $repositoryAttributes['provider_repository_id']);
if ($query->exists()) {
throw ValidationException::withMessages([
'provider_repository_id' => 'This repository is already linked to the project.',
]);
}
}
}
@@ -0,0 +1,67 @@
<?php
namespace App\Actions\Repositories;
use App\Models\Project;
use App\Models\RepositoryConnection;
use App\Models\User;
use Illuminate\Support\Arr;
use Illuminate\Validation\ValidationException;
class UpdateProjectRepositoryConnection
{
public function __construct(
private readonly ResolveRepositorySelectionAttributes $resolveRepositorySelectionAttributes,
) {}
/**
* @param array{provider_repository_id: string, user_git_connection_id: int} $validated
*/
public function handle(
Project $project,
RepositoryConnection $repositoryConnection,
User $user,
array $validated,
): RepositoryConnection {
$repositoryAttributes = $this->resolveRepositorySelectionAttributes->handle(
$user,
(int) Arr::get($validated, 'user_git_connection_id'),
(string) Arr::get($validated, 'provider_repository_id'),
);
$this->ensureRepositoryConnectionIsUnique($project, $repositoryAttributes, $repositoryConnection);
$repositoryConnection->update($repositoryAttributes);
return $repositoryConnection->fresh();
}
/**
* @param array{
* base_url: string,
* owner_name: string,
* provider: mixed,
* provider_repository_id: string,
* repository_full_name: string,
* repository_name: string,
* repository_url: string
* } $repositoryAttributes
*/
private function ensureRepositoryConnectionIsUnique(
Project $project,
array $repositoryAttributes,
RepositoryConnection $repositoryConnection,
): void {
$query = $project->repositoryConnections()
->where('provider', $repositoryAttributes['provider'])
->where('base_url', $repositoryAttributes['base_url'])
->where('provider_repository_id', $repositoryAttributes['provider_repository_id'])
->whereKeyNot($repositoryConnection->id);
if ($query->exists()) {
throw ValidationException::withMessages([
'provider_repository_id' => 'This repository is already linked to the project.',
]);
}
}
}