API: Allow usage of self in user routes

This commit is contained in:
Igor Scheller 2023-12-14 18:45:49 +01:00 committed by Michael Weimann
parent 02f998fc38
commit da8178b0bc
14 changed files with 338 additions and 34 deletions

View File

@ -37,6 +37,7 @@ return [
\Engelsystem\Http\HttpClientServiceProvider::class, \Engelsystem\Http\HttpClientServiceProvider::class,
\Engelsystem\Helpers\DumpServerServiceProvider::class, \Engelsystem\Helpers\DumpServerServiceProvider::class,
\Engelsystem\Helpers\UuidServiceProvider::class, \Engelsystem\Helpers\UuidServiceProvider::class,
\Engelsystem\Controllers\Api\UsesAuthServiceProvider::class,
], ],
// Application middleware // Application middleware

View File

@ -130,9 +130,9 @@ $route->addGroup(
$route->get('/news', 'Api\NewsController@index'); $route->get('/news', 'Api\NewsController@index');
$route->get('/users/self', 'Api\UsersController@self'); $route->get('/users/{user_id:(?:\d+|self)}', 'Api\UsersController@user');
$route->get('/users/{user_id:\d+}/angeltypes', 'Api\AngelTypeController@ofUser'); $route->get('/users/{user_id:(?:\d+|self)}/angeltypes', 'Api\AngelTypeController@ofUser');
$route->get('/users/{user_id:\d+}/shifts', 'Api\ShiftsController@entriesByUser'); $route->get('/users/{user_id:(?:\d+|self)}/shifts', 'Api\ShiftsController@entriesByUser');
$route->addRoute( $route->addRoute(
['POST', 'PUT', 'DELETE', 'PATCH'], ['POST', 'PUT', 'DELETE', 'PATCH'],

View File

@ -505,7 +505,17 @@ paths:
'404': '404':
$ref: '#/components/responses/NotFoundError' $ref: '#/components/responses/NotFoundError'
/users/self: /users/{id}:
parameters:
- name: id
in: path
required: true
description: The user identifier or `self`
example: 42
schema:
oneOf:
- type: string
- type: integer
get: get:
tags: tags:
- user - user
@ -519,7 +529,9 @@ paths:
type: object type: object
properties: properties:
data: data:
$ref: '#/components/schemas/UserDetail' anyOf:
- $ref: '#/components/schemas/UserDetail'
- $ref: '#/components/schemas/User'
'401': '401':
$ref: '#/components/responses/UnauthorizedError' $ref: '#/components/responses/UnauthorizedError'
'403': '403':
@ -532,10 +544,12 @@ paths:
- name: id - name: id
in: path in: path
required: true required: true
description: The user identifier description: The user identifier or `self`
example: 42 example: 42
schema: schema:
type: integer oneOf:
- type: string
- type: integer
get: get:
tags: tags:
- angeltype - angeltype
@ -565,10 +579,12 @@ paths:
- name: id - name: id
in: path in: path
required: true required: true
description: The user identifier description: The user identifier or `self`
example: 42 example: 42
schema: schema:
type: integer oneOf:
- type: string
- type: integer
get: get:
tags: tags:
- shift - shift

View File

@ -9,10 +9,11 @@ use Engelsystem\Controllers\Api\Resources\UserAngelTypeResource;
use Engelsystem\Http\Request; use Engelsystem\Http\Request;
use Engelsystem\Http\Response; use Engelsystem\Http\Response;
use Engelsystem\Models\AngelType; use Engelsystem\Models\AngelType;
use Engelsystem\Models\User\User;
class AngelTypeController extends ApiController class AngelTypeController extends ApiController
{ {
use UsesAuth;
public function index(): Response public function index(): Response
{ {
$models = AngelType::query() $models = AngelType::query()
@ -26,9 +27,10 @@ class AngelTypeController extends ApiController
public function ofUser(Request $request): Response public function ofUser(Request $request): Response
{ {
$id = (int) $request->getAttribute('user_id'); $id = $request->getAttribute('user_id');
$model = User::findOrFail($id); $user = $this->getUser($id);
$data = ['data' => UserAngelTypeResource::collection($model->userAngelTypes)];
$data = ['data' => UserAngelTypeResource::collection($user->userAngelTypes)];
return $this->response return $this->response
->withContent(json_encode($data)); ->withContent(json_encode($data));

View File

@ -15,11 +15,12 @@ use Engelsystem\Models\Location;
use Engelsystem\Models\Shifts\NeededAngelType; use Engelsystem\Models\Shifts\NeededAngelType;
use Engelsystem\Models\Shifts\Shift; use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry; use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\User;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
class ShiftsController extends ApiController class ShiftsController extends ApiController
{ {
use UsesAuth;
public function entriesByAngeltype(Request $request): Response public function entriesByAngeltype(Request $request): Response
{ {
$id = (int) $request->getAttribute('angeltype_id'); $id = (int) $request->getAttribute('angeltype_id');
@ -72,9 +73,9 @@ class ShiftsController extends ApiController
public function entriesByUser(Request $request): Response public function entriesByUser(Request $request): Response
{ {
$id = (int) $request->getAttribute('user_id'); $id = $request->getAttribute('user_id');
/** @var User $user */ $user = $this->getUser($id);
$user = User::findOrFail($id);
/** @var ShiftEntry[]|Collection $shifts */ /** @var ShiftEntry[]|Collection $shifts */
$shiftEntries = $user->shiftEntries() $shiftEntries = $user->shiftEntries()
->with([ ->with([

View File

@ -5,21 +5,21 @@ declare(strict_types=1);
namespace Engelsystem\Controllers\Api; namespace Engelsystem\Controllers\Api;
use Engelsystem\Controllers\Api\Resources\UserDetailResource; use Engelsystem\Controllers\Api\Resources\UserDetailResource;
use Engelsystem\Helpers\Authenticator; use Engelsystem\Controllers\Api\Resources\UserResource;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response; use Engelsystem\Http\Response;
class UsersController extends ApiController class UsersController extends ApiController
{ {
public function __construct(Response $response, protected Authenticator $auth) use UsesAuth;
{
parent::__construct($response);
}
public function self(): Response public function user(Request $request): Response
{ {
$user = $this->auth->user(); $id = $request->getAttribute('user_id');
$user = $this->getUser($id);
$data = ['data' => (new UserDetailResource($user))->toArray()]; $userData = $user->id == $this->auth->user()->id ? new UserDetailResource($user) : new UserResource($user);
$data = ['data' => $userData->toArray()];
return $this->response return $this->response
->withContent(json_encode($data)); ->withContent(json_encode($data));
} }

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Controllers\Api;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Models\User\User;
trait UsesAuth
{
protected ?Authenticator $auth = null;
public function setAuth(Authenticator $auth): void
{
$this->auth = $auth;
}
protected function getUser(int|string $userId): ?User
{
if ($userId == 'self' && $this->auth) {
return $this->auth->user();
}
return User::findOrFail($userId);
}
}

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Controllers\Api;
use Engelsystem\Application;
use Engelsystem\Container\ServiceProvider;
use Engelsystem\Helpers\Authenticator;
class UsesAuthServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->afterResolving(function ($object, Application $app): void {
if (!$object instanceof ApiController || !method_exists($object, 'setAuth')) {
return;
}
/** @var UsesAuth $object */
$object->setAuth($app->get(Authenticator::class));
});
}
}

View File

@ -5,11 +5,13 @@ declare(strict_types=1);
namespace Engelsystem\Test\Unit\Controllers\Api; namespace Engelsystem\Test\Unit\Controllers\Api;
use Engelsystem\Controllers\Api\AngelTypeController; use Engelsystem\Controllers\Api\AngelTypeController;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Http\Request; use Engelsystem\Http\Request;
use Engelsystem\Http\Response; use Engelsystem\Http\Response;
use Engelsystem\Models\AngelType; use Engelsystem\Models\AngelType;
use Engelsystem\Models\User\User; use Engelsystem\Models\User\User;
use Engelsystem\Models\UserAngelType; use Engelsystem\Models\UserAngelType;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class AngelTypeControllerTest extends ApiBaseControllerTest class AngelTypeControllerTest extends ApiBaseControllerTest
{ {
@ -18,7 +20,6 @@ class AngelTypeControllerTest extends ApiBaseControllerTest
*/ */
public function testIndex(): void public function testIndex(): void
{ {
$this->initDatabase();
$items = AngelType::factory(3)->create(); $items = AngelType::factory(3)->create();
$controller = new AngelTypeController(new Response()); $controller = new AngelTypeController(new Response());
@ -42,7 +43,6 @@ class AngelTypeControllerTest extends ApiBaseControllerTest
*/ */
public function testOfUser(): void public function testOfUser(): void
{ {
$this->initDatabase();
$user = User::factory()->create(); $user = User::factory()->create();
$items = UserAngelType::factory(3)->create(['user_id' => $user->id]); $items = UserAngelType::factory(3)->create(['user_id' => $user->id]);
@ -61,4 +61,38 @@ class AngelTypeControllerTest extends ApiBaseControllerTest
return $item['name'] == $items->first()->angelType->name; return $item['name'] == $items->first()->angelType->name;
})); }));
} }
/**
* @covers \Engelsystem\Controllers\Api\AngelTypeController::ofUser
*/
public function testEntriesOfUserSelf(): void
{
$user = User::factory()->create();
$auth = $this->createMock(Authenticator::class);
$this->setExpects($auth, 'user', null, $user);
$request = new Request();
$request = $request->withAttribute('user_id', 'self');
$controller = new AngelTypeController(new Response());
$controller->setAuth($auth);
$response = $controller->ofUser($request);
$this->validateApiResponse('/users/{id}/angeltypes', 'get', $response);
}
/**
* @covers \Engelsystem\Controllers\Api\AngelTypeController::ofUser
*/
public function testEntriesByUserNotFound(): void
{
$request = new Request();
$request = $request->withAttribute('user_id', 42);
$controller = new AngelTypeController(new Response());
$this->expectException(ModelNotFoundException::class);
$controller->ofUser($request);
}
} }

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Engelsystem\Test\Unit\Controllers\Api; namespace Engelsystem\Test\Unit\Controllers\Api;
use Engelsystem\Controllers\Api\ShiftsController; use Engelsystem\Controllers\Api\ShiftsController;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Helpers\Carbon; use Engelsystem\Helpers\Carbon;
use Engelsystem\Http\Request; use Engelsystem\Http\Request;
use Engelsystem\Http\Response; use Engelsystem\Http\Response;
@ -17,6 +18,7 @@ use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\Contact; use Engelsystem\Models\User\Contact;
use Engelsystem\Models\User\PersonalData; use Engelsystem\Models\User\PersonalData;
use Engelsystem\Models\User\User; use Engelsystem\Models\User\User;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class ShiftsControllerTest extends ApiBaseControllerTest class ShiftsControllerTest extends ApiBaseControllerTest
{ {
@ -133,6 +135,40 @@ class ShiftsControllerTest extends ApiBaseControllerTest
$this->assertTrue(count($shift['entries']) >= 1); $this->assertTrue(count($shift['entries']) >= 1);
} }
/**
* @covers \Engelsystem\Controllers\Api\ShiftsController::entriesByUser
*/
public function testEntriesByUserSelf(): void
{
$user = User::query()->first();
$auth = $this->createMock(Authenticator::class);
$this->setExpects($auth, 'user', null, $user);
$request = new Request();
$request = $request->withAttribute('user_id', 'self');
$controller = new ShiftsController(new Response());
$controller->setAuth($auth);
$response = $controller->entriesByUser($request);
$this->validateApiResponse('/users/{id}/shifts', 'get', $response);
}
/**
* @covers \Engelsystem\Controllers\Api\ShiftsController::entriesByUser
*/
public function testEntriesByUserNotFound(): void
{
$request = new Request();
$request = $request->withAttribute('user_id', 42);
$controller = new ShiftsController(new Response());
$this->expectException(ModelNotFoundException::class);
$controller->entriesByUser($request);
}
public function setUp(): void public function setUp(): void
{ {
parent::setUp(); parent::setUp();

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Test\Unit\Controllers\Api\Stub;
use Engelsystem\Controllers\Api\ApiController;
use Engelsystem\Controllers\Api\UsesAuth;
use Engelsystem\Models\User\User;
class UsesAuthImplementation extends ApiController
{
use UsesAuth;
public function user(string|int $id): ?User
{
return $this->getUser($id);
}
}

View File

@ -6,6 +6,7 @@ namespace Engelsystem\Test\Unit\Controllers\Api;
use Engelsystem\Controllers\Api\UsersController; use Engelsystem\Controllers\Api\UsersController;
use Engelsystem\Helpers\Authenticator; use Engelsystem\Helpers\Authenticator;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response; use Engelsystem\Http\Response;
use Engelsystem\Models\User\Contact; use Engelsystem\Models\User\Contact;
use Engelsystem\Models\User\PersonalData; use Engelsystem\Models\User\PersonalData;
@ -17,11 +18,10 @@ use PHPUnit\Framework\MockObject\MockObject;
class UsersControllerTest extends ApiBaseControllerTest class UsersControllerTest extends ApiBaseControllerTest
{ {
/** /**
* @covers \Engelsystem\Controllers\Api\UsersController::__construct * @covers \Engelsystem\Controllers\Api\UsersController::user
* @covers \Engelsystem\Controllers\Api\UsersController::self
* @covers \Engelsystem\Controllers\Api\Resources\UserDetailResource::toArray * @covers \Engelsystem\Controllers\Api\Resources\UserDetailResource::toArray
*/ */
public function testSelf(): void public function testUser(): void
{ {
$user = User::factory() $user = User::factory()
->has(Contact::factory()) ->has(Contact::factory())
@ -32,12 +32,16 @@ class UsersControllerTest extends ApiBaseControllerTest
/** @var Authenticator|MockObject $auth */ /** @var Authenticator|MockObject $auth */
$auth = $this->createMock(Authenticator::class); $auth = $this->createMock(Authenticator::class);
$this->setExpects($auth, 'user', null, $user); $this->setExpects($auth, 'user', null, $user, $this->atLeastOnce());
$controller = new UsersController(new Response(), $auth); $request = new Request();
$request = $request->withAttribute('user_id', 'self');
$response = $controller->self(); $controller = new UsersController(new Response());
$this->validateApiResponse('/users/self', 'get', $response); $controller->setAuth($auth);
$response = $controller->user($request);
$this->validateApiResponse('/users/{id}', 'get', $response);
$this->assertEquals(['application/json'], $response->getHeader('content-type')); $this->assertEquals(['application/json'], $response->getHeader('content-type'));
$this->assertJson($response->getContent()); $this->assertJson($response->getContent());
@ -48,4 +52,36 @@ class UsersControllerTest extends ApiBaseControllerTest
$this->assertEquals($user->id, $data['data']['id']); $this->assertEquals($user->id, $data['data']['id']);
$this->assertArrayHasKey('dates', $data['data']); $this->assertArrayHasKey('dates', $data['data']);
} }
/**
* @covers \Engelsystem\Controllers\Api\UsersController::user
*/
public function testUserById(): void
{
$user = User::factory()->create();
$otherUser = User::factory()
->has(Contact::factory())
->has(PersonalData::factory())
->has(Settings::factory())
->has(State::factory())
->create();
/** @var Authenticator|MockObject $auth */
$auth = $this->createMock(Authenticator::class);
$this->setExpects($auth, 'user', null, $user, $this->atLeastOnce());
$request = new Request();
$request = $request->withAttribute('user_id', $otherUser->id);
$controller = new UsersController(new Response());
$controller->setAuth($auth);
$response = $controller->user($request);
$this->validateApiResponse('/users/{id}', 'get', $response);
$data = json_decode($response->getContent(), true);
$this->assertArrayHasKey('id', $data['data']);
$this->assertEquals($otherUser->id, $data['data']['id']);
$this->assertArrayNotHasKey('dates', $data['data']);
}
} }

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Test\Unit\Controllers\Api;
use Engelsystem\Controllers\Api\UsesAuthServiceProvider;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Models\User\User;
use Engelsystem\Test\Unit\Controllers\Api\Stub\UsesAuthImplementation;
use Engelsystem\Test\Unit\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
class UsesAuthServiceProviderTest extends TestCase
{
/**
* @covers \Engelsystem\Controllers\Api\UsesAuthServiceProvider::register
*/
public function testRegister(): void
{
$serviceProvider = new UsesAuthServiceProvider($this->app);
$serviceProvider->register();
$user = new User();
/** @var Authenticator|MockObject $auth */
$auth = $this->createMock(Authenticator::class);
$this->setExpects($auth, 'user', null, $user);
$this->app->instance(Authenticator::class, $auth);
/** @var UsesAuthImplementation $instance */
$instance = $this->app->make(UsesAuthImplementation::class);
$this->assertEquals($user, $instance->user('self'));
}
}

View File

@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Test\Unit\Controllers\Api;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Http\Response;
use Engelsystem\Models\User\User;
use Engelsystem\Test\Unit\Controllers\Api\Stub\UsesAuthImplementation;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use PHPUnit\Framework\MockObject\MockObject;
class UsesAuthTest extends ApiBaseControllerTest
{
/**
* @covers \Engelsystem\Controllers\Api\UsesAuth::getUser
*/
public function testGetUserNoAuthNotFound(): void
{
$usesAuth = $this->createInstance();
$this->expectException(ModelNotFoundException::class);
$usesAuth->user('self');
}
/**
* @covers \Engelsystem\Controllers\Api\UsesAuth::getUser
*/
public function testGetUserNotFound(): void
{
$usesAuth = $this->createInstance();
$this->expectException(ModelNotFoundException::class);
$usesAuth->user(42);
}
/**
* @covers \Engelsystem\Controllers\Api\UsesAuth::getUser
*/
public function testGetUserWithoutAuth(): void
{
$user = User::factory()->create();
$usesAuth = $this->createInstance();
$this->assertInstanceOf(User::class, $usesAuth->user($user->id));
}
/**
* @covers \Engelsystem\Controllers\Api\UsesAuth::setAuth
* @covers \Engelsystem\Controllers\Api\UsesAuth::getUser
*/
public function testGetUser(): void
{
$user = User::factory()->create();
/** @var Authenticator|MockObject $auth */
$auth = $this->createMock(Authenticator::class);
$this->setExpects($auth, 'user', null, $user);
$usesAuth = $this->createInstance();
$usesAuth->setAuth($auth);
$this->assertEquals($user, $usesAuth->user('self'));
}
protected function createInstance(): object
{
return new UsesAuthImplementation(new Response());
}
}