diff --git a/laravel/app/Http/Controllers/DepartmentController.php b/laravel/app/Http/Controllers/DepartmentController.php index 82a54d99..f12c9e8a 100644 --- a/laravel/app/Http/Controllers/DepartmentController.php +++ b/laravel/app/Http/Controllers/DepartmentController.php @@ -18,6 +18,7 @@ public function __construct() { $this->middleware('auth'); $this->middleware('bindings'); + $this->middleware('published:department'); } // Display list of departments in an event diff --git a/laravel/app/Http/Controllers/EventController.php b/laravel/app/Http/Controllers/EventController.php index 4109298f..2faa0761 100644 --- a/laravel/app/Http/Controllers/EventController.php +++ b/laravel/app/Http/Controllers/EventController.php @@ -23,6 +23,7 @@ public function __construct() { $this->middleware('auth'); $this->middleware('bindings'); + $this->middleware('published:event'); } // Private function to manage file uploads @@ -281,4 +282,30 @@ public function cloneEvent(Request $request, Event $event) $request->session()->flash('success', 'Event has been cloned.'); return redirect('/event/' . $newEvent->id); } + + /** + * Publish/Unpublish an event + * + * @param Request $request + * @param Event $event targeted event in the query string + * @return Response redirect to the targeted event + */ + public function publish(Request $request, Event $event) + { + $publish = $request->input('publish'); + + // toggle if publish does not exist + if($publish === null) { + $publish = ($event->published_at === null); + } + + if($publish) { // publish + $event->published_at = Carbon::now(); + } else { // unpublish + $event->published_at = null; + } + $event->save(); + + return redirect("/event/{$event->id}"); + } } diff --git a/laravel/app/Http/Controllers/ScheduleController.php b/laravel/app/Http/Controllers/ScheduleController.php index 3e81b528..47ecebc7 100644 --- a/laravel/app/Http/Controllers/ScheduleController.php +++ b/laravel/app/Http/Controllers/ScheduleController.php @@ -22,6 +22,7 @@ public function __construct() { $this->middleware('auth'); $this->middleware('bindings'); + $this->middleware('published:schedule'); } // Helper function to convert form input into database-friendly information diff --git a/laravel/app/Http/Controllers/ShiftController.php b/laravel/app/Http/Controllers/ShiftController.php index 7ea7638d..cf3337fb 100644 --- a/laravel/app/Http/Controllers/ShiftController.php +++ b/laravel/app/Http/Controllers/ShiftController.php @@ -21,6 +21,7 @@ public function __construct() { $this->middleware('auth'); $this->middleware('bindings'); + $this->middleware('published:shift'); } // Display list of shifts in an event diff --git a/laravel/app/Http/Controllers/SlotController.php b/laravel/app/Http/Controllers/SlotController.php index 851a9e4e..31fbb25a 100644 --- a/laravel/app/Http/Controllers/SlotController.php +++ b/laravel/app/Http/Controllers/SlotController.php @@ -24,6 +24,7 @@ public function __construct() { $this->middleware('auth'); $this->middleware('bindings'); + $this->middleware('published:slot'); } // Helper function to determine if an event has passed diff --git a/laravel/app/Http/Kernel.php b/laravel/app/Http/Kernel.php index 78830f4c..32bcf967 100644 --- a/laravel/app/Http/Kernel.php +++ b/laravel/app/Http/Kernel.php @@ -51,5 +51,6 @@ class Kernel extends HttpKernel 'admin' => \App\Http\Middleware\IsAdmin::class, 'lead' => \App\Http\Middleware\IsLead::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, + 'published' => \App\Http\Middleware\CheckPublished::class, ]; } diff --git a/laravel/app/Http/Middleware/CheckPublished.php b/laravel/app/Http/Middleware/CheckPublished.php new file mode 100644 index 00000000..99895a40 --- /dev/null +++ b/laravel/app/Http/Middleware/CheckPublished.php @@ -0,0 +1,82 @@ +auth = $auth; + } + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle($request, Closure $next, $model_name) + { + if($request->{$model_name}) + { + $event = $this->childModelToEvent($request->{$model_name}); + + $is_published = ($event->published_at !== null); + $is_admin= $this->auth->user()->hasRole('admin'); + $is_department_lead = $this->auth->user()->hasRole('department-lead'); + if(!$is_published && !$is_admin && !$is_department_lead) + { + return response('Unauthorized.', 401); + } + } + + return $next($request); + } + + /** + * Get parent event of shift, slot, schedule, or department + * + * @param Model $model descendent model of Event + * @return Event Event or parent Event + */ + public static function childModelToEvent($model) + { + + $model_class = get_class($model); + $event = $model; + if($model_class === Department::class) { + $event = $model->event; + } + if($model_class === Shift::class) { + $event = $model->event; + } + if($model_class === Schedule::class) { + $event = $model->shift->event; + } + if($model_class === Slot::class) { + $event = $model->schedule->shift->event; + } + return $event; + } +} diff --git a/laravel/app/Http/routes.php b/laravel/app/Http/routes.php index 100d06c0..3ad9a541 100644 --- a/laravel/app/Http/routes.php +++ b/laravel/app/Http/routes.php @@ -39,13 +39,14 @@ Route::post('/event/{event}/edit', 'EventController@edit'); Route::get('/event/{event}/delete', 'EventController@deleteForm'); -Route::post('/event/{event}/delete', 'EventController@delete'); +Route::post('/event{event}/delete', 'EventController@delete'); Route::get('/event/{event}/clone', 'EventController@cloneForm'); Route::post('/event/{event}/clone', 'EventController@cloneEvent'); Route::get('/event/{event}', 'EventController@view'); +Route::post('/event/{event}/publish', 'EventController@publish'); // Department routes Route::get('/event/{event}/departments', 'DepartmentController@listDepartments'); diff --git a/laravel/app/Providers/AuthServiceProvider.php b/laravel/app/Providers/AuthServiceProvider.php index 4cdba34f..8f0903e6 100644 --- a/laravel/app/Providers/AuthServiceProvider.php +++ b/laravel/app/Providers/AuthServiceProvider.php @@ -35,6 +35,7 @@ class AuthServiceProvider extends ServiceProvider 'create-event', 'edit-event', 'delete-event', + 'publish-event', 'read-department', 'create-department', 'edit-department', @@ -52,6 +53,7 @@ class AuthServiceProvider extends ServiceProvider 'department-lead' => [ + 'publish-event', 'create-schedule', 'edit-schedule', 'delete-schedule', diff --git a/laravel/database/migrations/2019_08_26_124054_add_published_at_to_events_table.php b/laravel/database/migrations/2019_08_26_124054_add_published_at_to_events_table.php new file mode 100644 index 00000000..3c80f319 --- /dev/null +++ b/laravel/database/migrations/2019_08_26_124054_add_published_at_to_events_table.php @@ -0,0 +1,32 @@ +timestamp('published_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('events', function (Blueprint $table) { + $table->drop('published_at'); + }); + } +} diff --git a/laravel/resources/views/pages/dashboard.blade.php b/laravel/resources/views/pages/dashboard.blade.php index 04d51bb2..66792d46 100644 --- a/laravel/resources/views/pages/dashboard.blade.php +++ b/laravel/resources/views/pages/dashboard.blade.php @@ -41,14 +41,21 @@
@foreach($future as $event) -

- @if($event->featured) - - @endif + getRoleNames(); + $is_lead = in_array('department-lead', $user_roles); + $is_admin = in_array('admin', $user_roles); + ?> + @if($event->published_at !== null || $is_lead || $is_admin) +

+ @if($event->featured) + + @endif - {{ $event->name }} - from {{ $event->start_date }} until {{ $event->end_date }} -

+ {{ $event->name }} + from {{ $event->start_date }} until {{ $event->end_date }} +

+ @endif @endforeach
diff --git a/laravel/resources/views/pages/event/view.blade.php b/laravel/resources/views/pages/event/view.blade.php index 79cffbee..d0fb20fb 100644 --- a/laravel/resources/views/pages/event/view.blade.php +++ b/laravel/resources/views/pages/event/view.blade.php @@ -3,6 +3,19 @@ @section('content')
+ @can('publish-event') + + {!! Form::open(['url' => "/event/{$event->id}/publish", 'method' => 'post']) !!} + published_at === null); + $publish_text = ($unpublished) ? 'Publish Event' : 'Unpublish Event'; + ?> + + + {!! Form::close() !!} + + @endcan + @can('create-event') Clone Event @endcan diff --git a/laravel/tests/Feature/CheckPublishedTest.php b/laravel/tests/Feature/CheckPublishedTest.php new file mode 100644 index 00000000..cd285f21 --- /dev/null +++ b/laravel/tests/Feature/CheckPublishedTest.php @@ -0,0 +1,54 @@ +states('admin')->create(); + + $admin->data()->save(factory(UserData::class)->make()); + $slot = factory(Slot::class)->create(); + + // When + $response = $this->actingAs($admin)->get("/slot/{$slot->id}/view"); + + // Then + $response->assertStatus(200); + } + + /** + * @test + * + * @return void + */ + public function unpublished_event_not_viewable_by_volunteer() + { + // Given + $volunteer = factory(User::class)->create(); + $slot = factory(Slot::class)->create(); + + // When + $response = $this->actingAs($volunteer)->get("/slot/{$slot->id}/view"); + + // Then + $response->assertStatus(401); + } +} diff --git a/laravel/tests/Feature/SlotControllerTest.php b/laravel/tests/Feature/SlotControllerTest.php index 225284a2..29ba96f9 100644 --- a/laravel/tests/Feature/SlotControllerTest.php +++ b/laravel/tests/Feature/SlotControllerTest.php @@ -10,6 +10,8 @@ class SlotControllerTest extends TestCase { + use RefreshDatabase; + /** * @test * @return void diff --git a/laravel/tests/Unit/CheckPublishedTest.php b/laravel/tests/Unit/CheckPublishedTest.php new file mode 100644 index 00000000..810104d0 --- /dev/null +++ b/laravel/tests/Unit/CheckPublishedTest.php @@ -0,0 +1,33 @@ +create(); + + // When + $event = CheckPublished::childModelToEvent($slot); + + // Then + $this->assertEquals(Event::class, get_class($event)); + } +}