Commit 116f3a89 authored by Jacob Priddy's avatar Jacob Priddy 👌
Browse files

Add group events api endpoint and page

parent 5b62745a
......@@ -2,16 +2,19 @@
namespace App\Http\Controllers\Api;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Source\Authorization\Permissions;
use Source\Exceptions\DeleteFailedException;
use Source\UseCases\Groups\GetGroup\GetGroupUseCase;
use Source\UseCases\Groups\GetGroups\GetGroupsUseCase;
use Source\UseCases\Door\ScheduleEvents\EventsPresenter;
use Source\UseCases\Groups\CreateGroup\CreateGroupUseCase;
use Source\UseCases\Groups\DeleteGroup\DeleteGroupUseCase;
use Source\UseCases\Groups\UpdateGroup\UpdateGroupUseCase;
use Source\UseCases\DoorGroup\GetGroupDoors\GetGroupDoorsUseCase;
use Source\UseCases\GroupUser\GetGroupUsers\GetGroupUsersUseCase;
use Source\UseCases\GroupSchedule\GroupEvents\GroupEventsUseCase;
use Source\UseCases\Groups\GetGroup\APIPresenter as GetGroupAPIPresenter;
use Source\UseCases\Groups\GetGroups\APIPresenter as AllGroupsAPIPresenter;
use Source\UseCases\Groups\CreateGroup\APIPresenter as CreateGroupAPIPresenter;
......@@ -239,4 +242,45 @@ class GroupsController extends ApiController
return $this->respondWithData($presenter->getViewModel());
}
/**
* Group Schedule Events
*
* This endpoint retrieves the resulting schedules for a group.
*
* @authenticated
* @urlParam groupId required The group to view events for. Example: 1
* @queryParam start required The start date to view events for. Example: 2020-04-03 12:43:22
* @queryParam end required The end date to see events for. Example: 2020-04-05 12:43:22
*
* @param string $groupId
* @param \Source\UseCases\GroupSchedule\GroupEvents\GroupEventsUseCase $groupEvents
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Validation\ValidationException
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityNotFoundException
* @throws \Exception
*/
public function events(string $groupId, GroupEventsUseCase $groupEvents): JsonResponse
{
$this->authorizer->protectOne([Permissions::MANAGE_GROUPS, Permissions::LOGS_READ]);
$this->validate($this->request, [
'start' => 'required|date|before:end',
'end' => 'required|date|after:start',
]);
// Cannot be null since it was validated up above and is known to exist
$startDate = new Carbon($this->request->input('start'));
$endDate = new Carbon($this->request->input('end'));
$eventsPresenter = new EventsPresenter();
$groupEvents->events(
$groupId,
$startDate,
$endDate,
$eventsPresenter
);
return new JsonResponse($eventsPresenter->getViewModel(), 200);
}
}
......@@ -264,7 +264,7 @@ class SchedulesController extends ApiController
* This endpoint returns all the events in a schedule for a specified date range.
*
* @authenticated
* @urlParam scheduleId required The schedule to get the events for. Example: 2
* @urlParam scheduleId required The schedule to get the events for. Example: 1
* @queryParam start required The start date to view events for. Example: 2020-04-03 12:43:22
* @queryParam end required The end date to see events for. Example: 2020-04-05 12:43:22
*
......
......@@ -131,4 +131,13 @@ class GroupsController extends Controller
return redirect()->back()->with($return);
}
/**
* @param string $scheduleId
* @return \Illuminate\View\View
*/
public function events(string $scheduleId): View
{
return view('admin.entities.groupSchedule', ['id' => $scheduleId]);
}
}
......@@ -285,6 +285,7 @@ return [
Source\UseCases\Overrides\OverrideUpdate\OverrideUpdateUseCaseServiceProvider::class,
// Group Schedule
Source\UseCases\GroupSchedule\GroupEvents\GroupEventsUseCaseServiceProvider::class,
Source\UseCases\GroupSchedule\GetGroupSchedules\GetGroupSchedulesUseCaseServiceProvider::class,
Source\UseCases\GroupSchedule\GetScheduleGroups\GetScheduleGroupsUseCaseServiceProvider::class,
Source\UseCases\GroupSchedule\AddSchedulesToGroups\AddSchedulesToGroupsUseCaseServiceProvider::class,
......
......@@ -26,7 +26,7 @@
initialView: 'timeGridWeek',
nowIndicator: true,
events: {
url: '/api/schedules/events',
url: '{{ route('schedules.events') }}',
method: 'GET',
extraParams: {
door_id: doorId
......
@extends('layouts.admin')
@section('title', 'Group Events')
@section('style')
<link href="{{ asset('vendor/fullcalendar/main.min.css') }}" rel="stylesheet"/>
@endsection
@section('main-content')
<div class="row">
<div class="col-md-8 col-sm-12">
<h1 class="text-center">
Schedule events for group {{ $id }}.
</h1>
</div>
<div class="col-md-4 col-sm-12 text-center">
<a href="{{ url()->previous() }}" class="btn btn-primary btn-lg">
Back
</a>
</div>
</div>
<hr>
<div class="row">
<div class="col-12">
<div id='calendar'></div>
</div>
</div>
@endsection
@section('script')
<script src="{{ asset('vendor/fullcalendar/main.min.js') }}"></script>
<script type="text/javascript">
let calendarEl = document.getElementById('calendar');
if (calendarEl) {
let calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'timeGridWeek',
nowIndicator: true,
events: {
url: '{{ route('groups.events', ['groupId' => $id]) }}',
method: 'GET',
failure: function() {
alert('There was an error while fetching door schedules!');
}
}
});
calendar.render();
}
</script>
@endsection
......@@ -39,7 +39,7 @@
initialView: 'timeGridWeek',
nowIndicator: true,
events: {
url: '/api/schedules/{{ $id }}/events',
url: '{{ route('schedules.event', ['scheduleId' => $id]) }}',
method: 'GET',
failure: function() {
alert('There was an error while fetching door schedules!');
......
......@@ -48,7 +48,7 @@
initialView: 'timeGridWeek',
nowIndicator: true,
events: {
url: '/api/schedules/events',
url: '{{ route('schedules.events') }}',
method: 'GET',
extraParams: {
door_id: doorId,
......
<div class="d-flex flex-row">
<form action="{{ route('web.admin.doors.removeGroup', ['doorId' => $doorId, 'groupId' => $id]) }}" method="POST">
<form action="{{ route('web.admin.doors.removeGroup', ['doorId' => $doorId, 'groupId' => $id]) }}" method="POST">
@csrf
@method('DELETE')
<button class="btn btn-danger" type="submit" data-toggle="tooltip" title="Detach door and group.">
<i class="fas fa-trash"></i>
</button>
</form>
</div>
</form>
<div class="d-flex flex-row">
<a class="btn btn-secondary" href="{{ route('web.admin.doors.groups', ['doorId' => $id]) }}" data-toggle="tooltip" title="Manage groups for the door.">
<a class="btn btn-secondary" href="{{ route('web.admin.doors.groups', ['doorId' => $id]) }}" data-toggle="tooltip" title="Manage groups for the door.">
<i class="fas fa-object-group"></i>
</a>
<a class="btn btn-dark" href="{{ route('web.admin.doors.schedule', ['doorId' => $id]) }}" data-toggle="tooltip" title="View Open Mode Schedule">
</a>
<a class="btn btn-dark" href="{{ route('web.admin.doors.schedule', ['doorId' => $id]) }}" data-toggle="tooltip" title="View Open Mode Schedule">
<i class="fas fa-calendar-alt"></i>
</a>
<a class="btn btn-primary" href="{{ route('web.admin.doors.edit', ['doorId' => $id]) }}" data-toggle="tooltip"
</a>
<a class="btn btn-primary" href="{{ route('web.admin.doors.edit', ['doorId' => $id]) }}" data-toggle="tooltip"
title="Edit">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-warning" href="#" data-toggle="modal" data-target="#regenerate{{ $id }}">
</a>
<a class="btn btn-warning" href="#" data-toggle="modal" data-target="#regenerate{{ $id }}">
<i class="fas fa-cog"></i>
</a>
<a class="btn btn-danger" href="#" data-toggle="modal" data-target="#delete{{$id}}">
</a>
<a class="btn btn-danger" href="#" data-toggle="modal" data-target="#delete{{$id}}">
<i class="fas fa-trash"></i>
</a>
</div>
</a>
<div class="modal fade" id="regenerate{{ $id }}" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document">
......
<div class="d-flex flex-row">
{{-- <a class="btn btn-dark" href="{{ route('web.admin.doors.schedule', ['doorId' => $id]) }}" data-toggle="tooltip" title="View Open Mode Schedule">--}}
{{-- <i class="fas fa-calendar-alt"></i>--}}
{{-- </a>--}}
<a class="btn btn-primary" href="{{ route('web.admin.groups.edit', ['groupId' => $id]) }}" data-toggle="tooltip"
<a class="btn btn-dark" href="{{ route('web.admin.groups.events', ['groupId' => $id]) }}" data-toggle="tooltip" title="View Open Mode Schedule">
<i class="fas fa-calendar-alt"></i>
</a>
<a class="btn btn-primary" href="{{ route('web.admin.groups.edit', ['groupId' => $id]) }}" data-toggle="tooltip"
title="Edit">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger" href="#" data-toggle="modal" data-target="#delete{{$id}}">
</a>
<a class="btn btn-danger" href="#" data-toggle="modal" data-target="#delete{{$id}}">
<i class="fas fa-trash"></i>
</a>
</div>
</a>
<div class="modal fade" id="delete{{ $id }}" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document">
......
<div class="d-flex flex-row">
<a class="btn btn-primary" href="{{ route('web.admin.overrides.edit', ['overrideId' => $id]) }}" data-toggle="tooltip"
<a class="btn btn-primary" href="{{ route('web.admin.overrides.edit', ['overrideId' => $id]) }}" data-toggle="tooltip"
title="Edit">
<i class="fas fa-edit"></i>
</a>
</div>
</a>
<div class="d-flex flex-row">
<a class="btn btn-dark" href="{{ route('web.admin.schedules.events', ['scheduleId' => $id]) }}" data-toggle="tooltip" title="View Schedule Events">
<a class="btn btn-dark" href="{{ route('web.admin.schedules.events', ['scheduleId' => $id]) }}" data-toggle="tooltip" title="View Schedule Events">
<i class="fas fa-calendar-alt"></i>
</a>
<a class="btn btn-primary" href="{{ route('web.admin.schedules.edit', ['scheduleId' => $id]) }}" data-toggle="tooltip"
</a>
<a class="btn btn-primary" href="{{ route('web.admin.schedules.edit', ['scheduleId' => $id]) }}" data-toggle="tooltip"
title="Edit">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger" href="#" data-toggle="modal" data-target="#delete{{$id}}">
</a>
<a class="btn btn-danger" href="#" data-toggle="modal" data-target="#delete{{$id}}">
<i class="fas fa-trash"></i>
</a>
</div>
</a>
<div class="modal fade" id="delete{{ $id }}" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document">
......
<div class="d-flex flex-row">
<a class="btn btn-primary" href="{{ route('web.admin.tokens.edit', ['tokenId' => $id]) }}" data-toggle="tooltip"
<a class="btn btn-primary" href="{{ route('web.admin.tokens.edit', ['tokenId' => $id]) }}" data-toggle="tooltip"
title="Edit">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger" href="#" data-toggle="modal" data-target="#expire{{$id}}">
</a>
<a class="btn btn-danger" href="#" data-toggle="modal" data-target="#expire{{$id}}">
<i class="fas fa-unlink"></i>
</a>
</div>
</a>
<div class="modal fade" id="expire{{ $id }}" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document">
......
<div class="d-flex flex-row">
<a class="btn btn-secondary" href="{{ route('web.admin.entries.index', ['user_id' => $id]) }}" data-toggle="tooltip" title="View the user's door history">
<a class="btn btn-secondary" href="{{ route('web.admin.entries.index', ['user_id' => $id]) }}" data-toggle="tooltip" title="View the user's door history">
<i class="fas fa-history"></i>
</a>
<a class="btn btn-dark" href="{{ route('web.admin.users.access', ['userId' => $id]) }}" data-toggle="tooltip" title="View the user's access schedule">
</a>
<a class="btn btn-dark" href="{{ route('web.admin.users.access', ['userId' => $id]) }}" data-toggle="tooltip" title="View the user's access schedule">
<i class="fas fa-calendar-alt"></i>
</a>
<a class="btn btn-primary" href="{{ route('web.admin.users.edit', ['userId' => $id]) }}" data-toggle="tooltip"
</a>
<a class="btn btn-primary" href="{{ route('web.admin.users.edit', ['userId' => $id]) }}" data-toggle="tooltip"
title="Edit">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger" href="#" data-toggle="modal" data-target="#delete{{$id}}">
</a>
<a class="btn btn-danger" href="#" data-toggle="modal" data-target="#delete{{$id}}">
<i class="fas fa-trash"></i>
</a>
</div>
</a>
<div class="modal fade" id="delete{{ $id }}" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document">
......
......@@ -46,7 +46,9 @@
@endforeach
@if(isset($controls))
<td>
<div class="d-inline-flex justify-content-center flex-row">
@include('partials.controls.' . $controls, ['id' => $row['id']])
</div>
</td>
@endif
</tr>
......
......@@ -55,9 +55,9 @@ Route::group(['middleware' => 'auth:api'], static function () {
Route::put('{groupId}', [GroupsController::class, 'update'])->name('groups.update');
Route::delete('{groupId}', [GroupsController::class, 'delete'])->name('groups.delete');
Route::get('{groupId}/events', [GroupsController::class, 'events'])->name('groups.events');
Route::get('{groupId}/users', [GroupsController::class, 'getUsersForGroup'])->name('groups.users');
Route::get('{groupId}/doors', [GroupsController::class, 'getDoorsForGroup'])->name('groups.doors');
Route::get('{groupId}/schedules', [GroupScheduleController::class, 'schedules'])->name('groups.schedules');
});
Route::group([
......
......@@ -109,6 +109,7 @@ Route::name('web.')->middleware(['auth:api'])->group(static function () {
Route::get('/{groupId}/edit', [GroupsController::class, 'edit'])->name('edit');
Route::put('/{groupId}', [GroupsController::class, 'update'])->name('update');
Route::delete('/{groupId}', [GroupsController::class, 'destroy'])->name('destroy');
Route::get('/{groupId}/events', [GroupsController::class, 'events'])->name('events');
});
Route::name('schedules.')
......
<?php
namespace Source\UseCases\GroupSchedule\GroupEvents;
use Carbon\Carbon;
use InvalidArgumentException;
use Source\Entities\Schedule;
use Source\Entities\ScheduleEvent;
use Source\UseCases\Door\ScheduleEvents\Presenter;
use Source\UseCases\Door\ScheduleEvents\ResponseModel;
use Source\Gateways\GroupSchedule\GroupScheduleRepository;
use Source\Gateways\RecurrenceSet\RecurrenceSetRepository;
class GroupEvents implements GroupEventsUseCase
{
/**
* @var \Source\Gateways\GroupSchedule\GroupScheduleRepository
*/
protected GroupScheduleRepository $groupScheduleRepository;
/**
* @var \Source\Gateways\RecurrenceSet\RecurrenceSetRepository
*/
protected RecurrenceSetRepository $rset;
public function __construct(
RecurrenceSetRepository $rset,
GroupScheduleRepository $groupScheduleRepository
) {
$this->rset = $rset;
$this->groupScheduleRepository = $groupScheduleRepository;
}
/**
* @inheritDoc
*/
public function events(string $groupId, Carbon $begin, Carbon $end, Presenter $presenter): void
{
$response = new ResponseModel();
foreach ($this->groupScheduleRepository->getSchedulesForGroup($groupId) as $schedule) {
try {
$this->rset->parse($schedule->getRset());
} catch (InvalidArgumentException $e) {
continue;
}
$b = $schedule->subDuration($begin);
foreach ($this->rset->occurrencesBetween($b, $end) as $eventStart) {
$eventEnd = $schedule->addDuration($eventStart);
$scheduleEvent = new ScheduleEvent($eventStart, $eventEnd);
if ($schedule->hasTypeOf(Schedule::TYPE_OPEN_MODE)) {
$response->addOpenEvent($scheduleEvent);
} elseif ($schedule->hasTypeOf(Schedule::TYPE_USER_ACCESS)) {
$response->addUserEvent($scheduleEvent);
}
}
}
$presenter->present($response);
}
}
<?php
namespace Source\UseCases\GroupSchedule\GroupEvents;
use Carbon\Carbon;
use Source\UseCases\Door\ScheduleEvents\Presenter;
interface GroupEventsUseCase
{
/**
* Gets events for all schedules attached to a group
*
* @param string $groupId
* @param \Carbon\Carbon $begin
* @param \Carbon\Carbon $end
* @param \Source\UseCases\Door\ScheduleEvents\Presenter $presenter
*/
public function events(string $groupId, Carbon $begin, Carbon $end, Presenter $presenter): void;
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment