Commit 6aec333b authored by Jacob Priddy's avatar Jacob Priddy 👌

Add token creation

parent 095ab48f
Pipeline #5452 failed with stages
in 2 minutes and 9 seconds
......@@ -5,6 +5,8 @@ namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Source\Authorization\Permissions;
use Source\UseCases\Doors\GetDoor\GetDoorUseCase;
use Source\UseCases\Doors\CreateDoor\APIPresenter as CreateDoorAPIPresenter;
use Source\UseCases\Doors\CreateDoor\CreateDoorUseCase;
use Source\UseCases\Doors\GetAllDoors\GetAllDoorsUseCase;
use Source\UseCases\Doors\GetDoor\APIPresenter as GetDoorAPIPresenter;
use Source\UseCases\Doors\GetAllDoors\APIPresenter as GetAllDoorsAPIPresenter;
......@@ -45,4 +47,28 @@ class DoorsController extends ApiController
return $this->respondWithData($presenter->getViewModel());
}
/**
* @param \Source\UseCases\Doors\CreateDoor\CreateDoorUseCase $createDoor
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Validation\ValidationException
* @throws \Source\Exceptions\EntityExistsException
*/
public function store(CreateDoorUseCase $createDoor): JsonResponse
{
$this->validate($this->request, [
'location' => 'required|string|max:255',
'name' => 'required|string|max:255',
]);
$presenter = new CreateDoorAPIPresenter();
$attributes = $this->request->all();
$attributes['salt'] = config('app.key');
$createDoor->create($attributes, $presenter);
return $this->respondWithData($presenter->getViewModel());
}
}
......@@ -16,6 +16,7 @@ use Source\UseCases\Groups\GetGroup\GetGroupUseCaseServiceProvider;
use Source\UseCases\Users\CreateUser\CreateUserUseCaseServiceProvider;
use Source\UseCases\Users\DeleteUser\DeleteUserUseCaseServiceProvider;
use Source\UseCases\Users\UpdateUser\UpdateUserUseCaseServiceProvider;
use Source\UseCases\Doors\CreateDoor\CreateDoorUseCaseServiceProvider;
use Source\UseCases\Users\GetAllUsers\GetAllUsersUseCaseServiceProvider;
use Source\UseCases\Doors\GetAllDoors\GetAllDoorsUseCaseServiceProvider;
use Source\UseCases\Groups\CreateGroup\CreateGroupUseCaseServiceProvider;
......@@ -71,6 +72,7 @@ class AppServiceProvider extends ServiceProvider
// Doors
GetDoorUseCaseServiceProvider::class,
CreateDoorUseCaseServiceProvider::class,
GetAllDoorsUseCaseServiceProvider::class,
AuthenticateUseCaseServiceProvider::class,
DoorAuthenticateUseCaseServiceProvider::class,
......
......@@ -53,7 +53,7 @@ Route::group(['middleware' => 'auth:api'], static function () {
'prefix' => 'doors',
], static function () {
Route::get('/', [DoorsController::class, 'index']);
// Route::post('/', [DoorsController::class, 'store']);
Route::post('/', [DoorsController::class, 'store']);
Route::get('{doorId}', [DoorsController::class, 'get']);
// Route::put('{doorId}', [DoorsController::class, 'update']);
// Route::delete('{doorId}', [DoorsController::class, 'delete']);
......
......@@ -100,6 +100,15 @@ class Door
return $this->token;
}
public function hasNameOf(?string $name): bool
{
if (!$name) {
return false;
}
return $this->name === $name;
}
/**
* @param string $id
* @return bool
......
......@@ -5,30 +5,30 @@ namespace Source\Gateways\Doors;
use Source\Entities\Door;
use Source\Entities\HashedSearchable;
use Source\Exceptions\EntityExistsException;
class DatabaseDoorsRepository implements DoorsRepository
{
/**
* @inheritDoc
*/
public function create(Door $door): Door
public function create(Door $door): ?Door
{
if ($this->findByName($door->getName())) {
throw new EntityExistsException();
}
$dbDoor = new \App\Door();
$dbDoor->location = $door->getLocation();
$dbDoor->name = $door->getName();
$dbDoor->api_token = $door->getToken()->getHash();
$dbDoor->save();
if (!$dbDoor->save()) {
return null;
}
return new Door(
$dbDoor->id,
$dbDoor->location,
$dbDoor->name,
new HashedSearchable($dbDoor->api_token),
$dbDoor->createdAt,
$dbDoor->updatedAt
);
return self::makeDoorFromDb($dbDoor);
}
public static function makeDoorFromDb(\App\Door $door): Door
......@@ -82,4 +82,16 @@ class DatabaseDoorsRepository implements DoorsRepository
}, $doors
);
}
/**
* @inheritDoc
*/
public function findByName(?string $name): ?Door
{
if (!$name) {
return null;
}
return \App\Door::where('name', $name)->first();
}
}
......@@ -11,10 +11,11 @@ interface DoorsRepository
/**
* Create a new door
*
* @param Door $door
* @return Door
* @param \Source\Entities\Door $door
* @return \Source\Entities\Door|null
* @throws \Source\Exceptions\EntityExistsException
*/
public function create(Door $door): Door;
public function create(Door $door): ?Door;
/**
* Get all doors
......@@ -38,4 +39,10 @@ interface DoorsRepository
* @return \Source\Entities\Door|null
*/
public function get(string $doorId): ?Door;
/**
* @param string|null $name
* @return \Source\Entities\Door|null
*/
public function findByName(?string $name): ?Door;
}
......@@ -5,6 +5,7 @@ namespace Source\Gateways\Doors;
use Source\Entities\Door;
use Source\Entities\HashedSearchable;
use Source\Exceptions\EntityExistsException;
class InMemoryDoorsRepository implements DoorsRepository
{
......@@ -14,8 +15,12 @@ class InMemoryDoorsRepository implements DoorsRepository
/**
* @inheritDoc
*/
public function create(Door $door): Door
public function create(Door $door): ?Door
{
if ($this->findByName($door->getName())) {
throw new EntityExistsException();
}
$this->doors[] = $door;
return $door;
......@@ -56,4 +61,18 @@ class InMemoryDoorsRepository implements DoorsRepository
{
return $this->doors;
}
/**
* @inheritDoc
*/
public function findByName(?string $name): ?Door
{
foreach ($this->doors as $door) {
if ($door->hasNameOf($name)) {
return $door;
}
}
return null;
}
}
......@@ -6,6 +6,7 @@ namespace Source\Gateways\Tokens;
use App\User;
use Carbon\Carbon;
use Source\Entities\Token;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Builder;
use Source\Exceptions\EntityNotFoundException;
......@@ -80,4 +81,12 @@ class DatabaseTokensRepository implements TokensRepository
$found->save();
}
}
/**
* @inheritDoc
*/
public static function generateTokenString(): string
{
return Str::random(60);
}
}
......@@ -56,4 +56,14 @@ class InMemoryTokensRepository implements TokensRepository
$tok->setExpiresAt(Carbon::now());
}
}
/**
* @inheritDoc
*/
public static function generateTokenString(): string
{
// For now just do what the actual database implementation does
// We don't need to do anything special
return DatabaseTokensRepository::generateTokenString();
}
}
......@@ -25,4 +25,9 @@ interface TokensRepository
* @param string $token
*/
public function invalidateToken(string $token): void;
/**
* @return string
*/
public static function generateTokenString(): string;
}
......@@ -5,6 +5,7 @@ namespace Source\UseCases;
use Carbon\Carbon;
use Source\Entities\User;
use Source\Entities\Door;
use Source\Entities\Group;
abstract class BasePresenter
......@@ -77,4 +78,19 @@ abstract class BasePresenter
'updated_at' => $this->formatDateTime($group->getUpdatedAt()),
];
}
/**
* @param \Source\Entities\Door $door
* @return array
*/
public function formatDoor(Door $door): array
{
return [
'id' => $door->getId(),
'name' => $door->getName(),
'location' => $door->getLocation(),
'created_at' => $this->formatDateTime($door->getCreatedAt()),
'updated_at' => $this->formatDateTime($door->getUpdatedAt()),
];
}
}
<?php
namespace Source\UseCases\Doors\CreateDoor;
use Source\UseCases\BasePresenter;
class APIPresenter extends BasePresenter implements Presenter
{
protected array $viewModel = [];
/** @inheritDoc */
public function present(ResponseModel $responseModel): void
{
$this->viewModel['door_token'] = $responseModel->getToken();
$this->viewModel['door'] = $this->formatDoor($responseModel->getDoor());
}
/** @inheritDoc */
public function getViewModel(): array
{
return $this->viewModel;
}
}
<?php
namespace Source\UseCases\Doors\CreateDoor;
use Source\Entities\Door;
use Source\Entities\HashedSearchable;
use Source\Gateways\Doors\DoorsRepository;
use Source\Exceptions\EntityExistsException;
use Source\Gateways\Tokens\TokensRepository;
class CreateDoor implements CreateDoorUseCase
{
/**
* @var \Source\Gateways\Doors\DoorsRepository
*/
protected DoorsRepository $doors;
/**
* @var \Source\Gateways\Tokens\TokensRepository
*/
protected TokensRepository $tokens;
/**
* @param \Source\Gateways\Doors\DoorsRepository $doors
* @param \Source\Gateways\Tokens\TokensRepository $tokens
*/
public function __construct(DoorsRepository $doors, TokensRepository $tokens)
{
$this->doors = $doors;
$this->tokens = $tokens;
}
/**
* @inheritDoc
*/
public function create(array $attributes, Presenter $presenter): void
{
$token = $this->tokens::generateTokenString();
$door = new Door(
0,
$attributes['location'],
$attributes['name'],
HashedSearchable::hash($attributes['salt'], $token)
);
if (!($door = $this->doors->create($door))) {
throw new EntityExistsException();
}
$response = new ResponseModel($door, $token);
$presenter->present($response);
}
}
<?php
namespace Source\UseCases\Doors\CreateDoor;
use Source\Exceptions\EntityExistsException;
interface CreateDoorUseCase
{
/**
* Required attributes:
* location
* name
* salt (For hashing api key, application key is a good choice)
*
* @param array $attributes
* @param Presenter $presenter
* @throws EntityExistsException
*/
public function create(array $attributes, Presenter $presenter): void;
}
<?php
namespace Source\UseCases\Doors\CreateDoor;
use Illuminate\Support\ServiceProvider;
use Source\Gateways\Doors\DoorsRepository;
use Source\Gateways\Tokens\TokensRepository;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Support\DeferrableProvider;
/**
* Service provider must be registered in AppServiceProvider
*/
class CreateDoorUseCaseServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->bind(CreateDoorUseCase::class, static function (Application $app) {
return new CreateDoor($app->make(DoorsRepository::class), $app->make(TokensRepository::class));
});
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot(): void
{
}
/**
* @return array
*/
public function provides()
{
return [CreateDoorUseCase::class];
}
}
<?php
namespace Source\UseCases\Doors\CreateDoor;
interface Presenter
{
/**
* @param ResponseModel $responseModel
* @return void
*/
public function present(ResponseModel $responseModel): void;
/**
* @return array
*/
public function getViewModel(): array;
}
<?php
namespace Source\UseCases\Doors\CreateDoor;
use Source\Entities\Door;
class ResponseModel
{
/**
* @var \Source\Entities\Door
*/
protected Door $door;
/**
* @var string
*/
protected string $token;
/**
* @param \Source\Entities\Door $door
* @param string $token
*/
public function __construct(Door $door, string $token)
{
$this->door = $door;
$this->token = $token;
}
/**
* @return \Source\Entities\Door
*/
public function getDoor(): Door
{
return $this->door;
}
/**
* @return string
*/
public function getToken(): string
{
return $this->token;
}
}
......@@ -11,14 +11,7 @@ class APIPresenter extends BasePresenter implements Presenter
/** @inheritDoc */
public function present(ResponseModel $responseModel): void
{
$door = $responseModel->getDoor();
$this->viewModel['door'] = [
'id' => $door->getId(),
'name' => $door->getName(),
'location' => $door->getLocation(),
'created_at' => $this->formatDateTime($door->getCreatedAt()),
'updated_at' => $this->formatDateTime($door->getUpdatedAt()),
];
$this->viewModel['door'] = $this->formatDoor($responseModel->getDoor());
}
/** @inheritDoc */
......
......@@ -65,7 +65,7 @@ class Authenticate implements AuthenticateUseCase
new Token(
0,
$user->getId(),
Str::random(60),
$this->tokens::generateTokenString(),
null,
Carbon::now()->addDays(2)
)
......@@ -122,7 +122,7 @@ class Authenticate implements AuthenticateUseCase
new Token(
0,
$user->getId(),
Str::random(60),
$this->tokens::generateTokenString(),
null,
Carbon::now()->addDays(2)
)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment