From 2535885128bd828f9839712bce3d8cccda906a3a Mon Sep 17 00:00:00 2001 From: Sebastian Marcet Date: Thu, 30 Aug 2018 01:14:22 -0300 Subject: [PATCH] Fixes on member affiliation API * added endpoint to add new affiliation /api/v1/members/{member_id}/affiliations * fixed issues on delete affiliation * fixed validation on update affiliation * updated serializer on speaker to get all affiliations Change-Id: I31426265ab9e57e67a6d1ae2a24b49287ab8d700 --- .../Main/OAuth2MembersApiController.php | 49 +++++++++++- app/Http/routes.php | 1 + .../AdminPresentationSpeakerSerializer.php | 9 +++ app/Models/Foundation/Main/Affiliation.php | 14 ++++ app/Models/Foundation/Main/Member.php | 23 +++++- app/Services/Model/IMemberService.php | 8 ++ app/Services/Model/MemberService.php | 48 +++++++++++- database/seeds/ApiEndpointsSeeder.php | 8 ++ tests/OAuth2MembersApiTest.php | 77 +++++++++++++++++-- 9 files changed, 225 insertions(+), 12 deletions(-) diff --git a/app/Http/Controllers/Apis/Protected/Main/OAuth2MembersApiController.php b/app/Http/Controllers/Apis/Protected/Main/OAuth2MembersApiController.php index 129bf927..0703a11f 100644 --- a/app/Http/Controllers/Apis/Protected/Main/OAuth2MembersApiController.php +++ b/app/Http/Controllers/Apis/Protected/Main/OAuth2MembersApiController.php @@ -228,6 +228,53 @@ final class OAuth2MembersApiController extends OAuth2ProtectedController } } + public function addAffiliation($member_id){ + try { + if(!Request::isJson()) return $this->error400(); + $data = Input::json(); + + $member = $this->repository->getById($member_id); + if(is_null($member)) return $this->error404(); + + $rules = [ + 'is_current' => 'required|boolean', + 'start_date' => 'required|date_format:U|valid_epoch', + 'end_date' => 'sometimes|date_format:U|after_or_null_epoch:start_date', + 'organization_id' => 'required|integer', + 'job_title' => 'sometimes|string|max:255' + ]; + + // 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 + ); + } + + $affiliation = $this->member_service->addAffiliation($member, $data->all()); + + return $this->created(SerializerRegistry::getInstance()->getSerializer($affiliation)->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 int $member_id * @param int $affiliation_id @@ -263,7 +310,7 @@ final class OAuth2MembersApiController extends OAuth2ProtectedController $affiliation = $this->member_service->updateAffiliation($member, $affiliation_id, $data->all()); - return $this->ok(SerializerRegistry::getInstance()->getSerializer($affiliation)->serialize()); + return $this->updated(SerializerRegistry::getInstance()->getSerializer($affiliation)->serialize()); } catch (ValidationException $ex1) { Log::warning($ex1); diff --git a/app/Http/routes.php b/app/Http/routes.php index 24c1b372..337ce7cc 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -46,6 +46,7 @@ Route::group([ Route::group(['prefix' => 'affiliations'], function(){ Route::get('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2MembersApiController@getMemberAffiliations']); + Route::post('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2MembersApiController@addAffiliation']); Route::group(['prefix' => '{affiliation_id}'], function(){ Route::put('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2MembersApiController@updateAffiliation']); Route::delete('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2MembersApiController@deleteAffiliation']); diff --git a/app/ModelSerializers/Summit/Speakers/AdminPresentationSpeakerSerializer.php b/app/ModelSerializers/Summit/Speakers/AdminPresentationSpeakerSerializer.php index 6185a8d5..2f52e17f 100644 --- a/app/ModelSerializers/Summit/Speakers/AdminPresentationSpeakerSerializer.php +++ b/app/ModelSerializers/Summit/Speakers/AdminPresentationSpeakerSerializer.php @@ -111,6 +111,15 @@ final class AdminPresentationSpeakerSerializer extends PresentationSpeakerSerial } $values['organizational_roles'] = $organizational_roles; + $affiliations = []; + if($speaker->hasMember()) { + $member = $speaker->getMember(); + foreach ($member->getAllAffiliations() as $affiliation) { + $affiliations[] = SerializerRegistry::getInstance()->getSerializer($affiliation)->serialize('organization'); + } + } + $values['affiliations'] = $affiliations; + if (!empty($expand)) { foreach (explode(',', $expand) as $relation) { switch (trim($relation)) { diff --git a/app/Models/Foundation/Main/Affiliation.php b/app/Models/Foundation/Main/Affiliation.php index 54e47719..b6c79ad2 100644 --- a/app/Models/Foundation/Main/Affiliation.php +++ b/app/Models/Foundation/Main/Affiliation.php @@ -135,6 +135,10 @@ class Affiliation extends SilverstripeBaseModel return $this->owner; } + public function clearOwner(){ + $this->owner = null; + } + /** * @param Member $owner */ @@ -189,4 +193,14 @@ class Affiliation extends SilverstripeBaseModel { $this->job_title = $job_title; } + + public function __construct() + { + parent::__construct(); + $this->is_current = false; + $this->start_date = null; + $this->end_date = null; + $this->organization = null; + $this->owner = null; + } } \ No newline at end of file diff --git a/app/Models/Foundation/Main/Member.php b/app/Models/Foundation/Main/Member.php index facabc5a..8efceeb6 100644 --- a/app/Models/Foundation/Main/Member.php +++ b/app/Models/Foundation/Main/Member.php @@ -61,13 +61,13 @@ class Member extends SilverstripeBaseModel private $github_user; /** - * @ORM\OneToMany(targetEntity="models\summit\SummitEventFeedback", mappedBy="owner", cascade={"persist"}) + * @ORM\OneToMany(targetEntity="models\summit\SummitEventFeedback", mappedBy="owner", cascade={"persist"}, orphanRemoval=true) * @var SummitEventFeedback[] */ private $feedback; /** - * @ORM\OneToMany(targetEntity="Affiliation", mappedBy="owner", cascade={"persist"}) + * @ORM\OneToMany(targetEntity="Affiliation", mappedBy="owner", cascade={"persist"}, orphanRemoval=true) * @var Affiliation[] */ private $affiliations; @@ -1019,7 +1019,7 @@ SQL; */ public function getAffiliationById($affiliation_id){ $criteria = Criteria::create(); - $criteria->where(Criteria::expr()->eq('id', $affiliation_id)); + $criteria->where(Criteria::expr()->eq('id', intval($affiliation_id))); $affiliation = $this->affiliations->matching($criteria)->first(); @@ -1031,7 +1031,22 @@ SQL; * @return $this */ public function removeAffiliation(Affiliation $affiliation){ - $this->affiliations->removeElement($affiliation); + if($this->affiliations->contains($affiliation)) { + $this->affiliations->removeElement($affiliation); + $affiliation->clearOwner(); + } + return $this; + } + + /** + * @param Affiliation $affiliation + * @return $this + */ + public function addAffiliation(Affiliation $affiliation){ + if(!$this->affiliations->contains($affiliation)) { + $this->affiliations->add($affiliation); + $affiliation->setOwner($this); + } return $this; } diff --git a/app/Services/Model/IMemberService.php b/app/Services/Model/IMemberService.php index a2117acd..743f3140 100644 --- a/app/Services/Model/IMemberService.php +++ b/app/Services/Model/IMemberService.php @@ -19,6 +19,14 @@ use models\main\Member; */ interface IMemberService { + + /** + * @param Member $member + * @param array $data + * @return Affiliation + */ + public function addAffiliation(Member $member, array $data); + /** * @param Member $member * @param int $affiliation_id diff --git a/app/Services/Model/MemberService.php b/app/Services/Model/MemberService.php index 7bc676df..9ce61a73 100644 --- a/app/Services/Model/MemberService.php +++ b/app/Services/Model/MemberService.php @@ -63,7 +63,7 @@ final class MemberService $affiliation->setIsCurrent(boolval($data['is_current'])); if(isset($data['start_date'])) { $start_date = intval($data['start_date']); - $affiliation->setEndDate(new DateTime("@$start_date")); + $affiliation->setStartDate(new DateTime("@$start_date")); } if(isset($data['end_date'])) { $end_date = intval($data['end_date']); @@ -125,4 +125,50 @@ final class MemberService $member->removeRsvp($rsvp); }); } + + /** + * @param Member $member + * @param array $data + * @return Affiliation + */ + public function addAffiliation(Member $member, array $data) + { + return $this->tx_service->transaction(function() use($member, $data){ + + $affiliation = new Affiliation(); + + if(isset($data['is_current'])) + $affiliation->setIsCurrent(boolval($data['is_current'])); + if(isset($data['start_date'])) { + $start_date = intval($data['start_date']); + $affiliation->setStartDate(new DateTime("@$start_date")); + } + if(isset($data['end_date'])) { + $end_date = intval($data['end_date']); + $affiliation->setEndDate($end_date > 0 ? new DateTime("@$end_date") : null); + } + if(isset($data['organization_id'])) { + $org = $this->organization_repository->getById(intval($data['organization_id'])); + if(is_null($org)) + throw new EntityNotFoundException(sprintf("organization id %s not found", $data['organization_id'])); + $affiliation->setOrganization($org); + } + + if(isset($data['job_title'])) { + $affiliation->setJobTitle(trim($data['job_title'])); + } + + if($affiliation->isCurrent() && $affiliation->getEndDate() != null) + throw new ValidationException + ( + sprintf + ( + "in order to set affiliation as current end_date should be null" + ) + ); + + $member->addAffiliation($affiliation); + return $affiliation; + }); + } } \ No newline at end of file diff --git a/database/seeds/ApiEndpointsSeeder.php b/database/seeds/ApiEndpointsSeeder.php index 52cec733..9aa61ab3 100644 --- a/database/seeds/ApiEndpointsSeeder.php +++ b/database/seeds/ApiEndpointsSeeder.php @@ -1781,6 +1781,14 @@ class ApiEndpointsSeeder extends Seeder 'http_method' => 'GET', 'scopes' => [sprintf('%s/members/read', $current_realm)], ], + [ + 'name' => 'add-member-affiliation', + 'route' => '/api/v1/members/{member_id}/affiliations', + 'http_method' => 'POST', + 'scopes' => [ + sprintf(SummitScopes::WriteMemberData, $current_realm) + ], + ], [ 'name' => 'update-member-affiliation', 'route' => '/api/v1/members/{member_id}/affiliations/{affiliation_id}', diff --git a/tests/OAuth2MembersApiTest.php b/tests/OAuth2MembersApiTest.php index 28ca6508..a39ff8f0 100644 --- a/tests/OAuth2MembersApiTest.php +++ b/tests/OAuth2MembersApiTest.php @@ -161,16 +161,54 @@ final class OAuth2MembersApiTest extends ProtectedApiTest $this->assertResponseStatus(200); } - public function testUpdateMemberAffiliation(){ + public function testAddMemberAffiliation($member_id = 11624){ $params = [ - 'member_id' => 11624, - 'affiliation_id' => 61749, + 'member_id' => $member_id, ]; + $start_datetime = new DateTime( "2018-11-10 00:00:00"); + $start_datetime_unix = $start_datetime->getTimestamp(); + $data = [ 'is_current' => true, - 'end_date' => 0, - 'job_title' => 'test update' + 'start_date' => $start_datetime_unix, + 'job_title' => 'test affiliation', + 'organization_id' => 1 + ]; + + $headers = [ + "HTTP_Authorization" => " Bearer " . $this->access_token, + "CONTENT_TYPE" => "application/json" + ]; + + $response = $this->action( + "POST", + "OAuth2MembersApiController@addAffiliation", + $params, + [], + [], + [], + $headers, + json_encode($data) + ); + + $content = $response->getContent(); + $this->assertResponseStatus(201); + $affiliation = json_decode($content); + $this->assertTrue(!is_null($affiliation)); + return $affiliation; + } + + public function testUpdateMemberAffiliation($member_id = 11624){ + + $new_affiliation = $this->testAddMemberAffiliation($member_id); + $params = [ + 'member_id' => $member_id, + 'affiliation_id' => $new_affiliation->id, + ]; + + $data = [ + 'job_title' => 'job title update' ]; $headers = [ @@ -190,12 +228,39 @@ final class OAuth2MembersApiTest extends ProtectedApiTest ); $content = $response->getContent(); - $this->assertResponseStatus(200); + $this->assertResponseStatus(201); $affiliation = json_decode($content); $this->assertTrue(!is_null($affiliation)); return $affiliation; } + public function testDeleteMemberAffiliation($member_id = 11624){ + + $new_affiliation = $this->testAddMemberAffiliation($member_id); + $params = [ + 'member_id' => $member_id, + 'affiliation_id' => $new_affiliation->id, + ]; + + $headers = [ + "HTTP_Authorization" => " Bearer " . $this->access_token, + "CONTENT_TYPE" => "application/json" + ]; + + $response = $this->action( + "DELETE", + "OAuth2MembersApiController@deleteAffiliation", + $params, + [], + [], + [], + $headers + ); + + $content = $response->getContent(); + $this->assertResponseStatus(204); + } + public function testGetMemberAffiliation($member_id = 11624) {