Added Membership endpoints for my member

sign foundation

scopes

REAL_URL/members/write/me

PUT /api/v1/members/me/membership/foundation

resign foundation membetship

PUT /api/v1/members/me/membership/community

scopes

REAL_URL/members/write/me

Change-Id: Ib636cf5149cb8b0f633243726c58d3e9329c7234
Signed-off-by: smarcet <smarcet@gmail.com>
This commit is contained in:
smarcet 2021-01-12 18:41:26 -03:00
parent 9a67080bce
commit bd94b62c12
10 changed files with 415 additions and 19 deletions

View File

@ -455,4 +455,72 @@ final class OAuth2MembersApiController extends OAuth2ProtectedController
}
}
/**
* @param $member_id
* @return mixed
*/
public function signFoundationMembership(){
try{
$member = $this->resource_server_context->getCurrentUser();
if(is_null($member)) return $this->error404();
$member = $this->member_service->signFoundationMembership($member);
return $this->updated(SerializerRegistry::getInstance()->getSerializer
(
$member,
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 $member_id
* @return mixed
*/
public function resignFoundationMembership(){
try{
$member = $this->resource_server_context->getCurrentUser();
if(is_null($member)) return $this->error404();
$member = $this->member_service->resignFoundationMembership($member);
return $this->updated(SerializerRegistry::getInstance()->getSerializer
(
$member,
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);
}
}
}

View File

@ -40,6 +40,11 @@ Route::group([
Route::delete('', [ 'uses' => 'OAuth2MembersApiController@deleteMyAffiliation']);
});
});
Route::group(['prefix' => 'membership'], function(){
Route::put('foundation', ['uses' => 'OAuth2MembersApiController@signFoundationMembership']);
Route::put('community', ['uses' => 'OAuth2MembersApiController@resignFoundationMembership']);
});
});
Route::group(['prefix'=>'{member_id}'], function(){

View File

@ -0,0 +1,120 @@
<?php namespace models\main;
/**
* Copyright 2021 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use Doctrine\ORM\Mapping AS ORM;
use models\utils\SilverstripeBaseModel;
/**
* @ORM\Entity
* @ORM\Table(name="`LegalAgreement`")
* Class LegalAgreement
* @package models\main
*/
class LegalAgreement extends SilverstripeBaseModel
{
const Slug = 'the-openstack-foundation-individual-member-agreement';
/**
* @ORM\Column(name="Signature", type="string")
* @var string
*/
private $signature;
/**
* @ORM\Column(name="LegalDocumentPageID", type="integer")
* @var int
*/
private $document_id;
/**
* @ORM\ManyToOne(targetEntity="models\main\Member", inversedBy="legal_agreements")
* @ORM\JoinColumn(name="MemberID", referencedColumnName="ID")
* @var Member
*/
private $owner;
/**
* @return string
*/
public function getSignature(): ?string
{
return $this->signature;
}
/**
* @param string $signature
*/
public function setSignature(string $signature): void
{
$this->signature = $signature;
}
/**
* @return int
*/
public function getDocumentId(): int
{
return $this->document_id;
}
/**
* @param int $document_id
*/
public function setDocumentId(int $document_id): void
{
$this->document_id = $document_id;
}
/**
* @return Member
*/
public function getOwner(): Member
{
return $this->owner;
}
/**
* @param Member $owner
*/
public function setOwner(Member $owner): void
{
$this->owner = $owner;
}
/**
* this is for legacy reasons
* @param string $slug
* @return int|null
*/
public static function getLegalAgreementIDBySlug(string $slug):?int {
try {
$sql = <<<SQL
select ID FROM SiteTree
where SiteTree.URLSegment = :url_segment AND SiteTree.ClassName = :class_name
SQL;
$stmt = self::prepareRawSQLStatic($sql);
$stmt->execute([
'url_segment' => trim($slug),
'class_name' => "LegalDocumentPage"
]);
$res = $stmt->fetchAll();
if(count($res) == 0 ) return null;
$id = intval($res[0]['ID']);
return $id;
} catch (\Exception $ex) {
return null;
}
return null;
}
}

View File

@ -30,7 +30,6 @@ use models\summit\PresentationSpeaker;
use models\summit\RSVP;
use models\summit\Sponsor;
use models\summit\Summit;
use models\summit\SummitAttendee;
use models\summit\SummitAttendeeTicket;
use models\summit\SummitEvent;
use models\summit\SummitEventFeedback;
@ -40,7 +39,6 @@ use models\utils\SilverstripeBaseModel;
use Doctrine\ORM\Mapping AS ORM;
/**
* @ORM\Entity
* @ORM\Table(name="`Member`")
* @ORM\Entity(repositoryClass="App\Repositories\Summit\DoctrineMemberRepository")
* Class Member
@ -97,6 +95,12 @@ class Member extends SilverstripeBaseModel
*/
private $affiliations;
/**
* @ORM\OneToMany(targetEntity="LegalAgreement", mappedBy="owner", cascade={"persist"}, orphanRemoval=true)
* @var LegalAgreement[]
*/
protected $legal_agreements;
/**
* @ORM\Column(name="Active", type="boolean")
* @var bool
@ -170,6 +174,12 @@ class Member extends SilverstripeBaseModel
*/
private $user_external_id;
/**
* @var \DateTime
* @ORM\Column(name="ResignDate", type="datetime")
*/
protected $resign_date;
/**
* @ORM\ManyToOne(targetEntity="models\main\File")
* @ORM\JoinColumn(name="PhotoID", referencedColumnName="ID")
@ -313,6 +323,7 @@ class Member extends SilverstripeBaseModel
$this->schedule_shareable_links = new ArrayCollection();
$this->summit_permission_groups = new ArrayCollection();
$this->summit_attendance_metrics = new ArrayCollection();
$this->legal_agreements = new ArrayCollection();
}
/**
@ -323,6 +334,13 @@ class Member extends SilverstripeBaseModel
return $this->affiliations;
}
/**
* @return ArrayCollection|LegalAgreement[]
*/
public function getLegalAgreements(){
return $this->legal_agreements;
}
/**
* @return Affiliation[]
*/
@ -1808,4 +1826,40 @@ SQL;
}
public function resignFoundationMembership(){
// Remove member from Foundation group
foreach ($this->groups as $g) {
if ($g->getCode() === IGroup::FoundationMembers) {
$this->removeFromGroup($g);
break;
}
}
// Remove Member's Legal Agreements
$this->legal_agreements->clear();
$this->membership_type = self::MembershipTypeCommunity;
$this->resign_date = new \DateTime('now', new \DateTimeZone(self::DefaultTimeZone));
}
public function signFoundationMembership()
{
if (!$this->isFoundationMember()) {
// Set up member with legal agreement for becoming an OpenStack Foundation Member
$legalAgreement = new LegalAgreement();
$legalAgreement->setOwner($this);
$documentId = LegalAgreement::getLegalAgreementIDBySlug(LegalAgreement::Slug);
if(is_null($documentId))
throw new ValidationException(sprintf("LegalAgreement %s does not exists.", LegalAgreement::Slug));
$legalAgreement->setDocumentId($documentId);
$this->legal_agreements->add($legalAgreement);
$this->membership_type = self::MembershipTypeFoundation;
$this->resign_date = null;
}
}
public function isFoundationMember()
{
return $this->belongsToGroup(IGroup::FoundationMembers) && $this->legal_agreements->count() > 0;
}
}

