Commit 4248e26b authored by Jacob Priddy's avatar Jacob Priddy 👌
Browse files

Merge branch 'users-api' into 'master'

Users api

See merge request kretschmar/doorcode!5
parents 53b131c6 0219e640
Pipeline #1209 passed with stages
in 1 minute and 41 seconds
......@@ -28,4 +28,17 @@ test_web_backend_unit:
stage: test
script:
- cd src/web/backend
- vendor/bin/phpunit
- vendor/bin/phpunit --testsuite Unit
test_web_backend_feature:
stage: test
script:
- cd src/web/backend
- vendor/bin/phpunit --testsuite Feature
test_web_backend_integration:
stage: test
script:
- cd src/web/backend
- vendor/bin/phpunit --testsuite Integration
APP_NAME=doorcode
# Valid values are memory, local, production, testing
# Testing will use all in memory providers
# Memory will use all in memory providers with some filled out values
# production and local will use the actual database
# It really can be anything, but it defaults to production.
APP_ENV=local
APP_KEY=
APP_DEBUG=true
......
......@@ -19,10 +19,14 @@ class DummyClassRepositoryServiceProvider extends ServiceProvider implements Def
*/
public function register() {
$this->app->singleton(DummyClassRepository::class, static function (Application $app) {
if (env('APP_ENV') === 'local') {
if (env('APP_ENV') === 'memory') {
return new LocalDummyClassRepository();
}
if(env('APP_ENV') === 'testing') {
return new InMemoryDummyClassRepository();
}
return new DatabaseDummyClassRepository();
});
}
......
......@@ -5,7 +5,7 @@ namespace DummyNamespace;
use Source\UseCases\BasePresenter;
class DummyClass extends BasePresenter implements Presenter {
protected $viewModel = [];
protected array $viewModel = [];
/** @inheritDoc */
public function present(ResponseModel $responseModel): void {
......
......@@ -3,49 +3,55 @@
namespace App\Exceptions;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Source\Exceptions\EntityExistsException;
use Source\Exceptions\EntityNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
class Handler extends ExceptionHandler {
/**
* A list of the exception types that are not reported.
*
* @var array
*/
protected $dontReport = [
//
EntityNotFoundException::class,
EntityExistsException::class,
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Report or log an exception.
*
* @param \Exception $exception
* @return void
* @param string $message
* @param int $code
* @return JsonResponse
*/
public function report(Exception $exception)
{
parent::report($exception);
public function respondWithError(string $message, int $code): JsonResponse {
return new JsonResponse(
[
'status' => 'error',
'code' => $code,
'message' => $message,
],
$code
);
}
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
* @param Request $request
* @param \Exception $exception
* @return \Symfony\Component\HttpFoundation\Response
* @throws Exception
*/
public function render($request, Exception $exception)
{
public function render($request, Exception $exception) {
if (
$exception instanceof EntityNotFoundException ||
$exception instanceof EntityExistsException
) {
return $this->respondWithError($exception->getMessage(), $exception->getCode());
}
return parent::render($request, $exception);
}
}
<?php
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
abstract class ApiController extends Controller {
/**
* @var int
*/
protected int $status = 200;
/**
* @param int $code
*/
public function setStatusCode(int $code): void {
$this->status = $code;
}
/**
* @param array $data
* @return JsonResponse
*/
public function respondWithData(array $data): JsonResponse {
return response()->json(
array_merge(
$data,
[
'status' => 'success',
'code' => $this->status,
]
),
$this->status
);
}
public function respondWithMessage(string $message): JsonResponse {
return response()->json(
[
'message' => $message,
'status' => 'success',
'code' => $this->status,
],
$this->status
);
}
/**
* @param string $message
* @return JsonResponse
*/
public function respondWithError(string $message): JsonResponse {
return response()->json(
[
'status' => 'error',
'code' => $this->status,
'message' => $message,
],
$this->status
);
}
}
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Source\Exceptions\EntityExistsException;
use Source\Exceptions\EntityNotFoundException;
use Illuminate\Validation\ValidationException;
use Source\UseCases\Users\GetUser\GetUserUseCase;
use Source\UseCases\Users\DeleteUser\DeleteFailedException;
use Source\UseCases\Users\CreateUser\CreateUserUseCase;
use Source\UseCases\Users\DeleteUser\DeleteUserUseCase;
use Source\UseCases\Users\UpdateUser\UpdateUserUseCase;
use Source\UseCases\Users\GetAllUsers\GetAllUsersUseCase;
use Source\UseCases\Users\GetUser\APIPresenter as GetUserAPIPresenter;
use Source\UseCases\Users\GetAllUsers\APIPresenter as AllUsersAPIPresenter;
use Source\UseCases\Users\UpdateUser\APIPresenter as UpdateUserAPIPresenter;
use Source\UseCases\Users\DeleteUser\APIPresenter as DeleteUserAPIPresenter;
use Source\UseCases\Users\CreateUser\APIPresenter as CreateUserAPIPresenter;
class UsersController extends ApiController {
/**
* @var Request
*/
protected Request $request;
/**
* @param Request $request
*/
public function __construct(Request $request) {
$this->request = $request;
}
/**
* @param GetAllUsersUseCase $getAllUsers
* @return JsonResponse
*/
public function index(GetAllUsersUseCase $getAllUsers): JsonResponse {
$presenter = new AllUsersAPIPresenter();
$getAllUsers->all($presenter);
return $this->respondWithData($presenter->getViewModel());
}
/**
* @param GetUserUseCase $getUser
* @param string $userId
* @return JsonResponse
* @throws EntityNotFoundException
*/
public function get(GetUserUseCase $getUser, string $userId): JsonResponse {
$presenter = new GetUserAPIPresenter();
$getUser->get($userId, $presenter);
return $this->respondWithData($presenter->getViewModel());
}
/**
* @param CreateUserUseCase $createUser
* @return JsonResponse
* @throws ValidationException
* @throws EntityExistsException
*/
public function store(CreateUserUseCase $createUser): JsonResponse {
$this->validate(
$this->request,
[
'first_name' => 'required|string|max:255',
'last_name' => 'required|string|max:255',
'display_name' => 'required|string|max:255',
'emplid' => 'nullable|string|max:7|min:6',
'email' => 'required|email|max:255',
'password' => 'nullable|string|min:15|max:255',
'doorcode' => 'required|string|numeric|digits_between:4,255',
'expires_at' => 'nullable|string|date|max:255',
]
);
$presenter = new CreateUserAPIPresenter();
$createUser->create($this->request->all(), $presenter);
return $this->respondWithData($presenter->getViewModel());
}
/**
* @param UpdateUserUseCase $updateUser
* @param string $userId
* @return JsonResponse
* @throws ValidationException
* @throws EntityNotFoundException
*/
public function update(UpdateUserUseCase $updateUser, string $userId): JsonResponse {
$this->validate(
$this->request,
[
'first_name' => 'required|string|max:255',
'last_name' => 'required|string|max:255',
'display_name' => 'required|string|max:255',
'emplid' => 'nullable|string|max:7|min:6',
'email' => 'required|email|max:255',
'password' => 'nullable|string|max:255',
'doorcode' => 'nullable|string|numeric|digits_between:4,255',
'expires_at' => 'nullable|string|date|max:255',
]
);
$presenter = new UpdateUserAPIPresenter();
$updateUser->update($userId, $this->request->all(), $presenter);
return $this->respondWithData($presenter->getViewModel());
}
/**
* @param DeleteUserUseCase $deleteUser
* @param string $userId
* @return JsonResponse
*/
public function delete(DeleteUserUseCase $deleteUser, string $userId): JsonResponse {
$presenter = new DeleteUserAPIPresenter();
try {
$deleteUser->delete($userId, $presenter);
} catch (DeleteFailedException $e) {
return $this->respondWithError($e->getMessage());
}
return $this->respondWithData($presenter->getViewModel());
}
}
......@@ -3,6 +3,14 @@
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Source\UseCases\Users\GetUser\GetUserUseCase;
use Source\Gateways\Users\UsersRepositoryServiceProvider;
use Source\UseCases\Users\GetAllUsers\GetAllUsersUseCase;
use Source\UseCases\Users\GetUser\GetUserUseCaseServiceProvider;
use Source\UseCases\Users\CreateUser\CreateUserUseCaseServiceProvider;
use Source\UseCases\Users\DeleteUser\DeleteUserUseCaseServiceProvider;
use Source\UseCases\Users\UpdateUser\UpdateUserUseCaseServiceProvider;
use Source\UseCases\Users\GetAllUsers\GetAllUsersUseCaseServiceProvider;
class AppServiceProvider extends ServiceProvider
{
......@@ -11,12 +19,18 @@ class AppServiceProvider extends ServiceProvider
* @var string[]
*/
protected array $gatewayProviders = [
UsersRepositoryServiceProvider::class,
];
/**
* @var string[]
*/
protected array $useCaseProviders = [
GetUserUseCaseServiceProvider::class,
DeleteUserUseCaseServiceProvider::class,
UpdateUserUseCaseServiceProvider::class,
CreateUserUseCaseServiceProvider::class,
GetAllUsersUseCaseServiceProvider::class,
];
/**
......
......@@ -8,12 +8,13 @@
],
"license": "MIT",
"require": {
"php": "^7.2",
"php": "^7.4",
"fideloper/proxy": "^4.0",
"laravel/framework": "^6.2",
"laravel/tinker": "^2.0"
},
"require-dev": {
"roave/security-advisories": "dev-master",
"facade/ignition": "^1.4",
"fzaninotto/faker": "^1.4",
"mockery/mockery": "^1.0",
......
This diff is collapsed.
......@@ -25,7 +25,6 @@ class CreateUsersTable extends Migration
$table->string('password');
// hashed
$table->string('doorcode');
$table->boolean('is_admin')->default(false);
$table->timestamp('expires_at')->nullable();
$table->rememberToken();
$table->timestamps();
......
......@@ -18,6 +18,10 @@
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
<testsuite name="Integration">
<directory suffix="Test.php">./tests/Integration</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
......@@ -28,10 +32,10 @@
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
<server name="MAIL_DRIVER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<!-- <server name="DB_CONNECTION" value="sqlite"/>-->
<!-- <server name="DB_DATABASE" value=":memory:"/>-->
<!-- <server name="MAIL_DRIVER" value="array"/>-->
<!-- <server name="QUEUE_CONNECTION" value="sync"/>-->
<server name="SESSION_DRIVER" value="array"/>
</php>
</phpunit>
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UsersController;
/*
|--------------------------------------------------------------------------
| Web Routes
......@@ -11,6 +14,22 @@
|
*/
Route::get('/', function () {
return ['api endpoint'];
});
Route::get(
'/',
static function () {
return ['api endpoint'];
}
);
Route::group(
[
'prefix' => 'users',
],
static function () {
Route::get('/', [UsersController::class, 'index']);
Route::post('/', [UsersController::class, 'store']);
Route::get('{userId}', [UsersController::class, 'get']);
Route::put('{userId}', [UsersController::class, 'update']);
Route::delete('{userId}', [UsersController::class, 'delete']);
}
);
<?php
namespace Source\Entities;
use Carbon\Carbon;
class User {
/**
* @var int
*/
protected int $id;
/**
* @var string
*/
protected string $firstName;
/**
* @var string
*/
protected string $lastName;
/**
* @var string
*/
protected string $displayName;
/**
* @var string|null
*/
protected ?string $emplid;
/**
* @var string
*/
protected string $email;
/**
* @var string
*/
protected string $password;
/**
* @var string
*/
protected string $doorcode;
/**
* @var Carbon|null
*/
protected ?Carbon $expiresAt;
/**
* @var Carbon|null
*/
protected ?Carbon $createdAt;
/**
* @var Carbon|null
*/
protected ?Carbon $updatedAt;
/**
* @param int $id
* @param string $firstName
* @param string $lastName
* @param string $displayName
* @param string|null $emplid
* @param string $email
* @param string $password
* @param string $doorcode
* @param Carbon|null $expiresAt
* @param Carbon|null $createdAt
* @param Carbon|null $updatedAt
*/
public function __construct(int $id,
string $firstName,
string $lastName,
string $displayName,
?string $emplid,
string $email,
string $password,
string $doorcode,
?Carbon $expiresAt,
?Carbon $createdAt,
?Carbon $updatedAt) {
$this->id = $id;
$this->firstName = $firstName;
$this->lastName = $lastName;
$this->displayName = $displayName;
$this->emplid = $emplid;
$this->email = $email;
$this->password = $password;
$this->doorcode = $doorcode;
$this->expiresAt = $expiresAt;
$this->createdAt = $createdAt;
$this->updatedAt = $updatedAt;
}
/**
* @return int
*/
public function getId(): int {
return $this->id;
}
/**
* @return string
*/
public function getFirstName(): string {
return $this->firstName;
}
/**
* @return string
*/
public function getLastName(): string {
return $this->lastName;
}
/**
* @return string
*/
public function getDisplayName(): string {
return $this->displayName;
}
/**
* @return string|null
*/
public function getEmplid(): ?string {
return $this->emplid;
}
/**
* @return string
*/
public function getEmail(): string {
return $this->email;
}
/**
* @return string