From 1c0b1a24e4591c4092c3a85716b56c7c8ea82f14 Mon Sep 17 00:00:00 2001 From: Sebastian Marcet Date: Mon, 15 Jan 2018 11:35:25 -0300 Subject: [PATCH] Added new endpoint add attendee by summit POST /api/v1/summits/{id}/attendees payload * member_id ( int | required ) * share_contact_info ( boolean | optional ) * summit_hall_checked_in ( boolean | optional ) * summit_hall_checked_in_date ( epoch | optional ) Change-Id: Ie71d33941f8cc2dbe74446eefc747e306025f17f --- .../OAuth2SummitAttendeesApiController.php | 83 ++++++++++++++++-- app/Http/Middleware/UserAuthEndpoint.php | 10 +-- app/Http/routes.php | 1 + .../Summit/Attendees/SummitAttendee.php | 17 +++- .../Factories/SummitAttendeeFactory.php | 49 +++++++++++ .../ISummitAttendeeRepository.php | 10 ++- .../DoctrineSummitAttendeeRepository.php | 23 +++++ app/Security/SummitScopes.php | 17 ++-- app/Services/Model/AttendeeService.php | 87 +++++++++++++++++++ app/Services/Model/IAttendeeService.php | 28 ++++++ app/Services/ServicesProvider.php | 8 ++ database/seeds/ApiEndpointsSeeder.php | 8 ++ database/seeds/ApiScopesSeeder.php | 7 +- tests/OAuth2AttendeesApiTest.php | 31 +++++++ tests/ProtectedApiTest.php | 2 + 15 files changed, 352 insertions(+), 29 deletions(-) create mode 100644 app/Models/Foundation/Summit/Factories/SummitAttendeeFactory.php create mode 100644 app/Services/Model/AttendeeService.php create mode 100644 app/Services/Model/IAttendeeService.php diff --git a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitAttendeesApiController.php b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitAttendeesApiController.php index c89c9dfd..ff313559 100644 --- a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitAttendeesApiController.php +++ b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitAttendeesApiController.php @@ -11,6 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ +use App\Services\Model\IAttendeeService; use Exception; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Request; @@ -39,7 +40,12 @@ final class OAuth2SummitAttendeesApiController extends OAuth2ProtectedController /** * @var ISummitService */ - private $service; + private $summit_service; + + /** + * @var IAttendeeService + */ + private $attendee_service; /** * @var ISpeakerRepository @@ -61,7 +67,17 @@ final class OAuth2SummitAttendeesApiController extends OAuth2ProtectedController */ private $attendee_repository; - + /** + * OAuth2SummitAttendeesApiController constructor. + * @param ISummitAttendeeRepository $attendee_repository + * @param ISummitRepository $summit_repository + * @param ISummitEventRepository $event_repository + * @param ISpeakerRepository $speaker_repository + * @param IEventFeedbackRepository $event_feedback_repository + * @param ISummitService $summit_service + * @param IAttendeeService $attendee_service + * @param IResourceServerContext $resource_server_context + */ public function __construct ( ISummitAttendeeRepository $attendee_repository, @@ -69,7 +85,8 @@ final class OAuth2SummitAttendeesApiController extends OAuth2ProtectedController ISummitEventRepository $event_repository, ISpeakerRepository $speaker_repository, IEventFeedbackRepository $event_feedback_repository, - ISummitService $service, + ISummitService $summit_service, + IAttendeeService $attendee_service, IResourceServerContext $resource_server_context ) { parent::__construct($resource_server_context); @@ -78,7 +95,8 @@ final class OAuth2SummitAttendeesApiController extends OAuth2ProtectedController $this->speaker_repository = $speaker_repository; $this->event_repository = $event_repository; $this->event_feedback_repository = $event_feedback_repository; - $this->service = $service; + $this->summit_service = $summit_service; + $this->attendee_service = $attendee_service; } /** @@ -192,7 +210,7 @@ final class OAuth2SummitAttendeesApiController extends OAuth2ProtectedController $attendee = CheckAttendeeStrategyFactory::build(CheckAttendeeStrategyFactory::Own, $this->resource_server_context)->check($attendee_id, $summit); if (is_null($attendee)) return $this->error404(); - $this->service->addEventToMemberSchedule($summit, $attendee->getMember(), intval($event_id)); + $this->summit_service->addEventToMemberSchedule($summit, $attendee->getMember(), intval($event_id)); return $this->created(); } @@ -234,7 +252,7 @@ final class OAuth2SummitAttendeesApiController extends OAuth2ProtectedController $attendee = CheckAttendeeStrategyFactory::build(CheckAttendeeStrategyFactory::Own, $this->resource_server_context)->check($attendee_id, $summit); if (is_null($attendee)) return $this->error404(); - $this->service->removeEventFromMemberSchedule($summit, $attendee->getMember(), intval($event_id)); + $this->summit_service->removeEventFromMemberSchedule($summit, $attendee->getMember(), intval($event_id)); return $this->deleted(); @@ -282,7 +300,7 @@ final class OAuth2SummitAttendeesApiController extends OAuth2ProtectedController $attendee = CheckAttendeeStrategyFactory::build(CheckAttendeeStrategyFactory::Own, $this->resource_server_context)->check($attendee_id, $summit); if (is_null($attendee)) return $this->error404(); - $this->service->unRSVPEvent($summit, $attendee->getMember(), $event_id); + $this->summit_service->unRSVPEvent($summit, $attendee->getMember(), $event_id); return $this->deleted(); @@ -404,4 +422,55 @@ final class OAuth2SummitAttendeesApiController extends OAuth2ProtectedController } } + /** + * @param int $summit_id + * @return mixed + */ + public function addAttendee($summit_id){ + try { + if(!Request::isJson()) return $this->error403(); + $data = Input::json(); + + $summit = SummitFinderStrategyFactory::build($this->repository, $this->resource_server_context)->find($summit_id); + if (is_null($summit)) return $this->error404(); + + $rules = [ + + 'member_id' => 'required|integer', + 'share_contact_info' => 'sometimes|boolean', + 'summit_hall_checked_in' => 'sometimes|boolean', + 'summit_hall_checked_in_date' => 'sometimes|date_format:U', + ]; + + // Creates a Validator instance and validates the data. + $validation = Validator::make($data->all(), $rules); + + if ($validation->fails()) { + $messages = $validation->messages()->toArray(); + + return $this->error412 + ( + $messages + ); + } + + $attendee = $this->attendee_service->addAttendee($summit, $data->all()); + + return $this->created(SerializerRegistry::getInstance()->getSerializer($attendee)->serialize()); + } + catch (ValidationException $ex1) { + Log::warning($ex1); + return $this->error412(array($ex1->getMessage())); + } + catch(EntityNotFoundException $ex2) + { + Log::warning($ex2); + return $this->error404(array('message'=> $ex2->getMessage())); + } + catch (Exception $ex) { + Log::error($ex); + return $this->error500($ex); + } + } + } \ No newline at end of file diff --git a/app/Http/Middleware/UserAuthEndpoint.php b/app/Http/Middleware/UserAuthEndpoint.php index acf11fae..839981d8 100644 --- a/app/Http/Middleware/UserAuthEndpoint.php +++ b/app/Http/Middleware/UserAuthEndpoint.php @@ -55,17 +55,11 @@ final class UserAuthEndpoint $http_response = Response::json(['error' => 'member not found'], 403); return $http_response; } - - $groups = $member->getGroups(); - $required_groups = explode('|', $required_groups); foreach ($required_groups as $required_group) { - foreach ($groups as $member_group){ - if (strtolower($required_group) == strtolower($member_group->getCode())) { - return $next($request); - } - } + if($member->isOnGroup($required_group)) + return $next($request); } $http_response = Response::json(['error' => 'unauthorized member'], 403); diff --git a/app/Http/routes.php b/app/Http/routes.php index 4535f178..dffa74ed 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -155,6 +155,7 @@ Route::group([ Route::get('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitAttendeesApiController@getAttendeesBySummit']); Route::get('me', 'OAuth2SummitAttendeesApiController@getOwnAttendee'); + Route::post('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitAttendeesApiController@addAttendee']); Route::group(array('prefix' => '{attendee_id}'), function () { Route::get('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitAttendeesApiController@getAttendee']); diff --git a/app/Models/Foundation/Summit/Attendees/SummitAttendee.php b/app/Models/Foundation/Summit/Attendees/SummitAttendee.php index 15a16819..53d740ed 100644 --- a/app/Models/Foundation/Summit/Attendees/SummitAttendee.php +++ b/app/Models/Foundation/Summit/Attendees/SummitAttendee.php @@ -11,7 +11,6 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ - use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Mapping AS ORM; use Doctrine\Common\Collections\ArrayCollection; @@ -20,7 +19,6 @@ use models\exceptions\ValidationException; use models\main\Member; use models\main\SummitMemberSchedule; use models\utils\SilverstripeBaseModel; - /** * @ORM\Entity * @ORM\Table(name="SummitAttendee") @@ -70,6 +68,20 @@ class SummitAttendee extends SilverstripeBaseModel return (bool)$this->summit_hall_checked_in; } + /** + * @param bool $summit_hall_checked_in + */ + public function setSummitHallCheckedIn($summit_hall_checked_in){ + $this->summit_hall_checked_in = $summit_hall_checked_in; + } + + /** + * @param \DateTime $summit_hall_checked_in_date + */ + public function setSummitHallCheckedInDate(\DateTime $summit_hall_checked_in_date){ + $this->summit_hall_checked_in_date = $summit_hall_checked_in_date; + } + /** * @return boolean */ @@ -225,4 +237,5 @@ class SummitAttendee extends SilverstripeBaseModel return $this->member->getRsvpByEvent($event_id); } + } \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Factories/SummitAttendeeFactory.php b/app/Models/Foundation/Summit/Factories/SummitAttendeeFactory.php new file mode 100644 index 00000000..44b79bc4 --- /dev/null +++ b/app/Models/Foundation/Summit/Factories/SummitAttendeeFactory.php @@ -0,0 +1,49 @@ +setMember($member); + $attendee->setSummit($summit); + + if(isset($data['share_contact_info'])) + $attendee->setShareContactInfo($data['share_contact_info']); + + if(isset($data['summit_hall_checked_in'])) + $attendee->setSummitHallCheckedIn($data['summit_hall_checked_in']); + + if(isset($data['summit_hall_checked_in_date'])) + $attendee->setSummitHallCheckedInDate + ( + new \DateTime(intval($data['summit_hall_checked_in_date'])) + ); + + return $attendee; + } +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Repositories/ISummitAttendeeRepository.php b/app/Models/Foundation/Summit/Repositories/ISummitAttendeeRepository.php index a39cda9e..3f08027c 100644 --- a/app/Models/Foundation/Summit/Repositories/ISummitAttendeeRepository.php +++ b/app/Models/Foundation/Summit/Repositories/ISummitAttendeeRepository.php @@ -11,13 +11,12 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ - +use models\main\Member; use models\utils\IBaseRepository; use utils\Filter; use utils\Order; use utils\PagingInfo; use utils\PagingResponse; - /** * Class ISummitAttendeeRepository * @package models\summit @@ -32,4 +31,11 @@ interface ISummitAttendeeRepository extends IBaseRepository * @return PagingResponse */ public function getBySummit(Summit $summit, PagingInfo $paging_info, Filter $filter = null, Order $order = null); + + /** + * @param Summit $summit + * @param Member $member + * @return SummitAttendee + */ + public function getBySummitAndMember(Summit $summit, Member $member); } \ No newline at end of file diff --git a/app/Repositories/Summit/DoctrineSummitAttendeeRepository.php b/app/Repositories/Summit/DoctrineSummitAttendeeRepository.php index 622cf99f..8ef92416 100644 --- a/app/Repositories/Summit/DoctrineSummitAttendeeRepository.php +++ b/app/Repositories/Summit/DoctrineSummitAttendeeRepository.php @@ -12,6 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ +use models\main\Member; use models\summit\ISummitAttendeeRepository; use models\summit\Summit; use models\summit\SummitAttendee; @@ -130,4 +131,26 @@ final class DoctrineSummitAttendeeRepository $data ); } + + /** + * @param Summit $summit + * @param Member $member + * @return SummitAttendee + */ + public function getBySummitAndMember(Summit $summit, Member $member) + { + $query = $this->getEntityManager() + ->createQueryBuilder() + ->select("a") + ->from(SummitAttendee::class, "a") + ->leftJoin('a.summit', 's') + ->leftJoin('a.member', 'm') + ->where("s.id = :summit_id")->andWhere("m.id = :member_id") + ->setParameter("summit_id", $summit->getId()) + ->setParameter("member_id", $member->getId()); + + $res = $query->getQuery()->getOneOrNullResult(); + + return $res; + } } \ No newline at end of file diff --git a/app/Security/SummitScopes.php b/app/Security/SummitScopes.php index 03f0fe2e..7ce84b92 100644 --- a/app/Security/SummitScopes.php +++ b/app/Security/SummitScopes.php @@ -12,20 +12,21 @@ * limitations under the License. **/ - /** * Class SummitScopes * @package App\Security */ final class SummitScopes { - const ReadSummitData = '%s/summits/read'; - const ReadAllSummitData = '%s/summits/read/all'; + const ReadSummitData = '%s/summits/read'; + const ReadAllSummitData = '%s/summits/read/all'; - const WriteSummitData = '%s/summits/write'; - const WriteSpeakersData = '%s/speakers/write'; + const WriteSummitData = '%s/summits/write'; + const WriteSpeakersData = '%s/speakers/write'; - const PublishEventData = '%s/summits/publish-event'; - const WriteEventData = '%s/summits/write-event'; - const WriteVideoData = '%s/summits/write-videos'; + const PublishEventData = '%s/summits/publish-event'; + const WriteEventData = '%s/summits/write-event'; + const WriteVideoData = '%s/summits/write-videos'; + + const WriteAttendeesData = '%s/attendees/write'; } \ No newline at end of file diff --git a/app/Services/Model/AttendeeService.php b/app/Services/Model/AttendeeService.php new file mode 100644 index 00000000..bb700355 --- /dev/null +++ b/app/Services/Model/AttendeeService.php @@ -0,0 +1,87 @@ +attendee_repository = $attendee_repository; + $this->member_repository = $member_repository; + $this->tx_service = $tx_service; + } + + /** + * @param Summit $summit + * @param array $data + * @return SummitAttendee + */ + public function addAttendee(Summit $summit, array $data) + { + return $this->tx_service->transaction(function() use($summit, $data){ + + if(!isset($data['member_id'])) + throw new ValidationException("member_id is required"); + + $member_id = intval($data['member_id']); + $member = $this->member_repository->getById($member_id); + + if(is_null($member)) + throw new EntityNotFoundException("member not found"); + + // check if attendee already exist for this summit + + $old_attendee = $this->attendee_repository->getBySummitAndMember($summit, $member); + if(!is_null($old_attendee)) + throw new ValidationException(sprintf("attendee already exist for summit id %s and member id %s", $summit->getId(), $member->getIdentifier())); + + $attendee = SummitAttendeeFactory::build($summit, $member, $data); + + $this->attendee_repository->add($attendee); + + return $attendee; + }); + } +} \ No newline at end of file diff --git a/app/Services/Model/IAttendeeService.php b/app/Services/Model/IAttendeeService.php new file mode 100644 index 00000000..8fabcd96 --- /dev/null +++ b/app/Services/Model/IAttendeeService.php @@ -0,0 +1,28 @@ + 'PUT', 'scopes' => [sprintf(SummitScopes::WriteSummitData, $current_realm)], ), + array( + 'name' => 'add-attendee', + 'route' => '/api/v1/summits/{id}/attendees', + 'http_method' => 'POST', + 'scopes' => [ + sprintf(SummitScopes::WriteAttendeesData, $current_realm), + ], + ), // speakers array( 'name' => 'get-speakers', diff --git a/database/seeds/ApiScopesSeeder.php b/database/seeds/ApiScopesSeeder.php index 67c3635a..73ff0f97 100644 --- a/database/seeds/ApiScopesSeeder.php +++ b/database/seeds/ApiScopesSeeder.php @@ -11,14 +11,12 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ - use Illuminate\Database\Seeder; use Illuminate\Support\Facades\Config;; use App\Models\ResourceServer\ApiScope; use LaravelDoctrine\ORM\Facades\EntityManager; use Illuminate\Support\Facades\DB; use App\Security\SummitScopes; - /** * Class ApiScopesSeeder */ @@ -115,6 +113,11 @@ final class ApiScopesSeeder extends Seeder 'short_description' => 'Write Speakers Data', 'description' => 'Grants write access for Speakers Data', ), + array( + 'name' => sprintf(SummitScopes::WriteAttendeesData, $current_realm), + 'short_description' => 'Write Attendees Data', + 'description' => 'Grants write access for Attendees Data', + ), ]; foreach ($scopes as $scope_info) { diff --git a/tests/OAuth2AttendeesApiTest.php b/tests/OAuth2AttendeesApiTest.php index a0fa2391..f4c071b3 100644 --- a/tests/OAuth2AttendeesApiTest.php +++ b/tests/OAuth2AttendeesApiTest.php @@ -135,4 +135,35 @@ class OAuth2AttendeesApiTest extends ProtectedApiTest $attendees = json_decode($content); $this->assertTrue(!is_null($attendees)); } + + public function testAddAttendee(){ + $params = [ + 'id' => 23, + ]; + + $data = [ + 'member_id' => 1 + ]; + + $headers = [ + "HTTP_Authorization" => " Bearer " . $this->access_token, + "CONTENT_TYPE" => "application/json" + ]; + + $response = $this->action( + "POST", + "OAuth2SummitAttendeesApiController@addAttendee", + $params, + [], + [], + [], + $headers, + json_encode($data) + ); + + $content = $response->getContent(); + $this->assertResponseStatus(201); + $attendee = json_decode($content); + $this->assertTrue(!is_null($attendee)); + } } \ No newline at end of file diff --git a/tests/ProtectedApiTest.php b/tests/ProtectedApiTest.php index 05f3ce13..13751a26 100644 --- a/tests/ProtectedApiTest.php +++ b/tests/ProtectedApiTest.php @@ -57,6 +57,7 @@ class AccessTokenServiceStub implements IAccessTokenService $url . '/me/summits/events/favorites/add', $url . '/me/summits/events/favorites/delete', sprintf(SummitScopes::WriteSpeakersData, $url), + sprintf(SummitScopes::WriteAttendeesData, $url), ); return AccessToken::createFromParams('123456789', implode(' ', $scopes), '1', $realm, '1','11624', 3600, 'WEB_APPLICATION', '', ''); @@ -102,6 +103,7 @@ class AccessTokenServiceStub2 implements IAccessTokenService $url . '/me/summits/events/favorites/add', $url . '/me/summits/events/favorites/delete', sprintf(SummitScopes::WriteSpeakersData, $url), + sprintf(SummitScopes::WriteAttendeesData, $url), ); return AccessToken::createFromParams('123456789', implode(' ', $scopes), '1', $realm, null,null, 3600, 'SERVICE', '', '');