diff --git a/app/Http/Controllers/Apis/Protected/Main/OAuth2UserStoriesApiController.php b/app/Http/Controllers/Apis/Protected/Main/OAuth2UserStoriesApiController.php new file mode 100644 index 000000000..a788b903b --- /dev/null +++ b/app/Http/Controllers/Apis/Protected/Main/OAuth2UserStoriesApiController.php @@ -0,0 +1,81 @@ +repository = $repository; + } + + /** + * @return mixed + */ + public function getAllUserStories() + { + return $this->_getAll( + function () { + return [ + 'name' => ['=@', '==', '@@'], + ]; + }, + function () { + return [ + 'name' => 'sometimes|string', + ]; + }, + function () { + return [ + 'name', + 'id', + ]; + }, + function ($filter) { + return $filter; + }, + function () { + return $this->getEntitySerializerType(); + } + ); + } + + protected function getEntitySerializerType(): string + { + $currentUser = $this->resource_server_context->getCurrentUser(); + return !is_null($currentUser) ? SerializerRegistry::SerializerType_Private : + SerializerRegistry::SerializerType_Public; + } +} \ No newline at end of file diff --git a/app/ModelSerializers/ContinentSerializer.php b/app/ModelSerializers/ContinentSerializer.php new file mode 100644 index 000000000..dd5cc6467 --- /dev/null +++ b/app/ModelSerializers/ContinentSerializer.php @@ -0,0 +1,25 @@ + 'name:json_string' + ]; +} \ No newline at end of file diff --git a/app/ModelSerializers/SerializerRegistry.php b/app/ModelSerializers/SerializerRegistry.php index ba542b82f..4c8c454d5 100644 --- a/app/ModelSerializers/SerializerRegistry.php +++ b/app/ModelSerializers/SerializerRegistry.php @@ -17,6 +17,7 @@ use App\ModelSerializers\Audit\SummitEventAuditLogSerializer; use App\ModelSerializers\CCLA\TeamSerializer; use App\ModelSerializers\Companies\BaseCompanySerializer; +use App\ModelSerializers\ContinentSerializer; use App\ModelSerializers\Elections\CandidateSerializer; use App\ModelSerializers\Elections\ElectionSerializer; use App\ModelSerializers\Elections\NominationSerializer; @@ -116,6 +117,8 @@ use App\ModelSerializers\Summit\TrackTagGroups\TrackTagGroupAllowedTagSerializer; use App\ModelSerializers\Summit\TrackTagGroups\TrackTagGroupSerializer; use App\ModelSerializers\SummitScheduleFilterElementConfigSerializer; +use App\ModelSerializers\UserStoriesIndustrySerializer; +use App\ModelSerializers\UserStorySerializer; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Log; use Libs\ModelSerializers\AbstractSerializer; @@ -658,6 +661,12 @@ private function __construct() // summit lead report setting $this->registry['SummitLeadReportSetting'] = SummitLeadReportSettingSerializer::class; + + // user stories + $this->registry['UserStory'] = UserStorySerializer::class; + $this->registry['UserStoriesIndustry'] = UserStoriesIndustrySerializer::class; + + $this->registry['Continent'] = ContinentSerializer::class; } /** diff --git a/app/ModelSerializers/UserStoriesIndustrySerializer.php b/app/ModelSerializers/UserStoriesIndustrySerializer.php new file mode 100644 index 000000000..14f209fc4 --- /dev/null +++ b/app/ModelSerializers/UserStoriesIndustrySerializer.php @@ -0,0 +1,27 @@ + 'name:json_string', + 'Active' => 'active:json_boolean', + ]; +} \ No newline at end of file diff --git a/app/ModelSerializers/UserStorySerializer.php b/app/ModelSerializers/UserStorySerializer.php new file mode 100644 index 000000000..e74f6753e --- /dev/null +++ b/app/ModelSerializers/UserStorySerializer.php @@ -0,0 +1,97 @@ + 'name:json_string', + 'Description' => 'description:json_string', + 'ShortDescription' => 'short_description:json_string', + 'Link' => 'link:json_string', + 'Active' => 'active:json_boolean', + 'MillionCoreClub' => 'is_million_core_club:json_boolean', + 'OrganizationId' => 'organization_id:json_int', + 'IndustryId' => 'industry_id:json_int', + 'LocationId' => 'location_id:json_int', + 'ImageId' => 'image_id:json_int', + ]; + + protected static $allowed_relations = [ + 'tags' + ]; + + /** + * @param null $expand + * @param array $fields + * @param array $relations + * @param array $params + * @return array + */ + public function serialize($expand = null, array $fields = [], array $relations = [], array $params = []) + { + $user_story = $this->object; + if(!$user_story instanceof UserStory) return []; + $values = parent::serialize($expand, $fields, $relations, $params); + + if(in_array('tags', $relations) && !isset($values['tags'])) { + $tags = []; + foreach ($user_story->getTags() as $tag) { + $tags[] = $tag->getId(); + } + $values['tags'] = $tags; + } + + return $values; + } + + protected static $expand_mappings = [ + 'organization' => [ + 'type' => One2ManyExpandSerializer::class, + 'original_attribute' => 'organization_id', + 'getter' => 'getOrganization', + 'has' => 'hasOrganization' + ], + 'industry' => [ + 'type' => One2ManyExpandSerializer::class, + 'original_attribute' => 'industry_id', + 'getter' => 'getIndustry', + 'has' => 'hasIndustry' + ], + 'location' => [ + 'type' => One2ManyExpandSerializer::class, + 'original_attribute' => 'location_id', + 'getter' => 'getLocation', + 'has' => 'hasLocation' + ], + 'image' => [ + 'type' => One2ManyExpandSerializer::class, + 'original_attribute' => 'image_id', + 'getter' => 'getImage', + 'has' => 'hasImage' + ], + 'tags' => [ + 'type' => Many2OneExpandSerializer::class, + 'getter' => 'getTags', + ], + ]; +} \ No newline at end of file diff --git a/app/Models/Foundation/Main/Continent.php b/app/Models/Foundation/Main/Continent.php new file mode 100644 index 000000000..d5e8c75c6 --- /dev/null +++ b/app/Models/Foundation/Main/Continent.php @@ -0,0 +1,37 @@ +name; + } +} \ No newline at end of file diff --git a/app/Models/Foundation/Main/Repositories/IUserStoryRepository.php b/app/Models/Foundation/Main/Repositories/IUserStoryRepository.php new file mode 100644 index 000000000..a85f23dba --- /dev/null +++ b/app/Models/Foundation/Main/Repositories/IUserStoryRepository.php @@ -0,0 +1,25 @@ +user_stories = new ArrayCollection(); + } + + /** + * @return mixed + */ + public function getName() + { + return $this->name; + } + + /** + * @param mixed $name + */ + public function setName($name): void + { + $this->name = $name; + } + + /** + * @return bool + */ + public function isActive(): bool + { + return $this->is_active; + } + + /** + * @param bool $is_active + */ + public function setIsActive(bool $is_active): void + { + $this->is_active = $is_active; + } + + /** + * @return ArrayCollection + */ + public function getUserStories(): ArrayCollection + { + return $this->user_stories; + } + + /** + * @param UserStory $user_story + */ + public function addUserStory(UserStory $user_story) + { + if ($this->user_stories->contains($user_story)) return; + $this->user_stories->add($user_story); + } + + public function clearUserStories() + { + $this->user_stories->clear(); + } +} \ No newline at end of file diff --git a/app/Models/Foundation/UserStories/UserStory.php b/app/Models/Foundation/UserStories/UserStory.php new file mode 100644 index 000000000..73e1a9cd1 --- /dev/null +++ b/app/Models/Foundation/UserStories/UserStory.php @@ -0,0 +1,344 @@ + 'organization', + 'getIndustryId' => 'industry', + 'getLocationId' => 'location', + 'getImageId' => 'image', + ]; + + protected $hasPropertyMappings = [ + 'hasOrganization' => 'organization', + 'hasIndustry' => 'industry', + 'hasLocation' => 'location', + 'hasImage' => 'image', + ]; + + /** + * @ORM\Column(name="Name", type="string") + */ + private $name; + /** + * @ORM\Column(name="Description", type="string") + */ + private $description; + /** + * @ORM\Column(name="ShortDescription", type="string") + */ + private $short_description; + /** + * @ORM\Column(name="Link", type="string") + */ + private $link; + + /** + * @ORM\Column(name="Active", type="boolean") + * @var bool + */ + private $is_active; + + /** + * @ORM\Column(name="MillionCoreClub", type="boolean") + * @var bool + */ + private $is_million_core_club; + + /** + * @ORM\ManyToOne(targetEntity="models\main\Organization") + * @ORM\JoinColumn(name="OrganizationID", referencedColumnName="ID") + * @var Organization + */ + private $organization; + + /** + * @ORM\ManyToOne(targetEntity="App\Models\Foundation\UserStories\UserStoriesIndustry", inversedBy="user_stories") + * @ORM\JoinColumn(name="IndustryID", referencedColumnName="ID") + * @var UserStoriesIndustry + */ + protected $industry; + + /** + * @ORM\ManyToOne(targetEntity="App\Models\Foundation\Main\Continent") + * @ORM\JoinColumn(name="LocationID", referencedColumnName="ID") + * @var Continent + */ + protected $location; + + /** + * @ORM\ManyToOne(targetEntity="models\main\File") + * @ORM\JoinColumn(name="ImageID", referencedColumnName="ID") + * @var File + */ + private $image; + + /** + * @ORM\ManyToMany(targetEntity="models\main\Tag", cascade={"persist"}, fetch="EXTRA_LAZY") + * @ORM\JoinTable(name="UserStoryDO_Tags", + * joinColumns={@ORM\JoinColumn(name="UserStoryDOID", referencedColumnName="ID")}, + * inverseJoinColumns={@ORM\JoinColumn(name="TagID", referencedColumnName="ID")} + * ) + * @var Tag[] + */ + private $tags; + + public function __construct() + { + parent::__construct(); + $this->tags = new ArrayCollection(); + } + + /** + * @return mixed + */ + public function getName() + { + return $this->name; + } + + /** + * @param mixed $name + */ + public function setName($name): void + { + $this->name = $name; + } + + /** + * @return mixed + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param mixed $description + */ + public function setDescription($description): void + { + $this->description = $description; + } + + /** + * @return mixed + */ + public function getShortDescription() + { + return $this->short_description; + } + + /** + * @param mixed $short_description + */ + public function setShortDescription($short_description): void + { + $this->short_description = $short_description; + } + + /** + * @return mixed + */ + public function getLink() + { + return $this->link; + } + + /** + * @param mixed $link + */ + public function setLink($link): void + { + $this->link = $link; + } + + /** + * @return bool + */ + public function isActive(): bool + { + return $this->is_active; + } + + /** + * @param bool $is_active + */ + public function setIsActive(bool $is_active): void + { + $this->is_active = $is_active; + } + + /** + * @return bool + */ + public function isMillionCoreClub(): bool + { + return $this->is_million_core_club; + } + + /** + * @param bool $is_million_core_club + */ + public function setIsMillionCoreClub(bool $is_million_core_club): void + { + $this->is_million_core_club = $is_million_core_club; + } + + /** + * @return Organization|null + */ + public function getOrganization(): ?Organization + { + return $this->organization; + } + + /** + * @param Organization $organization + */ + public function setOrganization(Organization $organization): void + { + $this->organization = $organization; + } + + public function clearOrganization(): void + { + $this->organization = null; + } + + /** + * @return UserStoriesIndustry|null + */ + public function getIndustry(): ?UserStoriesIndustry + { + return $this->industry; + } + + /** + * @param UserStoriesIndustry $industry + */ + public function setIndustry(UserStoriesIndustry $industry): void + { + $this->industry = $industry; + } + + public function clearIndustry(): void + { + $this->industry = null; + } + + /** + * @return File|null + */ + public function getImage(): ?File + { + return $this->image; + } + + /** + * @return bool + */ + public function hasImage(): bool + { + return $this->getImageId() > 0; + } + + /** + * @return int + */ + public function getImageId(): int + { + try{ + return !is_null($this->image) ? $this->image->getId() : 0; + } + catch(\Exception $ex){ + return 0; + } + } + + /** + * @param File $image + */ + public function setImage(File $image): void + { + $this->image = $image; + } + + public function clearImage(): void + { + $this->image = null; + } + + /** + * @return Continent|null + */ + public function getLocation(): ?Continent + { + return $this->location; + } + + /** + * @param Continent $continent + */ + public function setLocation(Continent $continent): void + { + $this->location = $continent; + } + + public function clearLocation(): void + { + $this->location = null; + } + + /** + * @return mixed + */ + public function getTags() + { + return $this->tags; + } + + /** + * @param Tag $tag + */ + public function addTag(Tag $tag) + { + if ($this->tags->contains($tag)) return; + $this->tags->add($tag); + } + + public function clearTags() + { + $this->tags->clear(); + } +} \ No newline at end of file diff --git a/app/Repositories/Main/DoctrineUserStoryRepository.php b/app/Repositories/Main/DoctrineUserStoryRepository.php new file mode 100644 index 000000000..376e9584e --- /dev/null +++ b/app/Repositories/Main/DoctrineUserStoryRepository.php @@ -0,0 +1,55 @@ + 'e.name:json_string', + ]; + } + + /** + * @return array + */ + protected function getOrderMappings() + { + return [ + 'id' => 'e.id', + 'name' => 'e.name', + ]; + } +} \ No newline at end of file diff --git a/app/Repositories/RepositoriesProvider.php b/app/Repositories/RepositoriesProvider.php index f46c493b1..d15311d75 100644 --- a/app/Repositories/RepositoriesProvider.php +++ b/app/Repositories/RepositoriesProvider.php @@ -21,6 +21,7 @@ use App\Models\Foundation\Main\Repositories\ISponsoredProjectRepository; use App\Models\Foundation\Main\Repositories\ISummitAdministratorPermissionGroupRepository; use App\Models\Foundation\Main\Repositories\ISupportingCompanyRepository; +use App\Models\Foundation\Main\Repositories\IUserStoryRepository; use App\Models\Foundation\Marketplace\ICompanyServiceRepository; use App\Models\Foundation\Software\OpenStackRelease; use App\Models\Foundation\Software\Repositories\IOpenStackReleaseRepository; @@ -106,6 +107,7 @@ use App\Models\Foundation\Summit\Speakers\PresentationSpeakerAssignment; use App\Models\Foundation\Summit\Speakers\SpeakerEditPermissionRequest; use App\Models\Foundation\Summit\TrackTagGroupAllowedTag; +use App\Models\Foundation\UserStories\UserStory; use App\Models\ResourceServer\IApiRepository; use Illuminate\Support\Facades\App; use Illuminate\Support\ServiceProvider; @@ -939,5 +941,12 @@ function (){ return EntityManager::getRepository(SummitRefundRequest::class); } ); + + App::singleton( + IUserStoryRepository::class, + function (){ + return EntityManager::getRepository(UserStory::class); + } + ); } } \ No newline at end of file diff --git a/database/migrations/model/Version20240419161537.php b/database/migrations/model/Version20240419161537.php new file mode 100644 index 000000000..ca0c06bf7 --- /dev/null +++ b/database/migrations/model/Version20240419161537.php @@ -0,0 +1,50 @@ +hasTable("UserStoryDO") && !$builder->hasColumns("UserStoryDO", ["MillionCoreClub"])) { + $builder->table("UserStoryDO", function (Table $table) { + $table->boolean("MillionCoreClub")->setDefault(false); + }); + } + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema): void + { + $builder = new Builder($schema); + if($builder->hasTable("UserStoryDO") && $builder->hasColumns("UserStoryDO", ["MillionCoreClub"])) { + $builder->table("UserStoryDO", function (Table $table) { + $table->dropColumn("MillionCoreClub"); + }); + } + } +} diff --git a/routes/public_api.php b/routes/public_api.php index b9d69a254..383c2db8e 100644 --- a/routes/public_api.php +++ b/routes/public_api.php @@ -296,4 +296,9 @@ Route::group(['prefix' => 'companies'], function () { Route::get('', 'OAuth2CompaniesApiController@getAllCompanies'); +}); + +// user stories +Route::group(array('prefix' => 'user-stories'), function () { + Route::get('', 'OAuth2UserStoriesApiController@getAllUserStories'); }); \ No newline at end of file diff --git a/tests/OAuth2UserStoriesApiTest.php b/tests/OAuth2UserStoriesApiTest.php new file mode 100644 index 000000000..6ff2ba037 --- /dev/null +++ b/tests/OAuth2UserStoriesApiTest.php @@ -0,0 +1,42 @@ + ['name=@Test US'], + 'order' => '-id', + 'expand' => 'industry, organization, location, image, tags' + ]; + + $headers = array("HTTP_Authorization" => " Bearer " . $this->access_token); + $response = $this->action( + "GET", + "OAuth2UserStoriesApiController@getAllUserStories", + $params, + array(), + array(), + array(), + $headers + ); + + $this->assertResponseStatus(200); + $content = $response->getContent(); + $user_stories = json_decode($content); + $this->assertTrue(!is_null($user_stories)); + } +} \ No newline at end of file