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,19 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
abstract class ApiController extends Controller
{
protected function authenticatedUser(Request $request): User
{
$user = $request->user();
abort_if($user === null, 401);
return $user;
}
}
@@ -0,0 +1,48 @@
<?php
namespace App\Http\Controllers\Api;
use App\Actions\Permissions\PermissionService;
use App\Actions\Projects\BuildProjectBacklogPayload;
use App\Actions\Projects\BuildProjectBoardPayload;
use App\Enums\Permission;
use App\Models\Project;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ProjectBoardController extends ApiController
{
public function __construct(
private readonly BuildProjectBacklogPayload $buildProjectBacklogPayload,
private readonly BuildProjectBoardPayload $buildProjectBoardPayload,
private readonly PermissionService $permissionService,
) {}
public function board(Request $request, Project $project): JsonResponse
{
$user = $this->authenticatedUser($request);
abort_unless(
$this->permissionService->allows($user, Permission::ProjectRead, $project),
403,
);
return response()->json([
'data' => $this->buildProjectBoardPayload->handle($project, $user),
]);
}
public function backlog(Request $request, Project $project): JsonResponse
{
$user = $this->authenticatedUser($request);
abort_unless(
$this->permissionService->allows($user, Permission::ProjectRead, $project),
403,
);
return response()->json([
'data' => $this->buildProjectBacklogPayload->handle($project, $user),
]);
}
}
@@ -0,0 +1,167 @@
<?php
namespace App\Http\Controllers\Api;
use App\Actions\Permissions\PermissionService;
use App\Actions\ProjectBoard\DeleteBoardColumn;
use App\Actions\ProjectBoard\DeleteTicketStatus;
use App\Actions\ProjectBoard\StoreBoardColumn;
use App\Actions\ProjectBoard\StoreTicketStatus;
use App\Actions\ProjectBoard\UpdateBoardColumn;
use App\Actions\ProjectBoard\UpdateTicketStatus;
use App\Actions\ProjectBoard\UpdateTicketStatusTransitions;
use App\Actions\Projects\BuildProjectBoardSettingsPayload;
use App\Enums\Permission;
use App\Http\Requests\StoreBoardColumnRequest;
use App\Http\Requests\StoreTicketStatusRequest;
use App\Http\Requests\UpdateBoardColumnRequest;
use App\Http\Requests\UpdateTicketStatusRequest;
use App\Http\Requests\UpdateTicketStatusTransitionsRequest;
use App\Http\Resources\BoardColumnResource;
use App\Http\Resources\TicketStatusResource;
use App\Models\BoardColumn;
use App\Models\Project;
use App\Models\TicketStatus;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ProjectBoardSettingsController extends ApiController
{
public function __construct(
private readonly BuildProjectBoardSettingsPayload $buildProjectBoardSettingsPayload,
private readonly DeleteBoardColumn $deleteBoardColumn,
private readonly DeleteTicketStatus $deleteTicketStatus,
private readonly PermissionService $permissionService,
private readonly StoreBoardColumn $storeBoardColumn,
private readonly StoreTicketStatus $storeTicketStatus,
private readonly UpdateBoardColumn $updateBoardColumn,
private readonly UpdateTicketStatus $updateTicketStatus,
private readonly UpdateTicketStatusTransitions $updateTicketStatusTransitions,
) {}
public function show(Request $request, Project $project): JsonResponse
{
$this->abortUnlessCanManage($request, $project);
return response()->json([
'data' => $this->buildProjectBoardSettingsPayload->handle($project),
]);
}
public function storeColumn(StoreBoardColumnRequest $request, Project $project): JsonResponse
{
$this->abortUnlessCanManage($request, $project);
$boardColumn = $this->storeBoardColumn->handle($project, $request->validated());
return BoardColumnResource::make($boardColumn->load('ticketStatuses'))
->additional([
'meta' => [
'message' => "{$boardColumn->name} was added to the board workflow.",
],
])
->response()
->setStatusCode(201);
}
public function updateColumn(
UpdateBoardColumnRequest $request,
Project $project,
BoardColumn $boardColumn,
): JsonResponse {
$this->abortUnlessCanManage($request, $project);
$boardColumn = $this->updateBoardColumn->handle($project, $boardColumn, $request->validated());
return BoardColumnResource::make($boardColumn->load('ticketStatuses'))
->additional([
'meta' => [
'message' => "{$boardColumn->name} was updated.",
],
])
->response();
}
public function destroyColumn(Request $request, Project $project, BoardColumn $boardColumn): JsonResponse
{
$this->abortUnlessCanManage($request, $project);
$this->deleteBoardColumn->handle($project, $boardColumn);
return response()->json(null, 204);
}
public function storeStatus(StoreTicketStatusRequest $request, Project $project): JsonResponse
{
$this->abortUnlessCanManage($request, $project);
$ticketStatus = $this->storeTicketStatus->handle($project, $request->validated());
return TicketStatusResource::make($ticketStatus)
->additional([
'meta' => [
'message' => "{$ticketStatus->name} was added to {$this->statusContextLabel($ticketStatus)}.",
],
])
->response()
->setStatusCode(201);
}
public function updateStatus(
UpdateTicketStatusRequest $request,
Project $project,
TicketStatus $ticketStatus,
): JsonResponse {
$this->abortUnlessCanManage($request, $project);
$ticketStatus = $this->updateTicketStatus->handle($ticketStatus, $request->validated());
return TicketStatusResource::make($ticketStatus->load('boardColumn'))
->additional([
'meta' => [
'message' => "{$ticketStatus->name} was updated.",
],
])
->response();
}
public function destroyStatus(Request $request, Project $project, TicketStatus $ticketStatus): JsonResponse
{
$this->abortUnlessCanManage($request, $project);
$this->deleteTicketStatus->handle($project, $ticketStatus);
return response()->json(null, 204);
}
public function updateStatusTransitions(
UpdateTicketStatusTransitionsRequest $request,
Project $project,
TicketStatus $ticketStatus,
): JsonResponse {
$this->abortUnlessCanManage($request, $project);
$ticketStatus = $this->updateTicketStatusTransitions->handle($ticketStatus, $request->validated());
return TicketStatusResource::make($ticketStatus->load(['allowedTargetStatuses.boardColumn', 'boardColumn']))
->additional([
'meta' => [
'message' => "{$ticketStatus->name} move targets were updated.",
],
])
->response();
}
private function abortUnlessCanManage(Request $request, Project $project): void
{
abort_unless(
$this->permissionService->allows($this->authenticatedUser($request), Permission::ProjectAdmin, $project),
403,
);
}
private function statusContextLabel(TicketStatus $ticketStatus): string
{
return $ticketStatus->boardColumn?->name ?? 'Backlog';
}
}
@@ -0,0 +1,68 @@
<?php
namespace App\Http\Controllers\Api;
use App\Actions\Permissions\PermissionService;
use App\Actions\Projects\CreateProject;
use App\Actions\Projects\DeleteProject;
use App\Actions\Projects\ListVisibleProjects;
use App\Enums\Permission;
use App\Http\Requests\DeleteProjectRequest;
use App\Http\Requests\StoreProjectRequest;
use App\Http\Resources\ProjectResource;
use App\Models\Project;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
class ProjectController extends ApiController
{
public function __construct(
private readonly CreateProject $createProject,
private readonly DeleteProject $deleteProject,
private readonly ListVisibleProjects $listVisibleProjects,
private readonly PermissionService $permissionService,
) {}
public function index(Request $request): AnonymousResourceCollection
{
return ProjectResource::collection(
$this->listVisibleProjects->handle($this->authenticatedUser($request)),
);
}
public function store(StoreProjectRequest $request): JsonResponse
{
$project = $this->createProject->handle($request->validated(), $this->authenticatedUser($request));
return ProjectResource::make($project)
->additional([
'meta' => [
'message' => "{$project->name} was created.",
],
])
->response()
->setStatusCode(201);
}
public function show(Request $request, Project $project): ProjectResource
{
$user = $this->authenticatedUser($request);
abort_unless(
$this->permissionService->allows($user, Permission::ProjectRead, $project),
403,
);
return ProjectResource::make($project);
}
public function destroy(DeleteProjectRequest $request, Project $project): JsonResponse
{
$request->validated();
$this->deleteProject->handle($project);
return response()->json(null, 204);
}
}
@@ -0,0 +1,105 @@
<?php
namespace App\Http\Controllers\Api;
use App\Actions\Permissions\PermissionService;
use App\Actions\Repositories\BuildProjectRepositorySettingsPayload;
use App\Actions\Repositories\DeleteProjectRepositoryConnection;
use App\Actions\Repositories\StoreProjectRepositoryConnection;
use App\Actions\Repositories\UpdateProjectRepositoryConnection;
use App\Enums\Permission;
use App\Http\Requests\StoreRepositoryConnectionRequest;
use App\Http\Requests\UpdateRepositoryConnectionRequest;
use App\Http\Resources\RepositoryConnectionResource;
use App\Models\Project;
use App\Models\RepositoryConnection;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ProjectRepositorySettingsController extends ApiController
{
public function __construct(
private readonly BuildProjectRepositorySettingsPayload $buildProjectRepositorySettingsPayload,
private readonly DeleteProjectRepositoryConnection $deleteProjectRepositoryConnection,
private readonly PermissionService $permissionService,
private readonly StoreProjectRepositoryConnection $storeProjectRepositoryConnection,
private readonly UpdateProjectRepositoryConnection $updateProjectRepositoryConnection,
) {}
public function show(Request $request, Project $project): JsonResponse
{
$user = $this->authenticatedUser($request);
abort_unless(
$this->permissionService->allows($user, Permission::ProjectAdmin, $project),
403,
);
return response()->json([
'data' => $this->buildProjectRepositorySettingsPayload->handle($project, $user),
]);
}
public function store(StoreRepositoryConnectionRequest $request, Project $project): JsonResponse
{
$user = $this->authenticatedUser($request);
abort_unless(
$this->permissionService->allows($user, Permission::ProjectAdmin, $project),
403,
);
$repositoryConnection = $this->storeProjectRepositoryConnection->handle($project, $user, $request->validated());
return RepositoryConnectionResource::make($repositoryConnection)
->additional([
'meta' => [
'message' => "{$repositoryConnection->repository_full_name} was linked to {$project->name}.",
],
])
->response()
->setStatusCode(201);
}
public function update(
UpdateRepositoryConnectionRequest $request,
Project $project,
RepositoryConnection $repositoryConnection,
): JsonResponse {
$user = $this->authenticatedUser($request);
abort_unless(
$this->permissionService->allows($user, Permission::ProjectAdmin, $project),
403,
);
$repositoryConnection = $this->updateProjectRepositoryConnection->handle(
$project,
$repositoryConnection,
$user,
$request->validated(),
);
return RepositoryConnectionResource::make($repositoryConnection)
->additional([
'meta' => [
'message' => "{$repositoryConnection->repository_full_name} connection was updated.",
],
])
->response();
}
public function destroy(Request $request, Project $project, RepositoryConnection $repositoryConnection): JsonResponse
{
$user = $this->authenticatedUser($request);
abort_unless(
$this->permissionService->allows($user, Permission::ProjectAdmin, $project),
403,
);
$this->deleteProjectRepositoryConnection->handle($repositoryConnection);
return response()->json(null, 204);
}
}
@@ -0,0 +1,61 @@
<?php
namespace App\Http\Controllers\Api;
use App\Actions\Permissions\PermissionService;
use App\Actions\Tickets\CreateProjectTicketBranch;
use App\Actions\Tickets\ValidateProjectTicketBranch;
use App\Enums\Permission;
use App\Http\Requests\StoreProjectTicketBranchRequest;
use App\Http\Requests\ValidateProjectTicketBranchRequest;
use App\Models\Project;
use App\Models\Ticket;
use Illuminate\Http\JsonResponse;
class ProjectTicketBranchController extends ApiController
{
public function __construct(
private readonly CreateProjectTicketBranch $createProjectTicketBranch,
private readonly PermissionService $permissionService,
private readonly ValidateProjectTicketBranch $validateProjectTicketBranch,
) {}
public function validate(
ValidateProjectTicketBranchRequest $request,
Project $project,
Ticket $ticket,
): JsonResponse {
$user = $this->authenticatedUser($request);
abort_unless(
$this->permissionService->allows($user, Permission::ProjectWrite, $project),
403,
);
return response()->json([
'data' => $this->validateProjectTicketBranch->handle($project, $ticket, $user, $request->validated()),
]);
}
public function store(
StoreProjectTicketBranchRequest $request,
Project $project,
Ticket $ticket,
): JsonResponse {
$user = $this->authenticatedUser($request);
abort_unless(
$this->permissionService->allows($user, Permission::ProjectWrite, $project),
403,
);
$result = $this->createProjectTicketBranch->handle($project, $ticket, $user, $request->validated());
return response()->json([
'data' => $result,
'meta' => [
'message' => "{$result['branch_name']} was created in {$result['repository_full_name']}.",
],
], 201);
}
}
@@ -0,0 +1,183 @@
<?php
namespace App\Http\Controllers\Api;
use App\Actions\Permissions\PermissionService;
use App\Actions\Projects\BuildProjectTicketDetailPayload;
use App\Actions\Projects\BuildProjectTicketOptionsPayload;
use App\Actions\Projects\BuildProjectTicketStatusTargetsPayload;
use App\Actions\Tickets\CreateProjectTicket;
use App\Actions\Tickets\MoveProjectTicket;
use App\Actions\Tickets\UpdateProjectTicket;
use App\Enums\Permission;
use App\Http\Requests\Api\MoveProjectTicketRequest;
use App\Http\Requests\StoreProjectTicketRequest;
use App\Http\Requests\UpdateProjectTicketRequest;
use App\Http\Resources\TicketResource;
use App\Models\Project;
use App\Models\Ticket;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Support\Arr;
class ProjectTicketController extends ApiController
{
public function __construct(
private readonly BuildProjectTicketDetailPayload $buildProjectTicketDetailPayload,
private readonly BuildProjectTicketOptionsPayload $buildProjectTicketOptionsPayload,
private readonly BuildProjectTicketStatusTargetsPayload $buildProjectTicketStatusTargetsPayload,
private readonly CreateProjectTicket $createProjectTicket,
private readonly MoveProjectTicket $moveProjectTicket,
private readonly PermissionService $permissionService,
private readonly UpdateProjectTicket $updateProjectTicket,
) {}
public function index(Request $request, Project $project): AnonymousResourceCollection
{
$user = $this->authenticatedUser($request);
abort_unless(
$this->permissionService->allows($user, Permission::ProjectRead, $project),
403,
);
return TicketResource::collection(
$project->tickets()
->with([
'ticketPriority',
'ticketStatus.allowedTargetStatuses.boardColumn',
'ticketStatus.boardColumn',
'ticketType',
])
->get(),
);
}
public function options(Request $request, Project $project): JsonResponse
{
$user = $this->authenticatedUser($request);
abort_unless(
$this->permissionService->allows($user, Permission::ProjectWrite, $project),
403,
);
return response()->json([
'data' => $this->buildProjectTicketOptionsPayload->handle(
$project,
Arr::get($request->query(), 'source') === 'board' ? 'board' : 'backlog',
),
]);
}
public function store(StoreProjectTicketRequest $request, Project $project): JsonResponse
{
$user = $this->authenticatedUser($request);
abort_unless(
$this->permissionService->allows($user, Permission::ProjectWrite, $project),
403,
);
$ticket = $this->createProjectTicket->handle($project, $request->validated(), $user);
return TicketResource::make($ticket->load(['ticketPriority', 'ticketStatus.boardColumn', 'ticketType']))
->additional([
'meta' => [
'message' => "{$ticket->ticket_key} was created.",
],
])
->response()
->setStatusCode(201);
}
public function show(Request $request, Project $project, Ticket $ticket): JsonResponse
{
$user = $this->authenticatedUser($request);
abort_unless(
$this->permissionService->allows($user, Permission::ProjectRead, $project),
403,
);
return response()->json([
'data' => $this->buildProjectTicketDetailPayload->handle($project, $ticket, $user),
]);
}
public function update(
UpdateProjectTicketRequest $request,
Project $project,
Ticket $ticket,
): JsonResponse {
$user = $this->authenticatedUser($request);
abort_unless(
$this->permissionService->allows($user, Permission::ProjectWrite, $project),
403,
);
$wasUpdated = $this->updateProjectTicket->handle($ticket, $user, $request->validated());
$statusMessage = $wasUpdated
? "{$ticket->ticket_key} was updated."
: 'No ticket changes were detected.';
return TicketResource::make($ticket->fresh()->load([
'assignee',
'author',
'fieldValues.ticketFieldDefinition',
'fieldValues.userValue',
'ticketPriority',
'ticketStatus.boardColumn',
'ticketType',
'worklogEntries.actor',
]))
->additional([
'meta' => [
'message' => $statusMessage,
],
])
->response();
}
public function statusTargets(Request $request, Project $project, Ticket $ticket): JsonResponse
{
$user = $this->authenticatedUser($request);
abort_unless(
$this->permissionService->allows($user, Permission::ProjectRead, $project),
403,
);
return response()->json([
'data' => $this->buildProjectTicketStatusTargetsPayload->handle($ticket),
]);
}
public function move(
MoveProjectTicketRequest $request,
Project $project,
Ticket $ticket,
): JsonResponse {
$user = $this->authenticatedUser($request);
abort_unless(
$this->permissionService->allows($user, Permission::ProjectWrite, $project),
403,
);
$result = $this->moveProjectTicket->handle($project, $ticket, $user, $request->validated());
$message = $result['moved']
? "{$ticket->ticket_key} moved to {$result['destination_label']}."
: "{$ticket->ticket_key} already belongs to {$result['destination_label']}.";
return TicketResource::make($ticket->fresh()->load(['ticketPriority', 'ticketStatus.boardColumn', 'ticketType']))
->additional([
'meta' => [
'message' => $message,
],
])
->response();
}
}
@@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers\Api;
use App\Actions\Permissions\PermissionService;
use App\Actions\Tickets\DeleteProjectTicketLink;
use App\Actions\Tickets\StoreProjectTicketLink;
use App\Enums\Permission;
use App\Http\Requests\StoreTicketLinkRequest;
use App\Http\Resources\TicketLinkResource;
use App\Models\Project;
use App\Models\Ticket;
use App\Models\TicketLink;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ProjectTicketLinkController extends ApiController
{
public function __construct(
private readonly DeleteProjectTicketLink $deleteProjectTicketLink,
private readonly PermissionService $permissionService,
private readonly StoreProjectTicketLink $storeProjectTicketLink,
) {}
public function store(
StoreTicketLinkRequest $request,
Project $project,
Ticket $ticket,
): JsonResponse {
$user = $this->authenticatedUser($request);
abort_unless(
$this->permissionService->allows($user, Permission::ProjectWrite, $project),
403,
);
$ticketLink = $this->storeProjectTicketLink->handle($project, $ticket, $user, $request->validated());
$ticketLink->setAttribute('current_ticket_id', $ticket->id);
return TicketLinkResource::make($ticketLink->load(['sourceTicket', 'targetTicket', 'ticketLinkType']))
->additional([
'meta' => [
'message' => "{$ticketLink->ticketLinkType->label} link was saved.",
],
])
->response()
->setStatusCode(201);
}
public function destroy(
Request $request,
Project $project,
Ticket $ticket,
TicketLink $ticketLink,
): JsonResponse {
$user = $this->authenticatedUser($request);
abort_unless(
$this->permissionService->allows($user, Permission::ProjectWrite, $project),
403,
);
$this->deleteProjectTicketLink->handle($ticket, $ticketLink, $user);
return response()->json(null, 204);
}
}
@@ -0,0 +1,254 @@
<?php
namespace App\Http\Controllers\Api;
use App\Actions\Permissions\PermissionService;
use App\Actions\Projects\BuildProjectTicketMetadataSettingsPayload;
use App\Actions\ProjectTicketMetadata\DeleteProjectTicketPriority;
use App\Actions\ProjectTicketMetadata\DeleteProjectTicketType;
use App\Actions\ProjectTicketMetadata\DeleteTicketFieldDefinition;
use App\Actions\ProjectTicketMetadata\DeleteTicketLinkType;
use App\Actions\ProjectTicketMetadata\StoreProjectTicketPriority;
use App\Actions\ProjectTicketMetadata\StoreProjectTicketType;
use App\Actions\ProjectTicketMetadata\StoreTicketFieldDefinition;
use App\Actions\ProjectTicketMetadata\StoreTicketLinkType;
use App\Actions\ProjectTicketMetadata\UpdateProjectTicketMetadataSettings;
use App\Actions\ProjectTicketMetadata\UpdateProjectTicketPriority;
use App\Actions\ProjectTicketMetadata\UpdateProjectTicketType;
use App\Actions\ProjectTicketMetadata\UpdateTicketFieldDefinition;
use App\Actions\ProjectTicketMetadata\UpdateTicketLinkType;
use App\Enums\Permission;
use App\Http\Requests\DestroyProjectTicketTypeRequest;
use App\Http\Requests\StoreProjectNamedSettingRequest;
use App\Http\Requests\StoreProjectTicketTypeRequest;
use App\Http\Requests\StoreTicketFieldDefinitionRequest;
use App\Http\Requests\UpdateProjectNamedSettingRequest;
use App\Http\Requests\UpdateProjectTicketMetadataSettings as UpdateProjectTicketMetadataSettingsRequest;
use App\Http\Requests\UpdateProjectTicketTypeRequest;
use App\Http\Requests\UpdateTicketFieldDefinitionRequest;
use App\Http\Resources\ProjectResource;
use App\Http\Resources\TicketFieldDefinitionResource;
use App\Http\Resources\TicketLinkTypeResource;
use App\Http\Resources\TicketPriorityResource;
use App\Http\Resources\TicketTypeResource;
use App\Models\Project;
use App\Models\TicketFieldDefinition;
use App\Models\TicketLinkType;
use App\Models\TicketPriority;
use App\Models\TicketType;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
class ProjectTicketMetadataSettingsController extends ApiController
{
public function __construct(
private readonly BuildProjectTicketMetadataSettingsPayload $buildProjectTicketMetadataSettingsPayload,
private readonly DeleteProjectTicketPriority $deleteProjectTicketPriority,
private readonly DeleteProjectTicketType $deleteProjectTicketType,
private readonly DeleteTicketFieldDefinition $deleteTicketFieldDefinition,
private readonly DeleteTicketLinkType $deleteTicketLinkType,
private readonly PermissionService $permissionService,
private readonly StoreProjectTicketPriority $storeProjectTicketPriority,
private readonly StoreProjectTicketType $storeProjectTicketType,
private readonly StoreTicketFieldDefinition $storeTicketFieldDefinition,
private readonly StoreTicketLinkType $storeTicketLinkType,
private readonly UpdateProjectTicketMetadataSettings $updateProjectTicketMetadataSettings,
private readonly UpdateProjectTicketPriority $updateProjectTicketPriority,
private readonly UpdateProjectTicketType $updateProjectTicketType,
private readonly UpdateTicketFieldDefinition $updateTicketFieldDefinition,
private readonly UpdateTicketLinkType $updateTicketLinkType,
) {}
public function show(Request $request, Project $project): JsonResponse
{
$this->abortUnlessCanManage($request, $project);
return response()->json([
'data' => $this->buildProjectTicketMetadataSettingsPayload->handle($project),
]);
}
public function update(UpdateProjectTicketMetadataSettingsRequest $request, Project $project): JsonResponse
{
$this->abortUnlessCanManage($request, $project);
$project = $this->updateProjectTicketMetadataSettings->handle($project, $request->validated());
return ProjectResource::make($project)
->additional([
'meta' => [
'message' => 'Settings have been updated.',
],
])
->response();
}
public function storeType(StoreProjectTicketTypeRequest $request, Project $project): JsonResponse
{
$this->abortUnlessCanManage($request, $project);
$ticketType = $this->storeProjectTicketType->handle($project, $request->validated());
return TicketTypeResource::make($ticketType)
->additional(['meta' => ['message' => "{$ticketType->name} type was added."]])
->response()
->setStatusCode(201);
}
public function updateType(
UpdateProjectTicketTypeRequest $request,
Project $project,
TicketType $ticketType,
): JsonResponse {
$this->abortUnlessCanManage($request, $project);
$ticketType = $this->updateProjectTicketType->handle($project, $ticketType, $request->validated());
return TicketTypeResource::make($ticketType)
->additional(['meta' => ['message' => "{$ticketType->name} type was updated."]])
->response();
}
public function destroyType(
DestroyProjectTicketTypeRequest $request,
Project $project,
TicketType $ticketType,
): JsonResponse {
$this->abortUnlessCanManage($request, $project);
$result = $this->deleteProjectTicketType->handle(
$project,
$ticketType,
$this->authenticatedUser($request),
$request->validated(),
);
if (! $result['deleted']) {
throw ValidationException::withMessages([
'ticket_type_id' => $result['message'],
]);
}
return response()->json(null, 204);
}
public function storePriority(StoreProjectNamedSettingRequest $request, Project $project): JsonResponse
{
$this->abortUnlessCanManage($request, $project);
$ticketPriority = $this->storeProjectTicketPriority->handle($project, $request->validated());
return TicketPriorityResource::make($ticketPriority)
->additional(['meta' => ['message' => "{$ticketPriority->name} priority was added."]])
->response()
->setStatusCode(201);
}
public function updatePriority(
UpdateProjectNamedSettingRequest $request,
Project $project,
TicketPriority $ticketPriority,
): JsonResponse {
$this->abortUnlessCanManage($request, $project);
$ticketPriority = $this->updateProjectTicketPriority->handle($project, $ticketPriority, $request->validated());
return TicketPriorityResource::make($ticketPriority)
->additional(['meta' => ['message' => "{$ticketPriority->name} priority was updated."]])
->response();
}
public function destroyPriority(Request $request, Project $project, TicketPriority $ticketPriority): JsonResponse
{
$this->abortUnlessCanManage($request, $project);
$result = $this->deleteProjectTicketPriority->handle($project, $ticketPriority);
if (! $result['deleted']) {
throw ValidationException::withMessages([
'ticket_priority_id' => $result['message'],
]);
}
return response()->json(null, 204);
}
public function storeFieldDefinition(StoreTicketFieldDefinitionRequest $request, Project $project): JsonResponse
{
$this->abortUnlessCanManage($request, $project);
$ticketFieldDefinition = $this->storeTicketFieldDefinition->handle($project, $request->validated());
return TicketFieldDefinitionResource::make($ticketFieldDefinition)
->additional(['meta' => ['message' => "{$ticketFieldDefinition->label} field was added."]])
->response()
->setStatusCode(201);
}
public function updateFieldDefinition(
UpdateTicketFieldDefinitionRequest $request,
Project $project,
TicketFieldDefinition $ticketFieldDefinition,
): JsonResponse {
$this->abortUnlessCanManage($request, $project);
$ticketFieldDefinition = $this->updateTicketFieldDefinition->handle($project, $ticketFieldDefinition, $request->validated());
return TicketFieldDefinitionResource::make($ticketFieldDefinition)
->additional(['meta' => ['message' => "{$ticketFieldDefinition->label} field was updated."]])
->response();
}
public function destroyFieldDefinition(Request $request, Project $project, TicketFieldDefinition $ticketFieldDefinition): JsonResponse
{
$this->abortUnlessCanManage($request, $project);
$this->deleteTicketFieldDefinition->handle($project, $ticketFieldDefinition);
return response()->json(null, 204);
}
public function storeLinkType(StoreProjectNamedSettingRequest $request, Project $project): JsonResponse
{
$this->abortUnlessCanManage($request, $project);
$ticketLinkType = $this->storeTicketLinkType->handle($project, $request->validated());
return TicketLinkTypeResource::make($ticketLinkType)
->additional(['meta' => ['message' => "{$ticketLinkType->label} link type was added."]])
->response()
->setStatusCode(201);
}
public function updateLinkType(
UpdateProjectNamedSettingRequest $request,
Project $project,
TicketLinkType $ticketLinkType,
): JsonResponse {
$this->abortUnlessCanManage($request, $project);
$ticketLinkType = $this->updateTicketLinkType->handle($project, $ticketLinkType, $request->validated());
return TicketLinkTypeResource::make($ticketLinkType)
->additional(['meta' => ['message' => "{$ticketLinkType->label} link type was updated."]])
->response();
}
public function destroyLinkType(Request $request, Project $project, TicketLinkType $ticketLinkType): JsonResponse
{
$this->abortUnlessCanManage($request, $project);
$this->deleteTicketLinkType->handle($project, $ticketLinkType);
return response()->json(null, 204);
}
private function abortUnlessCanManage(Request $request, Project $project): void
{
abort_unless(
$this->permissionService->allows($this->authenticatedUser($request), Permission::ProjectAdmin, $project),
403,
);
}
}
@@ -0,0 +1,68 @@
<?php
namespace App\Http\Controllers\Api\Settings;
use App\Actions\UserGitConnections\DeleteUserGitConnection;
use App\Actions\UserGitConnections\StoreUserGitConnection;
use App\Actions\UserGitConnections\UpdateUserGitConnection;
use App\Http\Controllers\Api\ApiController;
use App\Http\Requests\Settings\StoreUserGitConnectionRequest;
use App\Http\Requests\Settings\UpdateUserGitConnectionRequest;
use App\Http\Resources\UserGitConnectionResource;
use App\Models\UserGitConnection;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
class GitConnectionController extends ApiController
{
public function __construct(
private readonly DeleteUserGitConnection $deleteUserGitConnection,
private readonly StoreUserGitConnection $storeUserGitConnection,
private readonly UpdateUserGitConnection $updateUserGitConnection,
) {}
public function index(Request $request): AnonymousResourceCollection
{
return UserGitConnectionResource::collection(
$this->authenticatedUser($request)
->gitConnections()
->get(),
);
}
public function store(StoreUserGitConnectionRequest $request): JsonResponse
{
$gitConnection = $this->storeUserGitConnection->handle($this->authenticatedUser($request), $request->validated());
return UserGitConnectionResource::make($gitConnection)
->additional(['meta' => ['message' => 'Git connection was added.']])
->response()
->setStatusCode(201);
}
public function update(UpdateUserGitConnectionRequest $request, UserGitConnection $gitConnection): JsonResponse
{
$this->abortUnlessOwner($request, $gitConnection);
$gitConnection = $this->updateUserGitConnection->handle($gitConnection, $request->validated());
return UserGitConnectionResource::make($gitConnection)
->additional(['meta' => ['message' => 'Git connection was updated.']])
->response();
}
public function destroy(Request $request, UserGitConnection $gitConnection): JsonResponse
{
$this->abortUnlessOwner($request, $gitConnection);
$this->deleteUserGitConnection->handle($gitConnection);
return response()->json(null, 204);
}
private function abortUnlessOwner(Request $request, UserGitConnection $gitConnection): void
{
abort_unless($gitConnection->user_id === $this->authenticatedUser($request)->id, 403);
}
}