Commit 0c3e6b41 authored by Jacob Priddy's avatar Jacob Priddy 👌
Browse files

Merge branch '52-manual-door-overrides' into 'master'

Resolve "Manual Door Overrides"

Closes #52

See merge request !45
parents e6c8b5bf 85c6c710
Pipeline #8843 passed with stages
in 3 minutes and 31 seconds
......@@ -36,6 +36,14 @@ class Door extends Authenticatable
return $this->hasMany(Attempt::class);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function overrides(): HasMany
{
return $this->hasMany(Override::class);
}
public static function boot()
{
parent::boot();
......@@ -55,6 +63,12 @@ class Door extends Authenticatable
foreach ($door->attempts() as $attempt) {
$attempt->delete();
}
// Delete all overrides
/** @var \App\Override $override */
foreach ($door->overrides() as $override) {
$override->delete();
}
});
}
}
<?php
namespace App\Http\Controllers;
use App\Guards\ApiGuard;
use Source\Entities\Override;
use Illuminate\Http\JsonResponse;
use Source\Authorization\Permissions;
use Source\UseCases\Overrides\ApiPresenter;
use Source\UseCases\Overrides\OverrideCreate\OverrideCreateUseCase;
use Source\UseCases\Overrides\OverridesForDoor\OverridesForDoorUseCase;
use Source\UseCases\Overrides\OverridesForDateRange\OverridesForDateRangeUseCase;
use Source\UseCases\Overrides\OverrideCreate\ApiPresenter as OverrideCreateApiPresenter;
class OverridesController extends ApiController
{
/**
* @param \Source\UseCases\Overrides\OverrideCreate\OverrideCreateUseCase $overrideCreate
* @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
{
$this->authorizer->protect(Permissions::MANAGE_DOORS);
$this->validate($this->request, [
'reason' => 'required|string|max:1024',
'door_id' => 'required|integer',
'type' => 'required|integer|between:' . Override::TYPE_OPEN . ',' . Override::TYPE_NORMAL,
'start' => 'required|date|before:end',
'end' => 'required|date|after:start',
]);
$presenter = new OverrideCreateApiPresenter();
$attributes = $this->request->all();
$attributes['user_id'] = $apiGuard->id();
$overrideCreate->create($attributes, $presenter);
$this->setStatusCode($presenter->getStatusCode());
return $this->respondWithData($presenter->getViewModel());
}
/**
* @param string $doorId
* @param \Source\UseCases\Overrides\OverridesForDoor\OverridesForDoorUseCase $overridesForDoor
* @return \Illuminate\Http\JsonResponse
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function forDoor(string $doorId, OverridesForDoorUseCase $overridesForDoor): JsonResponse
{
$this->authorizer->protect(Permissions::LOGS_READ);
$presenter = new ApiPresenter();
$overridesForDoor->getOverridesForDoor($doorId, $presenter);
return $this->respondWithData($presenter->getViewModel());
}
/**
* @param \Source\UseCases\Overrides\OverridesForDateRange\OverridesForDateRangeUseCase $overridesInDateRange
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Validation\ValidationException
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityNotFoundException
* @throws \Exception
*/
public function index(OverridesForDateRangeUseCase $overridesInDateRange): JsonResponse
{
$this->authorizer->protect(Permissions::LOGS_READ);
$this->validate($this->request, [
'start' => 'required|date|before:end',
'end' => 'required|date|after:start',
]);
$presenter = new ApiPresenter();
$overridesInDateRange->getOverridesForDateRange(
$this->request->input('start'),
$this->request->input('end'),
$presenter
);
return $this->respondWithData($presenter->getViewModel());
}
}
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Override extends Model
{
protected $dates = ['start', 'end'];
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function door(): BelongsTo
{
return $this->belongsTo(Door::class);
}
}
......@@ -17,6 +17,7 @@ use Source\UseCases\Doors\GetDoor\GetDoorUseCaseServiceProvider;
use Source\UseCases\Users\GetUser\GetUserUseCaseServiceProvider;
use Source\Gateways\DoorGroup\DoorGroupRepositoryServiceProvider;
use Source\Gateways\GroupUser\GroupUserRepositoryServiceProvider;
use Source\Gateways\Overrides\OverridesRepositoryServiceProvider;
use Source\Gateways\Schedules\SchedulesRepositoryServiceProvider;
use Source\UseCases\Groups\GetGroup\GetGroupUseCaseServiceProvider;
use Source\UseCases\Tokens\GetToken\GetTokenUseCaseServiceProvider;
......@@ -50,16 +51,19 @@ use Source\UseCases\GroupUser\GetUserGroups\GetUserGroupsUseCaseServiceProvider;
use Source\UseCases\TokenUser\GetUserTokens\GetUserTokensUseCaseServiceProvider;
use Source\UseCases\DoorGroup\AddDoorToGroup\AddDoorToGroupUseCaseServiceProvider;
use Source\UseCases\GroupUser\AddUserToGroup\AddUserToGroupUseCaseServiceProvider;
use Source\UseCases\Overrides\OverrideCreate\OverrideCreateUseCaseServiceProvider;
use Source\UseCases\Schedules\ScheduleCreate\ScheduleCreateUseCaseServiceProvider;
use Source\UseCases\Schedules\ScheduleUpdate\ScheduleUpdateUseCaseServiceProvider;
use Source\UseCases\Doors\GenerateDoorToken\GenerateDoorTokenUseCaseServiceProvider;
use Source\UseCases\Entries\GetEntriesForDoor\GetEntriesForDoorUseCaseServiceProvider;
use Source\UseCases\Entries\GetEntriesForUser\GetEntriesForUserUseCaseServiceProvider;
use Source\UseCases\Overrides\OverridesForDoor\OverridesForDoorUseCaseServiceProvider;
use Source\UseCases\Attempts\GetAttemptsForDoor\GetAttemptsForDoorUseCaseServiceProvider;
use Source\UseCases\DoorGroup\RemoveDoorFromGroup\RemoveDoorFromGroupUseCaseServiceProvider;
use Source\UseCases\GroupSchedule\SchedulesForGroup\SchedulesForGroupUseCaseServiceProvider;
use Source\UseCases\GroupUser\RemoveUserFromGroup\RemoveUserFromGroupUseCaseServiceProvider;
use Source\UseCases\Entries\GetEntriesForDateRange\GetEntriesForDateRangeUseCaseServiceProvider;
use Source\UseCases\Overrides\OverridesForDateRange\OverridesForDateRangeUseCaseServiceProvider;
use Source\UseCases\Attempts\GetAttemptCountForDoor\GetAttemptCountForDoorUseCaseServiceProvider;
use Source\UseCases\Entries\GetEntriesForDoorAndUser\GetEntriesForDoorAndUserUseCaseServiceProvider;
use Source\UseCases\GroupSchedule\ActiveSchedulesForGroup\ActiveSchedulesForGroupUseCaseServiceProvider;
......@@ -84,6 +88,7 @@ class AppServiceProvider extends ServiceProvider
SchedulesRepositoryServiceProvider::class,
DoorGroupRepositoryServiceProvider::class,
GroupUserRepositoryServiceProvider::class,
OverridesRepositoryServiceProvider::class,
DoorScheduleRepositoryServiceProvider::class,
RecurrenceSetRepositoryServiceProvider::class,
];
......@@ -163,6 +168,11 @@ class AppServiceProvider extends ServiceProvider
// DoorUser
DoorUserGroupMapUseCaseServiceProvider::class,
// Overrides
OverrideCreateUseCaseServiceProvider::class,
OverridesForDoorUseCaseServiceProvider::class,
OverridesForDateRangeUseCaseServiceProvider::class,
];
/**
......
......@@ -12,14 +12,7 @@ class User extends Authenticatable
'*',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'expires_at' => 'datetime',
];
protected $dates = ['expires_at'];
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
......@@ -45,6 +38,14 @@ class User extends Authenticatable
return $this->belongsToMany(Group::class);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function overrides(): HasMany
{
return $this->hasMany(Override::class);
}
public static function boot()
{
parent::boot();
......@@ -63,6 +64,11 @@ class User extends Authenticatable
foreach ($user->entries() as $entry) {
$entry->delete();
}
/** @var \App\Override $override */
foreach ($user->overrides() as $override) {
$override->delete();
}
});
}
}
......@@ -11,7 +11,7 @@ class CreateUsersTable extends Migration
*
* @return void
*/
public function up()
public function up(): void
{
// This table holds the user objects in the system
Schema::create('users', static function (Blueprint $table) {
......@@ -35,7 +35,7 @@ class CreateUsersTable extends Migration
*
* @return void
*/
public function down()
public function down(): void
{
Schema::dropIfExists('users');
}
......
......@@ -11,7 +11,7 @@ class CreateFailedJobsTable extends Migration
*
* @return void
*/
public function up()
public function up(): void
{
Schema::create('failed_jobs', static function (Blueprint $table) {
$table->id();
......@@ -28,7 +28,7 @@ class CreateFailedJobsTable extends Migration
*
* @return void
*/
public function down()
public function down(): void
{
Schema::dropIfExists('failed_jobs');
}
......
......@@ -11,7 +11,7 @@ class CreateGroupsTable extends Migration
*
* @return void
*/
public function up()
public function up(): void
{
// This table stores the groups objects stored in the system
Schema::create('groups', static function (Blueprint $table) {
......@@ -27,7 +27,7 @@ class CreateGroupsTable extends Migration
*
* @return void
*/
public function down()
public function down(): void
{
Schema::dropIfExists('groups');
}
......
......@@ -11,7 +11,7 @@ class CreateGroupUserTable extends Migration
*
* @return void
*/
public function up()
public function up(): void
{
// This table is a pivot table mapping users into groups
Schema::create('group_user', static function (Blueprint $table) {
......@@ -29,7 +29,7 @@ class CreateGroupUserTable extends Migration
*
* @return void
*/
public function down()
public function down(): void
{
Schema::dropIfExists('group_user');
}
......
......@@ -11,7 +11,7 @@ class CreateDoorsTable extends Migration
*
* @return void
*/
public function up()
public function up(): void
{
// This table contains the base door objects stored on the system
Schema::create('doors', static function (Blueprint $table) {
......@@ -31,7 +31,7 @@ class CreateDoorsTable extends Migration
*
* @return void
*/
public function down()
public function down(): void
{
Schema::dropIfExists('doors');
}
......
......@@ -11,7 +11,7 @@ class CreateUserDoorTable extends Migration
*
* @return void
*/
public function up()
public function up(): void
{
// This table maps special cases of users getting access to doors
// without being in a special group.
......@@ -31,7 +31,7 @@ class CreateUserDoorTable extends Migration
*
* @return void
*/
public function down()
public function down(): void
{
Schema::dropIfExists('user_door');
}
......
......@@ -11,7 +11,7 @@ class CreateDoorGroupTable extends Migration
*
* @return void
*/
public function up()
public function up(): void
{
// This table maps the permissions a specific group has to a specific door
Schema::create('door_group', static function (Blueprint $table) {
......@@ -29,7 +29,7 @@ class CreateDoorGroupTable extends Migration
*
* @return void
*/
public function down()
public function down(): void
{
Schema::dropIfExists('door_group');
}
......
......@@ -11,7 +11,7 @@ class CreateEntriesTable extends Migration
*
* @return void
*/
public function up()
public function up(): void
{
// This table logs all invalid attempts to open a door
Schema::create('entries', static function (Blueprint $table) {
......@@ -30,7 +30,7 @@ class CreateEntriesTable extends Migration
*
* @return void
*/
public function down()
public function down(): void
{
Schema::dropIfExists('entries');
}
......
......@@ -11,7 +11,7 @@ class CreateTokensTable extends Migration
*
* @return void
*/
public function up()
public function up(): void
{
Schema::create('tokens', static function (Blueprint $table) {
$table->id();
......@@ -29,7 +29,7 @@ class CreateTokensTable extends Migration
*
* @return void
*/
public function down()
public function down(): void
{
Schema::dropIfExists('tokens');
}
......
......@@ -11,7 +11,7 @@ class CreateAttemptsTable extends Migration
*
* @return void
*/
public function up()
public function up(): void
{
Schema::create('attempts', static function (Blueprint $table) {
$table->id();
......@@ -26,7 +26,7 @@ class CreateAttemptsTable extends Migration
*
* @return void
*/
public function down()
public function down(): void
{
Schema::dropIfExists('attempts');
}
......
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateOverridesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::create('overrides', static function (Blueprint $table) {
$table->id();
$table->string('reason');
$table->unsignedBigInteger('user_id');
$table->unsignedBigInteger('door_id');
/*
* 0 -> door open mode
* 1 -> door locked
*/
$table->unsignedInteger('type');
$table->timestamp('start');
$table->timestamp('end');
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users');
$table->foreign('door_id')->references('id')->on('doors');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::dropIfExists('overrides');
}
}
......@@ -9,6 +9,7 @@ use App\Http\Controllers\GroupsController;
use App\Http\Controllers\TokensController;
use App\Http\Controllers\EntriesController;
use App\Http\Controllers\AttemptsController;
use App\Http\Controllers\OverridesController;
use App\Http\Controllers\SchedulesController;
/*
......@@ -76,6 +77,8 @@ Route::group(['middleware' => 'auth:api'], static function () {
Route::get('{doorId}/groups', [DoorsController::class, 'getGroupsForDoor']);
Route::post('{doorId}/group/{groupId}', [DoorsController::class, 'addDoorToGroup']);
Route::delete('{doorId}/group/{groupId}', [DoorsController::class, 'removeDoorFromGroup']);
Route::get('{doorId}/overrides', [OverridesController::class, 'forDoor']);
});
Route::group([
......@@ -110,6 +113,14 @@ Route::group(['middleware' => 'auth:api'], static function () {
Route::get('/', [AttemptsController::class, 'index']);
});
Route::group([
'prefix' => 'overrides',
], static function () {
Route::post('/', [OverridesController::class, 'create']);
Route::get('/', [OverridesController::class, 'index']);
});
Route::group([
'prefix' => 'me',
], static function () {
......
<?php
namespace Source\Entities;
use Carbon\Carbon;
use InvalidArgumentException;
class Override
{
public const TYPE_OPEN = 0;
public const TYPE_LOCKED = 1;
public const TYPE_NORMAL = 2;
protected int $id;
protected int $userId;
protected int $doorId;
protected int $type;
/**
* @var \Carbon\Carbon
*/
protected Carbon $start;
/**
* @var \Carbon\Carbon
*/
protected Carbon $end;
/**
* @var \Carbon\Carbon|null
*/
protected ?Carbon $createdAt;
/**
* @var \Carbon\Carbon|null
*/
protected ?Carbon $updatedAt;
protected string $reason;
public function __construct(
int $id,
string $reason,
int $userId,
int $doorId,
int $type,
Carbon $start,
Carbon $end,
?Carbon $createdAt = null,
?Carbon $updatedAt = null
) {
if ($type > self::TYPE_NORMAL || $type < self::TYPE_OPEN) {
throw new InvalidArgumentException('Type not valid override type.');
}
$this->id = $id;
$this->userId = $userId;
$this->doorId = $doorId;
$this->type = $type;
$this->start = $start;
$this->end = $end;
$this->createdAt = $createdAt;
$this->updatedAt = $updatedAt;
$this->reason = $reason;
}
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* @return string
*/
public function getReason(): string
{
return $this->reason;
}
/**
* @return int
*/
public function getUserId(): int
{
return $this->userId;
}
/**
* @return int
*/
public function getDoorId(): int
{
return $this->doorId;