View File

@ -155,4 +155,11 @@ class ApiScope extends ResourceServerEntity implements IApiScope
* @var bool
*/
private $default;
public function __construct()
{
parent::__construct();
$this->active = false;
$this->default = false;
}
}

View File

@ -11,6 +11,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use models\exceptions\EntityNotFoundException;
use models\exceptions\ValidationException;
use models\main\Affiliation;
use models\main\Member;
/**
@ -86,4 +89,20 @@ interface IMemberService
* @throws \Exception
*/
public function emitRegistrationRequest(string $email, string $first_name, string $last_name):array;
/**
* @param Member $member
* @return Member
* @throws EntityNotFoundException
* @throws ValidationException
*/
public function signFoundationMembership(Member $member):Member;
/**
* @param Member $member
* @return Member
* @throws EntityNotFoundException
* @throws ValidationException
*/
public function resignFoundationMembership(Member $member):Member;
}

View File

@ -493,4 +493,44 @@ final class MemberService
$last_name
);
}
/**
* @inheritDoc
*/
public function signFoundationMembership(Member $member): Member
{
return $this->tx_service->transaction(function() use($member){
if($member->isFoundationMember())
throw new ValidationException(sprintf("Member %s is already a foundation member", $member->getId()));
$group = $this->group_repository->getBySlug(IGroup::FoundationMembers);
if(is_null($group))
throw new EntityNotFoundException(sprintf("Group %s not found", IGroup::FoundationMembers));
$member->add2Group($group);
$member->signFoundationMembership();
return $member;
});
}
/**
* @inheritDoc
*/
public function resignFoundationMembership(Member $member): Member
{
return $this->tx_service->transaction(function() use($member){
if(!$member->isFoundationMember())
throw new ValidationException(sprintf("Member %s is not a foundation member", $member->getId()));
$member->resignFoundationMembership();
$group = $this->group_repository->getBySlug(IGroup::CommunityMembers);
if(is_null($group))
throw new EntityNotFoundException(sprintf("Group %s not found", IGroup::CommunityMembers));
$member->add2Group($group);
return $member;
});
}
}

