From bddf4e5c0908a2b2a1048ed8924aadbb1125784c Mon Sep 17 00:00:00 2001 From: Romulus21 Date: Fri, 21 Aug 2020 11:37:52 +0200 Subject: [PATCH] add guest invitation read & valide with route --- app/Http/Controllers/EventController.php | 36 ++++++ app/Http/Kernel.php | 1 + app/Http/Middleware/EventGuestWithEmail.php | 32 ++++++ app/Http/Resources/Event.php | 4 + app/Http/Resources/EventGuestWithoutEmail.php | 23 ++++ app/Models/Event.php | 6 + app/Models/EventGuestsNonUsers.php | 12 ++ database/factories/EventFactory.php | 2 +- ...741_create_event_non_user_guests_table.php | 46 ++++++++ routes/api.php | 6 + tests/Feature/EventsTest.php | 107 +++++++++++++++++- tests/Feature/MemosTest.php | 2 +- 12 files changed, 271 insertions(+), 6 deletions(-) create mode 100644 app/Http/Middleware/EventGuestWithEmail.php create mode 100644 app/Http/Resources/EventGuestWithoutEmail.php create mode 100644 app/Models/EventGuestsNonUsers.php create mode 100644 database/migrations/2020_08_20_131741_create_event_non_user_guests_table.php diff --git a/app/Http/Controllers/EventController.php b/app/Http/Controllers/EventController.php index 3bcd95d..32ed6f6 100644 --- a/app/Http/Controllers/EventController.php +++ b/app/Http/Controllers/EventController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers; use App\Http\Requests\EventRequest; use App\Models\Event; use App\Http\Resources\Event as EventResource; +use App\Models\EventGuestsNonUsers; use App\User; use Illuminate\Http\Request; @@ -150,4 +151,39 @@ class EventController extends Controller return response([], 204); } + + public function addGuestWithEmail(Event $event) + { + $data = request()->validate([ + 'email' => 'required|email', + ]); + + $event->emailedGuests()->save(new EventGuestsNonUsers(['email' => $data['email']])); + + return (new EventResource($event)) + ->response() + ->setStatusCode(201); + } + + public function guestCanReadEvent(Event $event) + { + $guest = request()->guest; + + if (!$guest->read_at) { + $guest->update(['read_at' => now()->toDateTimeString()]); + } + return (new EventResource($event)) + ->response() + ->setStatusCode(200); + } + + public function guestCanConfirmEvent(Event $event) + { + $guest = request()->guest; + $guest->update(['validated_at' => now()->toDateTimeString()]); + + return (new EventResource($event)) + ->response() + ->setStatusCode(200); + } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index a6af78b..6ca3a96 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -63,5 +63,6 @@ class Kernel extends HttpKernel 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + 'guest-mail' => \App\Http\Middleware\EventGuestWithEmail::class, ]; } diff --git a/app/Http/Middleware/EventGuestWithEmail.php b/app/Http/Middleware/EventGuestWithEmail.php new file mode 100644 index 0000000..459cf18 --- /dev/null +++ b/app/Http/Middleware/EventGuestWithEmail.php @@ -0,0 +1,32 @@ +validate([ + 'email' => 'required|email', + 'token' => 'required|uuid', + ]); + + $guest = EventGuestsNonUsers::where('email', $data['email'])->where('token', $data['token'])->first(); + if ($guest) { + $request->guest = $guest; + return $next($request); + } + + return response([], 403); + } +} diff --git a/app/Http/Resources/Event.php b/app/Http/Resources/Event.php index 0e20697..9522499 100644 --- a/app/Http/Resources/Event.php +++ b/app/Http/Resources/Event.php @@ -3,6 +3,7 @@ namespace App\Http\Resources; use App\Http\Resources\User as UserResource; +use App\Http\Resources\EventGuestWithoutEmail as GuestsWithoutEmailResource; use Illuminate\Http\Resources\Json\JsonResource; class Event extends JsonResource @@ -32,6 +33,9 @@ class Event extends JsonResource ], ], 'invitations' => UserResource::collection($this->guests), + 'invitations-with-email' => [ + 'data' => GuestsWithoutEmailResource::collection($this->emailedGuests) + ], ] ], ], diff --git a/app/Http/Resources/EventGuestWithoutEmail.php b/app/Http/Resources/EventGuestWithoutEmail.php new file mode 100644 index 0000000..1206d56 --- /dev/null +++ b/app/Http/Resources/EventGuestWithoutEmail.php @@ -0,0 +1,23 @@ + $this->email, + 'read_at' => $this->read_at, + 'validated_at' => $this->validated_at, + ]; + } +} diff --git a/app/Models/Event.php b/app/Models/Event.php index 338235f..7d037d6 100644 --- a/app/Models/Event.php +++ b/app/Models/Event.php @@ -5,6 +5,7 @@ namespace App\Models; use App\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\HasMany; class Event extends Model { @@ -16,4 +17,9 @@ class Event extends Model ->withPivot('is_staff', 'validated_at') ->withTimestamps(); } + + public function emailedGuests() :HasMany + { + return $this->hasMany(EventGuestsNonUsers::class, 'event_id'); + } } diff --git a/app/Models/EventGuestsNonUsers.php b/app/Models/EventGuestsNonUsers.php new file mode 100644 index 0000000..d418e06 --- /dev/null +++ b/app/Models/EventGuestsNonUsers.php @@ -0,0 +1,12 @@ +define(Event::class, function (Faker $faker) { 'category_id' => factory(EventCategory::class), 'name' => $faker->words(3, [false]), 'description' => $faker->words(rand(10, 300), [false]), - 'private' => rand(0,1), + 'private' => !(rand(0,1)), 'start_date' => $startDate, 'end_date' => $endDate, 'location' => $faker->city, diff --git a/database/migrations/2020_08_20_131741_create_event_non_user_guests_table.php b/database/migrations/2020_08_20_131741_create_event_non_user_guests_table.php new file mode 100644 index 0000000..98ad33b --- /dev/null +++ b/database/migrations/2020_08_20_131741_create_event_non_user_guests_table.php @@ -0,0 +1,46 @@ +id(); + $table->foreignId('event_id'); + $table->string('email'); + $table->uuid('token')->default(\Illuminate\Support\Str::uuid()); + $table->timestamp('read_at')->nullable(); + $table->timestamp('validated_at')->nullable(); + $table->timestamps(); + + $table->foreign('event_id') + ->references('id') + ->on('events') + ->onDelete('restrict') + ->onUpdate('restrict'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('event_non_user_guests', function (Blueprint $table) { + $table->dropForeign(['event_id']); + }); + + Schema::dropIfExists('event_non_user_guests'); + } +} diff --git a/routes/api.php b/routes/api.php index 62427fc..c53b141 100644 --- a/routes/api.php +++ b/routes/api.php @@ -14,6 +14,11 @@ use Illuminate\Support\Facades\Route; | */ +Route::middleware('guest-mail')->group(function () { + Route::get('/events/{event}/with-email', 'EventController@guestCanReadEvent'); + Route::patch('/events/{event}/with-email', 'EventController@guestCanConfirmEvent'); +}); + Route::middleware('auth:api')->group(function () { Route::get('auth-user', 'AuthUserController@show'); @@ -22,6 +27,7 @@ Route::middleware('auth:api')->group(function () { Route::delete('/events/{event}/invite/delete', 'EventController@userDeleteInvitation'); Route::delete('/events/{event}/invite/validation', 'EventController@userConfirmParticipation'); + Route::post('/events/{event}/invite/with-email', 'EventController@addGuestWithEmail'); Route::post('/events/{event}/invite/{user}', 'EventController@inviteUser'); Route::delete('/events/{event}/invite/{user}', 'EventController@removeInviteUser'); Route::post('/events/{event}/staff/{user}', 'EventController@addGuestToStaffEvent'); diff --git a/tests/Feature/EventsTest.php b/tests/Feature/EventsTest.php index d44cad0..e217a81 100644 --- a/tests/Feature/EventsTest.php +++ b/tests/Feature/EventsTest.php @@ -4,11 +4,10 @@ namespace Tests\Feature; use App\Models\Event; use App\Models\EventCategory; -use App\Models\Memo; +use App\Models\EventGuestsNonUsers; use App\User; use Illuminate\Foundation\Testing\RefreshDatabase; -use Illuminate\Foundation\Testing\WithFaker; -use Symfony\Component\HttpFoundation\Response; +use Illuminate\Support\Str; use Tests\TestCase; class EventsTest extends TestCase @@ -641,7 +640,107 @@ class EventsTest extends TestCase $this->assertCount(0, $event->guests); } - // owner_can_invite_a_non_user_with_an_email + /** @test */ + public function owner_or_staff_can_invite_a_non_user_with_an_email() + { + $this->withoutExceptionHandling(); + $this->actingAs($user = factory(User::class)->create(), 'api'); + $event = factory(Event::class)->create(['user_id' => $user->id, 'private' => false]); + + $response = $this->post('api/events/'.$event->id.'/invite/with-email', ['email' => 'test@mail.fr']); + + $event = $event->fresh(); + + $this->assertCount(1, $event->emailedGuests); + $response->assertJson([ + 'data' => [ + 'event_id' => $event->id, + 'attributes' => [ + 'data' => [ + 'invitations-with-email' => [ + 'data' => [ + [ + 'email' => 'test@mail.fr', + 'read_at' => null, + 'validated_at' => null, + ] + ], + ], + ] + ] + ] + ]); + } + + /** @test */ + public function a_guest_with_an_email_can_read_event() + { + $user = factory(User::class)->create(); + $event = factory(Event::class)->create(['user_id' => $user->id, 'private' => false]); + $token = (string) Str::uuid(); + $guest = new EventGuestsNonUsers(['event_id' => $event->id, 'email' => 'test@mail.fr', 'token' => $token]); + $guest->save(); + + $response = $this->get('api/events/'.$event->id.'/with-email?email=test@mail.fr&token='.$token); + + $response->assertStatus(200); + $this->assertCount(0, $event->guests); + $this->assertCount(1, $event->emailedGuests); + $response->assertJson([ + 'data' => [ + 'event_id' => $event->id, + 'attributes' => [ + 'data' => [ + 'invitations-with-email' => [ + 'data' => [ + [ + 'email' => 'test@mail.fr', + 'read_at' => now()->toDateTimeString(), + 'validated_at' => null, + ] + ], + ], + ] + ] + ] + ]); + } + + /** @test */ + public function a_guest_with_an_email_can_confirm_participation() + { + $this->withoutExceptionHandling(); + $user = factory(User::class)->create(); + $event = factory(Event::class)->create(['user_id' => $user->id, 'private' => false]); + $token = (string) Str::uuid(); + $guest = new EventGuestsNonUsers(['event_id' => $event->id, 'email' => 'test@mail.fr', 'token' => $token]); + $guest->save(); + + $response = $this->patch('api/events/'.$event->id.'/with-email', [ 'email' => 'test@mail.fr', 'token' => $token ]); + + $response->assertStatus(200); + $this->assertCount(1, $event->emailedGuests); + $response->assertJson([ + 'data' => [ + 'event_id' => $event->id, + 'attributes' => [ + 'data' => [ + 'invitations-with-email' => [ + 'data' => [ + [ + 'validated_at' => now()->toDateTimeString(), + ] + ], + ], + ] + ] + ] + ]); + } + + // an_invitation_mail_is_seed_to_new_guest + + // a_guest_can_confirm_participation_in_mail private function data() { diff --git a/tests/Feature/MemosTest.php b/tests/Feature/MemosTest.php index a2334e0..31b9949 100644 --- a/tests/Feature/MemosTest.php +++ b/tests/Feature/MemosTest.php @@ -34,7 +34,7 @@ class MemosTest extends TestCase $memo = Memo::first(); -// $this->assertCount(1, Contact::all()); + $this->assertCount(1, Memo::all()); $this->assertEquals('Test Name', $memo->name); $this->assertEquals('Test Memo', $memo->memo);