mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-18 21:22: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\MemberIndexRequest;
|
||||||
use App\Http\Requests\V1\Member\MemberUpdateRequest;
|
use App\Http\Requests\V1\Member\MemberUpdateRequest;
|
||||||
use App\Http\Resources\V1\Member\MemberCollection;
|
use App\Http\Resources\V1\Member\MemberCollection;
|
||||||
use App\Http\Resources\V1\Member\MemberPivotResource;
|
|
||||||
use App\Http\Resources\V1\Member\MemberResource;
|
use App\Http\Resources\V1\Member\MemberResource;
|
||||||
use App\Models\Member;
|
use App\Models\Member;
|
||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
@@ -40,7 +39,7 @@ class MemberController extends Controller
|
|||||||
/**
|
/**
|
||||||
* List all members of an organization
|
* List all members of an organization
|
||||||
*
|
*
|
||||||
* @return MemberCollection<MemberPivotResource>>
|
* @return MemberCollection<MemberResource>
|
||||||
*
|
*
|
||||||
* @throws AuthorizationException
|
* @throws AuthorizationException
|
||||||
*
|
*
|
||||||
@@ -50,7 +49,9 @@ class MemberController extends Controller
|
|||||||
{
|
{
|
||||||
$this->checkPermission($organization, 'members:view');
|
$this->checkPermission($organization, 'members:view');
|
||||||
|
|
||||||
$members = $organization->users()
|
$members = Member::query()
|
||||||
|
->whereBelongsTo($organization, 'organization')
|
||||||
|
->with(['user'])
|
||||||
->paginate(config('app.pagination_per_page_default'));
|
->paginate(config('app.pagination_per_page_default'));
|
||||||
|
|
||||||
return MemberCollection::make($members);
|
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
|
* @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\TagController;
|
||||||
use App\Http\Controllers\Api\V1\TaskController;
|
use App\Http\Controllers\Api\V1\TaskController;
|
||||||
use App\Http\Controllers\Api\V1\TimeEntryController;
|
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 App\Http\Controllers\Api\V1\UserTimeEntryController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
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');
|
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
|
// Invitation routes
|
||||||
Route::name('invitations.')->group(static function () {
|
Route::name('invitations.')->group(static function () {
|
||||||
Route::get('/organizations/{organization}/invitations', [InvitationController::class, 'index'])->name('index');
|
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