diff --git a/Libs/ModelSerializers/AbstractSerializer.php b/Libs/ModelSerializers/AbstractSerializer.php index 86e99e43..855c3df0 100644 --- a/Libs/ModelSerializers/AbstractSerializer.php +++ b/Libs/ModelSerializers/AbstractSerializer.php @@ -179,4 +179,24 @@ abstract class AbstractSerializer implements IModelSerializer return $values; } + + /** + * @param string $expand_str + * @param string $prefix + * @return string + */ + protected static function filterExpandByPrefix($expand_str, $prefix ){ + + $expand_to = explode(',', $expand_str); + $filtered_expand = array_filter($expand_to, function($element) use($prefix){ + return preg_match('/^' . preg_quote($prefix, '/') . '/', strtolower(trim($element))) > 0; + }); + $res = ''; + foreach($filtered_expand as $filtered_expand_elem){ + if(strlen($res) > 0) $res .= ','; + $res .= explode('.', strtolower(trim($filtered_expand_elem)))[1]; + } + + return $res; + } } \ No newline at end of file diff --git a/app/Events/MyFavoritesAdd.php b/app/Events/MyFavoritesAdd.php new file mode 100644 index 00000000..cd794a77 --- /dev/null +++ b/app/Events/MyFavoritesAdd.php @@ -0,0 +1,50 @@ +member = $member; + $this->summit = $summit; + parent::__construct($event_id); + } + + public function getMember(){ return $this->member; } + + public function getSummit(){ return $this->summit;} +} \ No newline at end of file diff --git a/app/Events/MyFavoritesRemove.php b/app/Events/MyFavoritesRemove.php new file mode 100644 index 00000000..6475dc7a --- /dev/null +++ b/app/Events/MyFavoritesRemove.php @@ -0,0 +1,22 @@ +attendee = $attendee; - $this->event_id = $event_id; + parent::__construct($event_id); } /** * @return SummitAttendee */ public function getAttendee(){ return $this->attendee;} - - /** - * @return int - */ - public function getEventId(){ return $this->event_id;} } \ No newline at end of file diff --git a/app/Events/SummitEventAction.php b/app/Events/SummitEventAction.php new file mode 100644 index 00000000..1dbb760a --- /dev/null +++ b/app/Events/SummitEventAction.php @@ -0,0 +1,45 @@ +event_id = $event_id; + } + + /** + * @return int + */ + public function getEventId(){ return $this->event_id;} +} \ No newline at end of file diff --git a/app/Http/Controllers/apis/protected/summit/OAuth2SummitLocationsApiController.php b/app/Http/Controllers/apis/protected/summit/OAuth2SummitLocationsApiController.php index 1786a57a..53d58436 100644 --- a/app/Http/Controllers/apis/protected/summit/OAuth2SummitLocationsApiController.php +++ b/app/Http/Controllers/apis/protected/summit/OAuth2SummitLocationsApiController.php @@ -90,7 +90,17 @@ final class OAuth2SummitLocationsApiController extends OAuth2ProtectedController $locations[] = SerializerRegistry::getInstance()->getSerializer($location)->serialize(); } - return $this->ok($locations); + $response = new PagingResponse + ( + count($locations), + count($locations), + 1, + 1, + $locations + ); + + return $this->ok($response->toArray($expand = Input::get('expand',''))); + } catch (Exception $ex) { Log::error($ex); return $this->error500($ex); @@ -280,7 +290,17 @@ final class OAuth2SummitLocationsApiController extends OAuth2ProtectedController $locations[] = SerializerRegistry::getInstance()->getSerializer($location)->serialize(); } - return $this->ok($locations); + + $response = new PagingResponse + ( + count($locations), + count($locations), + 1, + 1, + $locations + ); + + return $this->ok($response->toArray($expand = Input::get('expand',''))); } catch (Exception $ex) { Log::error($ex); @@ -305,7 +325,17 @@ final class OAuth2SummitLocationsApiController extends OAuth2ProtectedController $locations[] = SerializerRegistry::getInstance()->getSerializer($location)->serialize(); } - return $this->ok($locations); + $response = new PagingResponse + ( + count($locations), + count($locations), + 1, + 1, + $locations + ); + + return $this->ok($response->toArray($expand = Input::get('expand',''))); + } catch (Exception $ex) { Log::error($ex); return $this->error500($ex); @@ -329,7 +359,17 @@ final class OAuth2SummitLocationsApiController extends OAuth2ProtectedController $locations[] = SerializerRegistry::getInstance()->getSerializer($location)->serialize(); } - return $this->ok($locations); + $response = new PagingResponse + ( + count($locations), + count($locations), + 1, + 1, + $locations + ); + + return $this->ok($response->toArray($expand = Input::get('expand',''))); + } catch (Exception $ex) { Log::error($ex); return $this->error500($ex); @@ -354,7 +394,17 @@ final class OAuth2SummitLocationsApiController extends OAuth2ProtectedController $locations[] = SerializerRegistry::getInstance()->getSerializer($location)->serialize(); } - return $this->ok($locations); + $response = new PagingResponse + ( + count($locations), + count($locations), + 1, + 1, + $locations + ); + + return $this->ok($response->toArray($expand = Input::get('expand',''))); + } catch (Exception $ex) { Log::error($ex); return $this->error500($ex); diff --git a/app/Http/Controllers/apis/protected/summit/OAuth2SummitMembersApiController.php b/app/Http/Controllers/apis/protected/summit/OAuth2SummitMembersApiController.php index 0454bf1d..6934cafa 100644 --- a/app/Http/Controllers/apis/protected/summit/OAuth2SummitMembersApiController.php +++ b/app/Http/Controllers/apis/protected/summit/OAuth2SummitMembersApiController.php @@ -12,11 +12,17 @@ * limitations under the License. **/ +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Request; +use models\exceptions\EntityNotFoundException; +use models\exceptions\ValidationException; use models\main\IMemberRepository; use models\oauth2\IResourceServerContext; use models\summit\ISummitRepository; use ModelSerializers\SerializerRegistry; +use services\model\ISummitService; +use utils\PagingResponse; +use Illuminate\Support\Facades\Input; /** * Class OAuth2SummitMembersApiController @@ -29,24 +35,32 @@ final class OAuth2SummitMembersApiController extends OAuth2ProtectedController */ private $summit_repository; + /** + * @var ISummitService + */ + private $summit_service; + /** * OAuth2SummitMembersApiController constructor. * @param IMemberRepository $member_repository * @param ISummitRepository $summit_repository + * @param ISummitService $summit_service * @param IResourceServerContext $resource_server_context */ public function __construct ( IMemberRepository $member_repository, ISummitRepository $summit_repository, + ISummitService $summit_service, IResourceServerContext $resource_server_context ) { parent::__construct($resource_server_context); $this->summit_repository = $summit_repository; $this->repository = $member_repository; + $this->summit_service = $summit_service; } - public function getMyMember($summit_id){ + public function getMyMember($summit_id, $member_id){ $summit = SummitFinderStrategyFactory::build($this->summit_repository)->find($summit_id); if (is_null($summit)) return $this->error404(); @@ -60,10 +74,10 @@ final class OAuth2SummitMembersApiController extends OAuth2ProtectedController $fields = Request::input('fields', null); $relations = Request::input('relations', null); - return $this->ok ( - SerializerRegistry::getInstance()->getSerializer($current_member)->serialize + SerializerRegistry::getInstance()->getSerializer($current_member, SerializerRegistry::SerializerType_Private) + ->serialize ( Request::input('expand', ''), is_null($fields) ? [] : explode(',', $fields), @@ -72,4 +86,134 @@ final class OAuth2SummitMembersApiController extends OAuth2ProtectedController ) ); } + + public function getMemberFavoritesSummitEvents($summit_id, $member_id){ + + try { + $summit = SummitFinderStrategyFactory::build($this->summit_repository)->find($summit_id); + if (is_null($summit)) return $this->error404(); + + $current_member_id = $this->resource_server_context->getCurrentUserExternalId(); + if (is_null($current_member_id)) return $this->error403(); + + $current_member = $this->repository->getById($current_member_id); + if (is_null($current_member)) return $this->error404(); + + $favorites = array(); + foreach ($current_member->getFavoritesSummitEvents() as $favorite_event) + { + if(!$summit->isEventOnSchedule($favorite_event->getId())) continue; + $favorites[] = SerializerRegistry::getInstance()->getSerializer($favorite_event)->serialize(); + } + + $response = new PagingResponse + ( + count($favorites), + count($favorites), + 1, + 1, + $favorites + ); + + return $this->ok($response->toArray($expand = Input::get('expand',''))); + } + 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(\HTTP401UnauthorizedException $ex3) + { + Log::warning($ex3); + return $this->error401(); + } + catch (\Exception $ex) + { + Log::error($ex); + return $this->error500($ex); + } + + } + + public function addEventToMemberFavorites($summit_id, $member_id, $event_id){ + + try { + $summit = SummitFinderStrategyFactory::build($this->summit_repository)->find($summit_id); + if (is_null($summit)) return $this->error404(); + + $current_member_id = $this->resource_server_context->getCurrentUserExternalId(); + if (is_null($current_member_id)) return $this->error403(); + + $current_member = $this->repository->getById($current_member_id); + if (is_null($current_member)) return $this->error404(); + + $this->summit_service->addEventToMemberFavorites($summit, $current_member, intval($event_id)); + + return $this->created(); + + } + 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(\HTTP401UnauthorizedException $ex3) + { + Log::warning($ex3); + return $this->error401(); + } + catch (\Exception $ex) + { + Log::error($ex); + return $this->error500($ex); + } + } + + public function removeEventFromMemberFavorites($summit_id, $member_id, $event_id){ + + try { + $summit = SummitFinderStrategyFactory::build($this->summit_repository)->find($summit_id); + if (is_null($summit)) return $this->error404(); + + $current_member_id = $this->resource_server_context->getCurrentUserExternalId(); + if (is_null($current_member_id)) return $this->error403(); + + $current_member = $this->repository->getById($current_member_id); + if (is_null($current_member)) return $this->error404(); + + $this->summit_service->removeEventFromMemberFavorites($summit, $current_member, intval($event_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(\HTTP401UnauthorizedException $ex3) + { + Log::warning($ex3); + return $this->error401(); + } + catch (\Exception $ex) + { + Log::error($ex); + return $this->error500($ex); + } + } } \ No newline at end of file diff --git a/app/Http/Middleware/RateLimitMiddleware.php b/app/Http/Middleware/RateLimitMiddleware.php index 085be5cf..4d323abc 100644 --- a/app/Http/Middleware/RateLimitMiddleware.php +++ b/app/Http/Middleware/RateLimitMiddleware.php @@ -43,7 +43,7 @@ final class RateLimitMiddleware public function __construct(IApiEndpointRepository $endpoint_repository, ICacheService $cache_service) { $this->endpoint_repository = $endpoint_repository; - $this->cache_service = $cache_service; + $this->cache_service = $cache_service; } /** diff --git a/app/Http/routes.php b/app/Http/routes.php index 441b4150..9cbbf20d 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -14,13 +14,13 @@ use Illuminate\Support\Facades\Config; //OAuth2 Protected API -Route::group(array( +Route::group([ 'namespace' => 'App\Http\Controllers', - 'prefix' => 'api/v1', - 'before' => [], - 'after' => [], + 'prefix' => 'api/v1', + 'before' => [], + 'after' => [], 'middleware' => ['ssl', 'oauth2.protected', 'rate.limit','etags'] -), function () { +], function () { Route::group(array('prefix' => 'marketplace'), function () { @@ -105,11 +105,11 @@ Route::group(array( Route::group(array('prefix' => '{attendee_id}'), function () { - Route::get('', 'OAuth2SummitAttendeesApiController@getAttendee')->where('attendee_id', 'me|[0-9]+'); + Route::get('', 'OAuth2SummitAttendeesApiController@getAttendee')->where('attendee_id', 'me'); Route::group(array('prefix' => 'schedule'), function () { - Route::get('', 'OAuth2SummitAttendeesApiController@getAttendeeSchedule')->where('attendee_id', 'me|[0-9]+'); + Route::get('', 'OAuth2SummitAttendeesApiController@getAttendeeSchedule')->where('attendee_id', 'me'); Route::group(array('prefix' => '{event_id}'), function (){ Route::post('', 'OAuth2SummitAttendeesApiController@addEventToAttendeeSchedule')->where('attendee_id', 'me|[0-9]+'); @@ -198,8 +198,17 @@ Route::group(array( // member Route::group(array('prefix' => 'members'), function () { - Route::group(array('prefix' => 'me'), function () { - Route::get('', 'OAuth2SummitMembersApiController@getMyMember'); + Route::group(array('prefix' => '{member_id}'), function () { + Route::get('', 'OAuth2SummitMembersApiController@getMyMember')->where('member_id', 'me'); + Route::group(array('prefix' => 'favorites'), function () + { + Route::get('', 'OAuth2SummitMembersApiController@getMemberFavoritesSummitEvents')->where('member_id', 'me'); + + Route::group(array('prefix' => '{event_id}'), function (){ + Route::post('', 'OAuth2SummitMembersApiController@addEventToMemberFavorites')->where('member_id', 'me'); + Route::delete('', 'OAuth2SummitMembersApiController@removeEventFromMemberFavorites')->where('member_id', 'me'); + }); + }); }); }); @@ -220,13 +229,13 @@ Route::group(array( }); //OAuth2 Protected API V2 -Route::group(array( - 'namespace' => 'App\Http\Controllers', - 'prefix' => 'api/v2', - 'before' => [], - 'after' => [], +Route::group([ + 'namespace' => 'App\Http\Controllers', + 'prefix' => 'api/v2', + 'before' => [], + 'after' => [], 'middleware' => ['ssl', 'oauth2.protected', 'rate.limit','etags'] -), function () { +], function () { // summits Route::group(array('prefix' => 'summits'), function () { diff --git a/app/ModelSerializers/AbstractMemberSerializer.php b/app/ModelSerializers/AbstractMemberSerializer.php new file mode 100644 index 00000000..391cf724 --- /dev/null +++ b/app/ModelSerializers/AbstractMemberSerializer.php @@ -0,0 +1,91 @@ + 'first_name:json_string', + 'LastName' => 'last_name:json_string', + 'Gender' => 'gender:json_string', + 'Bio' => 'bio:json_string', + 'LinkedInProfile' => 'linked_in:json_string', + 'IrcHandle' => 'irc:json_string', + 'TwitterHandle' => 'twitter:json_string', + 'State' => 'state:json_string', + 'Country' => 'country:json_string', + ]; + + protected static $allowed_relations = [ + 'groups', + 'affiliations', + ]; + + /** + * @param null $expand + * @param array $fields + * @param array $relations + * @param array $params + * @return array + */ + public function serialize($expand = null, array $fields = array(), array $relations = array(), array $params = array()) + { + $member = $this->object; + if(!$member instanceof Member) return []; + + if(!count($relations)) $relations = $this->getAllowedRelations(); + + $values = parent::serialize($expand, $fields, $relations, $params); + $values['pic'] = Config::get("server.assets_base_url", 'https://www.openstack.org/'). 'profile_images/members/'. $member->getId(); + + if(in_array('groups', $relations)) + $values['groups'] = $member->getGroupsIds(); + + if(in_array('affiliations', $relations)){ + $res = []; + foreach ($member->getAffiliations() as $affiliation){ + $res[] = SerializerRegistry::getInstance() + ->getSerializer($affiliation) + ->serialize('organization'); + } + $values['affiliations'] = $res; + } + + if (!empty($expand)) { + $exp_expand = explode(',', $expand); + foreach ($exp_expand as $relation) { + switch (trim($relation)) { + case 'groups': { + if(!in_array('groups', $relations)) break; + $groups = []; + unset($values['groups']); + foreach ($member->getGroups() as $g) { + $groups[] = SerializerRegistry::getInstance()->getSerializer($g)->serialize(null, [], ['none']); + } + $values['groups'] = $groups; + } + break; + } + } + } + return $values; + } +} \ No newline at end of file diff --git a/app/ModelSerializers/ChatTeams/ChatTeamMemberSerializer.php b/app/ModelSerializers/ChatTeams/ChatTeamMemberSerializer.php index 920ebb27..a3a5e036 100644 --- a/app/ModelSerializers/ChatTeams/ChatTeamMemberSerializer.php +++ b/app/ModelSerializers/ChatTeams/ChatTeamMemberSerializer.php @@ -53,6 +53,14 @@ final class ChatTeamMemberSerializer extends SilverStripeSerializer } } break; + case 'team': { + if (isset($values['team_id'])) { + unset($values['team_id']); + + $values['team'] = SerializerRegistry::getInstance()->getSerializer($team_member->getTeam())->serialize(self::filterExpandByPrefix($expand, 'team.')); + } + } + break; } } } diff --git a/app/ModelSerializers/ChatTeams/ChatTeamSerializer.php b/app/ModelSerializers/ChatTeams/ChatTeamSerializer.php index 7d9d55c2..cf9dbc98 100644 --- a/app/ModelSerializers/ChatTeams/ChatTeamSerializer.php +++ b/app/ModelSerializers/ChatTeams/ChatTeamSerializer.php @@ -77,7 +77,7 @@ final class ChatTeamSerializer extends SilverStripeSerializer // add pending invitations $invitations = []; foreach($team->getInvitations() as $invitation){ - $invitations[] = SerializerRegistry::getInstance()->getSerializer($invitation)->serialize('inviter,invitee,'); + $invitations[] = SerializerRegistry::getInstance()->getSerializer($invitation)->serialize('inviter,invitee'); } $values['invitations'] = $invitations; } diff --git a/app/ModelSerializers/MemberSerializer.php b/app/ModelSerializers/OwnMemberSerializer.php similarity index 72% rename from app/ModelSerializers/MemberSerializer.php rename to app/ModelSerializers/OwnMemberSerializer.php index ca50df77..e1c4828a 100644 --- a/app/ModelSerializers/MemberSerializer.php +++ b/app/ModelSerializers/OwnMemberSerializer.php @@ -1,6 +1,6 @@ 'first_name:json_string', - 'LastName' => 'last_name:json_string', - 'Gender' => 'gender:json_string', - 'Bio' => 'bio:json_string', - 'LinkedInProfile' => 'linked_in:json_string', - 'IrcHandle' => 'irc:json_string', - 'TwitterHandle' => 'twitter:json_string', - ]; protected static $allowed_relations = [ - - 'groups', + 'team_memberships', 'groups_events', - 'feedback', - 'affiliations', + 'favorite_summit_events' ]; private static $expand_group_events = [ @@ -64,16 +50,11 @@ final class MemberSerializer extends SilverStripeSerializer if(!count($relations)) $relations = $this->getAllowedRelations(); $values = parent::serialize($expand, $fields, $relations, $params); - $values['pic'] = Config::get("server.assets_base_url", 'https://www.openstack.org/'). 'profile_images/members/'. $member->getId(); $summit = isset($params['summit'])? $params['summit'] :null; - $speaker = !is_null($summit)? $summit->getSpeakerByMember($member): null; $attendee = !is_null($summit)? $summit->getAttendeeByMember($member): null; $groups_events = !is_null($summit)? $summit->getGroupEventsFor($member): null; - if(in_array('groups', $relations)) - $values['groups'] = $member->getGroupsIds(); - if(!is_null($speaker)) $values['speaker_id'] = $speaker->getId(); @@ -90,14 +71,22 @@ final class MemberSerializer extends SilverStripeSerializer $values['groups_events'] = $res; } - if(in_array('affiliations', $relations)){ + if(in_array('team_memberships', $relations)){ $res = []; - foreach ($member->getAffiliations() as $affiliation){ + foreach ($member->getTeamMemberships() as $team_membership){ $res[] = SerializerRegistry::getInstance() - ->getSerializer($affiliation) - ->serialize('organization'); + ->getSerializer($team_membership) + ->serialize('team,team.member'); } - $values['affiliations'] = $res; + $values['team_memberships'] = $res; + } + + if(in_array('favorite_summit_events', $relations) && !is_null($summit)){ + $res = []; + foreach ($member->getFavoritesEventsIds($summit) as $event_id){ + $res[] = intval($event_id); + } + $values['favorite_summit_events'] = $res; } if (!empty($expand)) { @@ -131,20 +120,21 @@ final class MemberSerializer extends SilverStripeSerializer $values['feedback'] = $feedback; } break; - case 'groups': { - if(!in_array('groups', $relations)) break; - $groups = []; - unset($values['groups']); - foreach ($member->getGroups() as $g) { - $groups[] = SerializerRegistry::getInstance()->getSerializer($g)->serialize(null, [], ['none']); + case 'favorite_summit_events':{ + if(!in_array('favorite_summit_events', $relations)) break; + if(is_null($summit)) break; + $favorites = []; + foreach ($member->getFavoritesSummitEvents($summit) as $events){ + $favorites[] = SerializerRegistry::getInstance() + ->getSerializer($events) + ->serialize($expand); } - $values['groups'] = $groups; + $values['favorite_summit_events'] = $favorites; } break; } } } - return $values; } } \ No newline at end of file diff --git a/app/ModelSerializers/PublicMemberSerializer.php b/app/ModelSerializers/PublicMemberSerializer.php new file mode 100644 index 00000000..56fee439 --- /dev/null +++ b/app/ModelSerializers/PublicMemberSerializer.php @@ -0,0 +1,22 @@ +registry['SummitLocationImage'] = SummitLocationImageSerializer::class; // member - $this->registry['Member'] = MemberSerializer::class; + $this->registry['Member'] = [ + self::SerializerType_Public => PublicMemberSerializer::class, + self::SerializerType_Private => OwnMemberSerializer::class + ]; $this->registry['Group'] = GroupSerializer::class; $this->registry['Affiliation'] = AffiliationSerializer::class; $this->registry['Organization'] = OrganizationSerializer::class; @@ -105,15 +109,24 @@ final class SerializerRegistry /** * @param object $object + * @param string $type * @return IModelSerializer */ - public function getSerializer($object){ + public function getSerializer($object, $type = self::SerializerType_Public){ $reflect = new \ReflectionClass($object); $class = $reflect->getShortName(); if(!isset($this->registry[$class])) throw new \InvalidArgumentException('Serializer not found for '.$class); $serializer_class = $this->registry[$class]; + + if(is_array($serializer_class)){ + if(!isset($serializer_class[$type])) + throw new \InvalidArgumentException(sprintf('Serializer not found for %s , type %s', $class, $type)); + $serializer_class = $serializer_class[$type]; + } + + return new $serializer_class($object); } } \ No newline at end of file diff --git a/app/Models/Foundation/Main/Member.php b/app/Models/Foundation/Main/Member.php index cf33dd98..ca971d70 100644 --- a/app/Models/Foundation/Main/Member.php +++ b/app/Models/Foundation/Main/Member.php @@ -14,6 +14,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; +use models\exceptions\ValidationException; use models\summit\Summit; use models\summit\SummitEvent; use models\summit\SummitEventFeedback; @@ -28,11 +29,16 @@ use models\utils\SilverstripeBaseModel; */ class Member extends SilverstripeBaseModel { + /** + * Member constructor. + */ public function __construct(){ parent::__construct(); - $this->feedback = new ArrayCollection(); - $this->groups = new ArrayCollection(); - $this->affiliations = new ArrayCollection(); + $this->feedback = new ArrayCollection(); + $this->groups = new ArrayCollection(); + $this->affiliations = new ArrayCollection(); + $this->team_memberships = new ArrayCollection(); + $this->favorites_summit_events = new ArrayCollection(); } /** @@ -50,6 +56,22 @@ class Member extends SilverstripeBaseModel return $this->groups; } + /** + * @return ChatTeamMember[] + */ + public function getTeamMemberships() + { + return $this->team_memberships; + } + + /** + * @param ChatTeamMember[] $team_memberships + */ + public function setTeamMemberships($team_memberships) + { + $this->team_memberships = $team_memberships; + } + /** * @param mixed $groups */ @@ -68,6 +90,39 @@ class Member extends SilverstripeBaseModel */ private $groups; + + /** + * @ORM\OneToMany(targetEntity="ChatTeamMember", mappedBy="member", cascade={"persist"}, orphanRemoval=true) + * @var ChatTeamMember[] + */ + private $team_memberships; + + /** + * @return SummitEvent[] + */ + public function getFavoritesSummitEvents() + { + return $this->favorites_summit_events; + } + + /** + * @param SummitEvent[] $favorites_summit_events + */ + public function setFavoritesSummitEvents($favorites_summit_events) + { + $this->favorites_summit_events = $favorites_summit_events; + } + + /** + * @ORM\ManyToMany(targetEntity="models\summit\SummitEvent") + * @ORM\JoinTable(name="Member_FavoriteSummitEvents", + * joinColumns={@ORM\JoinColumn(name="MemberID", referencedColumnName="ID")}, + * inverseJoinColumns={@ORM\JoinColumn(name="SummitEventID", referencedColumnName="ID")} + * ) + * @var SummitEvent[] + */ + private $favorites_summit_events; + /** * @return string */ @@ -118,6 +173,82 @@ class Member extends SilverstripeBaseModel */ private $bio; + /** + * @ORM\Column(name="State", type="string") + * @var string + */ + private $state; + + /** + * @return string + */ + public function getState() + { + return $this->state; + } + + /** + * @param string $state + */ + public function setState($state) + { + $this->state = $state; + } + + /** + * @return string + */ + public function getCountry() + { + return $this->country; + } + + /** + * @param string $country + */ + public function setCountry($country) + { + $this->country = $country; + } + + /** + * @return string + */ + public function getSecondEmail() + { + return $this->second_email; + } + + /** + * @param string $second_email + */ + public function setSecondEmail($second_email) + { + $this->second_email = $second_email; + } + + /** + * @return string + */ + public function getThirdEmail() + { + return $this->third_email; + } + + /** + * @param string $third_email + */ + public function setThirdEmail($third_email) + { + $this->third_email = $third_email; + } + + /** + * @ORM\Column(name="Country", type="string") + * @var string + */ + private $country; + /** * @ORM\Column(name="Email", type="string") * @var string @@ -373,4 +504,73 @@ class Member extends SilverstripeBaseModel } return $codes; } + + /** + * @param SummitEvent $event + * @throws ValidationException + */ + public function addFavoriteSummitEvent(SummitEvent $event){ + if($this->isOnFavorite($event)) + throw new ValidationException + ( + sprintf('Event %s already belongs to member %s favorites.', $event->getId(), $this->getId()) + ); + if(!$event->isPublished()) + throw new ValidationException + ( + sprintf('Event %s is not published', $event->getId()) + ); + $this->favorites_summit_events->add($event); + } + + /** + * @param SummitEvent $event + * @return bool + */ + public function isOnFavorite(SummitEvent $event){ + $sql = <<prepareRawSQL($sql); + $stmt->execute([ + 'member_id' => $this->getId(), + 'event_id' => $event->getId() + ]); + $res = $stmt->fetchAll(\PDO::FETCH_COLUMN); + return count($res) > 0 ? intval($res[0]) > 0 : false; + } + + /** + * @param SummitEvent $event + * @throws ValidationException + */ + public function removeFavoriteSummitEvent(SummitEvent $event){ + if(!$this->isOnFavorite($event)){ + throw new ValidationException + ( + sprintf('Event %s does not belongs to member %s favorites.', $event->getId(), $this->getId()) + ); + } + + $this->favorites_summit_events->removeElement($event); + } + + /** + * @return int[] + */ + public function getFavoritesEventsIds(){ + $sql = <<prepareRawSQL($sql); + $stmt->execute(['member_id' => $this->getId()]); + return $stmt->fetchAll(\PDO::FETCH_COLUMN); + } } \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Attendees/SummitAttendee.php b/app/Models/Foundation/Summit/Attendees/SummitAttendee.php index e436ff5c..66d6e74c 100644 --- a/app/Models/Foundation/Summit/Attendees/SummitAttendee.php +++ b/app/Models/Foundation/Summit/Attendees/SummitAttendee.php @@ -179,6 +179,12 @@ class SummitAttendee extends SilverstripeBaseModel sprintf('Event %s already belongs to attendee %s schedule.', $event->getId(), $this->getId()) ); + if(!$event->isPublished()) + throw new ValidationException + ( + sprintf('Event %s is not published', $event->getId()) + ); + $schedule = new SummitAttendeeSchedule; $schedule->setAttendee($this); diff --git a/app/Models/Foundation/Summit/EntityEvents/SummitEntityEventProcessContext.php b/app/Models/Foundation/Summit/EntityEvents/SummitEntityEventProcessContext.php index 8c1af3b7..a83859f7 100644 --- a/app/Models/Foundation/Summit/EntityEvents/SummitEntityEventProcessContext.php +++ b/app/Models/Foundation/Summit/EntityEvents/SummitEntityEventProcessContext.php @@ -156,8 +156,9 @@ final class SummitEntityEventProcessContext foreach ($ops as $op) { if (!is_null($last_idx)) unset($this->list[$last_idx]); - $last_op = $op['op']; - $last_idx = intval($op['idx']); + + $last_op = $op['op']; + $last_idx = intval($op['idx']); $must_insert = !$must_insert && $last_op === 'INSERT' ? true : $must_insert; } $last_op = $must_insert && $last_op !== 'DELETE' ? 'INSERT' : $last_op; diff --git a/app/Models/Foundation/Summit/EntityEvents/Types/EntityEventTypeFactory.php b/app/Models/Foundation/Summit/EntityEvents/Types/EntityEventTypeFactory.php index 02b06e08..aa894ddc 100644 --- a/app/Models/Foundation/Summit/EntityEvents/Types/EntityEventTypeFactory.php +++ b/app/Models/Foundation/Summit/EntityEvents/Types/EntityEventTypeFactory.php @@ -68,6 +68,7 @@ final class EntityEventTypeFactory } break; case 'MySchedule': + case 'MyFavorite': { return new MyScheduleEntityEventType($e, $ctx); } @@ -77,11 +78,6 @@ final class EntityEventTypeFactory return new SummitEntityEventType($e, $ctx); } break; - case 'SummitType': - { - return new SummitTypeEntityEventType($e, $ctx); - } - break; case 'SummitEventType': { return new SummitEventTypeEntityEventType($e, $ctx); diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 1440b82f..a39aa940 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -1,5 +1,6 @@ setEntityClassName('MySchedule'); $entity_event->setEntityId($event->getEventId()); @@ -58,10 +57,23 @@ class EventServiceProvider extends ServiceProvider $em->flush(); }); + Event::listen(\App\Events\MyFavoritesAdd::class, function($event) + { + $entity_event = new SummitEntityEvent; + $entity_event->setEntityClassName('MyFavorite'); + $entity_event->setEntityId($event->getEventId()); + $entity_event->setType('INSERT'); + $entity_event->setOwner($event->getMember()); + $entity_event->setSummit($event->getSummit()); + $entity_event->setMetadata(''); + + $em = Registry::getManager('ss'); + $em->persist($entity_event); + $em->flush(); + }); + Event::listen(\App\Events\MyScheduleRemove::class, function($event) { - if(!$event instanceof MyScheduleRemove) return; - $entity_event = new SummitEntityEvent; $entity_event->setEntityClassName('MySchedule'); $entity_event->setEntityId($event->getEventId()); @@ -76,10 +88,25 @@ class EventServiceProvider extends ServiceProvider }); + Event::listen(\App\Events\MyFavoritesRemove::class, function($event) + { + + $entity_event = new SummitEntityEvent; + $entity_event->setEntityClassName('MyFavorite'); + $entity_event->setEntityId($event->getEventId()); + $entity_event->setType('DELETE'); + $entity_event->setOwner($event->getMember()); + $entity_event->setSummit($event->getSummit()); + $entity_event->setMetadata(''); + + $em = Registry::getManager('ss'); + $em->persist($entity_event); + $em->flush(); + + }); + Event::listen(\App\Events\SummitEventCreated::class, function($event) { - if(!$event instanceof SummitEventCreated) return; - $resource_server_context = App::make(\models\oauth2\IResourceServerContext::class); $member_repository = App::make(\models\main\IMemberRepository::class); $owner_id = $resource_server_context->getCurrentUserExternalId(); diff --git a/app/Repositories/Summit/Doctrine/DoctrineSummitEntityEventRepository.php b/app/Repositories/Summit/Doctrine/DoctrineSummitEntityEventRepository.php index 74e36521..3ee3c140 100644 --- a/app/Repositories/Summit/Doctrine/DoctrineSummitEntityEventRepository.php +++ b/app/Repositories/Summit/Doctrine/DoctrineSummitEntityEventRepository.php @@ -39,11 +39,11 @@ final class DoctrineSummitEntityEventRepository public function getEntityEvents ( Summit $summit, - $member_id = null, - $from_id = null, + $member_id = null, + $from_id = null, DateTime $from_date = null, - $limit = 25, - $detach = true + $limit = 25, + $detach = true ) { $filters = ''; @@ -65,7 +65,7 @@ SELECT * FROM SELECT * FROM SummitEntityEvent WHERE ( - (EntityClassName <> 'MySchedule' AND EntityClassName <> 'SummitAttendee') + (EntityClassName <> 'MySchedule' AND EntityClassName <> 'SummitAttendee' AND EntityClassName <> 'MyFavorite') -- GLOBAL TRUNCATE OR (EntityClassName = 'WipeData' AND EntityID = 0) ) @@ -84,7 +84,7 @@ SELECT * FROM SELECT * FROM SummitEntityEvent WHERE ( - EntityClassName = 'MySchedule' + EntityClassName = 'MySchedule' OR EntityClassName = 'MyFavorite' AND OwnerID = {$member_id} ) AND SummitID = {$summit->getId()} diff --git a/app/Services/Model/ISummitService.php b/app/Services/Model/ISummitService.php index 858a7ca4..06831e45 100644 --- a/app/Services/Model/ISummitService.php +++ b/app/Services/Model/ISummitService.php @@ -1,9 +1,11 @@ tx_service->transaction(function () use ($summit, $member, $event_id) { + $event = $summit->getScheduleEvent($event_id); + if (is_null($event)) { + throw new EntityNotFoundException('event not found on summit!'); + } + if(!Summit::allowToSee($event, $member)) + throw new EntityNotFoundException('event not found on summit!'); + $member->addFavoriteSummitEvent($event); + }); + Event::fire(new MyFavoritesAdd($member, $summit, $event_id)); + } + catch (UniqueConstraintViolationException $ex){ + throw new ValidationException + ( + sprintf('Event %s already belongs to member %s favorites.', $event_id, $member->getId()) + ); + } + } + /** * @param Summit $summit * @param SummitAttendee $attendee @@ -203,6 +234,24 @@ final class SummitService implements ISummitService Event::fire(new MyScheduleRemove($attendee, $event_id)); } + + /** + * @param Summit $summit + * @param Member $member + * @param int $event_id + * @throws EntityNotFoundException + */ + public function removeEventFromMemberFavorites(Summit $summit, Member $member, $event_id){ + $this->tx_service->transaction(function () use ($summit, $member, $event_id) { + $event = $summit->getScheduleEvent($event_id); + if (is_null($event)) + throw new EntityNotFoundException('event not found on summit!'); + $member->removeFavoriteSummitEvent($event); + }); + + Event::fire(new MyFavoritesRemove($member, $summit, $event_id)); + } + /** * @param Summit $summit * @param SummitEvent $event diff --git a/database/seeds/ApiEndpointsSeeder.php b/database/seeds/ApiEndpointsSeeder.php index 7c10884a..d7ad0b2d 100644 --- a/database/seeds/ApiEndpointsSeeder.php +++ b/database/seeds/ApiEndpointsSeeder.php @@ -453,10 +453,28 @@ class ApiEndpointsSeeder extends Seeder //members array( 'name' => 'get-own-member', - 'route' => '/api/v1/summits/{id}/members/me', + 'route' => '/api/v1/summits/{id}/members/{member_id}', 'http_method' => 'GET', 'scopes' => [sprintf('%s/me/read', $current_realm)], ), + array( + 'name' => 'get-own-member-favorites', + 'route' => '/api/v1/summits/{id}/members/{member_id}/favorites', + 'http_method' => 'GET', + 'scopes' => [sprintf('%s/me/read', $current_realm)], + ), + array( + 'name' => 'add-2-own-member-favorites', + 'route' => '/api/v1/summits/{id}/members/{member_id}/favorites/{event_id}', + 'http_method' => 'POST', + 'scopes' => [sprintf('%s/me/summits/events/favorites/add', $current_realm)], + ), + array( + 'name' => 'remove-from-own-member-favorites', + 'route' => '/api/v1/summits/{id}/members/{member_id}/favorites/{event_id}', + 'http_method' => 'DELETE', + 'scopes' => [sprintf('%s/me/summits/events/favorites/delete', $current_realm)], + ), // notifications array( 'name' => 'get-notifications', diff --git a/database/seeds/ApiScopesSeeder.php b/database/seeds/ApiScopesSeeder.php index c17a8674..a8cef6dd 100644 --- a/database/seeds/ApiScopesSeeder.php +++ b/database/seeds/ApiScopesSeeder.php @@ -105,8 +105,18 @@ final class ApiScopesSeeder extends Seeder ), array( 'name' => sprintf('%s/me/read', $current_realm), - 'short_description' => 'Get own member data', - 'description' => 'Grants read only access for our own member data', + 'short_description' => 'Get own summit member data', + 'description' => 'Grants read only access for our own summit member data', + ), + array( + 'name' => sprintf('%s/me/summits/events/favorites/add', $current_realm), + 'short_description' => 'Allows to add Summit events as favorite', + 'description' => 'Allows to add Summit events as favorite', + ), + array( + 'name' => sprintf('%s/me/summits/events/favorites/delete', $current_realm), + 'short_description' => 'Allows to remove Summit events as favorite', + 'description' => 'Allows to remove Summit events as favorite', ), array( 'name' => sprintf('%s/summits/write', $current_realm), diff --git a/tests/OAuth2MembersApiTest.php b/tests/OAuth2MembersApiTest.php index 82565998..c66ece9f 100644 --- a/tests/OAuth2MembersApiTest.php +++ b/tests/OAuth2MembersApiTest.php @@ -22,7 +22,7 @@ final class OAuth2MembersApiTest extends ProtectedApiTest //AND FILTER 'filter' => 'first_name=@Seba', 'filter' => 'last_name=@Marcet', - 'order' => '+first_name,-last_name' + 'order' => '+first_name,-last_name' ]; $headers = array("HTTP_Authorization" => " Bearer " . $this->access_token); diff --git a/tests/OAuth2SummitApiTest.php b/tests/OAuth2SummitApiTest.php index 300eecf4..12d94c44 100644 --- a/tests/OAuth2SummitApiTest.php +++ b/tests/OAuth2SummitApiTest.php @@ -109,7 +109,7 @@ final class OAuth2SummitApiTest extends ProtectedApiTest array(), $headers ); - $end = time(); + $end = time(); $delta = $end - $start; echo "execution call " . $delta . " seconds ..."; $content = $response->getContent(); @@ -1216,7 +1216,7 @@ final class OAuth2SummitApiTest extends ProtectedApiTest { $params = array ( - 'id' => 'current', + 'id' => '7', 'from_date' => 1460148342, 'limit' => 100 ); @@ -1278,7 +1278,7 @@ final class OAuth2SummitApiTest extends ProtectedApiTest $this->assertTrue(!is_null($events)); } - public function testGetEntityEventsFromCurrentSummitGreaterThanGivenID($summit_id = 7, $last_event_id = 665707) + public function testGetEntityEventsFromCurrentSummitGreaterThanGivenID($summit_id = 7, $last_event_id = 702471) { $params = array ( @@ -1997,32 +1997,6 @@ final class OAuth2SummitApiTest extends ProtectedApiTest } - public function testGetMyMemberFromCurrentSummit() - { - - $params = [ - - 'expand' => 'attendee,speaker,feedback,groups, presentations', - 'id' => 7, - ]; - - $headers = array("HTTP_Authorization" => " Bearer " . $this->access_token); - $response = $this->action( - "GET", - "OAuth2SummitMembersApiController@getMyMember", - $params, - array(), - array(), - array(), - $headers - ); - - $content = $response->getContent(); - $this->assertResponseStatus(200); - $member = json_decode($content); - $this->assertTrue(!is_null($member)); - } - public function testGetSummitNotifications() { @@ -2134,4 +2108,101 @@ final class OAuth2SummitApiTest extends ProtectedApiTest $events = json_decode($content); $this->assertTrue(!is_null($events)); } + + public function testAdd2Favorite($summit_id = 7, $event_id = 14964){ + $params = array + ( + 'id' => $summit_id, + 'member_id' => 'me', + 'event_id' => $event_id + ); + + $headers = array("HTTP_Authorization" => " Bearer " . $this->access_token); + $response = $this->action( + "POST", + "OAuth2SummitMembersApiController@addEventToMemberFavorites", + $params, + array(), + array(), + array(), + $headers + ); + $content = $response->getContent(); + $this->assertResponseStatus(201); + } + + public function testRemoveFromFavorites($summit_id = 7, $event_id = 14964){ + + $params = array + ( + 'id' => $summit_id, + 'member_id' => 'me', + 'event_id' => $event_id + ); + + $headers = array("HTTP_Authorization" => " Bearer " . $this->access_token); + $response = $this->action( + "DELETE", + "OAuth2SummitMembersApiController@removeEventFromMemberFavorites", + $params, + array(), + array(), + array(), + $headers + ); + $content = $response->getContent(); + $this->assertResponseStatus(204); + } + + public function testGetMyFavorites(){ + + $params = [ + + 'member_id' => 'me', + 'id' => 7, + ]; + + $headers = array("HTTP_Authorization" => " Bearer " . $this->access_token); + $response = $this->action( + "GET", + "OAuth2SummitMembersApiController@getMemberFavoritesSummitEvents", + $params, + array(), + array(), + array(), + $headers + ); + + $content = $response->getContent(); + $this->assertResponseStatus(200); + $favorites = json_decode($content); + $this->assertTrue(!is_null($favorites)); + } + + public function testGetMyMemberFromCurrentSummit() + { + + $params = [ + + 'expand' => 'attendee,speaker,feedback,groups,presentations', + 'member_id' => 'me', + 'id' => 7, + ]; + + $headers = array("HTTP_Authorization" => " Bearer " . $this->access_token); + $response = $this->action( + "GET", + "OAuth2SummitMembersApiController@getMyMember", + $params, + array(), + array(), + array(), + $headers + ); + + $content = $response->getContent(); + $this->assertResponseStatus(200); + $member = json_decode($content); + $this->assertTrue(!is_null($member)); + } } \ No newline at end of file diff --git a/tests/ProtectedApiTest.php b/tests/ProtectedApiTest.php index 7cae16f2..e5f44f20 100644 --- a/tests/ProtectedApiTest.php +++ b/tests/ProtectedApiTest.php @@ -53,9 +53,11 @@ class AccessTokenServiceStub implements IAccessTokenService $url . '/members/invitations/write', $url . '/teams/read', $url . '/teams/write', + $url . '/me/summits/events/favorites/add', + $url . '/me/summits/events/favorites/delete', ); - return AccessToken::createFromParams('123456789', implode(' ', $scopes), '1', $realm, '1','11624', 3600, 'WEB_APPLICATION', '', ''); + return AccessToken::createFromParams('123456789', implode(' ', $scopes), '1', $realm, '1','13867', 3600, 'WEB_APPLICATION', '', ''); } } @@ -93,6 +95,8 @@ class AccessTokenServiceStub2 implements IAccessTokenService $url . '/members/invitations/write', $url . '/teams/read', $url . '/teams/write', + $url . '/me/summits/events/favorites/add', + $url . '/me/summits/events/favorites/delete', ); return AccessToken::createFromParams('123456789', implode(' ', $scopes), '1', $realm, null,null, 3600, 'SERVICE', '', '');