Commit cfa4e1c0 authored by Jacob Priddy's avatar Jacob Priddy 👌
Browse files

Refactor schedules to use a filter type with pagination

parent a6f3e357
......@@ -75,19 +75,28 @@ class SchedulesController extends ApiController
$this->authorizer->protect(Permissions::MANAGE_DOORS);
$this->validate($this->request, [
'start' => 'required|date|before:end',
'end' => 'required|date|after:start',
'start' => 'date',
'end' => 'date',
'type' => 'integer',
'group_id' => 'integer',
]);
$presenter = new SchedulesGetApiPresenter();
$schedulesBetween->getBetweenDates(
$schedulesBetween->filter(
$this->request->input('group_id'),
$this->request->input('type'),
$this->request->input('start'),
$this->request->input('end'),
$presenter
);
return $this->respondWithData($presenter->getViewModel());
return $this->respondWithData($presenter->getViewModel([
'group_id' => $this->request->input('group_id'),
'type' => $this->request->input('type'),
'start' => $this->request->input('start'),
'end' => $this->request->input('end'),
]));
}
/**
......@@ -119,24 +128,6 @@ class SchedulesController extends ApiController
return $this->respondWithData($presenter->getViewModel());
}
/**
* @param string $groupId
* @param \Source\UseCases\GroupSchedule\SchedulesForGroup\SchedulesForGroupUseCase $forGroup
* @return \Illuminate\Http\JsonResponse
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function getForGroup(string $groupId, SchedulesForGroupUseCase $forGroup): JsonResponse
{
$this->authorizer->protect(Permissions::MANAGE_DOORS);
$presenter = new SchedulesForGroupApiPresenter();
$forGroup->getForGroup($groupId, $presenter);
return $this->respondWithData($presenter->getViewModel());
}
/**
* @param string $groupId
* @param \Source\UseCases\GroupSchedule\ActiveSchedulesForGroup\ActiveSchedulesForGroupUseCase $activeForGroup
......
......@@ -63,7 +63,6 @@ use Source\UseCases\Schedules\ScheduleCreate\ScheduleCreateUseCaseServiceProvide
use Source\UseCases\Schedules\ScheduleUpdate\ScheduleUpdateUseCaseServiceProvider;
use Source\UseCases\Doors\GenerateDoorToken\GenerateDoorTokenUseCaseServiceProvider;
use Source\UseCases\DoorGroup\RemoveDoorFromGroup\RemoveDoorFromGroupUseCaseServiceProvider;
use Source\UseCases\GroupSchedule\SchedulesForGroup\SchedulesForGroupUseCaseServiceProvider;
use Source\UseCases\GroupUser\RemoveUserFromGroup\RemoveUserFromGroupUseCaseServiceProvider;
use Source\UseCases\GroupSchedule\ActiveSchedulesForGroup\ActiveSchedulesForGroupUseCaseServiceProvider;
use Source\UseCases\Door\Authenticate\AuthenticateUseCaseServiceProvider as DoorAuthenticateUseCaseServiceProvider;
......@@ -164,7 +163,6 @@ class AppServiceProvider extends ServiceProvider
SchedulesGetUseCaseServiceProvider::class,
ScheduleCreateUseCaseServiceProvider::class,
ScheduleUpdateUseCaseServiceProvider::class,
SchedulesForGroupUseCaseServiceProvider::class,
ActiveSchedulesForGroupUseCaseServiceProvider::class,
// Overrides
......
......@@ -55,7 +55,6 @@ Route::group(['middleware' => 'auth:api'], static function () {
Route::get('{groupId}/users', [GroupsController::class, 'getUsersForGroup']);
Route::get('{groupId}/doors', [GroupsController::class, 'getDoorsForGroup']);
Route::get('{groupId}/schedules', [SchedulesController::class, 'getForGroup']);
Route::get('{groupId}/active', [SchedulesController::class, 'activeForGroup']);
});
......
......@@ -21,7 +21,7 @@ class LocalDoorScheduleRepository extends InMemoryDoorScheduleRepository
try {
foreach ($doors->query() as $door) {
foreach ($doorGroups->getGroupsForDoor($door->getId()) as $group) {
foreach ($schedules->allForGroup($group->getId()) as $schedule) {
foreach ($schedules->filter($group->getId()) as $schedule) {
$this->attachScheduleToDoor($door->getId(), $schedule);
}
}
......
......@@ -79,9 +79,9 @@ class DatabaseOverridesRepository implements OverridesRepository
if ($begin && $end) {
$query->whereRaw('((:BEGIN, :END) OVERLAPS ("start", "end"))', [':BEGIN' => $begin, ':END' => $end]);
} elseif ($begin) {
$query->where('start', '>', $begin);
$query->where('end', '>', $begin);
} elseif ($end) {
$query->where('end', '<', $end);
$query->where('start', '<', $end);
}
return array_map(static function (\App\Override $override) {
......
......@@ -21,10 +21,6 @@ class InMemoryOverridesRepository implements OverridesRepository
return array_filter($this->overrides, static function (Override $override) use ($begin, $end, $doorId, $userId) {
$include = true;
if ($begin || $end) {
$include = $include && ($override->isActiveForDate($begin) || $override->isActiveForDate($end));
}
if ($doorId) {
$include = $include && $override->hasDoorIdOf($doorId);
}
......@@ -33,6 +29,10 @@ class InMemoryOverridesRepository implements OverridesRepository
$include = $include && $override->hasUserIdOf($userId);
}
if ($begin || $end) {
$include = $override->isActiveForDate($begin) || $override->isActiveForDate($end);
}
return $include;
});
}
......
......@@ -60,19 +60,6 @@ class DatabaseSchedulesRepository implements SchedulesRepository
return self::toSchedule($dbSchedule);
}
/**
* @inheritDoc
*/
public function allForGroup(string $groupId): array
{
$schedules = \App\Schedule::query()->where('group_id', $this->castToInt($groupId))
->get()->values()->all();
return array_map(static function (\App\Schedule $schedule) {
return self::toSchedule($schedule);
}, $schedules);
}
/**
* @inheritDoc
*/
......@@ -119,22 +106,35 @@ class DatabaseSchedulesRepository implements SchedulesRepository
/**
* @inheritDoc
*/
public function getAllBetween(Carbon $begin, Carbon $end): array
public function filter(?string $groupId = null, ?int $type = null, ?Carbon $begin = null, ?Carbon $end = null): array
{
$schedules = \App\Schedule::query()->whereBetween('start', [$begin, $end])
->orWhereBetween('end', [$begin, $end])
->orWhere(static function (Builder $query) use ($begin, $end) {
$query->where('start', '<', $begin)
->where('end', '>', $end)
->orWhere(static function (Builder $query) use ($begin) {
$query->whereNull('end')
->where('start', '<', $begin);
});
})->get()->values()->all();
$query = \App\Schedule::query()->orderByDesc('created_at');
if ($groupId) {
$query->where('group_id', $this->castToInt($groupId));
}
if ($type !== null) {
$query->where('type', $type);
}
if ($begin && $end) {
$query->where(static function (Builder $builder) use ($begin, $end) {
$builder->whereRaw('((?, ?) OVERLAPS ("start", "end"))', [$begin, $end])
->orWhereBetween('start', [$begin, $end]);
});
} elseif ($begin) {
$query->where(static function (Builder $builder) use ($begin) {
$builder->where('end', '>', $begin)
->orWhereNull('end');
});
} elseif ($end) {
$query->where('start', '<', $end);
}
return array_map(static function (\App\Schedule $schedule) {
return self::toSchedule($schedule);
}, $schedules);
}, $query->get()->values()->all());
}
/**
......
......@@ -22,16 +22,6 @@ class InMemorySchedulesRepository implements SchedulesRepository
return $schedule;
}
/**
* @inheritDoc
*/
public function allForGroup(string $groupId): array
{
return array_filter($this->schedules, static function (Schedule $schedule) use ($groupId) {
return $schedule->hasGroupIdOf($groupId);
});
}
/**
* @inheritDoc
*/
......@@ -65,10 +55,24 @@ class InMemorySchedulesRepository implements SchedulesRepository
/**
* @inheritDoc
*/
public function getAllBetween(Carbon $begin, Carbon $end): array
public function filter(?string $groupId = null, ?int $type = null, ?Carbon $begin = null, ?Carbon $end = null): array
{
return array_filter($this->schedules, static function (Schedule $schedule) use ($begin, $end) {
return $schedule->isActiveForDate($begin) || $schedule->isActiveForDate($end);
return array_filter($this->schedules, static function (Schedule $schedule) use ($groupId, $type, $begin, $end) {
$include = true;
if ($type) {
$include = $include && $schedule->hasTypeOf($type);
}
if ($groupId) {
$include = $include && $schedule->hasGroupIdOf($groupId);
}
if ($begin || $end) {
$include = $schedule->isActiveForDate($begin) || $schedule->isActiveForDate($end);
}
return $include;
});
}
......
......@@ -16,14 +16,6 @@ interface SchedulesRepository
*/
public function create(Schedule $schedule): ?Schedule;
/**
* Gets all schedules for a group
*
* @param string $groupId
* @return Schedule[]
*/
public function allForGroup(string $groupId): array;
/**
* Get a schedule
*
......@@ -43,11 +35,13 @@ interface SchedulesRepository
public function getActiveForGroupAndDate(string $groupId, Carbon $date, ?int $type = null): array;
/**
* @param string|null $groupId
* @param int|null $type
* @param \Carbon\Carbon $begin
* @param \Carbon\Carbon $end
* @return Schedule[]
*/
public function getAllBetween(Carbon $begin, Carbon $end): array;
public function filter(?string $groupId = null, ?int $type = null, ?Carbon $begin = null, ?Carbon $end = null): array;
/**
* Updates a schedule
......
......@@ -48,7 +48,7 @@ class UserDoorAccess implements UserDoorAccessUseCase
$groupId = $group->getId();
$doors = $this->doorGroupRepository->getDoorsForGroup($groupId);
$schedules = $this->schedulesRepository->allForGroup($groupId);
$schedules = $this->schedulesRepository->filter($groupId);
foreach ($doors as $door) {
$response->addDoorToGroup($groupId, $door);
......
<?php
namespace Source\UseCases\GroupSchedule\SchedulesForGroup;
use Source\Gateways\Groups\GroupsRepository;
use Source\UseCases\GroupSchedule\Presenter;
use Source\Exceptions\EntityNotFoundException;
use Source\UseCases\GroupSchedule\ResponseModel;
use Source\Gateways\Schedules\SchedulesRepository;
class SchedulesForGroup implements SchedulesForGroupUseCase
{
/**
* @var \Source\Gateways\Schedules\SchedulesRepository
*/
protected SchedulesRepository $schedules;
/**
* @var \Source\Gateways\Groups\GroupsRepository
*/
protected GroupsRepository $groups;
public function __construct(SchedulesRepository $schedules, GroupsRepository $groups)
{
$this->schedules = $schedules;
$this->groups = $groups;
}
/**
* @inheritDoc
*/
public function getForGroup(string $groupId, Presenter $presenter): void
{
if (!$this->groups->get($groupId)) {
throw new EntityNotFoundException('Group not found.');
}
$schedules = $this->schedules->allForGroup($groupId);
$response = new ResponseModel();
foreach ($schedules as $schedule) {
$response->addSchedule($schedule);
}
$presenter->present($response);
}
}
<?php
namespace Source\UseCases\GroupSchedule\SchedulesForGroup;
use Source\UseCases\GroupSchedule\Presenter;
interface SchedulesForGroupUseCase
{
/**
* @param string $groupId
* @param \Source\UseCases\GroupSchedule\Presenter $presenter
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function getForGroup(string $groupId, Presenter $presenter): void;
}
<?php
namespace Source\UseCases\GroupSchedule\SchedulesForGroup;
use Illuminate\Support\ServiceProvider;
use Source\Gateways\Groups\GroupsRepository;
use Illuminate\Contracts\Foundation\Application;
use Source\Gateways\Schedules\SchedulesRepository;
use Illuminate\Contracts\Support\DeferrableProvider;
/**
* Service provider must be registered in AppServiceProvider
*/
class SchedulesForGroupUseCaseServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->bind(SchedulesForGroupUseCase::class, static function (Application $app) {
return new SchedulesForGroup($app->make(SchedulesRepository::class), $app->make(GroupsRepository::class));
});
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot(): void
{
}
/**
* @return array
*/
public function provides()
{
return [SchedulesForGroupUseCase::class];
}
}
......@@ -3,23 +3,26 @@
namespace Source\UseCases\Schedules\SchedulesGet;
use Source\Entities\Schedule;
use Source\Sanitize\Paginates;
use Source\UseCases\BasePresenter;
class APIPresenter extends BasePresenter implements Presenter
{
protected array $viewModel = [];
use Paginates;
protected array $schedules = [];
/** @inheritDoc */
public function present(ResponseModel $responseModel): void
{
$this->viewModel['schedules'] = array_map(function (Schedule $schedule) {
$this->schedules = array_map(function (Schedule $schedule) {
return $this->formatSchedule($schedule);
}, $responseModel->getSchedules());
}
/** @inheritDoc */
public function getViewModel(): array
public function getViewModel(array $appends = []): array
{
return $this->viewModel;
return $this->paginate($this->schedules, $appends);
}
}
......@@ -11,7 +11,8 @@ interface Presenter
public function present(ResponseModel $responseModel): void;
/**
* @param array $appends
* @return array
*/
public function getViewModel(): array;
public function getViewModel(array $appends = []): array;
}
......@@ -2,11 +2,13 @@
namespace Source\UseCases\Schedules\SchedulesGet;
use Carbon\Carbon;
use Source\Sanitize\CastsTo;
use Source\Gateways\Schedules\SchedulesRepository;
class SchedulesGet implements SchedulesGetUseCase
{
use CastsTo;
/**
* @var \Source\Gateways\Schedules\SchedulesRepository
*/
......@@ -23,9 +25,11 @@ class SchedulesGet implements SchedulesGetUseCase
/**
* @inheritDoc
*/
public function getBetweenDates(string $begin, string $end, Presenter $presenter): void
public function filter(?string $groupId, ?int $type, ?string $begin, ?string $end, Presenter $presenter): void
{
$schedules = $this->schedules->getAllBetween(new Carbon($begin), new Carbon($end));
$schedules = $this->schedules->filter(
$groupId, $type, $this->liberalCastToCarbon($begin), $this->liberalCastToCarbon($end)
);
$response = new ResponseModel($schedules);
......
......@@ -8,10 +8,12 @@ interface SchedulesGetUseCase
/**
* Gets all schedules active between the given dates
*
* @param string $begin
* @param string $end
* @param string|null $groupId
* @param int|null $type
* @param string|null $begin
* @param string|null $end
* @param \Source\UseCases\Schedules\SchedulesGet\Presenter $presenter
* @throws \Exception
*/
public function getBetweenDates(string $begin, string $end, Presenter $presenter): void;
public function filter(?string $groupId, ?int $type, ?string $begin, ?string $end, Presenter $presenter): void;
}
......@@ -74,8 +74,8 @@ class ScheduleDatabaseTest extends DatabaseTestCase
$this->assertNotNull($this->repository->get($s1->getId()));
$this->assertCount(1, $this->repository->getActiveForGroupAndDate($g1->getId(), Carbon::now()));
$this->assertCount(0, $this->repository->getActiveForGroupAndDate($g1->getId(), Carbon::now(), Schedule::TYPE_OPEN_MODE));
$this->assertCount(1, $this->repository->getAllBetween(Carbon::now(), Carbon::now()->addMinute()));
$this->assertCount(2, $this->repository->allForGroup($g1->getId()));
$this->assertCount(1, $this->repository->filter(null, null, Carbon::now(), Carbon::now()->addMinute()));
$this->assertCount(2, $this->repository->filter($g1->getId()));
}
/**
......
<?php
namespace Tests\Unit\Source\UseCases\GroupSchedule;
use Carbon\Carbon;
use Source\Entities\Group;
use Source\Entities\Schedule;
use PHPUnit\Framework\TestCase;
use Source\Exceptions\EntityNotFoundException;
use Source\UseCases\GroupSchedule\ResponseModel;
use Source\Gateways\Groups\InMemoryGroupsRepository;
use Source\Gateways\Schedules\InMemorySchedulesRepository;
use Source\UseCases\GroupSchedule\SchedulesForGroup\SchedulesForGroup;
class SchedulesForGroupTest extends TestCase
{
protected const VALID_GROUP_ID = 101700;
/**
* @var \Source\Gateways\Groups\InMemoryGroupsRepository
*/
protected InMemoryGroupsRepository $groups;
/**
* @var \Source\Gateways\Schedules\InMemorySchedulesRepository
*/
protected InMemorySchedulesRepository $schedules;
/**
* @var \Source\UseCases\GroupSchedule\SchedulesForGroup\SchedulesForGroup
*/
protected SchedulesForGroup $useCase;
/**
* @var \Tests\Unit\Source\UseCases\GroupSchedule\PresenterStub
*/
protected PresenterStub $presenter;
/**
* @var \Source\UseCases\GroupSchedule\ResponseModel
*/
protected ResponseModel $response;
public function setUp(): void
{
parent::setUp();
$this->schedules = new InMemorySchedulesRepository();
$this->groups = new InMemoryGroupsRepository();
$this->useCase = new SchedulesForGroup($this->schedules, $this->groups);
$this->presenter = new PresenterStub();
$this->groups->create(new Group(self::VALID_GROUP_ID, '', ''));
}
/**
* @param string $groupId
* @throws \Source\Exceptions\EntityNotFoundException
*/
protected function handleTest(string $groupId): void
{
$this->useCase->getForGroup($groupId, $this->presenter);
$this->response = $this->presenter->response;
}
/**
* @test
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function it_will_not_get_schedules_for_non_existent_group(): void
{
$this->expectException(EntityNotFoundException::class);
$this->handleTest('1');
}
/**
* @test