From 483ca6fc1d36c3c4fcfa152988c125d398d7da76 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Sat, 15 Aug 2026 21:50:37 +0300 Subject: [PATCH] add dated assignment endpoint --- .../Http/Controllers/ScheduleController.php | 58 ++++++++++++++++++ backend/app/Schedule/AssignmentForDate.php | 27 +++++++++ .../Schedule/EloquentScheduleRepository.php | 56 +++++++++++++++--- .../app/Schedule/ScheduleAssignmentModel.php | 10 ++++ backend/app/Schedule/ScheduleRepository.php | 9 +++ .../ListAssignmentsForDate.php | 59 +++++++++++++++++++ .../ListAssignmentsForDateRequest.php | 13 ++++ backend/routes/api.php | 1 + .../tests/Fakes/FakeScheduleRepository.php | 27 +++++++++ 9 files changed, 252 insertions(+), 8 deletions(-) create mode 100644 backend/app/Schedule/AssignmentForDate.php create mode 100644 backend/app/Schedule/UseCases/ListAssignmentsForDate/ListAssignmentsForDate.php create mode 100644 backend/app/Schedule/UseCases/ListAssignmentsForDate/ListAssignmentsForDateRequest.php diff --git a/backend/app/Http/Controllers/ScheduleController.php b/backend/app/Http/Controllers/ScheduleController.php index cc8cd60..9ec6588 100644 --- a/backend/app/Http/Controllers/ScheduleController.php +++ b/backend/app/Http/Controllers/ScheduleController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers; use App\Exceptions\BadRequestException; use App\Exceptions\NotFoundException; +use App\Schedule\AssignmentForDate; use App\Schedule\Schedule; use App\Schedule\ScheduleAssignment; use App\Schedule\UseCases\CreateSchedule\CreateSchedule; @@ -11,6 +12,8 @@ use App\Schedule\UseCases\CreateSchedule\CreateScheduleRequest; use App\Schedule\UseCases\GetSchedule\GetSchedule; use App\Schedule\UseCases\GetSchedule\GetScheduleRequest; use App\Schedule\UseCases\ListSchedules\ListSchedules; +use App\Schedule\UseCases\ListAssignmentsForDate\ListAssignmentsForDate; +use App\Schedule\UseCases\ListAssignmentsForDate\ListAssignmentsForDateRequest; use App\Shared\Http\RequestInput; use App\User\User; use Illuminate\Http\JsonResponse; @@ -22,6 +25,7 @@ class ScheduleController extends Controller private CreateSchedule $createSchedule, private ListSchedules $listSchedules, private GetSchedule $getSchedule, + private ListAssignmentsForDate $listAssignmentsForDate, ) {} public function store(Request $request): JsonResponse @@ -90,6 +94,36 @@ class ScheduleController extends Controller ]); } + public function assignments(Request $request): JsonResponse + { + $input = new RequestInput($request); + $date = $input->string('date'); + + try { + $assignments = $this->listAssignmentsForDate->execute( + new ListAssignmentsForDateRequest( + user: $this->user($request), + date: $date, + ), + ); + } catch (BadRequestException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], + 400, + ); + } + + return new JsonResponse([ + 'date' => $date, + 'assignments' => array_map( + function (AssignmentForDate $assignment): array { + return $this->assignmentForDatePayload($assignment); + }, + $assignments, + ), + ]); + } + /** * @return array */ @@ -165,6 +199,30 @@ class ScheduleController extends Controller ]; } + /** + * @return array + */ + private function assignmentForDatePayload( + AssignmentForDate $assignmentForDate, + ): array { + $assignment = $assignmentForDate->getAssignment(); + + return [ + 'id' => $assignment->getId(), + 'schedule' => [ + 'id' => $assignmentForDate->getScheduleId(), + 'set' => [ + 'name' => $assignmentForDate->getSetName(), + ], + ], + 'element' => [ + 'name' => $assignment->getName(), + 'kind' => $assignment->getKind(), + 'path' => $assignment->getPath(), + ], + ]; + } + private function user(Request $request): User { /** @var User $user */ diff --git a/backend/app/Schedule/AssignmentForDate.php b/backend/app/Schedule/AssignmentForDate.php new file mode 100644 index 0000000..b877c39 --- /dev/null +++ b/backend/app/Schedule/AssignmentForDate.php @@ -0,0 +1,27 @@ +scheduleId; + } + + public function getSetName(): string + { + return $this->setName; + } + + public function getAssignment(): ScheduleAssignment + { + return $this->assignment; + } +} diff --git a/backend/app/Schedule/EloquentScheduleRepository.php b/backend/app/Schedule/EloquentScheduleRepository.php index 31bb859..7aec036 100644 --- a/backend/app/Schedule/EloquentScheduleRepository.php +++ b/backend/app/Schedule/EloquentScheduleRepository.php @@ -61,6 +61,40 @@ class EloquentScheduleRepository implements ScheduleRepository return $schedules; } + public function findAssignmentsForUserOnDate( + User $user, + DateTimeImmutable $date, + ): array { + $assignmentModels = ScheduleAssignmentModel::query() + ->select('schedule_assignments.*') + ->join( + 'schedules', + 'schedules.id', + '=', + 'schedule_assignments.schedule_id', + ) + ->where('schedules.user_id', $user->getId()) + ->where('schedule_assignments.scheduled_date', $date->format( + 'Y-m-d', + )) + ->with('schedule') + ->orderByDesc('schedules.id') + ->orderBy('schedule_assignments.position') + ->orderBy('schedule_assignments.id') + ->get(); + $assignments = []; + + foreach ($assignmentModels as $assignmentModel) { + $assignments[] = new AssignmentForDate( + scheduleId: $assignmentModel->schedule->id, + setName: $assignmentModel->schedule->set_name, + assignment: $this->assignmentToDomain($assignmentModel), + ); + } + + return $assignments; + } + private function toDomain(ScheduleModel $model, User $user): Schedule { $assignmentModels = ScheduleAssignmentModel::query() @@ -71,14 +105,7 @@ class EloquentScheduleRepository implements ScheduleRepository $assignments = []; foreach ($assignmentModels as $assignmentModel) { - $assignments[] = new ScheduleAssignment( - id: $assignmentModel->id, - name: $assignmentModel->element_name, - kind: $assignmentModel->element_kind, - path: $assignmentModel->element_path, - scheduledDate: $this->date($assignmentModel->scheduled_date), - position: $assignmentModel->position, - ); + $assignments[] = $this->assignmentToDomain($assignmentModel); } return new Schedule( @@ -92,6 +119,19 @@ class EloquentScheduleRepository implements ScheduleRepository ); } + private function assignmentToDomain( + ScheduleAssignmentModel $model, + ): ScheduleAssignment { + return new ScheduleAssignment( + id: $model->id, + name: $model->element_name, + kind: $model->element_kind, + path: $model->element_path, + scheduledDate: $this->date($model->scheduled_date), + position: $model->position, + ); + } + private function date(string $value): DateTimeImmutable { return new DateTimeImmutable($value, new DateTimeZone('UTC')); diff --git a/backend/app/Schedule/ScheduleAssignmentModel.php b/backend/app/Schedule/ScheduleAssignmentModel.php index f0f33df..3cd5577 100644 --- a/backend/app/Schedule/ScheduleAssignmentModel.php +++ b/backend/app/Schedule/ScheduleAssignmentModel.php @@ -5,6 +5,7 @@ namespace App\Schedule; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * @property int $id @@ -14,6 +15,7 @@ use Illuminate\Database\Eloquent\Model; * @property list $element_path * @property string $scheduled_date * @property int $position + * @property-read ScheduleModel $schedule * * @method static Builder|ScheduleAssignmentModel newModelQuery() * @method static Builder|ScheduleAssignmentModel newQuery() @@ -46,4 +48,12 @@ class ScheduleAssignmentModel extends Model 'position' => 'integer', ]; } + + /** + * @return BelongsTo + */ + public function schedule(): BelongsTo + { + return $this->belongsTo(ScheduleModel::class, 'schedule_id'); + } } diff --git a/backend/app/Schedule/ScheduleRepository.php b/backend/app/Schedule/ScheduleRepository.php index bd02515..fcdcb8c 100644 --- a/backend/app/Schedule/ScheduleRepository.php +++ b/backend/app/Schedule/ScheduleRepository.php @@ -3,6 +3,7 @@ namespace App\Schedule; use App\User\User; +use DateTimeImmutable; interface ScheduleRepository { @@ -14,4 +15,12 @@ interface ScheduleRepository * @return list */ public function findAllForUser(User $user): array; + + /** + * @return list + */ + public function findAssignmentsForUserOnDate( + User $user, + DateTimeImmutable $date, + ): array; } diff --git a/backend/app/Schedule/UseCases/ListAssignmentsForDate/ListAssignmentsForDate.php b/backend/app/Schedule/UseCases/ListAssignmentsForDate/ListAssignmentsForDate.php new file mode 100644 index 0000000..9e3017d --- /dev/null +++ b/backend/app/Schedule/UseCases/ListAssignmentsForDate/ListAssignmentsForDate.php @@ -0,0 +1,59 @@ + + * @throws BadRequestException + */ + public function execute(ListAssignmentsForDateRequest $request): array + { + $date = $this->parseDate($request->date); + + return $this->scheduleRepository->findAssignmentsForUserOnDate( + $request->user, + $date, + ); + } + + /** + * @throws BadRequestException + */ + private function parseDate(?string $value): DateTimeImmutable + { + if ($value === null || $value === '') { + throw new BadRequestException('date is required'); + } + + $date = DateTimeImmutable::createFromFormat( + '!Y-m-d', + $value, + new DateTimeZone('UTC'), + ); + $errors = DateTimeImmutable::getLastErrors(); + if ( + $date === false + || $date->format('Y-m-d') !== $value + || ($errors !== false + && ($errors['warning_count'] > 0 || $errors['error_count'] > 0)) + ) { + throw new BadRequestException( + 'date must be a valid date in YYYY-MM-DD format', + ); + } + + return $date; + } +} diff --git a/backend/app/Schedule/UseCases/ListAssignmentsForDate/ListAssignmentsForDateRequest.php b/backend/app/Schedule/UseCases/ListAssignmentsForDate/ListAssignmentsForDateRequest.php new file mode 100644 index 0000000..f0db6a9 --- /dev/null +++ b/backend/app/Schedule/UseCases/ListAssignmentsForDate/ListAssignmentsForDateRequest.php @@ -0,0 +1,13 @@ +group(function (): void { Route::get('/sets', [SetController::class, 'index']); Route::get('/sets/{setId}', [SetController::class, 'show']) ->whereNumber('setId'); + Route::get('/assignments', [ScheduleController::class, 'assignments']); Route::post('/schedules', [ScheduleController::class, 'store']); Route::get('/schedules', [ScheduleController::class, 'index']); Route::get('/schedules/{scheduleId}', [ScheduleController::class, 'show']) diff --git a/backend/tests/Fakes/FakeScheduleRepository.php b/backend/tests/Fakes/FakeScheduleRepository.php index de1ae76..e30caf2 100644 --- a/backend/tests/Fakes/FakeScheduleRepository.php +++ b/backend/tests/Fakes/FakeScheduleRepository.php @@ -3,10 +3,12 @@ namespace Tests\Fakes; use App\Schedule\CreateScheduleDto; +use App\Schedule\AssignmentForDate; use App\Schedule\Schedule; use App\Schedule\ScheduleAssignment; use App\Schedule\ScheduleRepository; use App\User\User; +use DateTimeImmutable; class FakeScheduleRepository implements ScheduleRepository { @@ -72,6 +74,31 @@ class FakeScheduleRepository implements ScheduleRepository }, array_values($schedules)); } + public function findAssignmentsForUserOnDate( + User $user, + DateTimeImmutable $date, + ): array { + $assignments = []; + + foreach ($this->findAllForUser($user) as $schedule) { + foreach ($schedule->getAssignments() as $assignment) { + if ($assignment->getScheduledDate()->format('Y-m-d') + !== $date->format('Y-m-d') + ) { + continue; + } + + $assignments[] = new AssignmentForDate( + scheduleId: $schedule->getId(), + setName: $schedule->getSetName(), + assignment: $assignment, + ); + } + } + + return $assignments; + } + private function copy(Schedule $schedule): Schedule { $assignments = array_map(