Add application API and OpenAPI docs
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user