View File

@ -5443,6 +5443,19 @@ class ApiEndpointsSeeder extends Seeder
'http_method' => 'GET',
'scopes' => [sprintf('%s/members/read/me', $current_realm)],
],
// my membership
[
'name' => 'sign-foundation-membership',
'route' => '/api/v1/members/me/membership/foundation',
'http_method' => 'PUT',
'scopes' => [sprintf(MemberScopes::WriteMyMemberData, $current_realm)],
],
[
'name' => 'resign-foundation-membership',
'route' => '/api/v1/members/me/membership/community',
'http_method' => 'PUT',
'scopes' => [sprintf(MemberScopes::WriteMyMemberData, $current_realm)],
],
// my member affiliations
[
'name' => 'get-my-member-affiliations',

View File

@ -46,8 +46,8 @@ abstract class BrowserKitTestCase extends BaseTestCase
*/
protected function prepareForTests()
{
// Artisan::call('doctrine:migrations:migrate', ["--connection" => 'config']);
// Artisan::call('doctrine:migrations:migrate', ["--connection" => 'model']);
Artisan::call('doctrine:migrations:migrate', ["--connection" => 'config']);
Artisan::call('doctrine:migrations:migrate', ["--connection" => 'model']);
//Mail::pretend(true);
$this->seed('TestSeeder');
}

View File

@ -14,6 +14,19 @@
**/
final class OAuth2MembersApiTest extends ProtectedApiTest
{
use InsertSummitTestData;
protected function setUp()
{
parent::setUp();
self::insertTestData();
}
public function tearDown()
{
self::clearTestData();
Mockery::close();
}
public function testGetMembers()
{
@ -352,21 +365,6 @@ final class OAuth2MembersApiTest extends ProtectedApiTest
$this->assertResponseStatus(200);
}
use InsertSummitTestData;
protected function setUp()
{
parent::setUp();
self::insertTestData();
}
public function tearDown()
{
self::clearTestData();
Mockery::close();
}
public function testGetAllSummitPermissions(){
self::$member2->addSummitEditPermission(self::$summit);
self::$member2->addSummitEditPermission(self::$summit2);
@ -391,4 +389,76 @@ final class OAuth2MembersApiTest extends ProtectedApiTest
$this->assertTrue(!is_null($pemissions));
$this->assertResponseStatus(200);
}
public function testSignFoundationMembership(){
$params = [
'member_id' => 'me',
];
$headers = [
"HTTP_Authorization" => " Bearer " . $this->access_token,
"CONTENT_TYPE" => "application/json"
];
$response = $this->action(
"PUT",
"OAuth2MembersApiController@signFoundationMembership",
$params,
[],
[],
[],
$headers,
""
);
$content = $response->getContent();
$this->assertResponseStatus(201);
$member = json_decode($content);
$this->assertTrue(!is_null($member));
return $member;
}
public function testSignResignFoundationMembership(){
$params = [
'member_id' => 'me',
];
$headers = [
"HTTP_Authorization" => " Bearer " . $this->access_token,
"CONTENT_TYPE" => "application/json"
];
$response = $this->action(
"PUT",
"OAuth2MembersApiController@signFoundationMembership",
$params,
[],
[],
[],
$headers,
""
);
$content = $response->getContent();
$this->assertResponseStatus(201);
$member = json_decode($content);
$this->assertTrue(!is_null($member));
$response = $this->action(
"PUT",
"OAuth2MembersApiController@resignFoundationMembership",
$params,
[],
[],
[],
$headers,
""
);
$content = $response->getContent();
$this->assertResponseStatus(201);
$member = json_decode($content);
$this->assertTrue(!is_null($member));
return $member;
}
}