mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-17 12:42:15 +01:00
Added invitation delete endpoint
This commit is contained in:
committed by
Constantin Graf
parent
ad6146c483
commit
a149db655c
@@ -9,12 +9,21 @@ use App\Http\Requests\V1\Invitation\InvitationStoreRequest;
|
|||||||
use App\Http\Resources\V1\Invitation\InvitationCollection;
|
use App\Http\Resources\V1\Invitation\InvitationCollection;
|
||||||
use App\Http\Resources\V1\Invitation\InvitationResource;
|
use App\Http\Resources\V1\Invitation\InvitationResource;
|
||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
|
use App\Models\OrganizationInvitation;
|
||||||
use Illuminate\Auth\Access\AuthorizationException;
|
use Illuminate\Auth\Access\AuthorizationException;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Laravel\Jetstream\Contracts\InvitesTeamMembers;
|
use Laravel\Jetstream\Contracts\InvitesTeamMembers;
|
||||||
|
|
||||||
class InvitationController extends Controller
|
class InvitationController extends Controller
|
||||||
{
|
{
|
||||||
|
protected function checkPermission(Organization $organization, string $permission, ?OrganizationInvitation $organizationInvitation = null): void
|
||||||
|
{
|
||||||
|
parent::checkPermission($organization, $permission);
|
||||||
|
if ($organizationInvitation !== null && $organizationInvitation->organization_id !== $organization->id) {
|
||||||
|
throw new AuthorizationException('Invitation does not belong to organization');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List all invitations of an organization
|
* List all invitations of an organization
|
||||||
*
|
*
|
||||||
@@ -54,4 +63,20 @@ class InvitationController extends Controller
|
|||||||
|
|
||||||
return response()->json(null, 204);
|
return response()->json(null, 204);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a pending invitation
|
||||||
|
*
|
||||||
|
* @throws AuthorizationException
|
||||||
|
*
|
||||||
|
* @operationId removeInvitation
|
||||||
|
*/
|
||||||
|
public function destroy(Organization $organization, OrganizationInvitation $invitation): JsonResponse
|
||||||
|
{
|
||||||
|
$this->checkPermission($organization, 'invitations:remove', $invitation);
|
||||||
|
|
||||||
|
$invitation->delete();
|
||||||
|
|
||||||
|
return response()->json(null, 204);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Database\Factories\OrganizationInvitationFactory;
|
||||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Laravel\Jetstream\Jetstream;
|
use Laravel\Jetstream\Jetstream;
|
||||||
use Laravel\Jetstream\TeamInvitation as JetstreamTeamInvitation;
|
use Laravel\Jetstream\TeamInvitation as JetstreamTeamInvitation;
|
||||||
@@ -15,9 +17,12 @@ use Laravel\Jetstream\TeamInvitation as JetstreamTeamInvitation;
|
|||||||
* @property string $role
|
* @property string $role
|
||||||
* @property string $organization_id
|
* @property string $organization_id
|
||||||
* @property-read Organization $organization
|
* @property-read Organization $organization
|
||||||
|
*
|
||||||
|
* @method static OrganizationInvitationFactory factory()
|
||||||
*/
|
*/
|
||||||
class OrganizationInvitation extends JetstreamTeamInvitation
|
class OrganizationInvitation extends JetstreamTeamInvitation
|
||||||
{
|
{
|
||||||
|
use HasFactory;
|
||||||
use HasUuids;
|
use HasUuids;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -86,5 +86,6 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
});
|
});
|
||||||
|
|
||||||
Route::model('member', Membership::class);
|
Route::model('member', Membership::class);
|
||||||
|
Route::model('invitation', OrganizationInvitation::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,9 @@ class JetstreamServiceProvider extends ServiceProvider
|
|||||||
'organizations:view',
|
'organizations:view',
|
||||||
'organizations:update',
|
'organizations:update',
|
||||||
'import',
|
'import',
|
||||||
|
'invitations:view',
|
||||||
|
'invitations:create',
|
||||||
|
'invitations:remove',
|
||||||
'members:view',
|
'members:view',
|
||||||
'members:invite-placeholder',
|
'members:invite-placeholder',
|
||||||
'members:change-role',
|
'members:change-role',
|
||||||
|
|||||||
37
database/factories/OrganizationInvitationFactory.php
Normal file
37
database/factories/OrganizationInvitationFactory.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Enums\Role;
|
||||||
|
use App\Models\Organization;
|
||||||
|
use App\Models\OrganizationInvitation;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends Factory<OrganizationInvitation>
|
||||||
|
*/
|
||||||
|
class OrganizationInvitationFactory extends Factory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Define the model's default state.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'email' => $this->faker->unique()->safeEmail(),
|
||||||
|
'role' => Role::Employee->value,
|
||||||
|
'organization_id' => Organization::factory(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function forOrganization(Organization $organization): self
|
||||||
|
{
|
||||||
|
return $this->state(fn (array $attributes) => [
|
||||||
|
'organization_id' => $organization->getKey(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,7 @@ Route::middleware([
|
|||||||
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');
|
||||||
Route::post('/organizations/{organization}/invitations', [InvitationController::class, 'store'])->name('store');
|
Route::post('/organizations/{organization}/invitations', [InvitationController::class, 'store'])->name('store');
|
||||||
|
Route::delete('/organizations/{organization}/invitations/{invitation}', [InvitationController::class, 'destroy'])->name('destroy');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Project routes
|
// Project routes
|
||||||
|
|||||||
@@ -76,4 +76,52 @@ class InvitationEndpointTest extends ApiEndpointTestAbstract
|
|||||||
$this->assertEquals('test@asdf.at', $invitation->email);
|
$this->assertEquals('test@asdf.at', $invitation->email);
|
||||||
$this->assertEquals('employee', $invitation->role);
|
$this->assertEquals('employee', $invitation->role);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_delete_fails_if_user_has_no_permission_to_remove_invitations(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$data = $this->createUserWithPermission([
|
||||||
|
]);
|
||||||
|
Passport::actingAs($data->user);
|
||||||
|
$invitation = OrganizationInvitation::factory()->forOrganization($data->organization)->create();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->deleteJson(route('api.v1.invitations.destroy', [$data->organization->getKey(), $invitation->getKey()]));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertStatus(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_delete_fails_if_invitation_belongs_to_different_organization(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$data = $this->createUserWithPermission([
|
||||||
|
'invitations:remove',
|
||||||
|
]);
|
||||||
|
Passport::actingAs($data->user);
|
||||||
|
$invitation = OrganizationInvitation::factory()->create();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->deleteJson(route('api.v1.invitations.destroy', [$data->organization->getKey(), $invitation->getKey()]));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertStatus(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_delete_removes_invitation(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$data = $this->createUserWithPermission([
|
||||||
|
'invitations:remove',
|
||||||
|
]);
|
||||||
|
Passport::actingAs($data->user);
|
||||||
|
$invitation = OrganizationInvitation::factory()->forOrganization($data->organization)->create();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->deleteJson(route('api.v1.invitations.destroy', [$data->organization->getKey(), $invitation->getKey()]));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertStatus(204);
|
||||||
|
$this->assertNull(OrganizationInvitation::find($invitation->getKey()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user