diff --git a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSpeakersApiController.php b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSpeakersApiController.php index bf27bb22..34d2619e 100644 --- a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSpeakersApiController.php +++ b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSpeakersApiController.php @@ -19,7 +19,6 @@ use Illuminate\Support\Facades\Validator; use libs\utils\HTMLCleaner; use models\exceptions\EntityNotFoundException; use models\exceptions\ValidationException; -use models\main\Group; use models\main\IMemberRepository; use models\oauth2\IResourceServerContext; use models\summit\IEventFeedbackRepository; @@ -29,9 +28,7 @@ use models\summit\ISummitRepository; use ModelSerializers\ISerializerTypeSelector; use ModelSerializers\SerializerRegistry; use services\model\ISpeakerService; -use services\model\ISummitService; use utils\FilterParser; -use utils\FilterParserException; use utils\OrderParser; use utils\PagingInfo; use Illuminate\Http\Request as LaravelRequest; @@ -472,9 +469,9 @@ final class OAuth2SummitSpeakersApiController extends OAuth2ProtectedController 'bio', ]; - $speaker = $this->service->updateSpeaker($summit, $speaker, HTMLCleaner::cleanData($data->all(), $fields)); + $speaker = $this->service->updateSpeakerBySummit($summit, $speaker, HTMLCleaner::cleanData($data->all(), $fields)); - return $this->created(SerializerRegistry::getInstance()->getSerializer($speaker)->serialize()); + return $this->updated(SerializerRegistry::getInstance()->getSerializer($speaker)->serialize()); } catch (ValidationException $ex1) { Log::warning($ex1); @@ -491,6 +488,11 @@ final class OAuth2SummitSpeakersApiController extends OAuth2ProtectedController } } + /** + * @param LaravelRequest $request + * @param $speaker_id + * @return mixed + */ public function addSpeakerPhoto(LaravelRequest $request, $speaker_id){ try { @@ -620,4 +622,98 @@ final class OAuth2SummitSpeakersApiController extends OAuth2ProtectedController } } + /** + * @param $speaker_id + * @return mixed + */ + public function updateSpeaker($speaker_id){ + try { + if(!Request::isJson()) return $this->error403(); + $data = Input::json(); + + + $speaker = $this->speaker_repository->getById($speaker_id); + 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', + 'notes' => 'sometimes|string', + 'irc' => 'sometimes|string|max:50', + 'twitter' => 'sometimes|string|max:50', + 'member_id' => 'sometimes|integer', + 'email' => 'sometimes|string|max:50', + 'available_for_bureau' => 'sometimes|boolean', + 'funded_travel' => 'sometimes|boolean', + 'willing_to_travel' => 'sometimes|boolean', + 'willing_to_present_video' => 'sometimes|boolean', + ); + + // 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', + 'notes', + ]; + + $speaker = $this->service->updateSpeaker($speaker, HTMLCleaner::cleanData($data->all(), $fields)); + + return $this->updated(SerializerRegistry::getInstance()->getSerializer($speaker, SerializerRegistry::SerializerType_Private)->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); + } + } + + /** + * @param $speaker_id + * @return mixed + */ + public function deleteSpeaker($speaker_id){ + try { + + $speaker = $this->speaker_repository->getById($speaker_id); + if (is_null($speaker)) return $this->error404(); + $this->service->deleteSpeaker($speaker_id); + return $this->deleted(); + } + 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/Controllers/JsonController.php b/app/Http/Controllers/JsonController.php index 7f9289ae..63e56bc2 100644 --- a/app/Http/Controllers/JsonController.php +++ b/app/Http/Controllers/JsonController.php @@ -60,9 +60,9 @@ abstract class JsonController extends Controller return $res; } - protected function updated($data = 'ok') + protected function updated($data = 'ok', $has_content = true) { - $res = Response::json($data, 204); + $res = Response::json($data, $has_content ? 201 : 204); //jsonp if (Input::has('callback')) { $res->setCallback(Input::get('callback')); diff --git a/app/Http/routes.php b/app/Http/routes.php index 0e2ad620..77a8c235 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -352,6 +352,8 @@ Route::group([ Route::post('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitSpeakersApiController@addSpeaker']); Route::put('merge/{speaker_from_id}/{speaker_to_id}', 'OAuth2SummitSpeakersApiController@merge'); Route::group(['prefix' => '{speaker_id}'], function () { + Route::put('',[ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitSpeakersApiController@updateSpeaker'])->where('speaker_id', 'me|[0-9]+'); + Route::delete('',[ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitSpeakersApiController@deleteSpeaker'])->where('speaker_id', 'me|[0-9]+'); Route::get('', 'OAuth2SummitSpeakersApiController@getSpeaker'); Route::post('/photo', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitSpeakersApiController@addSpeakerPhoto']); }); diff --git a/app/Models/Foundation/Summit/Events/Presentations/PresentationSpeaker.php b/app/Models/Foundation/Summit/Events/Presentations/PresentationSpeaker.php index 75255206..29d4f0fd 100644 --- a/app/Models/Foundation/Summit/Events/Presentations/PresentationSpeaker.php +++ b/app/Models/Foundation/Summit/Events/Presentations/PresentationSpeaker.php @@ -107,20 +107,20 @@ class PresentationSpeaker extends SilverstripeBaseModel private $org_has_cloud; /** - * @ORM\ManyToOne(targetEntity="SpeakerRegistrationRequest", cascade={"persist"}) + * @ORM\ManyToOne(targetEntity="SpeakerRegistrationRequest", cascade={"persist"}), orphanRemoval=true * @ORM\JoinColumn(name="RegistrationRequestID", referencedColumnName="ID") * @var SpeakerRegistrationRequest */ private $registration_request; /** - * @ORM\OneToMany(targetEntity="PresentationSpeakerSummitAssistanceConfirmationRequest", mappedBy="speaker", cascade={"persist"}) + * @ORM\OneToMany(targetEntity="PresentationSpeakerSummitAssistanceConfirmationRequest", mappedBy="speaker", cascade={"persist"}, orphanRemoval=true) * @var PresentationSpeakerSummitAssistanceConfirmationRequest[] */ private $summit_assistances; /** - * @ORM\OneToMany(targetEntity="SpeakerSummitRegistrationPromoCode", mappedBy="speaker", cascade={"persist"}) + * @ORM\OneToMany(targetEntity="SpeakerSummitRegistrationPromoCode", mappedBy="speaker", cascade={"persist"}, orphanRemoval=true) * @var SpeakerSummitRegistrationPromoCode[] */ private $promo_codes; @@ -152,25 +152,25 @@ class PresentationSpeaker extends SilverstripeBaseModel private $member; /** - * @ORM\OneToMany(targetEntity="SpeakerExpertise", mappedBy="speaker", cascade={"persist"}) + * @ORM\OneToMany(targetEntity="SpeakerExpertise", mappedBy="speaker", cascade={"persist"}, orphanRemoval=true) * @var SpeakerExpertise[] */ private $areas_of_expertise; /** - * @ORM\OneToMany(targetEntity="SpeakerPresentationLink", mappedBy="speaker", cascade={"persist"}) + * @ORM\OneToMany(targetEntity="SpeakerPresentationLink", mappedBy="speaker", cascade={"persist"}, orphanRemoval=true) * @var SpeakerPresentationLink[] */ private $other_presentation_links; /** - * @ORM\OneToMany(targetEntity="SpeakerTravelPreference", mappedBy="speaker", cascade={"persist"}) + * @ORM\OneToMany(targetEntity="SpeakerTravelPreference", mappedBy="speaker", cascade={"persist"}, orphanRemoval=true) * @var SpeakerTravelPreference[] */ private $travel_preferences; /** - * @ORM\OneToMany(targetEntity="SpeakerLanguage", mappedBy="speaker", cascade={"persist"}) + * @ORM\OneToMany(targetEntity="SpeakerLanguage", mappedBy="speaker", cascade={"persist"}, orphanRemoval=true) * @var SpeakerLanguage[] */ private $languages; diff --git a/app/Services/Model/ISpeakerService.php b/app/Services/Model/ISpeakerService.php index 74ed9465..177d9022 100644 --- a/app/Services/Model/ISpeakerService.php +++ b/app/Services/Model/ISpeakerService.php @@ -46,7 +46,15 @@ interface ISpeakerService * @return PresentationSpeaker * @throws ValidationException */ - public function updateSpeaker(Summit $summit, PresentationSpeaker $speaker, array $data); + public function updateSpeakerBySummit(Summit $summit, PresentationSpeaker $speaker, array $data); + + /** + * @param array $data + * @param PresentationSpeaker $speaker + * @return PresentationSpeaker + * @throws ValidationException + */ + public function updateSpeaker(PresentationSpeaker $speaker, array $data); /** * @param PresentationSpeaker $speaker @@ -74,4 +82,12 @@ interface ISpeakerService * @return void */ public function merge(PresentationSpeaker $speaker_from, PresentationSpeaker $speaker_to, array $data); + + /** + * @param int $speaker_id + * @throws ValidationException + * @throws EntityNotFoundException + * @return void + */ + public function deleteSpeaker($speaker_id); } \ No newline at end of file diff --git a/app/Services/Model/SpeakerService.php b/app/Services/Model/SpeakerService.php index a24eb3e5..48f824ce 100644 --- a/app/Services/Model/SpeakerService.php +++ b/app/Services/Model/SpeakerService.php @@ -327,7 +327,7 @@ final class SpeakerService implements ISpeakerService * @throws ValidationException * @throws EntityNotFoundException */ - public function updateSpeaker(Summit $summit, PresentationSpeaker $speaker, array $data) + public function updateSpeakerBySummit(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; @@ -618,4 +618,58 @@ final class SpeakerService implements ISpeakerService }); } + /** + * @param array $data + * @param PresentationSpeaker $speaker + * @return PresentationSpeaker + * @throws ValidationException + */ + public function updateSpeaker(PresentationSpeaker $speaker, array $data) + { + return $this->tx_service->transaction(function() use ($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); + + return $speaker; + }); + } + + /** + * @param int $speaker_id + * @throws ValidationException + * @throws EntityNotFoundException + * @return void + */ + public function deleteSpeaker($speaker_id) + { + return $this->tx_service->transaction(function() use($speaker_id){ + $speaker = $this->speaker_repository->getById($speaker_id); + if(is_null($speaker)) + throw new EntityNotFoundException; + + $this->speaker_repository->delete($speaker); + }); + } } \ No newline at end of file diff --git a/database/seeds/ApiEndpointsSeeder.php b/database/seeds/ApiEndpointsSeeder.php index 9a685643..657b84b2 100644 --- a/database/seeds/ApiEndpointsSeeder.php +++ b/database/seeds/ApiEndpointsSeeder.php @@ -215,7 +215,7 @@ class ApiEndpointsSeeder extends Seeder ], ), array( - 'name' => 'update-speaker', + 'name' => 'update-speaker-by-summit', 'route' => '/api/v1/summits/{id}/speakers/{speaker_id}', 'http_method' => 'PUT', 'scopes' => [ @@ -238,6 +238,22 @@ class ApiEndpointsSeeder extends Seeder sprintf(SummitScopes::WriteSpeakersData, $current_realm), ], ), + array( + 'name' => 'update-speaker', + 'route' => '/api/v1/speakers/{speaker_id}', + 'http_method' => 'PUT', + 'scopes' => [ + sprintf(SummitScopes::WriteSpeakersData, $current_realm), + ], + ), + array( + 'name' => 'delete-speaker', + 'route' => '/api/v1/speakers/{speaker_id}', + 'http_method' => 'DELETE', + '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 14c9d153..8ed83711 100644 --- a/tests/OAuth2SpeakersApiTest.php +++ b/tests/OAuth2SpeakersApiTest.php @@ -91,6 +91,73 @@ final class OAuth2SpeakersApiTest extends ProtectedApiTest return $speaker; } + public function testUpdateSpeaker() + { + $speaker = $this->testPostSpeaker(); + + $headers = [ + "HTTP_Authorization" => " Bearer " . $this->access_token, + "CONTENT_TYPE" => "application/json" + ]; + + $data = [ + 'title' => 'Developer update!', + 'first_name' => 'Sebastian update', + 'last_name' => 'Marcet update', + 'notes' => 'test update', + 'willing_to_present_video' => false, + ]; + + $response = $this->action + ( + "PUT", + "OAuth2SummitSpeakersApiController@updateSpeaker", + [ + 'speaker_id' => $speaker->id + ], + [], + [], + [], + $headers, + json_encode($data) + ); + + $this->assertResponseStatus(201); + $content = $response->getContent(); + $speaker = json_decode($content); + $this->assertTrue($speaker->id > 0); + $this->assertTrue($speaker->notes == "test update"); + $this->assertTrue($speaker->willing_to_present_video == false); + return $speaker; + } + + public function testDeleteSpeaker() + { + $speaker = $this->testPostSpeaker(); + + $headers = [ + "HTTP_Authorization" => " Bearer " . $this->access_token, + "CONTENT_TYPE" => "application/json" + ]; + + + $response = $this->action + ( + "DELETE", + "OAuth2SummitSpeakersApiController@deleteSpeaker", + [ + 'speaker_id' => $speaker->id + ], + [], + [], + [], + $headers + ); + + $this->assertResponseStatus(204); + $content = $response->getContent(); + } + public function testPostSpeakerRegCodeBySummit($summit_id = 23) { $params = [