add dated assignment endpoint

This commit is contained in:
Yisroel Baum 2026-08-15 21:50:37 +03:00
parent 6afe7864be
commit 483ca6fc1d
Signed by: yisroelbaum
GPG key ID: 0FA60884F75520A9
9 changed files with 252 additions and 8 deletions

View file

@ -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<string, mixed>
*/
@ -165,6 +199,30 @@ class ScheduleController extends Controller
];
}
/**
* @return array<string, mixed>
*/
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 */

View file

@ -0,0 +1,27 @@
<?php
namespace App\Schedule;
final readonly class AssignmentForDate
{
public function __construct(
private int $scheduleId,
private string $setName,
private ScheduleAssignment $assignment,
) {}
public function getScheduleId(): int
{
return $this->scheduleId;
}
public function getSetName(): string
{
return $this->setName;
}
public function getAssignment(): ScheduleAssignment
{
return $this->assignment;
}
}

View file

@ -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'));

View file

@ -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<string> $element_path
* @property string $scheduled_date
* @property int $position
* @property-read ScheduleModel $schedule
*
* @method static Builder<static>|ScheduleAssignmentModel newModelQuery()
* @method static Builder<static>|ScheduleAssignmentModel newQuery()
@ -46,4 +48,12 @@ class ScheduleAssignmentModel extends Model
'position' => 'integer',
];
}
/**
* @return BelongsTo<ScheduleModel, $this>
*/
public function schedule(): BelongsTo
{
return $this->belongsTo(ScheduleModel::class, 'schedule_id');
}
}

View file

@ -3,6 +3,7 @@
namespace App\Schedule;
use App\User\User;
use DateTimeImmutable;
interface ScheduleRepository
{
@ -14,4 +15,12 @@ interface ScheduleRepository
* @return list<Schedule>
*/
public function findAllForUser(User $user): array;
/**
* @return list<AssignmentForDate>
*/
public function findAssignmentsForUserOnDate(
User $user,
DateTimeImmutable $date,
): array;
}

View file

@ -0,0 +1,59 @@
<?php
namespace App\Schedule\UseCases\ListAssignmentsForDate;
use App\Exceptions\BadRequestException;
use App\Schedule\AssignmentForDate;
use App\Schedule\ScheduleRepository;
use DateTimeImmutable;
use DateTimeZone;
class ListAssignmentsForDate
{
public function __construct(
private ScheduleRepository $scheduleRepository,
) {}
/**
* @return list<AssignmentForDate>
* @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;
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace App\Schedule\UseCases\ListAssignmentsForDate;
use App\User\User;
final readonly class ListAssignmentsForDateRequest
{
public function __construct(
public User $user,
public ?string $date,
) {}
}