mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 00:02:15 +01:00
Added resend invitation endpoint
This commit is contained in:
committed by
Constantin Graf
parent
9973368156
commit
53ba60bbe5
@@ -12,7 +12,9 @@ use App\Models\Organization;
|
||||
use App\Models\OrganizationInvitation;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Laravel\Jetstream\Contracts\InvitesTeamMembers;
|
||||
use Laravel\Jetstream\Mail\TeamInvitation;
|
||||
|
||||
class InvitationController extends Controller
|
||||
{
|
||||
@@ -64,6 +66,22 @@ class InvitationController extends Controller
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resend email for a pending invitation
|
||||
*
|
||||
* @throws AuthorizationException
|
||||
*
|
||||
* @operationId resendInvitationEmail
|
||||
*/
|
||||
public function resend(Organization $organization, OrganizationInvitation $invitation): JsonResponse
|
||||
{
|
||||
$this->checkPermission($organization, 'invitations:resend', $invitation);
|
||||
|
||||
Mail::to($invitation->email)->send(new TeamInvitation($invitation));
|
||||
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a pending invitation
|
||||
*
|
||||
|
||||
@@ -71,6 +71,7 @@ class JetstreamServiceProvider extends ServiceProvider
|
||||
'project-members:update',
|
||||
'project-members:delete',
|
||||
'tasks:view',
|
||||
'tasks:view:all',
|
||||
'tasks:create',
|
||||
'tasks:update',
|
||||
'tasks:delete',
|
||||
@@ -95,6 +96,7 @@ class JetstreamServiceProvider extends ServiceProvider
|
||||
'import',
|
||||
'invitations:view',
|
||||
'invitations:create',
|
||||
'invitations:resend',
|
||||
'invitations:remove',
|
||||
'members:view',
|
||||
'members:invite-placeholder',
|
||||
@@ -114,6 +116,7 @@ class JetstreamServiceProvider extends ServiceProvider
|
||||
'project-members:update',
|
||||
'project-members:delete',
|
||||
'tasks:view',
|
||||
'tasks:view:all',
|
||||
'tasks:create',
|
||||
'tasks:update',
|
||||
'tasks:delete',
|
||||
@@ -136,6 +139,10 @@ class JetstreamServiceProvider extends ServiceProvider
|
||||
'organizations:view',
|
||||
'organizations:update',
|
||||
'import',
|
||||
'invitations:view',
|
||||
'invitations:create',
|
||||
'invitations:resend',
|
||||
'invitations:remove',
|
||||
'members:view',
|
||||
'members:invite-placeholder',
|
||||
])->description('Administrator users can perform any action.');
|
||||
@@ -151,6 +158,7 @@ class JetstreamServiceProvider extends ServiceProvider
|
||||
'project-members:update',
|
||||
'project-members:delete',
|
||||
'tasks:view',
|
||||
'tasks:view:all',
|
||||
'tasks:create',
|
||||
'tasks:update',
|
||||
'tasks:delete',
|
||||
@@ -171,6 +179,7 @@ class JetstreamServiceProvider extends ServiceProvider
|
||||
'clients:update',
|
||||
'clients:delete',
|
||||
'organizations:view',
|
||||
'invitations:view',
|
||||
'members:view',
|
||||
])->description('Managers have the ability to read, create, and update their own time entries as well as those of their team.');
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ Route::middleware([
|
||||
Route::name('invitations.')->group(static function () {
|
||||
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/{invitation}/resend', [InvitationController::class, 'resend'])->name('resend');
|
||||
Route::delete('/organizations/{organization}/invitations/{invitation}', [InvitationController::class, 'destroy'])->name('destroy');
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Tests;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use TiMacDonald\Log\LogFake;
|
||||
|
||||
abstract class TestCase extends BaseTestCase
|
||||
@@ -15,6 +16,7 @@ abstract class TestCase extends BaseTestCase
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Mail::fake();
|
||||
LogFake::bind();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ declare(strict_types=1);
|
||||
namespace Tests\Unit\Endpoint\Api\V1;
|
||||
|
||||
use App\Models\OrganizationInvitation;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Laravel\Jetstream\Mail\TeamInvitation;
|
||||
use Laravel\Passport\Passport;
|
||||
|
||||
class InvitationEndpointTest extends ApiEndpointTestAbstract
|
||||
@@ -77,6 +79,65 @@ class InvitationEndpointTest extends ApiEndpointTestAbstract
|
||||
$this->assertEquals('employee', $invitation->role);
|
||||
}
|
||||
|
||||
public function test_resend_fails_if_user_has_no_permission_to_resend_the_invitation(): void
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission([
|
||||
]);
|
||||
Passport::actingAs($data->user);
|
||||
$invitation = OrganizationInvitation::factory()->forOrganization($data->organization)->create();
|
||||
|
||||
// Act
|
||||
$response = $this->postJson(route('api.v1.invitations.resend', [
|
||||
$data->organization->getKey(),
|
||||
$invitation->getKey(),
|
||||
]));
|
||||
|
||||
// Assert
|
||||
Mail::assertNothingSent();
|
||||
Mail::assertNothingQueued();
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
public function test_resend_fails_if_invitation_belongs_to_different_organization(): void
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission([
|
||||
'invitations:resend',
|
||||
]);
|
||||
Passport::actingAs($data->user);
|
||||
$invitation = OrganizationInvitation::factory()->create();
|
||||
|
||||
// Act
|
||||
$response = $this->postJson(route('api.v1.invitations.resend', [$data->organization->getKey(), $invitation->getKey()]));
|
||||
|
||||
// Assert
|
||||
Mail::assertNothingSent();
|
||||
Mail::assertNothingQueued();
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
public function test_resend_resends_invitation_email(): void
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission([
|
||||
'invitations:resend',
|
||||
]);
|
||||
Passport::actingAs($data->user);
|
||||
$invitation = OrganizationInvitation::factory()->forOrganization($data->organization)->create();
|
||||
|
||||
// Act
|
||||
$response = $this->postJson(route('api.v1.invitations.resend', [
|
||||
$data->organization->getKey(),
|
||||
$invitation->getKey(),
|
||||
]));
|
||||
|
||||
// Assert
|
||||
Mail::assertSent(fn (TeamInvitation $mail): bool => $mail->invitation->is($invitation));
|
||||
Mail::assertNothingQueued();
|
||||
$response->assertStatus(204);
|
||||
}
|
||||
|
||||
public function test_delete_fails_if_user_has_no_permission_to_remove_invitations(): void
|
||||
{
|
||||
// Arrange
|
||||
|
||||
Reference in New Issue
Block a user