From 5dd7f6e4fc43d445a02c2b4ae5479b0e40174372 Mon Sep 17 00:00:00 2001 From: Sebastian Marcet Date: Tue, 12 Dec 2017 08:10:40 -0300 Subject: [PATCH] Added new Endpoint Update Speaker PUT /api/v1/summits/{summit_id}/speakers/{speaker_id} Change-Id: I8d071826c5e039a7fea1d033113c64de4ffbdf73 --- app/Events/PresentationSpeakerCreated.php | 22 ++++ app/Events/PresentationSpeakerDeleted.php | 21 ++++ .../PresentationSpeakerEntityStateChanged.php | 62 +++++++++ app/Events/PresentationSpeakerUpdated.php | 21 ++++ .../OAuth2SummitSpeakersApiController.php | 65 +++++++++- app/Http/routes.php | 5 +- .../Presentations/PresentationSpeaker.php | 119 +++++++++++++++++- .../Foundation/Summit/Events/SummitEvent.php | 1 - app/Providers/EventServiceProvider.php | 102 ++++++++++++++- app/Services/Model/ISpeakerService.php | 9 ++ app/Services/Model/SpeakerService.php | 99 +++++++++++++-- database/seeds/ApiEndpointsSeeder.php | 8 ++ tests/OAuth2SpeakersApiTest.php | 36 ++++++ 13 files changed, 549 insertions(+), 21 deletions(-) create mode 100644 app/Events/PresentationSpeakerCreated.php create mode 100644 app/Events/PresentationSpeakerDeleted.php create mode 100644 app/Events/PresentationSpeakerEntityStateChanged.php create mode 100644 app/Events/PresentationSpeakerUpdated.php diff --git a/app/Events/PresentationSpeakerCreated.php b/app/Events/PresentationSpeakerCreated.php new file mode 100644 index 00000000..994d1a33 --- /dev/null +++ b/app/Events/PresentationSpeakerCreated.php @@ -0,0 +1,22 @@ +speaker = $speaker; + $this->args = $args; + } + + /** + * @return PresentationSpeaker + */ + public function getPresentationSpeaker() + { + return $this->speaker; + } + + /** + * @return LifecycleEventArgs + */ + public function getArgs() + { + return $this->args; + } +} \ No newline at end of file diff --git a/app/Events/PresentationSpeakerUpdated.php b/app/Events/PresentationSpeakerUpdated.php new file mode 100644 index 00000000..a0b481a1 --- /dev/null +++ b/app/Events/PresentationSpeakerUpdated.php @@ -0,0 +1,21 @@ +error403(); @@ -315,4 +314,68 @@ final class OAuth2SummitSpeakersApiController extends OAuth2ProtectedController return $this->error500($ex); } } + + public function updateSpeaker($summit_id, $speaker_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(); + + $speaker = CheckSpeakerStrategyFactory::build(CheckSpeakerStrategyFactory::Me, $this->resource_server_context)->check($speaker_id, $summit); + if (is_null($speaker)) return $this->error404(); + + $rules = array + ( + 'title' => 'sometimes|string|max:100', + 'first_name' => 'sometimes|string|max:100', + 'last_name' => 'sometimes|string|max:100', + 'bio' => 'sometimes|string', + 'irc' => 'sometimes|string|max:50', + 'twitter' => 'sometimes|string|max:50', + 'member_id' => 'sometimes|integer', + 'email' => 'sometimes|string|max:50', + 'on_site_phone' => 'sometimes|string|max:50', + 'registered' => 'sometimes|boolean', + 'confirmed' => 'sometimes|boolean', + 'checked_in' => 'sometimes|boolean', + 'registration_code' => 'sometimes|string', + ); + + // 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 + ); + } + + $fields = [ + 'title', + 'bio', + ]; + + $speaker = $this->service->updateSpeaker($summit, $speaker, HTMLCleaner::cleanData($data->all(), $fields)); + + return $this->created(SerializerRegistry::getInstance()->getSerializer($speaker)->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/routes.php b/app/Http/routes.php index 65e856a9..e8655166 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -181,13 +181,14 @@ Route::group([ }); // speakers - Route::group(array('prefix' => 'speakers'), function () { + Route::group(['prefix' => 'speakers'], function () { Route::post('', [ 'middleware' => 'auth.user:administrators', 'uses' => 'OAuth2SummitSpeakersApiController@addSpeaker']); Route::get('', 'OAuth2SummitSpeakersApiController@getSpeakers'); - Route::group(array('prefix' => '{speaker_id}'), function () { + Route::group(['prefix' => '{speaker_id}'], function () { Route::get('', 'OAuth2SummitSpeakersApiController@getSpeaker')->where('speaker_id', 'me|[0-9]+'); + Route::put('',[ 'middleware' => 'auth.user:administrators', 'uses' => 'OAuth2SummitSpeakersApiController@updateSpeaker'])->where('speaker_id', 'me|[0-9]+'); }); }); diff --git a/app/Models/Foundation/Summit/Events/Presentations/PresentationSpeaker.php b/app/Models/Foundation/Summit/Events/Presentations/PresentationSpeaker.php index 66b2b82a..adb59323 100644 --- a/app/Models/Foundation/Summit/Events/Presentations/PresentationSpeaker.php +++ b/app/Models/Foundation/Summit/Events/Presentations/PresentationSpeaker.php @@ -11,18 +11,24 @@ * 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 App\Events\PresentationSpeakerCreated; +use App\Events\PresentationSpeakerDeleted; +use App\Events\PresentationSpeakerUpdated; +use Doctrine\ORM\Query\ResultSetMappingBuilder; use models\main\File; use models\main\Member; +use models\utils\PreRemoveEventArgs; use models\utils\SilverstripeBaseModel; -use Doctrine\ORM\Mapping AS ORM; use Doctrine\Common\Collections\ArrayCollection; - +use Doctrine\ORM\Event\PreUpdateEventArgs; +use Illuminate\Support\Facades\Event; +use Doctrine\Common\Collections\Criteria; /** * @ORM\Entity * @ORM\Table(name="PresentationSpeaker") * @ORM\Entity(repositoryClass="App\Repositories\Summit\DoctrineSpeakerRepository") + * @ORM\HasLifecycleCallbacks * Class PresentationSpeaker * @package models\summit */ @@ -420,6 +426,17 @@ class PresentationSpeaker extends SilverstripeBaseModel return $this; } + /** + * @param Summit $summit + * @return PresentationSpeakerSummitAssistanceConfirmationRequest + */ + public function getAssistanceFor(Summit $summit) + { + $criteria = Criteria::create(); + $criteria->where(Criteria::expr()->eq('summit', $summit)); + return $this->summit_assistances->matching($criteria)->first(); + } + /** * @return mixed */ @@ -447,4 +464,98 @@ class PresentationSpeaker extends SilverstripeBaseModel $request->setSpeaker($this); return $request; } + + /** + * @return Summit[] + */ + public function getRelatedSummits(){ + + $query = <<getEM()); + $rsm->addRootEntityFromClassMetadata(\models\summit\Summit::class, 's'); + + // build rsm here + $native_query = $this->getEM()->createNativeQuery($query, $rsm); + + $native_query->setParameter("speaker_id", $this->id); + + $summits = $native_query->getResult(); + if(count($summits) == 0){ + $assistance = $this->getLatestAssistance(); + if(is_null($assistance)) return []; + return [ $assistance->getSummit() ]; + } + return $summits; + } + + /** + * @return PresentationSpeakerSummitAssistanceConfirmationRequest + */ + public function getLatestAssistance(){ + return $this->summit_assistances->last(); + } + + // life cycle events + + /** + * @var PreRemoveEventArgs + */ + private $pre_remove_events; + /** + * @ORM\PreRemove: + */ + public function deleting($args){ + $this->pre_remove_events = new PreRemoveEventArgs + ( + [ + 'id' => $this->id, + 'class_name' => "PresentationSpeaker", + 'summits' => $this->getRelatedSummits(), + ] + ); + } + + /** + * @ORM\PostRemove: + */ + public function deleted($args){ + + Event::fire(new PresentationSpeakerDeleted($this, $this->pre_remove_events)); + $this->pre_remove_events = null; + } + + /** + * @var PreUpdateEventArgs + */ + private $pre_update_args; + + /** + * @ORM\PreUpdate: + */ + public function updating(PreUpdateEventArgs $args){ + $this->pre_update_args = $args; + } + + /** + * @ORM\PostUpdate: + */ + public function updated($args) + { + Event::fire(new PresentationSpeakerUpdated($this, $this->pre_update_args)); + $this->pre_update_args = null; + } + + /** + * @ORM\PostPersist + */ + public function inserted($args){ + Event::fire(new PresentationSpeakerCreated($this, $args)); + } } \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/SummitEvent.php b/app/Models/Foundation/Summit/Events/SummitEvent.php index e6d6972e..a60ef1ac 100644 --- a/app/Models/Foundation/Summit/Events/SummitEvent.php +++ b/app/Models/Foundation/Summit/Events/SummitEvent.php @@ -19,7 +19,6 @@ use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Event\PreUpdateEventArgs; use models\exceptions\ValidationException; use models\main\Company; -use models\main\Member; use models\main\Tag; use models\utils\PreRemoveEventArgs; use models\utils\SilverstripeBaseModel; diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index bdace559..0a28f734 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -12,6 +12,9 @@ * limitations under the License. **/ use App\Events\MyFavoritesAdd; +use App\Events\PresentationSpeakerCreated; +use App\Events\PresentationSpeakerDeleted; +use App\Events\PresentationSpeakerUpdated; use App\Events\SummitEventCreated; use App\Events\SummitEventDeleted; use App\Events\SummitEventUpdated; @@ -49,7 +52,6 @@ class EventServiceProvider extends ServiceProvider ], ]; - /** * Register any other events for your application. * @return void @@ -359,5 +361,103 @@ class EventServiceProvider extends ServiceProvider }); + Event::listen(\App\Events\PresentationSpeakerCreated::class, function($event) + { + if(!$event instanceof PresentationSpeakerCreated) return; + + $resource_server_context = App::make(\models\oauth2\IResourceServerContext::class); + $member_repository = App::make(\models\main\IMemberRepository::class); + $owner_id = $resource_server_context->getCurrentUserExternalId(); + if(is_null($owner_id)) $owner_id = 0; + + $em = Registry::getManager('ss'); + + foreach($event->getPresentationSpeaker()->getRelatedSummits() as $summit) { + + $entity_event = new SummitEntityEvent; + $entity_event->setEntityClassName("PresentationSpeaker"); + $entity_event->setEntityId($event->getPresentationSpeaker()->getId()); + $entity_event->setType('INSERT'); + + if ($owner_id > 0) { + $member = $member_repository->getById($owner_id); + $entity_event->setOwner($member); + } + + $entity_event->setSummit($summit); + $entity_event->setMetadata(''); + + $em->persist($entity_event); + $em->flush(); + } + + }); + + Event::listen(\App\Events\PresentationSpeakerUpdated::class, function($event) + { + if(!$event instanceof PresentationSpeakerUpdated) return; + + $resource_server_context = App::make(\models\oauth2\IResourceServerContext::class); + $member_repository = App::make(\models\main\IMemberRepository::class); + $owner_id = $resource_server_context->getCurrentUserExternalId(); + if(is_null($owner_id)) $owner_id = 0; + + $em = Registry::getManager('ss'); + + foreach($event->getPresentationSpeaker()->getRelatedSummits() as $summit) { + + $entity_event = new SummitEntityEvent; + $entity_event->setEntityClassName("PresentationSpeaker"); + $entity_event->setEntityId($event->getPresentationSpeaker()->getId()); + $entity_event->setType('UPDATE'); + + if ($owner_id > 0) { + $member = $member_repository->getById($owner_id); + $entity_event->setOwner($member); + } + + $entity_event->setSummit($summit); + $entity_event->setMetadata(''); + + $em->persist($entity_event); + $em->flush(); + } + + }); + + Event::listen(\App\Events\PresentationSpeakerDeleted::class, function($event) + { + if(!$event instanceof PresentationSpeakerDeleted) return; + $args = $event->getArgs(); + if(!$args instanceof PreRemoveEventArgs) return; + + $resource_server_context = App::make(\models\oauth2\IResourceServerContext::class); + $member_repository = App::make(\models\main\IMemberRepository::class); + $owner_id = $resource_server_context->getCurrentUserExternalId(); + if(is_null($owner_id)) $owner_id = 0; + $params = $args->getParams(); + $em = Registry::getManager('ss'); + + foreach($params['summits'] as $summit) { + + $entity_event = new SummitEntityEvent; + $entity_event->setEntityClassName($params['class_name']); + $entity_event->setEntityId($params['id']); + $entity_event->setType('DELETE'); + + if ($owner_id > 0) { + $member = $member_repository->getById($owner_id); + $entity_event->setOwner($member); + } + + $entity_event->setSummit($summit); + $entity_event->setMetadata(''); + + $em->persist($entity_event); + $em->flush(); + } + + }); + } } diff --git a/app/Services/Model/ISpeakerService.php b/app/Services/Model/ISpeakerService.php index 4ab7458c..7325c730 100644 --- a/app/Services/Model/ISpeakerService.php +++ b/app/Services/Model/ISpeakerService.php @@ -29,6 +29,15 @@ interface ISpeakerService */ public function addSpeaker(Summit $summit, array $data); + /** + * @param Summit $summit + * @param array $data + * @param PresentationSpeaker $speaker + * @return PresentationSpeaker + * @throws ValidationException + */ + public function updateSpeaker(Summit $summit, PresentationSpeaker $speaker, array $data); + /** * @param PresentationSpeaker $speaker * @param Summit $summit diff --git a/app/Services/Model/SpeakerService.php b/app/Services/Model/SpeakerService.php index aaa94085..91a310db 100644 --- a/app/Services/Model/SpeakerService.php +++ b/app/Services/Model/SpeakerService.php @@ -22,6 +22,7 @@ use models\summit\ISpeakerRegistrationRequestRepository; use models\summit\ISpeakerRepository; use models\summit\ISpeakerSummitRegistrationPromoCodeRepository; use models\summit\PresentationSpeaker; +use models\summit\PresentationSpeakerSummitAssistanceConfirmationRequest; use models\summit\SpeakerRegistrationRequest; use models\summit\SpeakerSummitRegistrationPromoCode; use models\summit\Summit; @@ -67,6 +68,16 @@ final class SpeakerService implements ISpeakerService private $tx_service; + /** + * SpeakerService constructor. + * @param ISpeakerRepository $speaker_repository + * @param IMemberRepository $member_repository + * @param ISpeakerRegistrationRequestRepository $speaker_registration_request_repository + * @param ISpeakerSummitRegistrationPromoCodeRepository $registration_code_repository + * @param IEmailCreationRequestRepository $email_creation_request_repository + * @param IFolderRepository $folder_repository + * @param ITransactionService $tx_service + */ public function __construct ( ISpeakerRepository $speaker_repository, @@ -150,18 +161,9 @@ final class SpeakerService implements ISpeakerService } } - $on_site_phone = isset($data['on_site_phone']) ? trim($data['on_site_phone']) : null; - $registered = isset($data['registered']) ? 1 : 0; - $checked_in = isset($data['checked_in']) ? 1 : 0; - $confirmed = isset($data['confirmed']) ? 1 : 0; - - $summit_assistance = $speaker->buildAssistanceFor($summit); - $summit_assistance->setOnSitePhone($on_site_phone); - $summit_assistance->setRegistered($registered); - $summit_assistance->setIsConfirmed($confirmed); - $summit_assistance->setCheckedIn($checked_in); - - $speaker->addSummitAssistance($summit_assistance); + $speaker->addSummitAssistance( + $this->updateSummitAssistance($speaker->buildAssistanceFor($summit), $data) + ); $reg_code = isset($data['registration_code']) ? trim($data['registration_code']) : null; if(!empty($reg_code)){ @@ -177,6 +179,25 @@ final class SpeakerService implements ISpeakerService }); } + /** + * @param PresentationSpeakerSummitAssistanceConfirmationRequest $summit_assistance + * @param array $data + * @return PresentationSpeakerSummitAssistanceConfirmationRequest + */ + private function updateSummitAssistance(PresentationSpeakerSummitAssistanceConfirmationRequest $summit_assistance, array $data){ + $on_site_phone = isset($data['on_site_phone']) ? trim($data['on_site_phone']) : null; + $registered = isset($data['registered']) ? 1 : 0; + $checked_in = isset($data['checked_in']) ? 1 : 0; + $confirmed = isset($data['confirmed']) ? 1 : 0; + + $summit_assistance->setOnSitePhone($on_site_phone); + $summit_assistance->setRegistered($registered); + $summit_assistance->setIsConfirmed($confirmed); + $summit_assistance->setCheckedIn($checked_in); + + return $summit_assistance; + } + /** * @param PresentationSpeaker $speaker * @param string $email @@ -239,6 +260,10 @@ final class SpeakerService implements ISpeakerService }); } + /** + * @param PresentationSpeaker $speaker + * @param array $data + */ private function updateSpeakerMainData(PresentationSpeaker $speaker, array $data){ if(isset($data['title'])) $speaker->setTitle(trim($data['title'])); @@ -260,4 +285,54 @@ final class SpeakerService implements ISpeakerService } + /** + * @param Summit $summit + * @param array $data + * @param PresentationSpeaker $speaker + * @return PresentationSpeaker + * @throws ValidationException + * @throws EntityNotFoundException + */ + public function updateSpeaker(Summit $summit, PresentationSpeaker $speaker, array $data) + { + return $this->tx_service->transaction(function() use ($summit, $speaker, $data){ + $member_id = isset($data['member_id']) ? intval($data['member_id']) : null; + if($member_id > 0) + { + $member = $this->member_repository->getById($member_id); + if(is_null($member)) + throw new EntityNotFoundException; + + $existent_speaker = $this->speaker_repository->getByMember($member); + if($existent_speaker && $existent_speaker->getId() !== $speaker->getId()) + throw new ValidationException + ( + sprintf + ( + "member_id %s already has assigned another speaker id (%s)", + $member_id, + $existent_speaker->getId() + ) + ); + + $speaker->setMember($member); + } + + $this->updateSpeakerMainData($speaker, $data); + + $summit_assistance = $speaker->getAssistanceFor($summit); + if(is_null($summit_assistance)){ + $speaker->addSummitAssistance( + $this->updateSummitAssistance($speaker->buildAssistanceFor($summit), $data) + ); + } + + $reg_code = isset($data['registration_code']) ? trim($data['registration_code']) : null; + if(!empty($reg_code)){ + $this->registerSummitPromoCodeByValue($speaker, $summit, $reg_code); + } + + return $speaker; + }); + } } \ No newline at end of file diff --git a/database/seeds/ApiEndpointsSeeder.php b/database/seeds/ApiEndpointsSeeder.php index 41b375c9..3fa308d4 100644 --- a/database/seeds/ApiEndpointsSeeder.php +++ b/database/seeds/ApiEndpointsSeeder.php @@ -165,6 +165,14 @@ class ApiEndpointsSeeder extends Seeder sprintf(SummitScopes::WriteSpeakersData, $current_realm), ], ), + array( + 'name' => 'update-speaker', + 'route' => '/api/v1/summits/{id}/speakers/{speaker_id}', + 'http_method' => 'PUT', + 'scopes' => [ + sprintf(SummitScopes::WriteSpeakersData, $current_realm), + ], + ), array( 'name' => 'get-all-speakers', 'route' => '/api/v1/speakers', diff --git a/tests/OAuth2SpeakersApiTest.php b/tests/OAuth2SpeakersApiTest.php index c91b97d2..06489853 100644 --- a/tests/OAuth2SpeakersApiTest.php +++ b/tests/OAuth2SpeakersApiTest.php @@ -130,4 +130,40 @@ class OAuth2SpeakersApiTest extends ProtectedApiTest $this->assertTrue($speaker->id > 0); return $speaker; } + + public function testUpdateSpeaker($summit_id = 23) + { + $params = [ + + 'id' => $summit_id, + 'speaker_id' => 1 + ]; + + $headers = [ + "HTTP_Authorization" => " Bearer " . $this->access_token, + "CONTENT_TYPE" => "application/json" + ]; + + $data = [ + 'title' => 'Legend!', + ]; + + $response = $this->action + ( + "PUT", + "OAuth2SummitSpeakersApiController@updateSpeaker", + $params, + [], + [], + [], + $headers, + json_encode($data) + ); + + $this->assertResponseStatus(201); + $content = $response->getContent(); + $speaker = json_decode($content); + $this->assertTrue($speaker->id > 0); + return $speaker; + } } \ No newline at end of file