mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 00:02:15 +01:00
add switch current organization endpoint
This commit is contained in:
committed by
Constantin Graf
parent
89a9341d91
commit
c5c129b239
@@ -6,11 +6,14 @@ namespace App\Http\Controllers\Api\V1;
|
|||||||
|
|
||||||
use App\Exceptions\Api\CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers;
|
use App\Exceptions\Api\CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers;
|
||||||
use App\Exceptions\Api\UserResendEmailVerificationNoPendingEmailApiException;
|
use App\Exceptions\Api\UserResendEmailVerificationNoPendingEmailApiException;
|
||||||
|
use App\Http\Requests\V1\User\UserUpdateCurrentOrganizationRequest;
|
||||||
use App\Http\Requests\V1\User\UserUpdateRequest;
|
use App\Http\Requests\V1\User\UserUpdateRequest;
|
||||||
use App\Http\Resources\V1\User\UserResource;
|
use App\Http\Resources\V1\User\UserResource;
|
||||||
use App\Mail\VerifyUpdatedEmailMail;
|
use App\Mail\VerifyUpdatedEmailMail;
|
||||||
|
use App\Models\Organization;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Service\DeletionService;
|
use App\Service\DeletionService;
|
||||||
|
use App\Service\UserService;
|
||||||
use App\Support\Base64File;
|
use App\Support\Base64File;
|
||||||
use Illuminate\Auth\Access\AuthorizationException;
|
use Illuminate\Auth\Access\AuthorizationException;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
@@ -36,6 +39,35 @@ class UserController extends Controller
|
|||||||
return new UserResource($user);
|
return new UserResource($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current organization of the current user
|
||||||
|
*
|
||||||
|
* Switches the organization that the user is currently working in. The user
|
||||||
|
* must be a member of the given organization. This endpoint is independent of
|
||||||
|
* the organization.
|
||||||
|
*
|
||||||
|
* @operationId updateMyCurrentOrganization
|
||||||
|
*
|
||||||
|
* @throws AuthorizationException
|
||||||
|
*/
|
||||||
|
public function updateMyCurrentOrganization(UserUpdateCurrentOrganizationRequest $request, UserService $userService): UserResource
|
||||||
|
{
|
||||||
|
$user = $this->user();
|
||||||
|
|
||||||
|
/** @var Organization|null $organization */
|
||||||
|
$organization = $user->organizations()
|
||||||
|
->whereKey($request->getOrganizationId())
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($organization === null) {
|
||||||
|
throw new AuthorizationException;
|
||||||
|
}
|
||||||
|
|
||||||
|
$userService->switchCurrentOrganization($user, $organization);
|
||||||
|
|
||||||
|
return new UserResource($user->refresh());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the current user
|
* Update the current user
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests\V1\User;
|
||||||
|
|
||||||
|
use App\Http\Requests\V1\BaseFormRequest;
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
|
||||||
|
class UserUpdateCurrentOrganizationRequest extends BaseFormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, array<string|ValidationRule>>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'organization_id' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'uuid',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOrganizationId(): string
|
||||||
|
{
|
||||||
|
return (string) $this->input('organization_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,6 +62,7 @@ Route::prefix('v1')->name('v1.')->group(static function (): void {
|
|||||||
// User routes
|
// User routes
|
||||||
Route::name('users.')->group(static function (): void {
|
Route::name('users.')->group(static function (): void {
|
||||||
Route::get('/users/me', [UserController::class, 'me'])->name('me');
|
Route::get('/users/me', [UserController::class, 'me'])->name('me');
|
||||||
|
Route::put('/users/me/current-organization', [UserController::class, 'updateMyCurrentOrganization'])->name('update-current-organization');
|
||||||
Route::put('/users/{user}', [UserController::class, 'update'])->name('update');
|
Route::put('/users/{user}', [UserController::class, 'update'])->name('update');
|
||||||
Route::post('/users/{user}/resend-email-verification', [UserController::class, 'resendEmailVerification'])->name('resend-email-verification');
|
Route::post('/users/{user}/resend-email-verification', [UserController::class, 'resendEmailVerification'])->name('resend-email-verification');
|
||||||
Route::delete('/users/{user}', [UserController::class, 'destroy'])->name('destroy');
|
Route::delete('/users/{user}', [UserController::class, 'destroy'])->name('destroy');
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Tests\Unit\Endpoint\Api\V1;
|
namespace Tests\Unit\Endpoint\Api\V1;
|
||||||
|
|
||||||
|
use App\Enums\Role;
|
||||||
use App\Enums\Weekday;
|
use App\Enums\Weekday;
|
||||||
use App\Mail\VerifyUpdatedEmailMail;
|
use App\Mail\VerifyUpdatedEmailMail;
|
||||||
|
use App\Models\Member;
|
||||||
|
use App\Models\Organization;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
@@ -46,6 +49,88 @@ class UserEndpointTest extends ApiEndpointTestAbstract
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_update_current_organization_fails_when_not_authenticated(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$organization = Organization::factory()->create();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->putJson(route('api.v1.users.update-current-organization'), [
|
||||||
|
'organization_id' => $organization->getKey(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertUnauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_update_current_organization_switches_the_current_organization_of_the_user(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$data = $this->createUserWithPermission([], isOwner: true);
|
||||||
|
$otherOrganization = Organization::factory()->create();
|
||||||
|
Member::factory()->forUser($data->user)->forOrganization($otherOrganization)->create([
|
||||||
|
'role' => Role::Admin->value,
|
||||||
|
]);
|
||||||
|
Passport::actingAs($data->user);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->putJson(route('api.v1.users.update-current-organization'), [
|
||||||
|
'organization_id' => $otherOrganization->getKey(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertSuccessful();
|
||||||
|
$this->assertSame($otherOrganization->getKey(), $data->user->fresh()->current_team_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_update_current_organization_fails_if_user_is_not_a_member_of_the_target_organization(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$data = $this->createUserWithPermission([], isOwner: true);
|
||||||
|
$currentOrganizationId = $data->user->current_team_id;
|
||||||
|
$otherOrganization = Organization::factory()->create();
|
||||||
|
Passport::actingAs($data->user);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->putJson(route('api.v1.users.update-current-organization'), [
|
||||||
|
'organization_id' => $otherOrganization->getKey(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertForbidden();
|
||||||
|
$this->assertSame($currentOrganizationId, $data->user->fresh()->current_team_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_update_current_organization_fails_if_organization_id_is_missing(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$data = $this->createUserWithPermission([], isOwner: true);
|
||||||
|
Passport::actingAs($data->user);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->putJson(route('api.v1.users.update-current-organization'), []);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertUnprocessable();
|
||||||
|
$response->assertJsonValidationErrors('organization_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_update_current_organization_fails_if_organization_id_is_not_a_uuid(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$data = $this->createUserWithPermission([], isOwner: true);
|
||||||
|
Passport::actingAs($data->user);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->putJson(route('api.v1.users.update-current-organization'), [
|
||||||
|
'organization_id' => 'not-a-uuid',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertUnprocessable();
|
||||||
|
$response->assertJsonValidationErrors('organization_id');
|
||||||
|
}
|
||||||
|
|
||||||
public function test_update_changes_user_name_timezone_and_week_start(): void
|
public function test_update_changes_user_name_timezone_and_week_start(): void
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|||||||
Reference in New Issue
Block a user