schedule explicit set levels
This commit is contained in:
parent
a64df64b76
commit
3cf1aff6a9
10 changed files with 156 additions and 38 deletions
|
|
@ -34,7 +34,7 @@ class ScheduleController extends Controller
|
|||
new CreateScheduleRequest(
|
||||
user: $user,
|
||||
setId: $input->integer('setId'),
|
||||
elementKind: $input->string('elementKind'),
|
||||
levelId: $input->integer('levelId'),
|
||||
startDate: $input->string('startDate'),
|
||||
targetDate: $input->string('targetDate'),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use App\Set\Set;
|
|||
use App\Set\UseCases\GetSetLayout\ElementLayoutNode;
|
||||
use App\Set\UseCases\GetSetLayout\GetSetLayout;
|
||||
use App\Set\UseCases\GetSetLayout\GetSetLayoutRequest;
|
||||
use App\Set\UseCases\GetSetLayout\SetLayoutLevel;
|
||||
use App\Set\UseCases\ListSets\ListSets;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
|
|
@ -49,6 +50,19 @@ class SetController extends Controller
|
|||
'id' => $set->getId(),
|
||||
'name' => $set->getName(),
|
||||
],
|
||||
'levels' => array_map(
|
||||
function (SetLayoutLevel $layoutLevel): array {
|
||||
$level = $layoutLevel->getLevel();
|
||||
|
||||
return [
|
||||
'id' => $level->getId(),
|
||||
'kind' => $level->getKind(),
|
||||
'depth' => $level->getDepth(),
|
||||
'elementCount' => $layoutLevel->getElementCount(),
|
||||
];
|
||||
},
|
||||
$layout->getLevels(),
|
||||
),
|
||||
'elements' => array_map(
|
||||
function (ElementLayoutNode $node): array {
|
||||
return $this->elementPayload($node);
|
||||
|
|
@ -63,6 +77,7 @@ class SetController extends Controller
|
|||
* id: int,
|
||||
* name: string,
|
||||
* kind: string,
|
||||
* levelId: int,
|
||||
* children: list<mixed>
|
||||
* }
|
||||
*/
|
||||
|
|
@ -74,6 +89,7 @@ class SetController extends Controller
|
|||
'id' => $element->getId(),
|
||||
'name' => $element->getName(),
|
||||
'kind' => $element->getKind(),
|
||||
'levelId' => $element->getLevel()->getId(),
|
||||
'children' => array_map(
|
||||
function (ElementLayoutNode $childNode): array {
|
||||
return $this->elementPayload($childNode);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use App\Schedule\CreateScheduleDto;
|
|||
use App\Schedule\Schedule;
|
||||
use App\Schedule\ScheduleRepository;
|
||||
use App\Set\Set;
|
||||
use App\Set\SetLevelRepository;
|
||||
use App\Set\SetRepository;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
|
|
@ -19,6 +20,7 @@ class CreateSchedule
|
|||
{
|
||||
public function __construct(
|
||||
private SetRepository $setRepository,
|
||||
private SetLevelRepository $setLevelRepository,
|
||||
private ElementRepository $elementRepository,
|
||||
private ScheduleRepository $scheduleRepository,
|
||||
) {}
|
||||
|
|
@ -38,8 +40,18 @@ class CreateSchedule
|
|||
throw new NotFoundException('set not found');
|
||||
}
|
||||
|
||||
if ($request->elementKind === null || $request->elementKind === '') {
|
||||
throw new BadRequestException('elementKind is required');
|
||||
if ($request->levelId === null || $request->levelId < 1) {
|
||||
throw new BadRequestException('levelId is required');
|
||||
}
|
||||
|
||||
$level = $this->setLevelRepository->find($request->levelId);
|
||||
if (
|
||||
$level === null
|
||||
|| $level->getSet()->getId() !== $set->getId()
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
'level is not available for set',
|
||||
);
|
||||
}
|
||||
|
||||
$startDate = $this->parseDate($request->startDate, 'startDate');
|
||||
|
|
@ -52,14 +64,12 @@ class CreateSchedule
|
|||
|
||||
$elements = array_values(array_filter(
|
||||
$this->orderedElements($set),
|
||||
function (Element $element) use ($request): bool {
|
||||
return $element->getKind() === $request->elementKind;
|
||||
function (Element $element) use ($level): bool {
|
||||
return $element->getLevel()->getId() === $level->getId();
|
||||
},
|
||||
));
|
||||
if ($elements === []) {
|
||||
throw new BadRequestException(
|
||||
'elementKind is not available for set',
|
||||
);
|
||||
throw new BadRequestException('level has no elements');
|
||||
}
|
||||
|
||||
$assignments = $this->assignments(
|
||||
|
|
@ -71,7 +81,7 @@ class CreateSchedule
|
|||
return $this->scheduleRepository->create(new CreateScheduleDto(
|
||||
user: $request->user,
|
||||
setName: $set->getName(),
|
||||
elementKind: $request->elementKind,
|
||||
elementKind: $level->getKind(),
|
||||
startDate: $startDate,
|
||||
targetDate: $targetDate,
|
||||
assignments: $assignments,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ final readonly class CreateScheduleRequest
|
|||
public function __construct(
|
||||
public User $user,
|
||||
public ?int $setId,
|
||||
public ?string $elementKind,
|
||||
public ?int $levelId,
|
||||
public ?string $startDate,
|
||||
public ?string $targetDate,
|
||||
) {}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@ use App\Element\Element;
|
|||
use App\Element\ElementRepository;
|
||||
use App\Exceptions\NotFoundException;
|
||||
use App\Set\SetRepository;
|
||||
use App\Set\SetLevelRepository;
|
||||
|
||||
class GetSetLayout
|
||||
{
|
||||
public function __construct(
|
||||
private SetRepository $setRepository,
|
||||
private SetLevelRepository $setLevelRepository,
|
||||
private ElementRepository $elementRepository,
|
||||
) {}
|
||||
|
||||
|
|
@ -24,10 +26,15 @@ class GetSetLayout
|
|||
throw new NotFoundException('set not found');
|
||||
}
|
||||
|
||||
$elements = $this->elementRepository->findBySet($set);
|
||||
$elementsByParentId = [];
|
||||
foreach ($this->elementRepository->findBySet($set) as $element) {
|
||||
$elementCountsByLevelId = [];
|
||||
foreach ($elements as $element) {
|
||||
$parentId = $element->getParentElement()?->getId() ?? 0;
|
||||
$elementsByParentId[$parentId][] = $element;
|
||||
$levelId = $element->getLevel()->getId();
|
||||
$elementCountsByLevelId[$levelId]
|
||||
= ($elementCountsByLevelId[$levelId] ?? 0) + 1;
|
||||
}
|
||||
|
||||
foreach ($elementsByParentId as &$siblings) {
|
||||
|
|
@ -46,8 +53,17 @@ class GetSetLayout
|
|||
}
|
||||
unset($siblings);
|
||||
|
||||
$layoutLevels = [];
|
||||
foreach ($this->setLevelRepository->findBySet($set) as $level) {
|
||||
$layoutLevels[] = new SetLayoutLevel(
|
||||
level: $level,
|
||||
elementCount: $elementCountsByLevelId[$level->getId()] ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
return new SetLayout(
|
||||
set: $set,
|
||||
levels: $layoutLevels,
|
||||
elements: $this->buildNodes(0, $elementsByParentId),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ use App\Set\Set;
|
|||
final readonly class SetLayout
|
||||
{
|
||||
/**
|
||||
* @param list<SetLayoutLevel> $levels
|
||||
* @param list<ElementLayoutNode> $elements
|
||||
*/
|
||||
public function __construct(
|
||||
private Set $set,
|
||||
private array $levels,
|
||||
private array $elements,
|
||||
) {}
|
||||
|
||||
|
|
@ -19,6 +21,14 @@ final readonly class SetLayout
|
|||
return $this->set;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<SetLayoutLevel>
|
||||
*/
|
||||
public function getLevels(): array
|
||||
{
|
||||
return $this->levels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<ElementLayoutNode>
|
||||
*/
|
||||
|
|
|
|||
23
backend/app/Set/UseCases/GetSetLayout/SetLayoutLevel.php
Normal file
23
backend/app/Set/UseCases/GetSetLayout/SetLayoutLevel.php
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\Set\UseCases\GetSetLayout;
|
||||
|
||||
use App\Set\SetLevel;
|
||||
|
||||
final readonly class SetLayoutLevel
|
||||
{
|
||||
public function __construct(
|
||||
private SetLevel $level,
|
||||
private int $elementCount,
|
||||
) {}
|
||||
|
||||
public function getLevel(): SetLevel
|
||||
{
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
public function getElementCount(): int
|
||||
{
|
||||
return $this->elementCount;
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ class DatabaseSeeder extends Seeder
|
|||
{
|
||||
$this->call(UserSeeder::class);
|
||||
$this->call(SetSeeder::class);
|
||||
$this->call(SetLevelSeeder::class);
|
||||
$this->call(ElementSeeder::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,11 @@ use App\Element\CreateElementDto;
|
|||
use App\Element\Element;
|
||||
use App\Element\ElementRepository;
|
||||
use App\Set\Set;
|
||||
use App\Set\SetLevel;
|
||||
use App\Set\SetLevelRepository;
|
||||
use App\Set\SetRepository;
|
||||
use Illuminate\Database\Seeder;
|
||||
use RuntimeException;
|
||||
|
||||
class ElementSeeder extends Seeder
|
||||
{
|
||||
|
|
@ -15,38 +18,31 @@ class ElementSeeder extends Seeder
|
|||
'Bible' => [
|
||||
[
|
||||
'name' => 'Genesis',
|
||||
'kind' => 'book',
|
||||
'children' => [
|
||||
[
|
||||
'name' => 'Creation',
|
||||
'kind' => 'portion',
|
||||
'children' => [
|
||||
[
|
||||
'name' => 'Chapter 1',
|
||||
'kind' => 'chapter',
|
||||
'children' => [],
|
||||
],
|
||||
[
|
||||
'name' => 'Chapter 2',
|
||||
'kind' => 'chapter',
|
||||
'children' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Noah',
|
||||
'kind' => 'portion',
|
||||
'children' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Exodus',
|
||||
'kind' => 'book',
|
||||
'children' => [
|
||||
[
|
||||
'name' => 'Shemot',
|
||||
'kind' => 'portion',
|
||||
'children' => [],
|
||||
],
|
||||
],
|
||||
|
|
@ -55,32 +51,26 @@ class ElementSeeder extends Seeder
|
|||
'Course' => [
|
||||
[
|
||||
'name' => 'Foundations',
|
||||
'kind' => 'module',
|
||||
'children' => [
|
||||
[
|
||||
'name' => 'Welcome',
|
||||
'kind' => 'lesson',
|
||||
'children' => [],
|
||||
],
|
||||
[
|
||||
'name' => 'Core Concepts',
|
||||
'kind' => 'lesson',
|
||||
'children' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Applied Practice',
|
||||
'kind' => 'module',
|
||||
'children' => [
|
||||
[
|
||||
'name' => 'Guided Exercise',
|
||||
'kind' => 'lesson',
|
||||
'children' => [],
|
||||
],
|
||||
[
|
||||
'name' => 'Final Review',
|
||||
'kind' => 'lesson',
|
||||
'children' => [],
|
||||
],
|
||||
],
|
||||
|
|
@ -89,31 +79,25 @@ class ElementSeeder extends Seeder
|
|||
'Fitness Program' => [
|
||||
[
|
||||
'name' => 'Foundation Phase',
|
||||
'kind' => 'phase',
|
||||
'children' => [
|
||||
[
|
||||
'name' => 'Strength Day',
|
||||
'kind' => 'workout',
|
||||
'children' => [
|
||||
[
|
||||
'name' => 'Squat',
|
||||
'kind' => 'exercise',
|
||||
'children' => [],
|
||||
],
|
||||
[
|
||||
'name' => 'Push-up',
|
||||
'kind' => 'exercise',
|
||||
'children' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Mobility Day',
|
||||
'kind' => 'workout',
|
||||
'children' => [
|
||||
[
|
||||
'name' => 'Hip Flow',
|
||||
'kind' => 'exercise',
|
||||
'children' => [],
|
||||
],
|
||||
],
|
||||
|
|
@ -122,11 +106,9 @@ class ElementSeeder extends Seeder
|
|||
],
|
||||
[
|
||||
'name' => 'Build Phase',
|
||||
'kind' => 'phase',
|
||||
'children' => [
|
||||
[
|
||||
'name' => 'Full Body Circuit',
|
||||
'kind' => 'workout',
|
||||
'children' => [],
|
||||
],
|
||||
],
|
||||
|
|
@ -137,6 +119,7 @@ class ElementSeeder extends Seeder
|
|||
public function run(): void
|
||||
{
|
||||
$elementRepository = app(ElementRepository::class);
|
||||
$setLevelRepository = app(SetLevelRepository::class);
|
||||
|
||||
foreach (app(SetRepository::class)->all() as $set) {
|
||||
$definitions = self::ELEMENTS_BY_SET[$set->getName()] ?? null;
|
||||
|
|
@ -147,6 +130,8 @@ class ElementSeeder extends Seeder
|
|||
$this->seedChildren(
|
||||
repository: $elementRepository,
|
||||
set: $set,
|
||||
levels: $setLevelRepository->findBySet($set),
|
||||
depth: 0,
|
||||
parentElement: null,
|
||||
definitions: $definitions,
|
||||
);
|
||||
|
|
@ -156,16 +141,27 @@ class ElementSeeder extends Seeder
|
|||
/**
|
||||
* @param list<array{
|
||||
* name: string,
|
||||
* kind: string,
|
||||
* children: list<mixed>
|
||||
* }> $definitions
|
||||
* @param list<SetLevel> $levels
|
||||
*/
|
||||
private function seedChildren(
|
||||
ElementRepository $repository,
|
||||
Set $set,
|
||||
array $levels,
|
||||
int $depth,
|
||||
?Element $parentElement,
|
||||
array $definitions,
|
||||
): void {
|
||||
if ($definitions === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$level = $levels[$depth] ?? null;
|
||||
if ($level === null) {
|
||||
throw new RuntimeException('element level not defined');
|
||||
}
|
||||
|
||||
$siblings = $parentElement === null
|
||||
? $repository->findTopLevelBySet($set)
|
||||
: $repository->findByParentElement($parentElement);
|
||||
|
|
@ -174,13 +170,12 @@ class ElementSeeder extends Seeder
|
|||
$element = $this->findSibling(
|
||||
siblings: $siblings,
|
||||
name: $definition['name'],
|
||||
kind: $definition['kind'],
|
||||
level: $level,
|
||||
);
|
||||
if ($element === null) {
|
||||
$element = $repository->create(new CreateElementDto(
|
||||
set: $set,
|
||||
name: $definition['name'],
|
||||
kind: $definition['kind'],
|
||||
level: $level,
|
||||
parentElement: $parentElement,
|
||||
));
|
||||
$siblings[] = $element;
|
||||
|
|
@ -189,6 +184,8 @@ class ElementSeeder extends Seeder
|
|||
$this->seedChildren(
|
||||
repository: $repository,
|
||||
set: $set,
|
||||
levels: $levels,
|
||||
depth: $depth + 1,
|
||||
parentElement: $element,
|
||||
definitions: $definition['children'],
|
||||
);
|
||||
|
|
@ -201,12 +198,12 @@ class ElementSeeder extends Seeder
|
|||
private function findSibling(
|
||||
array $siblings,
|
||||
string $name,
|
||||
string $kind,
|
||||
SetLevel $level,
|
||||
): ?Element {
|
||||
foreach ($siblings as $sibling) {
|
||||
if (
|
||||
$sibling->getName() === $name
|
||||
&& $sibling->getKind() === $kind
|
||||
&& $sibling->getLevel()->getId() === $level->getId()
|
||||
) {
|
||||
return $sibling;
|
||||
}
|
||||
|
|
|
|||
45
backend/database/seeders/SetLevelSeeder.php
Normal file
45
backend/database/seeders/SetLevelSeeder.php
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Set\CreateSetLevelDto;
|
||||
use App\Set\SetLevelRepository;
|
||||
use App\Set\SetRepository;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class SetLevelSeeder extends Seeder
|
||||
{
|
||||
private const array KINDS_BY_SET = [
|
||||
'Bible' => ['book', 'portion', 'chapter'],
|
||||
'Course' => ['module', 'lesson'],
|
||||
'Fitness Program' => ['phase', 'workout', 'exercise'],
|
||||
];
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$repository = app(SetLevelRepository::class);
|
||||
|
||||
foreach (app(SetRepository::class)->all() as $set) {
|
||||
$kinds = self::KINDS_BY_SET[$set->getName()] ?? null;
|
||||
if ($kinds === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$existingKinds = array_map(function ($level): string {
|
||||
return $level->getKind();
|
||||
}, $repository->findBySet($set));
|
||||
|
||||
foreach ($kinds as $kind) {
|
||||
if (in_array($kind, $existingKinds, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$repository->create(new CreateSetLevelDto(
|
||||
set: $set,
|
||||
kind: $kind,
|
||||
));
|
||||
$existingKinds[] = $kind;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue