Commit 95157362 authored by Jacob Priddy's avatar Jacob Priddy 👌

Make attempts and entries use shallow linking by location

so that they are not hard attached to doors. E.G. when a door gets
deleted the history sticks around, or if a door gets moved, it's tied to
location.

Also updated the authorizer to pass around a full door since it is there
anyway...
parent 5f489423
Pipeline #12511 passed with stages
in 2 minutes and 36 seconds
......@@ -3,17 +3,8 @@
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Attempt extends Model
{
protected $fillable = ['door_id'];
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function door(): BelongsTo
{
return $this->belongsTo(Door::class);
}
protected $fillable = ['door_location'];
}
......@@ -20,22 +20,6 @@ class Door extends Authenticatable
return $this->belongsToMany(Group::class);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function entries(): HasMany
{
return $this->hasMany(Entry::class);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function attempts(): HasMany
{
return $this->hasMany(Attempt::class);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
......@@ -44,7 +28,7 @@ class Door extends Authenticatable
return $this->hasMany(Override::class);
}
public static function boot()
public static function boot(): void
{
parent::boot();
......@@ -52,18 +36,6 @@ class Door extends Authenticatable
// Detach all groups
$door->groups()->detach();
// Delete all entries
/** @var \App\Entry $entry */
foreach ($door->entries() as $entry) {
$entry->delete();
}
// Delete all attempts
/** @var \App\Attempt $attempt */
foreach ($door->attempts() as $attempt) {
$attempt->delete();
}
// Delete all overrides
/** @var \App\Override $override */
foreach ($door->overrides() as $override) {
......
......@@ -7,15 +7,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Entry extends Model
{
protected $fillable = ['door_id', 'user_id', 'success'];
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function door(): BelongsTo
{
return $this->belongsTo(Door::class);
}
protected $fillable = ['door_location', 'user_id', 'success'];
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
......
......@@ -3,10 +3,12 @@
namespace App\Guards;
use Source\Entities\Door;
use Illuminate\Http\Request;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\Authenticatable;
use Source\Gateways\Doors\DatabaseDoorsRepository;
use Source\UseCases\Door\Authenticate\AuthenticateUseCase;
use Source\UseCases\Door\Authenticate\TranslationPresenter;
......@@ -71,6 +73,21 @@ class DoorGuard implements Guard
return $this->user = $user;
}
/**
* @return \Source\Entities\Door|null
*/
public function getDoor(): ?Door
{
/** @var \App\Door|null $door */
$door = $this->user();
if ($door) {
return DatabaseDoorsRepository::makeDoorFromDb($door);
}
return null;
}
/**
* Get the token for the current request.
*
......
......@@ -17,7 +17,7 @@ class AttemptsController extends ApiController
/**
* Get Attempts
*
* This route filters attempts based off of starting date, ending date, or door id.
* This route filters attempts based off of starting date, ending date, or door location.
* If only start is supplied, all attempts after the start date are given. If only end
* is supplied, all attempts before the start date are given. If both dates are supplied,
* all attempts between the given dates are returned. This route is paginated.
......@@ -26,9 +26,9 @@ class AttemptsController extends ApiController
* @paginated
* @queryParam start The beginning date to filter attempts by. Example: 2000-06-02 08:11:45
* @queryParam end The ending date to filter attempts by. Example: 2920-06-02 08:11:45
* @queryParam door_id The door id to filter on. Example: 1
* @queryParam door_location The door location to filter on. Example: The Amazon
*
* @response 422 {"message":"The given data was invalid.","errors":{"start":["The start is not a valid date."],"end":["The end is not a valid date."],"door_id":["The door id must be an integer."]}}
* @response 422 {"message":"The given data was invalid.","errors":{"start":["The start is not a valid date."],"end":["The end is not a valid date."],"door_location":["The door location must be a string."]}}
*
* @param \Source\UseCases\Attempts\GetAttempts\GetAttemptsUseCase $attempts
* @return \Illuminate\Http\JsonResponse
......@@ -44,7 +44,7 @@ class AttemptsController extends ApiController
$this->validate($this->request, [
'start' => 'nullable|date',
'end' => 'nullable|date',
'door_id' => 'nullable|integer',
'door_location' => 'nullable|string',
]);
$presenter = new GetAttemptsAPIPresenter();
......@@ -52,7 +52,7 @@ class AttemptsController extends ApiController
$attempts->getBetweenDates(
$this->request->input('start'),
$this->request->input('end'),
$this->request->input('door_id'),
$this->request->input('door_location'),
$presenter
);
......
......@@ -93,7 +93,11 @@ class DoorController extends ApiController
'doorcode' => 'required|string',
]);
$access->protectUserDoorAccessAtTime($this->doorGuard->id(), $this->request->input('doorcode'), Carbon::now());
$access->protectUserDoorAccessAtTime(
$this->doorGuard->getDoor(),
$this->request->input('doorcode'),
Carbon::now()
);
return $this->respondStatus();
}
......
......@@ -25,10 +25,10 @@ class EntriesController extends ApiController
* @paginated
* @queryParam start The beginning date to filter entries by. Example: 2000-06-02 08:11:45
* @queryParam end The ending date to filter entries by. Example: 2920-06-02 08:11:45
* @queryParam door_id The door id to filter entries on. Example: 1
* @queryParam door_id The door id to filter entries on. Example: Amazon
* @queryParam user_id The user id to filter entries on. Example: 420
*
* @response 422 {"message":"The given data was invalid.","errors":{"start":["The start is not a valid date."],"end":["The end is not a valid date."],"door_id":["The door id must be an integer."],"user_id":["The user id must be an integer."]}}
* @response 422 {"message":"The given data was invalid.","errors":{"start":["The start is not a valid date."],"end":["The end is not a valid date."],"door_location":["The door location must be a string."],"user_id":["The user id must be an integer."]}}
*
* @param \Source\UseCases\Entries\GetEntries\GetEntriesUseCase $userEntries
* @return \Illuminate\Http\JsonResponse
......@@ -43,7 +43,7 @@ class EntriesController extends ApiController
$this->validate($this->request, [
'start' => 'nullable|date',
'end' => 'nullable|date',
'door_id' => 'nullable|integer',
'door_location' => 'nullable|string',
'user_id' => 'nullable|integer',
]);
......@@ -51,7 +51,7 @@ class EntriesController extends ApiController
$userEntries->getEntries(
$this->request->input('user_id'),
$this->request->input('door_id'),
$this->request->input('door_location'),
$this->request->input('start'),
$this->request->input('end'),
$presenter
......
......@@ -14,13 +14,14 @@ class AttemptsController extends Controller
* @param \Source\UseCases\Attempts\GetAttempts\GetAttemptsUseCase $attempts
* @return \Illuminate\View\View
* @throws \Illuminate\Validation\ValidationException
* @throws \Exception
*/
public function index(GetAttemptsUseCase $attempts): View
{
$this->validate($this->request, [
'start' => 'nullable|date',
'end' => 'nullable|date',
'door_id' => 'nullable|integer',
'door_location' => 'nullable|string',
]);
$start = $this->request->input('start');
......
......@@ -22,7 +22,7 @@ class EntriesController extends Controller
$this->validate($this->request, [
'start' => 'nullable|date',
'end' => 'nullable|date',
'door_id' => 'nullable|integer',
'door_location' => 'nullable|string',
'user_id' => 'nullable|integer',
]);
......@@ -38,7 +38,7 @@ class EntriesController extends Controller
$entries->getEntries(
$this->request->input('user_id'),
$this->request->input('door_id'),
$this->request->input('door_location'),
$start,
$end,
$presenter
......
......@@ -17,9 +17,8 @@ class CreateEntriesTable extends Migration
Schema::create('entries', static function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->unsignedBigInteger('door_id');
$table->string('door_location');
$table->boolean('success');
$table->foreign('door_id')->references('id')->on('doors');
$table->foreign('user_id')->references('id')->on('users');
$table->timestamps();
});
......
......@@ -15,8 +15,7 @@ class CreateAttemptsTable extends Migration
{
Schema::create('attempts', static function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('door_id');
$table->foreign('door_id')->references('id')->on('doors');
$table->string('door_location');
$table->timestamps();
});
}
......
......@@ -38,6 +38,12 @@
{{ door($row[$header])['name'] }} (ID: {{ $row[$header] }})
</a>
</td>
@elseif($header === 'door_shallow_link')
<td>
<a href="{{ route('web.admin.doors.index', ['query' => $row[$header]]) }}">
{{ $row[$header] }}
</a>
</td>
@elseif($header === 'schedule_id')
<td>
<a href="{{ route('web.admin.schedules.edit', ['scheduleId' => $row[$header]]) }}">
......
......@@ -15,7 +15,7 @@ class Attempt
/**
* @var int
*/
protected int $doorId;
protected string $doorLocation;
/**
* @var \Carbon\Carbon|null
......@@ -29,12 +29,12 @@ class Attempt
public function __construct(
int $id,
int $doorId,
string $doorLocation,
?Carbon $createdAt = null,
?Carbon $updatedAt = null
) {
$this->id = $id;
$this->doorId = $doorId;
$this->doorLocation = $doorLocation;
$this->createdAt = $createdAt;
$this->updatedAt = $updatedAt;
}
......@@ -48,11 +48,11 @@ class Attempt
}
/**
* @return int
* @return string
*/
public function getDoorId(): int
public function getDoorLocation(): string
{
return $this->doorId;
return $this->doorLocation;
}
/**
......@@ -94,15 +94,15 @@ class Attempt
}
/**
* @param string|null $doorId
* @param string|null $doorLocation
* @return bool
*/
public function hasDoorIdOf(?string $doorId): bool
public function hasDoorLocationLike(?string $doorLocation): bool
{
if (!$doorId) {
if (!$doorLocation) {
return false;
}
return $this->getDoorId() === (int)$doorId;
return stripos($this->getDoorLocation(), $doorLocation) !== false;
}
}
......@@ -18,9 +18,9 @@ class Entry
protected int $userId;
/**
* @var int
* @var string
*/
protected int $doorId;
protected string $doorLocation;
/**
* @var \Carbon\Carbon|null
......@@ -40,14 +40,14 @@ class Entry
public function __construct(
int $id,
int $userId,
int $doorId,
string $doorLocation,
bool $success,
?Carbon $createdAt = null,
?Carbon $updatedAt = null
) {
$this->id = $id;
$this->userId = $userId;
$this->doorId = $doorId;
$this->doorLocation = $doorLocation;
$this->success = $success;
$this->createdAt = $createdAt;
$this->updatedAt = $updatedAt;
......@@ -70,11 +70,11 @@ class Entry
}
/**
* @return int
* @return string
*/
public function getDoorId(): int
public function getDoorLocation(): string
{
return $this->doorId;
return $this->doorLocation;
}
/**
......@@ -141,15 +141,15 @@ class Entry
}
/**
* @param string|null $doorId
* @param string|null $doorLocation
* @return bool
*/
public function hasDoorIdOf(?string $doorId): bool
public function hasDoorLocationLike(?string $doorLocation): bool
{
if (!$doorId) {
if (!$doorLocation) {
return false;
}
return $this->getDoorId() === (int)$doorId;
return stripos($this->getDoorLocation(), $doorLocation) !== false;
}
}
......@@ -17,8 +17,8 @@ interface AttemptsRepository
/**
* @param \Carbon\Carbon|null $begin
* @param \Carbon\Carbon|null $end
* @param string|null $doorId
* @param string|null $doorLocation
* @return \Source\Entities\Attempt[]
*/
public function getBetween(?Carbon $begin = null, ?Carbon $end = null, ?string $doorId = null): array;
public function getBetween(?Carbon $begin = null, ?Carbon $end = null, ?string $doorLocation = null): array;
}
......@@ -19,7 +19,7 @@ class DatabaseAttemptsRepository implements AttemptsRepository
{
return new Attempt(
$attempt->getAttribute('id'),
$attempt->getAttribute('door_id'),
$attempt->getAttribute('door_location'),
$attempt->getAttribute('created_at'),
$attempt->getAttribute('updated_at')
);
......@@ -28,12 +28,12 @@ class DatabaseAttemptsRepository implements AttemptsRepository
/**
* @inheritDoc
*/
public function getBetween(?Carbon $begin = null, ?Carbon $end = null, ?string $doorId = null): array
public function getBetween(?Carbon $begin = null, ?Carbon $end = null, ?string $doorLocation = null): array
{
$query = \App\Attempt::query()->orderByDesc('created_at');
if ($doorId) {
$query->where('door_id', self::castToInt($doorId));
if ($doorLocation) {
$query->where('door_location', 'ILIKE', "%$doorLocation%");
}
if ($begin && $end) {
......@@ -55,7 +55,7 @@ class DatabaseAttemptsRepository implements AttemptsRepository
public function add(Attempt $attempt): ?Attempt
{
$a = new \App\Attempt();
$a->setAttribute('door_id', $attempt->getDoorId());
$a->setAttribute('door_location', $attempt->getDoorLocation());
$a->save();
......
......@@ -34,11 +34,11 @@ class InMemoryAttemptsRepository implements AttemptsRepository
/**
* @inheritDoc
*/
public function getBetween(?Carbon $begin = null, ?Carbon $end = null, ?string $doorId = null): array
public function getBetween(?Carbon $begin = null, ?Carbon $end = null, ?string $doorLocation = null): array
{
return array_values(array_filter($this->attempts, static function (Attempt $attempt) use ($begin, $end, $doorId) {
if ($doorId) {
return $attempt->isBetween($begin, $end) && $attempt->hasDoorIdOf($doorId);
return array_values(array_filter($this->attempts, static function (Attempt $attempt) use ($begin, $end, $doorLocation) {
if ($doorLocation) {
return $attempt->isBetween($begin, $end) && $attempt->hasDoorLocationLike($doorLocation);
}
return $attempt->isBetween($begin, $end);
}));
......
......@@ -13,13 +13,13 @@ class LocalAttemptsRepository extends InMemoryAttemptsRepository
{
$this->add(new Attempt(
1,
LocalDoorsRepository::getTheBatCave()->getId(),
LocalDoorsRepository::getTheBatCave()->getLocation(),
Carbon::now()->subHour()
));
$this->add(new Attempt(
2,
LocalDoorsRepository::getAmazonDoor()->getId(),
LocalDoorsRepository::getAmazonDoor()->getLocation(),
Carbon::now()->subDays(10)
));
}
......
......@@ -20,7 +20,7 @@ class DatabaseEntriesRepository implements EntriesRepository
return new Entry(
$entry->getAttribute('id'),
$entry->getAttribute('user_id'),
$entry->getAttribute('door_id'),
$entry->getAttribute('door_location'),
$entry->getAttribute('success'),
$entry->getAttribute('created_at'),
$entry->getAttribute('updated_at')
......@@ -34,7 +34,7 @@ class DatabaseEntriesRepository implements EntriesRepository
{
$e = new \App\Entry();
$e->setAttribute('user_id', $entry->getUserId());
$e->setAttribute('door_id', $entry->getDoorId());
$e->setAttribute('door_location', $entry->getDoorLocation());
$e->setAttribute('success', $entry->wasSuccessful());
if (!$e->save()) {
......@@ -47,7 +47,7 @@ class DatabaseEntriesRepository implements EntriesRepository
/**
* @inheritDoc
*/
public function get(?Carbon $begin = null, ?Carbon $end = null, ?string $userId = null, ?string $doorId = null): array
public function get(?Carbon $begin = null, ?Carbon $end = null, ?string $userId = null, ?string $doorLocation = null): array
{
$query = \App\Entry::query()->orderByDesc('created_at');
......@@ -55,8 +55,8 @@ class DatabaseEntriesRepository implements EntriesRepository
$query->where('user_id', self::castToInt($userId));
}
if ($doorId) {
$query->where('door_id', self::castToInt($doorId));
if ($doorLocation) {
$query->where('door_location', 'ILIKE', "%$doorLocation%");
}
if ($begin && $end) {
......
......@@ -20,13 +20,13 @@ interface EntriesRepository
* @param \Carbon\Carbon|null $begin
* @param \Carbon\Carbon|null $end
* @param string|null $userId
* @param string|null $doorId
* @param string|null $doorLocation
* @return array
*/
public function get(
?Carbon $begin = null,
?Carbon $end = null,
?string $userId = null,
?string $doorId = null
?string $doorLocation = null
): array;
}
......@@ -34,17 +34,17 @@ class InMemoryEntriesRepository implements EntriesRepository
/**
* @inheritDoc
*/
public function get(?Carbon $begin = null, ?Carbon $end = null, ?string $userId = null, ?string $doorId = null): array
public function get(?Carbon $begin = null, ?Carbon $end = null, ?string $userId = null, ?string $doorLocation = null): array
{
return array_values(array_filter($this->entries, static function (Entry $entry) use ($begin, $end, $userId, $doorId) {
return array_values(array_filter($this->entries, static function (Entry $entry) use ($begin, $end, $userId, $doorLocation) {
$include = $entry->isBetween($begin, $end);
if ($userId) {
$include = $include && $entry->hasUserIdOf($userId);
}
if ($doorId) {
$include = $include && $entry->hasDoorIdOf($doorId);
if ($doorLocation) {
$include = $include && $entry->hasDoorLocationLike($doorLocation);
}
return $include;
......
......@@ -18,7 +18,7 @@ class LocalEntriesRepository extends InMemoryEntriesRepository
$this->add(new Entry(
1,
LocalUsersRepository::getEngineeringLabAccessStudent()->getId(),
LocalDoorsRepository::getAmazonDoor()->getId(),
LocalDoorsRepository::getAmazonDoor()->getLocation(),
false,
Carbon::now()->subDays(1)
));
......@@ -26,7 +26,7 @@ class LocalEntriesRepository extends InMemoryEntriesRepository
$this->add(new Entry(
2,
LocalUsersRepository::getEngineeringLabAccessStudent()->getId(),
LocalDoorsRepository::getTheBatCave()->getId(),
LocalDoorsRepository::getTheBatCave()->getLocation(),
true,
Carbon::now()->subDays(1)
));
......@@ -34,7 +34,7 @@ class LocalEntriesRepository extends InMemoryEntriesRepository
$this->add(new Entry(
3,