From 0d102fcee01a61b3952219b737fe2758fd160b1f Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Tue, 11 Aug 2026 22:47:34 +0300 Subject: [PATCH] add explicit set levels --- backend/app/Providers/AppServiceProvider.php | 6 ++ backend/app/Set/CreateSetLevelDto.php | 11 +++ .../app/Set/EloquentSetLevelRepository.php | 88 +++++++++++++++++++ backend/app/Set/SetLevel.php | 33 +++++++ backend/app/Set/SetLevelModel.php | 38 ++++++++ backend/app/Set/SetLevelRepository.php | 20 +++++ ...6_08_07_000000_create_set_levels_table.php | 27 ++++++ 7 files changed, 223 insertions(+) create mode 100644 backend/app/Set/CreateSetLevelDto.php create mode 100644 backend/app/Set/EloquentSetLevelRepository.php create mode 100644 backend/app/Set/SetLevel.php create mode 100644 backend/app/Set/SetLevelModel.php create mode 100644 backend/app/Set/SetLevelRepository.php create mode 100644 backend/database/migrations/2026_08_07_000000_create_set_levels_table.php diff --git a/backend/app/Providers/AppServiceProvider.php b/backend/app/Providers/AppServiceProvider.php index d0d019d..aaf8dfd 100644 --- a/backend/app/Providers/AppServiceProvider.php +++ b/backend/app/Providers/AppServiceProvider.php @@ -19,7 +19,9 @@ use App\Email\LaravelEmailFactory; use App\Element\ElementRepository; use App\Element\EloquentElementRepository; use App\Set\EloquentSetRepository; +use App\Set\EloquentSetLevelRepository; use App\Set\SetRepository; +use App\Set\SetLevelRepository; use App\Schedule\EloquentScheduleRepository; use App\Schedule\ScheduleRepository; use App\User\EloquentUserRepository; @@ -55,6 +57,10 @@ class AppServiceProvider extends ServiceProvider SetRepository::class, EloquentSetRepository::class, ); + $this->app->bind( + SetLevelRepository::class, + EloquentSetLevelRepository::class, + ); $this->app->bind( ElementRepository::class, EloquentElementRepository::class, diff --git a/backend/app/Set/CreateSetLevelDto.php b/backend/app/Set/CreateSetLevelDto.php new file mode 100644 index 0000000..1920a50 --- /dev/null +++ b/backend/app/Set/CreateSetLevelDto.php @@ -0,0 +1,11 @@ +where('set_id', $dto->set->getId()) + ->where('kind', $dto->kind) + ->exists(); + if ($kindExists) { + throw new DomainException( + 'level kind must be unique within set', + ); + } + + $currentMaximum = SetLevelModel::query() + ->where('set_id', $dto->set->getId()) + ->max('depth'); + $depth = $currentMaximum === null + ? 0 + : (int) $currentMaximum + 1; + $model = SetLevelModel::create([ + 'set_id' => $dto->set->getId(), + 'kind' => $dto->kind, + 'depth' => $depth, + ]); + + return new SetLevel( + id: $model->id, + set: $dto->set, + kind: $model->kind, + depth: $model->depth, + ); + } + + public function find(int $id): ?SetLevel + { + $model = SetLevelModel::find($id); + + return $model === null ? null : $this->toDomain($model); + } + + public function findBySet(Set $set): array + { + $models = SetLevelModel::query() + ->where('set_id', $set->getId()) + ->orderBy('depth') + ->orderBy('id') + ->get(); + $levels = []; + + foreach ($models as $model) { + $levels[] = new SetLevel( + id: $model->id, + set: $set, + kind: $model->kind, + depth: $model->depth, + ); + } + + return $levels; + } + + private function toDomain(SetLevelModel $model): SetLevel + { + $set = $this->setRepository->find($model->set_id); + if ($set === null) { + throw new RuntimeException('set level set not found'); + } + + return new SetLevel( + id: $model->id, + set: $set, + kind: $model->kind, + depth: $model->depth, + ); + } +} diff --git a/backend/app/Set/SetLevel.php b/backend/app/Set/SetLevel.php new file mode 100644 index 0000000..23b5a99 --- /dev/null +++ b/backend/app/Set/SetLevel.php @@ -0,0 +1,33 @@ +id; + } + + public function getSet(): Set + { + return $this->set; + } + + public function getKind(): string + { + return $this->kind; + } + + public function getDepth(): int + { + return $this->depth; + } +} diff --git a/backend/app/Set/SetLevelModel.php b/backend/app/Set/SetLevelModel.php new file mode 100644 index 0000000..50daf3e --- /dev/null +++ b/backend/app/Set/SetLevelModel.php @@ -0,0 +1,38 @@ +|SetLevelModel newModelQuery() + * @method static Builder|SetLevelModel newQuery() + * @method static Builder|SetLevelModel query() + * + * @mixin \Eloquent + */ +#[Fillable(['set_id', 'kind', 'depth'])] +class SetLevelModel extends Model +{ + protected $table = 'set_levels'; + + public $timestamps = false; + + /** + * @return array + */ + protected function casts(): array + { + return [ + 'set_id' => 'integer', + 'depth' => 'integer', + ]; + } +} diff --git a/backend/app/Set/SetLevelRepository.php b/backend/app/Set/SetLevelRepository.php new file mode 100644 index 0000000..ba4b68c --- /dev/null +++ b/backend/app/Set/SetLevelRepository.php @@ -0,0 +1,20 @@ + + */ + public function findBySet(Set $set): array; +} diff --git a/backend/database/migrations/2026_08_07_000000_create_set_levels_table.php b/backend/database/migrations/2026_08_07_000000_create_set_levels_table.php new file mode 100644 index 0000000..552c31a --- /dev/null +++ b/backend/database/migrations/2026_08_07_000000_create_set_levels_table.php @@ -0,0 +1,27 @@ +id(); + $table->foreignId('set_id') + ->constrained('sets') + ->cascadeOnDelete(); + $table->string('kind'); + $table->unsignedInteger('depth'); + $table->unique(['set_id', 'kind']); + $table->unique(['set_id', 'depth']); + }); + } + + public function down(): void + { + Schema::dropIfExists('set_levels'); + } +};