mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 00:02:15 +01:00
Added me endpoints
This commit is contained in:
committed by
Gregor Vostrak
parent
0e7dec2f40
commit
21207a4058
@@ -14,7 +14,6 @@ use App\Exceptions\Api\UserNotPlaceholderApiException;
|
||||
use App\Http\Requests\V1\Member\MemberIndexRequest;
|
||||
use App\Http\Requests\V1\Member\MemberUpdateRequest;
|
||||
use App\Http\Resources\V1\Member\MemberCollection;
|
||||
use App\Http\Resources\V1\Member\MemberPivotResource;
|
||||
use App\Http\Resources\V1\Member\MemberResource;
|
||||
use App\Models\Member;
|
||||
use App\Models\Organization;
|
||||
@@ -40,7 +39,7 @@ class MemberController extends Controller
|
||||
/**
|
||||
* List all members of an organization
|
||||
*
|
||||
* @return MemberCollection<MemberPivotResource>>
|
||||
* @return MemberCollection<MemberResource>
|
||||
*
|
||||
* @throws AuthorizationException
|
||||
*
|
||||
@@ -50,7 +49,9 @@ class MemberController extends Controller
|
||||
{
|
||||
$this->checkPermission($organization, 'members:view');
|
||||
|
||||
$members = $organization->users()
|
||||
$members = Member::query()
|
||||
->whereBelongsTo($organization, 'organization')
|
||||
->with(['user'])
|
||||
->paginate(config('app.pagination_per_page_default'));
|
||||
|
||||
return MemberCollection::make($members);
|
||||
|
||||
28
app/Http/Controllers/Api/V1/UserController.php
Normal file
28
app/Http/Controllers/Api/V1/UserController.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Resources\V1\User\UserResource;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Get the current user
|
||||
*
|
||||
* This endpoint is independent of organization.
|
||||
*
|
||||
* @operationId getMe
|
||||
*
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function me(): JsonResource
|
||||
{
|
||||
$user = $this->user();
|
||||
|
||||
return new UserResource($user);
|
||||
}
|
||||
}
|
||||
37
app/Http/Controllers/Api/V1/UserMemberController.php
Normal file
37
app/Http/Controllers/Api/V1/UserMemberController.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Resources\V1\Member\PersonalMemberCollection;
|
||||
use App\Http\Resources\V1\Member\PersonalMemberResource;
|
||||
use App\Models\Member;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class UserMemberController extends Controller
|
||||
{
|
||||
/**
|
||||
* Get the memberships of the current user
|
||||
*
|
||||
* This endpoint is independent of organization.
|
||||
*
|
||||
* @operationId getMyMemberships
|
||||
*
|
||||
* @return PersonalMemberCollection<PersonalMemberResource>
|
||||
*
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function myMembers(): JsonResource
|
||||
{
|
||||
$user = $this->user();
|
||||
|
||||
$members = Member::query()
|
||||
->whereBelongsTo($user, 'user')
|
||||
->with(['organization'])
|
||||
->get();
|
||||
|
||||
return new PersonalMemberCollection($members);
|
||||
}
|
||||
}
|
||||
@@ -14,5 +14,5 @@ class MemberCollection extends ResourceCollection implements PaginatedResourceCo
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $collects = MemberPivotResource::class;
|
||||
public $collects = MemberResource::class;
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\V1\Member;
|
||||
|
||||
use App\Http\Resources\V1\BaseResource;
|
||||
use App\Models\Member;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* @property User $resource
|
||||
*/
|
||||
class MemberPivotResource extends BaseResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, string|bool|int|null|array<string>>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
/** @var Member $member */
|
||||
$member = $this->resource->getRelationValue('membership');
|
||||
|
||||
return [
|
||||
/** @var string $id ID of membership */
|
||||
'id' => $member->id,
|
||||
/** @var string $id ID of user */
|
||||
'user_id' => $this->resource->id,
|
||||
/** @var string $name Name */
|
||||
'name' => $this->resource->name,
|
||||
/** @var string $email Email */
|
||||
'email' => $this->resource->email,
|
||||
/** @var string $role Role */
|
||||
'role' => $member->role,
|
||||
/** @var bool $is_placeholder Placeholder user for imports, user might not really exist and does not know about this placeholder membership */
|
||||
'is_placeholder' => $this->resource->is_placeholder,
|
||||
/** @var int|null $billable_rate Billable rate in cents per hour */
|
||||
'billable_rate' => $member->billable_rate,
|
||||
];
|
||||
}
|
||||
}
|
||||
18
app/Http/Resources/V1/Member/PersonalMemberCollection.php
Normal file
18
app/Http/Resources/V1/Member/PersonalMemberCollection.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\V1\Member;
|
||||
|
||||
use App\Http\Resources\PaginatedResourceCollection;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class PersonalMemberCollection extends ResourceCollection implements PaginatedResourceCollection
|
||||
{
|
||||
/**
|
||||
* The resource that this resource collects.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $collects = PersonalMemberResource::class;
|
||||
}
|
||||
36
app/Http/Resources/V1/Member/PersonalMemberResource.php
Normal file
36
app/Http/Resources/V1/Member/PersonalMemberResource.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\V1\Member;
|
||||
|
||||
use App\Http\Resources\V1\BaseResource;
|
||||
use App\Models\Member;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* @property Member $resource
|
||||
*/
|
||||
class PersonalMemberResource extends BaseResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, string|bool|int|null|array<string>>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
/** @var string $id ID of membership */
|
||||
'id' => $this->resource->id,
|
||||
'organization' => [
|
||||
/** @var int $id ID of organization */
|
||||
'id' => $this->resource->organization->id,
|
||||
/** @var string $name Name of organization */
|
||||
'name' => $this->resource->organization->name,
|
||||
],
|
||||
/** @var string $role Role */
|
||||
'role' => $this->resource->role,
|
||||
];
|
||||
}
|
||||
}
|
||||
39
app/Http/Resources/V1/User/UserResource.php
Normal file
39
app/Http/Resources/V1/User/UserResource.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\V1\User;
|
||||
|
||||
use App\Enums\Weekday;
|
||||
use App\Http\Resources\V1\BaseResource;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* @property User $resource
|
||||
*/
|
||||
class UserResource extends BaseResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, string|bool|int|null|array<string>>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
/** @var string $id ID of user */
|
||||
'id' => $this->resource->id,
|
||||
/** @var string $name Name of user */
|
||||
'name' => $this->resource->name,
|
||||
/** @var string $email Email of user */
|
||||
'email' => $this->resource->email,
|
||||
/** @var string $profile_photo_url Profile photo URL */
|
||||
'profile_photo_url' => $this->resource->profile_photo_url,
|
||||
/** @var string $timezone Timezone (f.e. Europe/Berlin or America/New_York) */
|
||||
'timezone' => $this->resource->timezone,
|
||||
/** @var Weekday $week_start Starting day of the week */
|
||||
'week_start' => $this->resource->week_start->value,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ use App\Http\Controllers\Api\V1\ProjectMemberController;
|
||||
use App\Http\Controllers\Api\V1\TagController;
|
||||
use App\Http\Controllers\Api\V1\TaskController;
|
||||
use App\Http\Controllers\Api\V1\TimeEntryController;
|
||||
use App\Http\Controllers\Api\V1\UserController;
|
||||
use App\Http\Controllers\Api\V1\UserMemberController;
|
||||
use App\Http\Controllers\Api\V1\UserTimeEntryController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
@@ -45,6 +47,16 @@ Route::middleware([
|
||||
Route::post('/organizations/{organization}/members/{member}/invite-placeholder', [MemberController::class, 'invitePlaceholder'])->name('invite-placeholder');
|
||||
});
|
||||
|
||||
// User routes
|
||||
Route::name('users.')->group(static function () {
|
||||
Route::get('/users/me', [UserController::class, 'me'])->name('me');
|
||||
});
|
||||
|
||||
// User Member routes
|
||||
Route::name('users.members.')->group(static function () {
|
||||
Route::get('/users/me/members', [UserMemberController::class, 'myMembers'])->name('my-members');
|
||||
});
|
||||
|
||||
// Invitation routes
|
||||
Route::name('invitations.')->group(static function () {
|
||||
Route::get('/organizations/{organization}/invitations', [InvitationController::class, 'index'])->name('index');
|
||||
|
||||
43
tests/Unit/Endpoint/Api/V1/UserEndpointTest.php
Normal file
43
tests/Unit/Endpoint/Api/V1/UserEndpointTest.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Endpoint\Api\V1;
|
||||
|
||||
use Laravel\Passport\Passport;
|
||||
|
||||
class UserEndpointTest extends ApiEndpointTestAbstract
|
||||
{
|
||||
public function test_me_fails_when_not_authenticated(): void
|
||||
{
|
||||
// Act
|
||||
$response = $this->getJson(route('api.v1.users.me'));
|
||||
|
||||
// Assert
|
||||
$response->assertUnauthorized();
|
||||
$response->assertJson(['message' => 'Unauthenticated.']);
|
||||
}
|
||||
|
||||
public function test_me_returns_information_about_the_current_user(): void
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission();
|
||||
Passport::actingAs($data->user);
|
||||
|
||||
// Act
|
||||
$response = $this->getJson(route('api.v1.users.me'));
|
||||
|
||||
// Assert
|
||||
$response->assertSuccessful();
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
'id' => $data->user->getKey(),
|
||||
'name' => $data->user->name,
|
||||
'email' => $data->user->email,
|
||||
'profile_photo_url' => $data->user->profile_photo_url,
|
||||
'timezone' => $data->user->timezone,
|
||||
'week_start' => $data->user->week_start->value,
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
43
tests/Unit/Endpoint/Api/V1/UserMemberEndpointTest.php
Normal file
43
tests/Unit/Endpoint/Api/V1/UserMemberEndpointTest.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Endpoint\Api\V1;
|
||||
|
||||
use App\Models\Member;
|
||||
use App\Models\Organization;
|
||||
use Laravel\Passport\Passport;
|
||||
|
||||
class UserMemberEndpointTest extends ApiEndpointTestAbstract
|
||||
{
|
||||
public function test_my_members_fails_when_not_authenticated(): void
|
||||
{
|
||||
// Act
|
||||
$response = $this->getJson(route('api.v1.users.members.my-members'));
|
||||
|
||||
// Assert
|
||||
$response->assertUnauthorized();
|
||||
$response->assertJson(['message' => 'Unauthenticated.']);
|
||||
}
|
||||
|
||||
public function test_my_members_returns_information_about_the_organization_membership_of_the_current_user(): void
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission();
|
||||
$otherOrganization = Organization::factory()->create();
|
||||
$otherMember = Member::factory()->forOrganization($otherOrganization)->forUser($data->user)->create();
|
||||
Passport::actingAs($data->user);
|
||||
|
||||
// Act
|
||||
$response = $this->getJson(route('api.v1.users.members.my-members'));
|
||||
|
||||
// Assert
|
||||
$response->assertSuccessful();
|
||||
$response->assertJsonCount(2, 'data');
|
||||
$otherMemberResponse = collect($response->json('data'))->where('id', '=', $otherMember->getKey())->first();
|
||||
$this->assertNotNull($otherMemberResponse);
|
||||
$this->assertSame($otherMember->organization->getKey(), $otherMemberResponse['organization']['id']);
|
||||
$memberResponse = collect($response->json('data'))->where('id', '=', $data->member->getKey())->first();
|
||||
$this->assertNotNull($memberResponse);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user