Add application API and OpenAPI docs
This commit is contained in:
@@ -3,9 +3,21 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Permissions\PermissionService;
|
||||
use App\Actions\Tickets\CreateTicketWorklogEntry;
|
||||
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 as UpdateProjectTicketMetadataSettingsAction;
|
||||
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\Enums\TicketWorklogEntryType;
|
||||
use App\Http\Requests\DestroyProjectTicketTypeRequest;
|
||||
use App\Http\Requests\StoreProjectNamedSettingRequest;
|
||||
use App\Http\Requests\StoreProjectTicketTypeRequest;
|
||||
@@ -14,60 +26,51 @@ use App\Http\Requests\UpdateProjectNamedSettingRequest;
|
||||
use App\Http\Requests\UpdateProjectTicketMetadataSettings;
|
||||
use App\Http\Requests\UpdateProjectTicketTypeRequest;
|
||||
use App\Http\Requests\UpdateTicketFieldDefinitionRequest;
|
||||
use App\Http\Resources\FieldTypeOptionResource;
|
||||
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\Ticket;
|
||||
use App\Models\TicketFieldDefinition;
|
||||
use App\Models\TicketLinkType;
|
||||
use App\Models\TicketPriority;
|
||||
use App\Models\TicketType;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ProjectTicketMetadataSettingsController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CreateTicketWorklogEntry $createTicketWorklogEntry,
|
||||
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 UpdateProjectTicketMetadataSettingsAction $updateProjectTicketMetadataSettings,
|
||||
private readonly UpdateProjectTicketPriority $updateProjectTicketPriority,
|
||||
private readonly UpdateProjectTicketType $updateProjectTicketType,
|
||||
private readonly UpdateTicketFieldDefinition $updateTicketFieldDefinition,
|
||||
private readonly UpdateTicketLinkType $updateTicketLinkType,
|
||||
) {}
|
||||
|
||||
public function show(Request $request, Project $project): Response
|
||||
{
|
||||
$this->abortUnlessCanManage($request, $project);
|
||||
|
||||
return Inertia::render('projects/settings/Metadata', [
|
||||
'fieldTypeOptions' => FieldTypeOptionResource::collection(collect(TicketFieldDefinition::FIELD_TYPES))->resolve(),
|
||||
'project' => ProjectResource::make($project)->resolve(),
|
||||
'error' => session('error'),
|
||||
'status' => session('status'),
|
||||
'ticketFieldDefinitions' => TicketFieldDefinitionResource::collection($project->ticketFieldDefinitions()->get())->resolve(),
|
||||
'ticketLinkTypes' => TicketLinkTypeResource::collection($project->ticketLinkTypes()->get())->resolve(),
|
||||
'ticketPriorities' => TicketPriorityResource::collection($project->ticketPriorities()->get())->resolve(),
|
||||
'ticketTypes' => TicketTypeResource::collection($project->ticketTypes()->withCount('tickets')->get())->resolve(),
|
||||
]);
|
||||
return Inertia::render(
|
||||
'projects/settings/Metadata',
|
||||
$this->buildProjectTicketMetadataSettingsPayload->handle($project, session('status'), session('error')),
|
||||
);
|
||||
}
|
||||
|
||||
public function update(UpdateProjectTicketMetadataSettings $request, Project $project): RedirectResponse
|
||||
{
|
||||
$this->abortUnlessCanManage($request, $project);
|
||||
|
||||
$validated = $request->validated();
|
||||
|
||||
$project->update([
|
||||
'show_author' => Arr::get($validated, 'show_author', false),
|
||||
'show_story_points' => Arr::get($validated, 'show_story_points', false),
|
||||
]);
|
||||
$this->updateProjectTicketMetadataSettings->handle($project, $request->validated());
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
->with('status', 'Settings have been updated.');
|
||||
@@ -77,23 +80,7 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
{
|
||||
$this->abortUnlessCanManage($request, $project);
|
||||
|
||||
$validated = $request->validated();
|
||||
|
||||
$ticketType = DB::transaction(function () use ($project, $validated): TicketType {
|
||||
$ticketType = $project->ticketTypes()->create([
|
||||
'name' => Arr::get($validated, 'name'),
|
||||
'position' => $project->ticketTypes()->count() + 1,
|
||||
'is_parent' => Arr::get($validated, 'is_parent', false),
|
||||
]);
|
||||
|
||||
$this->reorderModels(
|
||||
$project->ticketTypes()->whereKeyNot($ticketType->id)->get()->values(),
|
||||
$ticketType,
|
||||
Arr::get($validated, 'position') ?? $project->ticketTypes()->count(),
|
||||
);
|
||||
|
||||
return $ticketType;
|
||||
});
|
||||
$ticketType = $this->storeProjectTicketType->handle($project, $request->validated());
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
->with('status', "{$ticketType->name} type was added.");
|
||||
@@ -106,19 +93,7 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
): RedirectResponse {
|
||||
$this->abortUnlessCanManage($request, $project);
|
||||
|
||||
$validated = $request->validated();
|
||||
|
||||
DB::transaction(function () use ($project, $validated, $ticketType): void {
|
||||
$ticketType->update([
|
||||
'name' => Arr::get($validated, 'name'),
|
||||
]);
|
||||
|
||||
$this->reorderModels(
|
||||
$project->ticketTypes()->whereKeyNot($ticketType->id)->get()->values(),
|
||||
$ticketType,
|
||||
Arr::get($validated, 'position'),
|
||||
);
|
||||
});
|
||||
$ticketType = $this->updateProjectTicketType->handle($project, $ticketType, $request->validated());
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
->with('status', "{$ticketType->fresh()->name} type was updated.");
|
||||
@@ -131,78 +106,28 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
): RedirectResponse {
|
||||
$this->abortUnlessCanManage($request, $project);
|
||||
|
||||
if ($project->ticketTypes()->count() === 1) {
|
||||
$result = $this->deleteProjectTicketType->handle(
|
||||
$project,
|
||||
$ticketType,
|
||||
$request->user(),
|
||||
$request->validated(),
|
||||
);
|
||||
|
||||
if (! $result['deleted']) {
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
->with('error', true)
|
||||
->with('status', 'The last ticket type cannot be deleted.');
|
||||
->with('status', $result['message']);
|
||||
}
|
||||
|
||||
$deletedName = $ticketType->name;
|
||||
$validated = $request->validated();
|
||||
|
||||
DB::transaction(function () use ($deletedName, $project, $request, $ticketType, $validated): void {
|
||||
$replacementTicketTypeId = Arr::get($validated, 'replacement_ticket_type_id');
|
||||
|
||||
if ($replacementTicketTypeId !== null) {
|
||||
$replacementTicketType = $project->ticketTypes()
|
||||
->findOrFail($replacementTicketTypeId);
|
||||
|
||||
Ticket::query()
|
||||
->where('project_id', $project->id)
|
||||
->where('ticket_type_id', $ticketType->id)
|
||||
->with(['ticketType'])
|
||||
->get()
|
||||
->each(function (Ticket $ticket) use ($deletedName, $replacementTicketType, $request): void {
|
||||
$ticket->update([
|
||||
'ticket_type_id' => $replacementTicketType->id,
|
||||
]);
|
||||
|
||||
$this->createTicketWorklogEntry->handle(
|
||||
$ticket->fresh(),
|
||||
$request->user(),
|
||||
TicketWorklogEntryType::Updated,
|
||||
"Updated type because {$deletedName} type was deleted.",
|
||||
[
|
||||
'ticket_type' => [
|
||||
'from' => $deletedName,
|
||||
'to' => $replacementTicketType->name,
|
||||
],
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
$ticketType->delete();
|
||||
|
||||
$this->persistOrderedModels($project->ticketTypes()->get()->values());
|
||||
});
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
->with('status', "{$deletedName} type was removed.");
|
||||
->with('status', $result['message']);
|
||||
}
|
||||
|
||||
public function storePriority(StoreProjectNamedSettingRequest $request, Project $project): RedirectResponse
|
||||
{
|
||||
$this->abortUnlessCanManage($request, $project);
|
||||
|
||||
$validated = $request->validated();
|
||||
$name = Arr::get($validated, 'name');
|
||||
$position = Arr::get($validated, 'position');
|
||||
|
||||
$ticketPriority = DB::transaction(function () use ($project, $name, $position): TicketPriority {
|
||||
$ticketPriority = $project->ticketPriorities()->create([
|
||||
'name' => $name,
|
||||
'position' => $project->ticketPriorities()->count() + 1,
|
||||
]);
|
||||
|
||||
$this->reorderModels(
|
||||
$project->ticketPriorities()->whereKeyNot($ticketPriority->id)->get()->values(),
|
||||
$ticketPriority,
|
||||
$position ?? $project->ticketPriorities()->count(),
|
||||
);
|
||||
|
||||
return $ticketPriority;
|
||||
});
|
||||
$ticketPriority = $this->storeProjectTicketPriority->handle($project, $request->validated());
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
->with('status', "{$ticketPriority->name} priority was added.");
|
||||
@@ -215,21 +140,7 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
): RedirectResponse {
|
||||
$this->abortUnlessCanManage($request, $project);
|
||||
|
||||
$validated = $request->validated();
|
||||
$name = Arr::get($validated, 'name');
|
||||
$position = Arr::get($validated, 'position');
|
||||
|
||||
DB::transaction(function () use ($project, $name, $position, $ticketPriority): void {
|
||||
$ticketPriority->update([
|
||||
'name' => $name,
|
||||
]);
|
||||
|
||||
$this->reorderModels(
|
||||
$project->ticketPriorities()->whereKeyNot($ticketPriority->id)->get()->values(),
|
||||
$ticketPriority,
|
||||
$position,
|
||||
);
|
||||
});
|
||||
$ticketPriority = $this->updateProjectTicketPriority->handle($project, $ticketPriority, $request->validated());
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
->with('status', "{$ticketPriority->fresh()->name} priority was updated.");
|
||||
@@ -242,22 +153,16 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
): RedirectResponse {
|
||||
$this->abortUnlessCanManage($request, $project);
|
||||
|
||||
if ($project->ticketPriorities()->count() === 1) {
|
||||
$result = $this->deleteProjectTicketPriority->handle($project, $ticketPriority);
|
||||
|
||||
if (! $result['deleted']) {
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
->with('error', true)
|
||||
->with('status', 'The last ticket priority cannot be deleted.');
|
||||
->with('status', $result['message']);
|
||||
}
|
||||
|
||||
$deletedName = $ticketPriority->name;
|
||||
|
||||
DB::transaction(function () use ($project, $ticketPriority): void {
|
||||
$ticketPriority->delete();
|
||||
|
||||
$this->persistOrderedModels($project->ticketPriorities()->get()->values());
|
||||
});
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
->with('status', "{$deletedName} priority was removed.");
|
||||
->with('status', $result['message']);
|
||||
}
|
||||
|
||||
public function storeFieldDefinition(
|
||||
@@ -266,23 +171,7 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
): RedirectResponse {
|
||||
$this->abortUnlessCanManage($request, $project);
|
||||
|
||||
$validated = $request->validated();
|
||||
|
||||
$ticketFieldDefinition = DB::transaction(function () use ($project, $validated): TicketFieldDefinition {
|
||||
$ticketFieldDefinition = $project->ticketFieldDefinitions()->create([
|
||||
'field_type' => Arr::get($validated, 'field_type'),
|
||||
'label' => Arr::get($validated, 'label'),
|
||||
'position' => $project->ticketFieldDefinitions()->count() + 1,
|
||||
]);
|
||||
|
||||
$this->reorderModels(
|
||||
$project->ticketFieldDefinitions()->whereKeyNot($ticketFieldDefinition->id)->get()->values(),
|
||||
$ticketFieldDefinition,
|
||||
Arr::get($validated, 'position') ?? $project->ticketFieldDefinitions()->count(),
|
||||
);
|
||||
|
||||
return $ticketFieldDefinition;
|
||||
});
|
||||
$ticketFieldDefinition = $this->storeTicketFieldDefinition->handle($project, $request->validated());
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
->with('status', "{$ticketFieldDefinition->label} field was added.");
|
||||
@@ -295,20 +184,7 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
): RedirectResponse {
|
||||
$this->abortUnlessCanManage($request, $project);
|
||||
|
||||
$validated = $request->validated();
|
||||
|
||||
DB::transaction(function () use ($project, $ticketFieldDefinition, $validated): void {
|
||||
$ticketFieldDefinition->update([
|
||||
'field_type' => Arr::get($validated, 'field_type'),
|
||||
'label' => Arr::get($validated, 'label'),
|
||||
]);
|
||||
|
||||
$this->reorderModels(
|
||||
$project->ticketFieldDefinitions()->whereKeyNot($ticketFieldDefinition->id)->get()->values(),
|
||||
$ticketFieldDefinition,
|
||||
Arr::get($validated, 'position'),
|
||||
);
|
||||
});
|
||||
$ticketFieldDefinition = $this->updateTicketFieldDefinition->handle($project, $ticketFieldDefinition, $request->validated());
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
->with('status', "{$ticketFieldDefinition->fresh()->label} field was updated.");
|
||||
@@ -321,13 +197,7 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
): RedirectResponse {
|
||||
$this->abortUnlessCanManage($request, $project);
|
||||
|
||||
$deletedLabel = $ticketFieldDefinition->label;
|
||||
|
||||
DB::transaction(function () use ($project, $ticketFieldDefinition): void {
|
||||
$ticketFieldDefinition->delete();
|
||||
|
||||
$this->persistOrderedModels($project->ticketFieldDefinitions()->get()->values());
|
||||
});
|
||||
$deletedLabel = $this->deleteTicketFieldDefinition->handle($project, $ticketFieldDefinition);
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
->with('status', "{$deletedLabel} field was removed.");
|
||||
@@ -335,26 +205,9 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
|
||||
public function storeLinkType(StoreProjectNamedSettingRequest $request, Project $project): RedirectResponse
|
||||
{
|
||||
$validated = $request->validated();
|
||||
$name = Arr::get($validated, 'name');
|
||||
$position = Arr::get($validated, 'position');
|
||||
|
||||
$this->abortUnlessCanManage($request, $project);
|
||||
|
||||
$ticketLinkType = DB::transaction(function () use ($project, $position, $name): TicketLinkType {
|
||||
$ticketLinkType = $project->ticketLinkTypes()->create([
|
||||
'label' => $name,
|
||||
'position' => $project->ticketLinkTypes()->count() + 1,
|
||||
]);
|
||||
|
||||
$this->reorderModels(
|
||||
$project->ticketLinkTypes()->whereKeyNot($ticketLinkType->id)->get()->values(),
|
||||
$ticketLinkType,
|
||||
$position ?? $project->ticketLinkTypes()->count(),
|
||||
);
|
||||
|
||||
return $ticketLinkType;
|
||||
});
|
||||
$ticketLinkType = $this->storeTicketLinkType->handle($project, $request->validated());
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
->with('status', "{$ticketLinkType->label} link type was added.");
|
||||
@@ -366,23 +219,9 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
TicketLinkType $ticketLinkType,
|
||||
): RedirectResponse {
|
||||
|
||||
$validated = $request->validated();
|
||||
$name = Arr::get($validated, 'name');
|
||||
$position = Arr::get($validated, 'position');
|
||||
|
||||
$this->abortUnlessCanManage($request, $project);
|
||||
|
||||
DB::transaction(function () use ($project, $name, $position, $ticketLinkType): void {
|
||||
$ticketLinkType->update([
|
||||
'label' => $name,
|
||||
]);
|
||||
|
||||
$this->reorderModels(
|
||||
$project->ticketLinkTypes()->whereKeyNot($ticketLinkType->id)->get()->values(),
|
||||
$ticketLinkType,
|
||||
$position,
|
||||
);
|
||||
});
|
||||
$ticketLinkType = $this->updateTicketLinkType->handle($project, $ticketLinkType, $request->validated());
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
->with('status', "{$ticketLinkType->fresh()->label} link type was updated.");
|
||||
@@ -395,13 +234,7 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
): RedirectResponse {
|
||||
$this->abortUnlessCanManage($request, $project);
|
||||
|
||||
$deletedLabel = $ticketLinkType->label;
|
||||
|
||||
DB::transaction(function () use ($project, $ticketLinkType): void {
|
||||
$ticketLinkType->delete();
|
||||
|
||||
$this->persistOrderedModels($project->ticketLinkTypes()->get()->values());
|
||||
});
|
||||
$deletedLabel = $this->deleteTicketLinkType->handle($project, $ticketLinkType);
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
->with('status', "{$deletedLabel} link type was removed.");
|
||||
@@ -414,36 +247,4 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<int, Model> $models
|
||||
*/
|
||||
private function persistOrderedModels(Collection $models): void
|
||||
{
|
||||
$temporaryOffset = ((int) $models->max('position')) + $models->count() + 1;
|
||||
|
||||
$models->each(function (Model $model, int $index) use ($temporaryOffset): void {
|
||||
$model->forceFill([
|
||||
'position' => $temporaryOffset + $index,
|
||||
])->saveQuietly();
|
||||
});
|
||||
|
||||
$models->each(function (Model $model, int $index): void {
|
||||
$model->forceFill([
|
||||
'position' => $index + 1,
|
||||
])->saveQuietly();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<int, Model> $existingModels
|
||||
*/
|
||||
private function reorderModels(Collection $existingModels, Model $movingModel, int $desiredPosition): void
|
||||
{
|
||||
$insertAt = max(0, min($desiredPosition - 1, $existingModels->count()));
|
||||
|
||||
$existingModels->splice($insertAt, 0, [$movingModel]);
|
||||
|
||||
$this->persistOrderedModels($existingModels->values());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user