Add ticket-driven branch creation flow
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Git\Data\GitBranch;
|
||||
use App\Actions\Git\GitProviderManager;
|
||||
use App\Actions\Git\ValidateGitBranchName;
|
||||
use App\Actions\ProjectPermissions\ProjectPermissionService;
|
||||
use App\Http\Requests\StoreProjectTicketBranchRequest;
|
||||
use App\Http\Requests\ValidateProjectTicketBranchRequest;
|
||||
use App\Models\Project;
|
||||
use App\Models\RepositoryConnection;
|
||||
use App\Models\Ticket;
|
||||
use App\Models\UserGitConnection;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Throwable;
|
||||
|
||||
class ProjectTicketBranchController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GitProviderManager $gitProviderManager,
|
||||
private readonly ValidateGitBranchName $validateGitBranchName,
|
||||
private readonly ProjectPermissionService $projectPermissionService,
|
||||
) {}
|
||||
|
||||
public function validate(
|
||||
ValidateProjectTicketBranchRequest $request,
|
||||
Project $project,
|
||||
Ticket $ticket,
|
||||
): JsonResponse {
|
||||
abort_unless(
|
||||
$this->projectPermissionService->userHasPermission($request->user(), $project, 'edit'),
|
||||
403,
|
||||
);
|
||||
|
||||
$repositoryConnection = $this->projectRepositoryConnection(
|
||||
$project,
|
||||
$request->repositoryConnectionId(),
|
||||
);
|
||||
$userGitConnection = $this->matchingUserGitConnection(
|
||||
$request->user()->id,
|
||||
$repositoryConnection,
|
||||
);
|
||||
|
||||
if ($userGitConnection === null) {
|
||||
return response()->json([
|
||||
'is_valid' => false,
|
||||
'message' => 'Connect a matching Git connection in your user settings first.',
|
||||
]);
|
||||
}
|
||||
|
||||
$syntaxMessage = $this->validateGitBranchName->message($request->branchName());
|
||||
|
||||
if ($syntaxMessage !== null) {
|
||||
return response()->json([
|
||||
'is_valid' => false,
|
||||
'message' => $syntaxMessage,
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$branchAlreadyExists = collect(
|
||||
$this->gitProviderManager
|
||||
->client($userGitConnection->provider)
|
||||
->listBranches(
|
||||
$userGitConnection->base_url,
|
||||
$userGitConnection->api_token,
|
||||
$repositoryConnection->owner_name,
|
||||
$repositoryConnection->repository_name,
|
||||
),
|
||||
)
|
||||
->contains(
|
||||
fn (GitBranch $branch): bool => $branch->name === $request->branchName(),
|
||||
);
|
||||
} catch (Throwable) {
|
||||
return response()->json([
|
||||
'is_valid' => false,
|
||||
'message' => 'KanbanGit could not validate this branch name right now.',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($branchAlreadyExists) {
|
||||
return response()->json([
|
||||
'is_valid' => false,
|
||||
'message' => 'This branch name already exists in the selected repository.',
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'is_valid' => true,
|
||||
'message' => "Branch name is available for {$ticket->ticket_key}.",
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(
|
||||
StoreProjectTicketBranchRequest $request,
|
||||
Project $project,
|
||||
Ticket $ticket,
|
||||
): RedirectResponse {
|
||||
abort_unless(
|
||||
$this->projectPermissionService->userHasPermission($request->user(), $project, 'edit'),
|
||||
403,
|
||||
);
|
||||
|
||||
$repositoryConnection = $this->projectRepositoryConnection(
|
||||
$project,
|
||||
$request->repositoryConnectionId(),
|
||||
);
|
||||
$userGitConnection = $this->matchingUserGitConnection(
|
||||
$request->user()->id,
|
||||
$repositoryConnection,
|
||||
);
|
||||
|
||||
if ($userGitConnection === null) {
|
||||
throw ValidationException::withMessages([
|
||||
'repository_connection_id' => 'Connect a matching Git connection in your user settings first.',
|
||||
]);
|
||||
}
|
||||
|
||||
$syntaxMessage = $this->validateGitBranchName->message($request->branchName());
|
||||
|
||||
if ($syntaxMessage !== null) {
|
||||
throw ValidationException::withMessages([
|
||||
'branch_name' => $syntaxMessage,
|
||||
]);
|
||||
}
|
||||
|
||||
$gitProvider = $this->gitProviderManager->client($userGitConnection->provider);
|
||||
|
||||
try {
|
||||
$branchAlreadyExists = collect(
|
||||
$gitProvider->listBranches(
|
||||
$userGitConnection->base_url,
|
||||
$userGitConnection->api_token,
|
||||
$repositoryConnection->owner_name,
|
||||
$repositoryConnection->repository_name,
|
||||
),
|
||||
)
|
||||
->contains(
|
||||
fn (GitBranch $branch): bool => $branch->name === $request->branchName(),
|
||||
);
|
||||
} catch (Throwable) {
|
||||
throw ValidationException::withMessages([
|
||||
'branch_name' => 'KanbanGit could not validate this branch name right now.',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($branchAlreadyExists) {
|
||||
throw ValidationException::withMessages([
|
||||
'branch_name' => 'This branch name already exists in the selected repository.',
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$repository = $gitProvider->getRepository(
|
||||
$userGitConnection->base_url,
|
||||
$userGitConnection->api_token,
|
||||
$repositoryConnection->owner_name,
|
||||
$repositoryConnection->repository_name,
|
||||
);
|
||||
|
||||
$gitProvider->createBranch(
|
||||
$userGitConnection->base_url,
|
||||
$userGitConnection->api_token,
|
||||
$repositoryConnection->owner_name,
|
||||
$repositoryConnection->repository_name,
|
||||
$request->branchName(),
|
||||
$repository->defaultBranch,
|
||||
);
|
||||
} catch (Throwable) {
|
||||
throw ValidationException::withMessages([
|
||||
'branch_name' => 'KanbanGit could not create this branch right now.',
|
||||
]);
|
||||
}
|
||||
|
||||
return to_route('projects.tickets.show', [$project, $ticket])
|
||||
->with(
|
||||
'status',
|
||||
"{$request->branchName()} was created in {$repositoryConnection->repository_full_name}.",
|
||||
);
|
||||
}
|
||||
|
||||
private function projectRepositoryConnection(Project $project, int $repositoryConnectionId): RepositoryConnection
|
||||
{
|
||||
return $project->repositoryConnections()
|
||||
->findOrFail($repositoryConnectionId);
|
||||
}
|
||||
|
||||
private function matchingUserGitConnection(
|
||||
int $userId,
|
||||
RepositoryConnection $repositoryConnection,
|
||||
): ?UserGitConnection {
|
||||
return UserGitConnection::query()
|
||||
->where('user_id', $userId)
|
||||
->where('provider', $repositoryConnection->provider)
|
||||
->where('base_url', $repositoryConnection->base_url)
|
||||
->first();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Git\BuildSuggestedTicketBranchName;
|
||||
use App\Actions\Git\ResolveTicketBranchMatches;
|
||||
use App\Actions\Git\ResolveTicketPullRequestMatches;
|
||||
use App\Actions\ProjectPermissions\ProjectPermissionService;
|
||||
@@ -19,6 +20,7 @@ use Inertia\Response;
|
||||
class ProjectTicketController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly BuildSuggestedTicketBranchName $buildSuggestedTicketBranchName,
|
||||
private readonly ProjectPermissionService $projectPermissionService,
|
||||
private readonly ResolveTicketBranchMatches $resolveTicketBranchMatches,
|
||||
private readonly ResolveTicketPullRequestMatches $resolveTicketPullRequestMatches,
|
||||
@@ -61,6 +63,7 @@ class ProjectTicketController extends Controller
|
||||
$ticket,
|
||||
),
|
||||
'status' => session('status'),
|
||||
'suggestedBranchName' => $this->buildSuggestedTicketBranchName->handle($ticket),
|
||||
'ticket' => $ticket->load([
|
||||
'fieldValues.ticketFieldDefinition',
|
||||
'fieldValues.userValue',
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreProjectTicketBranchRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'branch_name' => ['required', 'string', 'max:255'],
|
||||
'repository_connection_id' => ['required', 'integer', 'exists:repository_connections,id'],
|
||||
];
|
||||
}
|
||||
|
||||
public function branchName(): string
|
||||
{
|
||||
return trim((string) $this->validated('branch_name'));
|
||||
}
|
||||
|
||||
public function repositoryConnectionId(): int
|
||||
{
|
||||
return (int) $this->validated('repository_connection_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ValidateProjectTicketBranchRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'branch_name' => ['required', 'string', 'max:255'],
|
||||
'repository_connection_id' => ['required', 'integer', 'exists:repository_connections,id'],
|
||||
];
|
||||
}
|
||||
|
||||
public function branchName(): string
|
||||
{
|
||||
return trim((string) $this->validated('branch_name'));
|
||||
}
|
||||
|
||||
public function repositoryConnectionId(): int
|
||||
{
|
||||
return (int) $this->validated('repository_connection_id');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user