add persisted set ordering

This commit is contained in:
Yisroel Baum 2026-07-31 11:00:41 +03:00
parent fde12ad561
commit dc034bf393
Signed by: yisroelbaum
GPG key ID: 0FA60884F75520A9
9 changed files with 340 additions and 6 deletions

View file

@ -11,6 +11,8 @@ use App\Set\UseCases\CreateSetWithRoot\CreateSetWithRoot;
use App\Set\UseCases\CreateSetWithRoot\CreateSetWithRootRequest;
use App\Set\UseCases\DeleteSet\DeleteSet;
use App\Set\UseCases\DeleteSet\DeleteSetRequest;
use App\Set\UseCases\ReorderSets\ReorderSets;
use App\Set\UseCases\ReorderSets\ReorderSetsRequest;
use App\Set\UseCases\UpdateSet\UpdateSet;
use App\Set\UseCases\UpdateSet\UpdateSetRequest;
use App\Shared\Files\Filesystem;
@ -26,19 +28,34 @@ class SetController
private CreateSetWithRoot $createSetWithRoot,
private UpdateSet $updateSet,
private DeleteSet $deleteSet,
private ReorderSets $reorderSets,
private Filesystem $filesystem,
) {
}
public function index(): JsonResponse
{
$sets = [];
foreach ($this->setRepository->getAll() as $set) {
$sets[] = $this->buildSetPayload($set);
return new JsonResponse([
'sets' => $this->buildSetPayloads(
$this->setRepository->getAll(),
),
], 200);
}
public function reorder(Request $request): JsonResponse
{
try {
$sets = $this->reorderSets->execute(new ReorderSetsRequest(
setIds: $this->intArrayInput($request, 'setIds'),
));
} catch (BadRequestException $exception) {
return new JsonResponse([
'error' => $exception->getMessage(),
], 400);
}
return new JsonResponse([
'sets' => $sets,
'sets' => $this->buildSetPayloads($sets),
], 200);
}
@ -141,6 +158,58 @@ class SetController
];
}
/**
* @param DomainSet[] $sets
* @return array<int, array{
* id: int,
* name: string,
* description: string,
* iconImageUrl: string,
* rootElementId: int|null
* }>
*/
private function buildSetPayloads(array $sets): array
{
$setPayloads = [];
foreach ($sets as $set) {
$setPayloads[] = $this->buildSetPayload($set);
}
return $setPayloads;
}
/**
* @return int[]|null
*/
private function intArrayInput(Request $request, string $key): ?array
{
if (! $request->exists($key)) {
return null;
}
$value = $request->input($key);
if (! is_array($value)) {
return null;
}
$integerValues = [];
foreach ($value as $item) {
if (is_int($item)) {
$integerValues[] = $item;
continue;
}
if (is_string($item) && ctype_digit($item)) {
$integerValues[] = (int) $item;
continue;
}
return null;
}
return $integerValues;
}
private function stringInput(Request $request, string $key): ?string
{
if (! $request->exists($key)) {

View file

@ -3,6 +3,7 @@
namespace App\Set;
use DomainException;
use Illuminate\Support\Facades\DB;
class EloquentSetRepository implements SetRepository
{
@ -12,6 +13,7 @@ class EloquentSetRepository implements SetRepository
'name' => $dto->name,
'description' => $dto->description,
'icon_image_url' => $dto->iconImageUrl,
'sort_order' => $this->nextSortOrder(),
]);
return $this->toDomain($model);
@ -55,7 +57,7 @@ class EloquentSetRepository implements SetRepository
public function getAll(): array
{
$models = SetModel::orderBy('id')->get();
$models = SetModel::orderBy('sort_order')->orderBy('id')->get();
$sets = [];
foreach ($models as $model) {
$sets[] = $this->toDomain($model);
@ -64,6 +66,30 @@ class EloquentSetRepository implements SetRepository
return $sets;
}
public function reorder(array $setIds): array
{
DB::transaction(function () use ($setIds): void {
$sortOrder = 1;
foreach ($setIds as $setId) {
SetModel::where('id', $setId)
->update(['sort_order' => $sortOrder]);
$sortOrder++;
}
});
return $this->getAll();
}
private function nextSortOrder(): int
{
$currentMaxSortOrder = SetModel::max('sort_order');
if ($currentMaxSortOrder === null) {
return 1;
}
return (int) $currentMaxSortOrder + 1;
}
private function toDomain(SetModel $model): Set
{
return new Set(

View file

@ -10,12 +10,14 @@ use Illuminate\Database\Eloquent\Model;
* @property string $name
* @property string $description
* @property string $icon_image_url
* @property int $sort_order
*
* @method static Builder<static>|SetModel newModelQuery()
* @method static Builder<static>|SetModel newQuery()
* @method static Builder<static>|SetModel query()
* @method static Builder<static>|SetModel whereId($value)
* @method static Builder<static>|SetModel whereName($value)
* @method static Builder<static>|SetModel whereSortOrder($value)
*
* @mixin \Eloquent
*/
@ -25,5 +27,14 @@ class SetModel extends Model
public $timestamps = false;
protected $fillable = ['name', 'description', 'icon_image_url'];
protected $fillable = [
'name',
'description',
'icon_image_url',
'sort_order',
];
protected $casts = [
'sort_order' => 'integer',
];
}

View file

@ -16,4 +16,10 @@ interface SetRepository
* @return Set[]
*/
public function getAll(): array;
/**
* @param int[] $setIds
* @return Set[]
*/
public function reorder(array $setIds): array;
}

View file

@ -0,0 +1,128 @@
<?php
namespace App\Set\UseCases\ReorderSets;
use App\Exceptions\BadRequestException;
use App\Set\Set;
use App\Set\SetRepository;
class ReorderSets
{
public function __construct(private SetRepository $setRepository)
{
}
/**
* @return Set[]
* @throws BadRequestException
*/
public function execute(ReorderSetsRequest $request): array
{
if ($request->setIds === null) {
throw new BadRequestException('setIds is required');
}
$setIds = $this->validatedSetIds($request->setIds);
$existingSetIds = $this->setIds($this->setRepository->getAll());
$this->validateNoDuplicateIds($setIds);
$this->validateAllIdsAreSets($setIds, $existingSetIds);
$this->validateEverySetWasSubmitted($setIds, $existingSetIds);
return $this->setRepository->reorder($setIds);
}
/**
* @param mixed[] $setIds
* @return int[]
* @throws BadRequestException
*/
private function validatedSetIds(array $setIds): array
{
$validatedSetIds = [];
foreach ($setIds as $setId) {
if (! is_int($setId)) {
throw new BadRequestException(
'setIds must contain integers',
);
}
$validatedSetIds[] = $setId;
}
return $validatedSetIds;
}
/**
* @param int[] $setIds
* @throws BadRequestException
*/
private function validateNoDuplicateIds(array $setIds): void
{
$seenSetIds = [];
foreach ($setIds as $setId) {
if (isset($seenSetIds[$setId])) {
throw new BadRequestException(
'Set order contains duplicate ids',
);
}
$seenSetIds[$setId] = true;
}
}
/**
* @param int[] $setIds
* @param int[] $existingSetIds
* @throws BadRequestException
*/
private function validateAllIdsAreSets(
array $setIds,
array $existingSetIds,
): void {
$existingSetIdsById = [];
foreach ($existingSetIds as $existingSetId) {
$existingSetIdsById[$existingSetId] = true;
}
foreach ($setIds as $setId) {
if (! isset($existingSetIdsById[$setId])) {
throw new BadRequestException(
'Set order contains invalid set',
);
}
}
}
/**
* @param int[] $setIds
* @param int[] $existingSetIds
* @throws BadRequestException
*/
private function validateEverySetWasSubmitted(
array $setIds,
array $existingSetIds,
): void {
if (count($setIds) === count($existingSetIds)) {
return;
}
throw new BadRequestException(
'Set order must include every set',
);
}
/**
* @param Set[] $sets
* @return int[]
*/
private function setIds(array $sets): array
{
$setIds = [];
foreach ($sets as $set) {
$setIds[] = $set->getId();
}
return $setIds;
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace App\Set\UseCases\ReorderSets;
class ReorderSetsRequest
{
/**
* @param mixed[]|null $setIds
*/
public function __construct(public ?array $setIds)
{
}
}