Commit 7d9dcf3f authored by Jacob Priddy's avatar Jacob Priddy 👌
Browse files

Overrides can now only have one active at a time and can be updated

Added use case for generating responses
parent 02ce4ebd
......@@ -17,11 +17,11 @@ class OverridesController extends ApiController
{
/**
* @param \Source\UseCases\Overrides\OverrideCreate\OverrideCreateUseCase $overrideCreate
* @param \App\Guards\ApiGuard $apiGuard
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Validation\ValidationException
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityNotFoundException
* @throws \Exception
*/
public function create(OverrideCreateUseCase $overrideCreate, ApiGuard $apiGuard): JsonResponse
{
......@@ -29,7 +29,7 @@ class OverridesController extends ApiController
$this->validate($this->request, [
'reason' => 'required|string|max:1024',
'door_id' => 'required|integer',
'type' => 'required|integer|between:' . Override::TYPE_OPEN . ',' . Override::TYPE_NORMAL,
'type' => 'required|integer|between:' . Override::TYPE_OPEN . ',' . Override::TYPE_LOCKED,
'start' => 'required|date|before:end',
'end' => 'required|date|after:start',
]);
......
......@@ -13,6 +13,9 @@ class CreateOverridesTable extends Migration
*/
public function up(): void
{
/*
* Only one override can be active at a time
*/
Schema::create('overrides', static function (Blueprint $table) {
$table->id();
$table->string('reason');
......
......@@ -10,7 +10,6 @@ class Override
{
public const TYPE_OPEN = 0;
public const TYPE_LOCKED = 1;
public const TYPE_NORMAL = 2;
protected int $id;
......@@ -53,7 +52,7 @@ class Override
?Carbon $createdAt = null,
?Carbon $updatedAt = null
) {
if ($type > self::TYPE_NORMAL || $type < self::TYPE_OPEN) {
if ($type > self::TYPE_LOCKED || $type < self::TYPE_OPEN) {
throw new InvalidArgumentException('Type not valid override type.');
}
......@@ -161,4 +160,22 @@ class Override
return $date->isAfter($this->getStart());
}
/**
* @param int $type
* @return bool
*/
public function hasTypeOf(int $type): bool
{
return $this->getType() === $type;
}
/**
* @param string $id
* @return bool
*/
public function hasIdOf(string $id): bool
{
return $this->getId() === $id;
}
}
......@@ -178,6 +178,10 @@ class Schedule
return $date->isAfter($this->getStart());
}
/**
* @param int|null $type
* @return bool
*/
public function hasTypeOf(?int $type): bool
{
return $this->type === $type;
......
......@@ -12,8 +12,6 @@ class DatabaseDoorScheduleRepository implements DoorScheduleRepository
{
use CastsTo;
protected const DB_DATE_FORMAT = 'Y-m-d H:i:s';
/**
* @var \Illuminate\Database\ConnectionInterface
*/
......@@ -29,16 +27,17 @@ class DatabaseDoorScheduleRepository implements DoorScheduleRepository
*/
public function getActiveSchedulesForDoor(string $doorId): array
{
$openModeType = Schedule::TYPE_OPEN_MODE;
$type = Schedule::TYPE_OPEN_MODE;
$query = <<<QUERY
select S.id, S.group_id, S.type, S.rset, S.start, S.end, S.duration_ms, S.description, S.created_at, S.updated_at
FROM schedules as S
INNER JOIN door_group AS DG ON DG.group_id = S.group_id AND DG.door_id = ?
WHERE S.type = $openModeType AND ((CURRENT_DATE BETWEEN S.start AND S.end) OR (CURRENT_DATE > S.start AND S.end IS NULL))
INNER JOIN door_group AS DG ON DG.group_id = S.group_id AND DG.door_id = :DOOR_ID
WHERE S.type = :TYPE AND ((CURRENT_DATE BETWEEN S.start AND S.end) OR (CURRENT_DATE > S.start AND S.end IS NULL))
QUERY;
$schedules = $this->db->select($query, [
$this->castToInt($doorId),
':DOOR_ID' => $this->castToInt($doorId),
':TYPE' => $this->castToInt($type),
]);
return array_map(function ($schedule) {
......@@ -49,10 +48,10 @@ QUERY;
$schedule->rset,
$schedule->duration_ms,
$schedule->description,
$this->castToDate($schedule->start),
$this->castToDate($schedule->end),
$this->castToDate($schedule->created_at),
$this->castToDate($schedule->updated_at)
$this->castToCarbon($schedule->start),
$this->castToCarbon($schedule->end),
$this->castToCarbon($schedule->created_at),
$this->castToCarbon($schedule->updated_at)
);
}, $schedules);
}
......@@ -60,25 +59,23 @@ QUERY;
/**
* @inheritDoc
*/
public function getSchedulesForDorBetween(string $doorId, Carbon $begin, Carbon $end, int $type = Schedule::TYPE_OPEN_MODE): array
public function getSchedulesForDoorBetween(string $doorId, Carbon $begin, Carbon $end, int $type = Schedule::TYPE_OPEN_MODE): array
{
$format = self::DB_DATE_FORMAT;
$query = <<<QUERY
select S.id, S.group_id, S.type, S.rset, S.start, S.end, S.duration_ms, S.description, S.created_at, S.updated_at
FROM schedules as S
INNER JOIN door_group AS DG ON DG.group_id = S.group_id AND DG.door_id = :DOOR_ID
WHERE S.type = :TYPE AND (((to_timestamp(:BEGIN, '$format'), to_timestamp(:END, '$format')) OVERLAPS (s.start, S.end))
OR (S.start < to_timestamp(:END, '$format') AND S.end IS NULL))
WHERE S.type = :TYPE AND (((:BEGIN, :END) OVERLAPS (s.start, S.end))
OR (S.start < :END AND S.end IS NULL))
QUERY;
$schedules = $this->db->select($query, [
':TYPE' => $this->castToInt($type),
':DOOR_ID' => $this->castToInt($doorId),
':BEGIN' => $begin->format(self::DB_DATE_FORMAT),
':END' => $end->format(self::DB_DATE_FORMAT),
':TYPE' => $this->castToInt($type),
':BEGIN' => $begin,
':END' => $end,
]);
return array_map(function ($schedule) {
return new Schedule(
$schedule->id,
......@@ -87,10 +84,10 @@ QUERY;
$schedule->rset,
$schedule->duration_ms,
$schedule->description,
$this->castToDate($schedule->start),
$this->castToDate($schedule->end),
$this->castToDate($schedule->created_at),
$this->castToDate($schedule->updated_at)
$this->castToCarbon($schedule->start),
$this->castToCarbon($schedule->end),
$this->castToCarbon($schedule->created_at),
$this->castToCarbon($schedule->updated_at)
);
}, $schedules);
}
......
......@@ -21,5 +21,5 @@ interface DoorScheduleRepository
* @param int $type
* @return \Source\Entities\Schedule[]
*/
public function getSchedulesForDorBetween(string $doorId, Carbon $begin, Carbon $end, int $type = Schedule::TYPE_OPEN_MODE): array;
public function getSchedulesForDoorBetween(string $doorId, Carbon $begin, Carbon $end, int $type = Schedule::TYPE_OPEN_MODE): array;
}
......@@ -37,7 +37,7 @@ class InMemoryDoorScheduleRepository implements DoorScheduleRepository
/**
* @inheritDoc
*/
public function getSchedulesForDorBetween(string $doorId, Carbon $begin, Carbon $end, int $type = Schedule::TYPE_OPEN_MODE): array
public function getSchedulesForDoorBetween(string $doorId, Carbon $begin, Carbon $end, int $type = Schedule::TYPE_OPEN_MODE): array
{
if (!isset($this->doorScheduleMap[$doorId])) {
return [];
......
......@@ -43,8 +43,8 @@ QUERY;
$group->id,
$group->title,
$group->description,
$this->castToDate($group->created_at),
$this->castToDate($group->updated_at)
$this->castToCarbon($group->created_at),
$this->castToCarbon($group->updated_at)
);
}, $commonGroups);
}
......
......@@ -6,12 +6,23 @@ namespace Source\Gateways\Overrides;
use Carbon\Carbon;
use Source\Sanitize\CastsTo;
use Source\Entities\Override;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\ConnectionInterface;
use Source\Exceptions\EntityNotFoundException;
class DatabaseOverridesRepository implements OverridesRepository
{
use CastsTo;
/**
* @var \Illuminate\Database\ConnectionInterface
*/
protected ConnectionInterface $db;
public function __construct(ConnectionInterface $db)
{
$this->db = $db;
}
/**
* @param \App\Override $override
* @return \Source\Entities\Override
......@@ -31,6 +42,25 @@ class DatabaseOverridesRepository implements OverridesRepository
);
}
/**
* @param $override
* @return \Source\Entities\Override
*/
protected function toOverrideFromRaw($override): Override
{
return new Override(
$override->id,
$override->reason,
$override->user_id,
$override->door_id,
$override->type,
$this->castToCarbon($override->start),
$this->castToCarbon($override->end),
$this->castToCarbon($override->created_at),
$this->castToCarbon($override->updated_at)
);
}
/**
* @inheritDoc
*/
......@@ -38,6 +68,7 @@ class DatabaseOverridesRepository implements OverridesRepository
{
$overrides = \App\Override::query()
->where('door_id', $this->castToInt($doorId))
->orderByDesc('created_at')
->get()->values()->all();
return array_map(static function (\App\Override $override) {
......@@ -50,37 +81,48 @@ class DatabaseOverridesRepository implements OverridesRepository
*/
public function overrideHistoryBetween(Carbon $begin, Carbon $end): array
{
$overrides = \App\Override::query()
->whereBetween('start', [$begin, $end])
->orWhereBetween('end', [$begin, $end])
->orWhere(static function (Builder $query) use ($begin, $end) {
$query->where('start', '<', $begin)
->where('end', '>', $end);
})->get()->values()->all();
return array_map(static function (\App\Override $override) {
return self::toOverride($override);
$query = <<<QUERY
select A.id, A.reason, A.user_id, A.door_id, A.type, A.start, A.end, A.created_at, A.updated_at
from overrides as A
WHERE ((:BEGIN, :END) OVERLAPS (A.start, A.end))
ORDER BY A.created_at DESC
QUERY;
$overrides = $this->db->select($query, [
':BEGIN' => $begin,
':END' => $end,
]);
return array_map(function ($override) {
return $this->toOverrideFromRaw($override);
}, $overrides);
}
/**
* @inheritDoc
*/
public function activeOverrideForDoor(string $doorId, Carbon $date): ?Override
public function activeOverrideForDoorBetween(string $doorId, Carbon $begin, Carbon $end): ?Override
{
/** @var \App\Override|null $override */
$override = \App\Override::query()
->where('door_id', $this->castToInt($doorId))
->where('start', '<', $date)
->where('end', '>', $date)
->orderByDesc('created_at')
->first();
$query = <<<QUERY
select A.id, A.reason, A.user_id, A.door_id, A.type, A.start, A.end, A.created_at, A.updated_at
from overrides as A
WHERE A.door_id = :DOOR_ID
AND ((:BEGIN, :END) OVERLAPS (A.start, A.end))
ORDER BY A.created_at DESC
LIMIT 1
QUERY;
$override = $this->db->selectOne($query, [
':DOOR_ID' => $this->castToInt($doorId),
':BEGIN' => $begin,
':END' => $end,
]);
if (!$override) {
return null;
}
return self::toOverride($override);
return $this->toOverrideFromRaw($override);
}
/**
......@@ -106,4 +148,27 @@ class DatabaseOverridesRepository implements OverridesRepository
return self::toOverride($o);
}
/**
* @inheritDoc
*/
public function updateOverride(string $overrideId, Override $override): ?Override
{
/** @var \App\Override $o */
$o = \App\Override::query()->find($this->castToInt($overrideId));
if (!$o) {
throw new EntityNotFoundException('Override with id "' . $overrideId . '" does not exist.');
}
$o->setAttribute('start', $override->getStart());
$o->setAttribute('end', $override->getEnd());
$o->setAttribute('reason', $override->getReason());
if (!$o->save()) {
return null;
}
return self::toOverride($o);
}
}
......@@ -8,6 +8,9 @@ use Source\Entities\Override;
class InMemoryOverridesRepository implements OverridesRepository
{
/**
* @var \Source\Entities\Override[]
*/
protected array $overrides = [];
/**
......@@ -33,24 +36,33 @@ class InMemoryOverridesRepository implements OverridesRepository
/**
* @inheritDoc
*/
public function activeOverrideForDoor(string $doorId, Carbon $date): ?Override
public function activeOverrideForDoorBetween(string $doorId, Carbon $begin, Carbon $end): ?Override
{
$overrides = array_filter($this->overrides, static function (Override $override) use ($doorId, $date) {
return $override->hasDoorIdOf($doorId) && $override->isActiveForDate($date);
$overrides = array_filter($this->overrides, static function (Override $override) use ($doorId, $begin, $end) {
return $override->hasDoorIdOf($doorId) && ($override->isActiveForDate($begin) || $override->isActiveForDate($end));
});
return array_shift($overrides);
}
if (count($overrides) > 0) {
return $overrides[0];
}
/**
* @inheritDoc
*/
public function addOverride(Override $override): ?Override
{
$this->overrides[] = $override;
return null;
return $override;
}
/**
* @inheritDoc
*/
public function addOverride(Override $override): ?Override
public function updateOverride(string $overrideId, Override $override): ?Override
{
$this->overrides = array_filter($this->overrides, static function (Override $o) use ($overrideId) {
return !$o->hasIdOf($overrideId);
});
$this->overrides[] = $override;
return $override;
......
......@@ -25,14 +25,25 @@ interface OverridesRepository
* Gets active override during date (if any). Returns null otherwise.
*
* @param string $doorId
* @param \Carbon\Carbon $date
* @param \Carbon\Carbon $begin
* @param \Carbon\Carbon $end
* @return \Source\Entities\Override|null
*/
public function activeOverrideForDoor(string $doorId, Carbon $date): ?Override;
public function activeOverrideForDoorBetween(string $doorId, Carbon $begin, Carbon $end): ?Override;
/**
* @param \Source\Entities\Override $override
* @return \Source\Entities\Override|null
*/
public function addOverride(Override $override): ?Override;
/**
* Will only update start, end, and reason.
*
* @param string $overrideId
* @param \Source\Entities\Override $override
* @return \Source\Entities\Override|null
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function updateOverride(string $overrideId, Override $override): ?Override;
}
......@@ -4,6 +4,7 @@
namespace Source\Gateways\Overrides;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\DatabaseManager;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Support\DeferrableProvider;
......@@ -28,7 +29,10 @@ class OverridesRepositoryServiceProvider extends ServiceProvider implements Defe
return new InMemoryOverridesRepository();
}
return new DatabaseOverridesRepository();
/** @var DatabaseManager $manager */
$manager = $app->make(DatabaseManager::class);
return new DatabaseOverridesRepository($manager->connection('doorcode'));
});
}
......
......@@ -11,7 +11,7 @@ trait CastsTo
* @param string $format
* @return \Carbon\Carbon|null
*/
protected function castToDate($date, $format = 'Y-m-d H:i:s'): ?Carbon
protected function castToCarbon($date, $format = 'Y-m-d H:i:s'): ?Carbon
{
if (empty($date)) {
return null;
......@@ -26,7 +26,7 @@ trait CastsTo
*/
protected function castToInt(?string $int): ?int
{
if (!$int) {
if ($int === null) {
return null;
}
......
......@@ -27,7 +27,8 @@ class DefaultDoorOverrideCheck implements DoorOverrideCheck
*/
public function checkForOverrides(string $doorId, Carbon $date): int
{
$active = $this->overrides->activeOverrideForDoor($doorId, $date);
// Over the next second
$active = $this->overrides->activeOverrideForDoorBetween($doorId, $date, $date->clone()->addSecond());
if (!$active) {
return DoorOverrideCheck::NO_ACTION;
......
......@@ -2,6 +2,7 @@
namespace Source\UseCases\Door\StatusResponse;
use Source\Entities\Schedule;
use Source\UseCases\BasePresenter;
class JsonPresenter extends BasePresenter implements Presenter
......@@ -11,6 +12,12 @@ class JsonPresenter extends BasePresenter implements Presenter
/** @inheritDoc */
public function present(ResponseModel $responseModel): void
{
$this->viewModel = array_map(function (OpenEvent $event) {
return [
'begins_at' => $this->formatDateTime($event->getBegin()),
'ends_at' => $this->formatDateTime($event->getEnd()),
];
}, $responseModel->getEvents());
}
/** @inheritDoc */
......
<?php
namespace Source\UseCases\Door\StatusResponse;
use Carbon\Carbon;
class OpenEvent
{
/**
* @var \Carbon\Carbon
*/
protected Carbon $begin;
/**
* @var \Carbon\Carbon
*/
protected Carbon $end;
public function __construct(
Carbon $begin,
Carbon $end
) {
$this->begin = $begin;
$this->end = $end;
}
/**
* @return \Carbon\Carbon
*/
public function getBegin(): Carbon
{
return $this->begin;
}
/**
* @return \Carbon\Carbon
*/
public function getEnd(): Carbon
{
return $this->end;
}
/**
* @param \Carbon\Carbon $begin
*/
public function setBegin(Carbon $begin): void
{
$this->begin = $begin;
}
/**
* @param \Carbon\Carbon $end
*/
public function setEnd(Carbon $end): void
{
$this->end = $end;
}
}
......@@ -4,4 +4,24 @@ namespace Source\UseCases\Door\StatusResponse;
class ResponseModel
{
/**
* @var \Source\UseCases\Door\StatusResponse\OpenEvent[]
*/
protected array $events = [];
/**
* @param \Source\UseCases\Door\StatusResponse\OpenEvent $event
*/
public function addEvent(OpenEvent $event): void