From 4dd14e812b1c2b17d4489d2168b13756d3b7fc56 Mon Sep 17 00:00:00 2001 From: romanetar Date: Tue, 16 Jun 2026 17:22:54 +0200 Subject: [PATCH] feat: add-on types crud Signed-off-by: romanetar --- ...sorshipAddOnTypeValidationRulesFactory.php | 36 +++ ...ponsorshipAddOnsValidationRulesFactory.php | 13 +- ...mmitSponsorshipAddOnTypesApiController.php | 301 ++++++++++++++++++ .../OAuth2SummitSponsorshipsApiController.php | 11 +- app/ModelSerializers/SerializerRegistry.php | 2 + .../SummitSponsorshipAddOnSerializer.php | 10 +- .../SummitSponsorshipAddOnTypeSerializer.php | 26 ++ .../SummitSponsorshipAddOnFactory.php | 3 - .../SummitSponsorshipAddOnTypeFactory.php | 44 +++ .../ISummitSponsorshipAddOnRepository.php | 6 + .../ISummitSponsorshipAddOnTypeRepository.php | 29 ++ .../Summit/SummitSponsorshipAddOn.php | 53 ++- .../Summit/SummitSponsorshipAddOnType.php | 41 +++ app/Repositories/RepositoriesProvider.php | 9 + ...ctrineSummitSponsorshipAddOnRepository.php | 27 +- ...neSummitSponsorshipAddOnTypeRepository.php | 65 ++++ .../ISummitSponsorshipAddOnTypeService.php | 47 +++ .../Imp/SummitSponsorshipAddOnTypeService.php | 108 +++++++ .../Model/Imp/SummitSponsorshipService.php | 48 ++- app/Services/ModelServicesProvider.php | 10 +- .../config/Version20260615000000.php | 100 ++++++ .../model/Version20260615000000.php | 78 +++++ .../model/Version20260615000001.php | 110 +++++++ database/seeders/ApiEndpointsSeeder.php | 67 ++++ routes/api_v1.php | 12 + tests/InsertSummitTestData.php | 33 +- tests/Unit/Entities/SummitSponsorshipTest.php | 5 +- ...SponsorshipAddOnTypesApiControllerTest.php | 299 +++++++++++++++++ ...uth2SummitSponsorshipApiControllerTest.php | 6 +- 29 files changed, 1535 insertions(+), 64 deletions(-) create mode 100644 app/Http/Controllers/Apis/Protected/Summit/Factories/SummitSponsorshipAddOnTypeValidationRulesFactory.php create mode 100644 app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSponsorshipAddOnTypesApiController.php create mode 100644 app/ModelSerializers/Summit/SummitSponsorshipAddOnTypeSerializer.php create mode 100644 app/Models/Foundation/Summit/Factories/SummitSponsorshipAddOnTypeFactory.php create mode 100644 app/Models/Foundation/Summit/Repositories/ISummitSponsorshipAddOnTypeRepository.php create mode 100644 app/Models/Foundation/Summit/SummitSponsorshipAddOnType.php create mode 100644 app/Repositories/Summit/DoctrineSummitSponsorshipAddOnTypeRepository.php create mode 100644 app/Services/Model/ISummitSponsorshipAddOnTypeService.php create mode 100644 app/Services/Model/Imp/SummitSponsorshipAddOnTypeService.php create mode 100644 database/migrations/config/Version20260615000000.php create mode 100644 database/migrations/model/Version20260615000000.php create mode 100644 database/migrations/model/Version20260615000001.php create mode 100644 tests/oauth2/OAuth2SummitSponsorshipAddOnTypesApiControllerTest.php diff --git a/app/Http/Controllers/Apis/Protected/Summit/Factories/SummitSponsorshipAddOnTypeValidationRulesFactory.php b/app/Http/Controllers/Apis/Protected/Summit/Factories/SummitSponsorshipAddOnTypeValidationRulesFactory.php new file mode 100644 index 000000000..4f5764db6 --- /dev/null +++ b/app/Http/Controllers/Apis/Protected/Summit/Factories/SummitSponsorshipAddOnTypeValidationRulesFactory.php @@ -0,0 +1,36 @@ + 'required|string|max:255', + ]; + } + + public static function buildForUpdate(array $payload = []): array + { + return [ + 'name' => 'sometimes|string|max:255', + ]; + } +} diff --git a/app/Http/Controllers/Apis/Protected/Summit/Factories/SummitSponsorshipAddOnsValidationRulesFactory.php b/app/Http/Controllers/Apis/Protected/Summit/Factories/SummitSponsorshipAddOnsValidationRulesFactory.php index f0efeb80c..f3e2e0b09 100644 --- a/app/Http/Controllers/Apis/Protected/Summit/Factories/SummitSponsorshipAddOnsValidationRulesFactory.php +++ b/app/Http/Controllers/Apis/Protected/Summit/Factories/SummitSponsorshipAddOnsValidationRulesFactory.php @@ -12,7 +12,6 @@ * limitations under the License. **/ use App\Http\ValidationRulesFactories\AbstractValidationRulesFactory; -use models\summit\SummitSponsorshipAddOn; /** * Class SummitSponsorshipAddOnsValidationRulesFactory @@ -24,16 +23,18 @@ final class SummitSponsorshipAddOnsValidationRulesFactory extends AbstractValida public static function buildForAdd(array $payload = []): array { return [ - 'name' => 'required|string|max:255', - 'type' => 'required|string|in:'.join(',', SummitSponsorshipAddOn::ValidTypes), + 'name' => 'required|string|max:255', + 'type_id' => 'required_without:type|integer|prohibited_with:type', + 'type' => 'required_without:type_id|string|max:255|prohibited_with:type_id', ]; } public static function buildForUpdate(array $payload = []): array { return [ - 'name' => 'sometimes|string|max:255', - 'type' => 'sometimes|string|required_with:name|in:'.join(',', SummitSponsorshipAddOn::ValidTypes), + 'name' => 'sometimes|string|max:255', + 'type_id' => 'sometimes|integer|prohibited_with:type', + 'type' => 'sometimes|string|max:255|prohibited_with:type_id', ]; } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSponsorshipAddOnTypesApiController.php b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSponsorshipAddOnTypesApiController.php new file mode 100644 index 000000000..53b9b55c1 --- /dev/null +++ b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSponsorshipAddOnTypesApiController.php @@ -0,0 +1,301 @@ +repository = $repository; + $this->summit_repository = $summit_repository; + $this->service = $service; + } + + /** + * @return array + */ + protected function getFilterRules(): array + { + return [ + 'name' => ['==', '=@'], + ]; + } + + /** + * @return array + */ + protected function getFilterValidatorRules(): array + { + return [ + 'name' => 'sometimes|required|string', + ]; + } + + /** + * @return array + */ + protected function getOrderRules(): array + { + return [ + 'id', + 'name', + ]; + } + + + /** + * @return ISummitRepository + */ + protected function getSummitRepository(): ISummitRepository + { + return $this->summit_repository; + } + + #[OA\Get( + path: "/api/v1/summits/all/add-on-types", + summary: "Get all sponsorship add-on types", + operationId: 'getAddOnTypes', + x: [ + 'required-groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + ] + ], + security: [['summit_sponsorship_oauth2' => [ + SummitScopes::ReadSummitData, + SummitScopes::ReadAllSummitData, + ]]], + tags: ["Sponsorship Add-On Types"], + parameters: [ + new OA\Parameter(name: "page", description: "Page number", in: "query", required: false, schema: new OA\Schema(type: "integer", default: 1)), + new OA\Parameter(name: "per_page", description: "Items per page", in: "query", required: false, schema: new OA\Schema(type: "integer", default: 10)), + new OA\Parameter(name: "filter", description: "Filter query (name==value, name=@value)", in: "query", required: false, schema: new OA\Schema(type: "string")), + new OA\Parameter(name: "order", description: "Order by (+id, -name)", in: "query", required: false, schema: new OA\Schema(type: "string")), + ], + responses: [ + new OA\Response(response: Response::HTTP_OK, description: "OK"), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: "Unauthorized"), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: "Forbidden"), + new OA\Response(response: Response::HTTP_INTERNAL_SERVER_ERROR, description: "Server Error"), + ] + )] + + #[OA\Post( + path: "/api/v1/summits/all/add-on-types", + summary: "Add a new sponsorship add-on type", + operationId: 'addAddOnType', + x: [ + 'required-groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + ] + ], + security: [['summit_sponsorship_oauth2' => [ + SummitScopes::WriteSummitData, + ]]], + tags: ["Sponsorship Add-On Types"], + requestBody: new OA\RequestBody( + required: true, + content: new OA\JsonContent( + required: ["name"], + properties: [ + new OA\Property(property: "name", type: "string"), + ] + ) + ), + responses: [ + new OA\Response(response: Response::HTTP_CREATED, description: "Created"), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: "Unauthorized"), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: "Forbidden"), + new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: "Validation Error"), + new OA\Response(response: Response::HTTP_INTERNAL_SERVER_ERROR, description: "Server Error"), + ] + )] + public function add() + { + return $this->processRequest(function () { + $payload = $this->getJsonPayload( + SummitSponsorshipAddOnTypeValidationRulesFactory::buildForAdd(), + true + ); + + $type = $this->service->add($payload); + + return $this->created(SerializerRegistry::getInstance()->getSerializer($type)->serialize( + SerializerUtils::getExpand(), + SerializerUtils::getFields(), + SerializerUtils::getRelations() + )); + }); + } + + #[OA\Get( + path: "/api/v1/summits/all/add-on-types/{id}", + summary: "Get a sponsorship add-on type by id", + operationId: 'getAddOnType', + security: [['summit_sponsorship_oauth2' => [ + SummitScopes::ReadSummitData, + SummitScopes::ReadAllSummitData, + ]]], + tags: ["Sponsorship Add-On Types"], + parameters: [ + new OA\Parameter(name: "id", description: "Add-on Type ID", in: "path", required: true, schema: new OA\Schema(type: "integer")), + ], + responses: [ + new OA\Response(response: Response::HTTP_OK, description: "OK"), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: "Not Found"), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: "Unauthorized"), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: "Forbidden"), + new OA\Response(response: Response::HTTP_INTERNAL_SERVER_ERROR, description: "Server Error"), + ] + )] + public function get($id) + { + return $this->processRequest(function () use ($id) { + $type = $this->repository->getById(intval($id)); + if (is_null($type)) + return $this->error404(); + + return $this->ok(SerializerRegistry::getInstance()->getSerializer($type)->serialize( + SerializerUtils::getExpand(), + SerializerUtils::getFields(), + SerializerUtils::getRelations() + )); + }); + } + + #[OA\Put( + path: "/api/v1/summits/all/add-on-types/{id}", + summary: "Update a sponsorship add-on type", + operationId: 'updateAddOnType', + x: [ + 'required-groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + ] + ], + security: [['summit_sponsorship_oauth2' => [ + SummitScopes::WriteSummitData, + ]]], + tags: ["Sponsorship Add-On Types"], + parameters: [ + new OA\Parameter(name: "id", description: "Add-on Type ID", in: "path", required: true, schema: new OA\Schema(type: "integer")), + ], + requestBody: new OA\RequestBody( + required: true, + content: new OA\JsonContent( + properties: [ + new OA\Property(property: "name", type: "string"), + ] + ) + ), + responses: [ + new OA\Response(response: Response::HTTP_OK, description: "OK"), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: "Not Found"), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: "Unauthorized"), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: "Forbidden"), + new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: "Validation Error"), + new OA\Response(response: Response::HTTP_INTERNAL_SERVER_ERROR, description: "Server Error"), + ] + )] + public function update($id) + { + return $this->processRequest(function () use ($id) { + $payload = $this->getJsonPayload( + SummitSponsorshipAddOnTypeValidationRulesFactory::buildForUpdate(), + true + ); + + $type = $this->service->update(intval($id), $payload); + + return $this->updated(SerializerRegistry::getInstance()->getSerializer($type)->serialize( + SerializerUtils::getExpand(), + SerializerUtils::getFields(), + SerializerUtils::getRelations() + )); + }); + } + + #[OA\Delete( + path: "/api/v1/summits/all/add-on-types/{id}", + summary: "Delete a sponsorship add-on type", + operationId: 'deleteAddOnType', + x: [ + 'required-groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + ] + ], + security: [['summit_sponsorship_oauth2' => [ + SummitScopes::WriteSummitData, + ]]], + tags: ["Sponsorship Add-On Types"], + parameters: [ + new OA\Parameter(name: "id", description: "Add-on Type ID", in: "path", required: true, schema: new OA\Schema(type: "integer")), + ], + responses: [ + new OA\Response(response: Response::HTTP_NO_CONTENT, description: "No Content"), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: "Not Found"), + new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: "Unauthorized"), + new OA\Response(response: Response::HTTP_FORBIDDEN, description: "Forbidden"), + new OA\Response(response: Response::HTTP_INTERNAL_SERVER_ERROR, description: "Server Error"), + ] + )] + public function delete($id) + { + return $this->processRequest(function () use ($id) { + $this->service->delete(intval($id)); + return $this->deleted(); + }); + } +} diff --git a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSponsorshipsApiController.php b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSponsorshipsApiController.php index aa18eaff1..c88dfbb6d 100644 --- a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSponsorshipsApiController.php +++ b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSponsorshipsApiController.php @@ -409,21 +409,22 @@ public function getAllAddOns($summit_id, $sponsor_id, $sponsorship_id): mixed return $this->_getAll( function () { return [ - 'name' => Filter::buildStringDefaultOperators(), - 'type' => Filter::buildStringDefaultOperators(), + 'name' => Filter::buildStringDefaultOperators(), + 'type' => ['==', '=@'], + 'type_id' => ['=='], ]; }, function () { return [ - 'name' => 'sometimes|string', - 'type' => 'sometimes|string', + 'name' => 'sometimes|string', + 'type' => 'sometimes|string', + 'type_id' => 'sometimes|integer', ]; }, function () { return [ 'id', 'name', - 'type', ]; }, function ($filter) use ($summit, $sponsor, $sponsorship) { diff --git a/app/ModelSerializers/SerializerRegistry.php b/app/ModelSerializers/SerializerRegistry.php index 19944fdf6..15760f096 100644 --- a/app/ModelSerializers/SerializerRegistry.php +++ b/app/ModelSerializers/SerializerRegistry.php @@ -122,6 +122,7 @@ use App\ModelSerializers\Summit\SummitSchedulePreFilterElementConfigSerializer; use App\ModelSerializers\Summit\SummitSignSerializer; use App\ModelSerializers\Summit\SummitSponsorshipAddOnSerializer; +use App\ModelSerializers\Summit\SummitSponsorshipAddOnTypeSerializer; use App\ModelSerializers\Summit\SummitSponsorshipSerializer; use App\ModelSerializers\Summit\SummitSponsorshipTypeSerializer; use App\ModelSerializers\Summit\TrackTagGroups\TrackTagGroupAllowedTagSerializer; @@ -607,6 +608,7 @@ private function __construct() $this->registry['SponsorshipType'] = SponsorshipTypeSerializer::class; $this->registry['SummitSponsorship'] = SummitSponsorshipSerializer::class; $this->registry['SummitSponsorshipAddOn'] = SummitSponsorshipAddOnSerializer::class; + $this->registry['SummitSponsorshipAddOnType'] = SummitSponsorshipAddOnTypeSerializer::class; $this->registry['SummitSponsorshipType'] = SummitSponsorshipTypeSerializer::class; $this->registry['Sponsor'] = [ self::SerializerType_Public => SponsorSerializer::class, diff --git a/app/ModelSerializers/Summit/SummitSponsorshipAddOnSerializer.php b/app/ModelSerializers/Summit/SummitSponsorshipAddOnSerializer.php index 2885afa94..2bc40d0f7 100644 --- a/app/ModelSerializers/Summit/SummitSponsorshipAddOnSerializer.php +++ b/app/ModelSerializers/Summit/SummitSponsorshipAddOnSerializer.php @@ -23,12 +23,18 @@ final class SummitSponsorshipAddOnSerializer extends SilverStripeSerializer { protected static $array_mappings = [ - 'Name' => 'name:json_string', - 'Type' => 'type:json_string', + 'Name' => 'name:json_string', + 'TypeId' => 'type_id:json_int', 'SponsorshipId' => 'sponsorship_id:json_int', ]; protected static $expand_mappings = [ + 'type' => [ + 'type' => One2ManyExpandSerializer::class, + 'original_attribute' => 'type_id', + 'getter' => 'getType', + 'has' => 'hasType' + ], 'sponsorship' => [ 'type' => One2ManyExpandSerializer::class, 'original_attribute' => 'sponsorship_id', diff --git a/app/ModelSerializers/Summit/SummitSponsorshipAddOnTypeSerializer.php b/app/ModelSerializers/Summit/SummitSponsorshipAddOnTypeSerializer.php new file mode 100644 index 000000000..af7ae184d --- /dev/null +++ b/app/ModelSerializers/Summit/SummitSponsorshipAddOnTypeSerializer.php @@ -0,0 +1,26 @@ + 'name:json_string', + ]; +} diff --git a/app/Models/Foundation/Summit/Factories/SummitSponsorshipAddOnFactory.php b/app/Models/Foundation/Summit/Factories/SummitSponsorshipAddOnFactory.php index 2006b9d2e..a61bd7277 100644 --- a/app/Models/Foundation/Summit/Factories/SummitSponsorshipAddOnFactory.php +++ b/app/Models/Foundation/Summit/Factories/SummitSponsorshipAddOnFactory.php @@ -40,9 +40,6 @@ public static function populate(SummitSponsorshipAddOn $add_on, array $data): Su if(isset($data['name'])) $add_on->setName(trim($data['name'])); - if(isset($data['type'])) - $add_on->setType(trim($data['type'])); - return $add_on; } } \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Factories/SummitSponsorshipAddOnTypeFactory.php b/app/Models/Foundation/Summit/Factories/SummitSponsorshipAddOnTypeFactory.php new file mode 100644 index 000000000..9b80d1497 --- /dev/null +++ b/app/Models/Foundation/Summit/Factories/SummitSponsorshipAddOnTypeFactory.php @@ -0,0 +1,44 @@ +setName(trim($data['name'])); + + return $type; + } +} diff --git a/app/Models/Foundation/Summit/Repositories/ISummitSponsorshipAddOnRepository.php b/app/Models/Foundation/Summit/Repositories/ISummitSponsorshipAddOnRepository.php index 059f58b6f..a48b0e073 100644 --- a/app/Models/Foundation/Summit/Repositories/ISummitSponsorshipAddOnRepository.php +++ b/app/Models/Foundation/Summit/Repositories/ISummitSponsorshipAddOnRepository.php @@ -25,4 +25,10 @@ interface ISummitSponsorshipAddOnRepository extends IBaseRepository * @return array */ public function getMetadata(Summit $summit): array; + + /** + * @param int $type_id + * @return int + */ + public function countByAddOnType(int $type_id): int; } \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Repositories/ISummitSponsorshipAddOnTypeRepository.php b/app/Models/Foundation/Summit/Repositories/ISummitSponsorshipAddOnTypeRepository.php new file mode 100644 index 000000000..faa9b0fab --- /dev/null +++ b/app/Models/Foundation/Summit/Repositories/ISummitSponsorshipAddOnTypeRepository.php @@ -0,0 +1,29 @@ + 'sponsorship', + 'getTypeId' => 'type', ]; protected $hasPropertyMappings = [ 'hasSponsorship' => 'sponsorship', - ]; - - const Booth_Type = 'Booth'; - const MeetingRoom_Type = 'Meeting_Room'; - const ScheduleSpot_Type = 'Schedule_Spot'; - const SignageSpot_Type = 'Signage_Spot'; - - const ValidTypes = [ - self::Booth_Type, - self::MeetingRoom_Type, - self::ScheduleSpot_Type, - self::SignageSpot_Type, + 'hasType' => 'type', ]; /** * @var string */ - #[ORM\Column(name: 'Type', type: 'string')] - private $type; + #[ORM\Column(name: 'Name', type: 'string')] + private $name; /** - * @var string + * @var SummitSponsorshipAddOnType|null */ - #[ORM\Column(name: 'Name', type: 'string')] - private $name; + #[ORM\ManyToOne(targetEntity: SummitSponsorshipAddOnType::class, fetch: 'EXTRA_LAZY')] + #[ORM\JoinColumn(name: 'AddOnTypeID', referencedColumnName: 'ID', onDelete: 'SET NULL', nullable: true)] + private $type; - /** + /** * @var SummitSponsorship */ #[ORM\ManyToOne(targetEntity: SummitSponsorship::class, fetch: 'EXTRA_LAZY', inversedBy: 'add_ons')] #[ORM\JoinColumn(name: 'SponsorshipID', referencedColumnName: 'ID')] protected $sponsorship; - public static function getMetadata(): array + public function getName(): string { - return self::ValidTypes; + return $this->name; } - public function getType(): string + public function setName(string $name): void { - return $this->type; + $this->name = $name; } - /** - * @throws ValidationException - */ - public function setType(string $type): void + public function getType(): ?SummitSponsorshipAddOnType { - if(!in_array($type, self::ValidTypes)) - throw new ValidationException(sprintf("%s is not a valid type.", $type)); - $this->type = $type; + return $this->type; } - public function getName(): string + public function setType(SummitSponsorshipAddOnType $type): void { - return $this->name; + $this->type = $type; } - public function setName(string $name): void + public function clearType(): void { - $this->name = $name; + $this->type = null; } public function getSponsorship(): SummitSponsorship diff --git a/app/Models/Foundation/Summit/SummitSponsorshipAddOnType.php b/app/Models/Foundation/Summit/SummitSponsorshipAddOnType.php new file mode 100644 index 000000000..d72d699c3 --- /dev/null +++ b/app/Models/Foundation/Summit/SummitSponsorshipAddOnType.php @@ -0,0 +1,41 @@ +name; + } + + public function setName(string $name): void + { + $this->name = $name; + } +} diff --git a/app/Repositories/RepositoriesProvider.php b/app/Repositories/RepositoriesProvider.php index e3363ee83..1709c2332 100644 --- a/app/Repositories/RepositoriesProvider.php +++ b/app/Repositories/RepositoriesProvider.php @@ -101,6 +101,7 @@ use App\Models\Foundation\Summit\Repositories\ISummitSelectionPlanExtraQuestionTypeRepository; use App\Models\Foundation\Summit\Repositories\ISummitSignRepository; use App\Models\Foundation\Summit\Repositories\ISummitSponsorshipAddOnRepository; +use App\Models\Foundation\Summit\Repositories\ISummitSponsorshipAddOnTypeRepository; use App\Models\Foundation\Summit\Repositories\ISummitSponsorshipRepository; use App\Models\Foundation\Summit\Repositories\ISummitSponsorshipTypeRepository; use App\Models\Foundation\Summit\Repositories\ISummitSubmissionInvitationRepository; @@ -185,6 +186,7 @@ use models\summit\SummitScheduleConfig; use models\summit\SummitSponsorship; use models\summit\SummitSponsorshipAddOn; +use models\summit\SummitSponsorshipAddOnType; use models\summit\SummitSponsorshipType; use models\summit\SummitSubmissionInvitation; use models\summit\SummitTaxType; @@ -639,6 +641,13 @@ function () { } ); + App::singleton( + ISummitSponsorshipAddOnTypeRepository::class, + function () { + return EntityManager::getRepository(SummitSponsorshipAddOnType::class); + } + ); + App::singleton( ISummitRefundPolicyTypeRepository::class, function () { diff --git a/app/Repositories/Summit/DoctrineSummitSponsorshipAddOnRepository.php b/app/Repositories/Summit/DoctrineSummitSponsorshipAddOnRepository.php index 9f9727002..eed74df49 100644 --- a/app/Repositories/Summit/DoctrineSummitSponsorshipAddOnRepository.php +++ b/app/Repositories/Summit/DoctrineSummitSponsorshipAddOnRepository.php @@ -39,6 +39,7 @@ protected function applyExtraJoins(QueryBuilder $query, ?Filter $filter = null, $query->innerJoin("e.sponsorship", "sps"); $query->innerJoin("sps.sponsor", "sp"); $query->innerJoin("sp.summit", "s"); + $query->leftJoin("e.type", "at"); return $query; } @@ -48,12 +49,13 @@ protected function applyExtraJoins(QueryBuilder $query, ?Filter $filter = null, protected function getFilterMappings(): array { return [ - 'id' => new DoctrineFilterMapping("e.id :operator :value"), - 'name' => 'e.name', - 'type' => 'e.type', + 'id' => new DoctrineFilterMapping("e.id :operator :value"), + 'name' => 'e.name', + 'type' => 'at.name', + 'type_id' => 'at.id', 'sponsorship_id' => 'sps.id', - 'sponsor_id' => 'sp.id', - 'summit_id' => 's.id', + 'sponsor_id' => 'sp.id', + 'summit_id' => 's.id', ]; } @@ -65,7 +67,6 @@ protected function getOrderMappings(): array return [ 'id' => 'e.id', 'name' => 'e.name', - 'type' => 'e.type', ]; } @@ -83,6 +84,18 @@ protected function getBaseEntity(): string */ public function getMetadata(Summit $summit): array { - return SummitSponsorshipAddOn::getMetadata(); + return []; + } + + /** + * @param int $type_id + * @return int + */ + public function countByAddOnType(int $type_id): int + { + return (int) $this->getEntityManager() + ->createQuery('SELECT COUNT(e.id) FROM ' . SummitSponsorshipAddOn::class . ' e WHERE e.type = :type_id') + ->setParameter('type_id', $type_id) + ->getSingleScalarResult(); } } \ No newline at end of file diff --git a/app/Repositories/Summit/DoctrineSummitSponsorshipAddOnTypeRepository.php b/app/Repositories/Summit/DoctrineSummitSponsorshipAddOnTypeRepository.php new file mode 100644 index 000000000..c8b71d6a0 --- /dev/null +++ b/app/Repositories/Summit/DoctrineSummitSponsorshipAddOnTypeRepository.php @@ -0,0 +1,65 @@ + 'e.id', + 'name' => 'e.name:json_string', + ]; + } + + /** + * @return array + */ + protected function getOrderMappings(): array + { + return [ + 'id' => 'e.id', + 'name' => 'e.name', + ]; + } + + /** + * @param string $name + * @return SummitSponsorshipAddOnType|null + */ + public function getByName(string $name): ?SummitSponsorshipAddOnType + { + return $this->findOneBy(['name' => trim($name)]); + } +} diff --git a/app/Services/Model/ISummitSponsorshipAddOnTypeService.php b/app/Services/Model/ISummitSponsorshipAddOnTypeService.php new file mode 100644 index 000000000..77c0f00c8 --- /dev/null +++ b/app/Services/Model/ISummitSponsorshipAddOnTypeService.php @@ -0,0 +1,47 @@ +repository = $repository; + $this->add_on_repository = $add_on_repository; + } + + /** + * @inheritDoc + */ + public function add(array $payload): SummitSponsorshipAddOnType + { + return $this->tx_service->transaction(function () use ($payload) { + if (isset($payload['name'])) { + $existing = $this->repository->getByName(trim($payload['name'])); + if (!is_null($existing)) + throw new ValidationException(sprintf("AddOnType with name '%s' already exists.", $payload['name'])); + } + + $type = SummitSponsorshipAddOnTypeFactory::build($payload); + $this->repository->add($type); + return $type; + }); + } + + /** + * @inheritDoc + */ + public function update(int $type_id, array $payload): SummitSponsorshipAddOnType + { + return $this->tx_service->transaction(function () use ($type_id, $payload) { + $type = $this->repository->getById($type_id); + if (is_null($type)) + throw new EntityNotFoundException("AddOnType not found."); + + if (isset($payload['name'])) { + $existing = $this->repository->getByName(trim($payload['name'])); + if (!is_null($existing) && $existing->getId() !== $type->getId()) + throw new ValidationException(sprintf("AddOnType with name '%s' already exists.", $payload['name'])); + } + + return SummitSponsorshipAddOnTypeFactory::populate($type, $payload); + }); + } + + /** + * @inheritDoc + */ + public function delete(int $type_id): void + { + $this->tx_service->transaction(function () use ($type_id) { + $type = $this->repository->getById($type_id); + if (is_null($type)) + throw new EntityNotFoundException("AddOnType not found."); + + if ($this->add_on_repository->countByAddOnType($type_id) > 0) + throw new ValidationException(sprintf("AddOnType '%s' is assigned to one or more add-ons and cannot be deleted.", $type->getName())); + + $this->repository->delete($type); + }); + } +} diff --git a/app/Services/Model/Imp/SummitSponsorshipService.php b/app/Services/Model/Imp/SummitSponsorshipService.php index 829234818..d815238b8 100644 --- a/app/Services/Model/Imp/SummitSponsorshipService.php +++ b/app/Services/Model/Imp/SummitSponsorshipService.php @@ -19,13 +19,17 @@ use App\Events\SponsorServices\DeletedEventDTO; use App\Jobs\SponsorServices\PublishSponsorServiceDomainEventsJob; use App\Models\Foundation\Summit\Factories\SummitSponsorshipAddOnFactory; +use App\Models\Foundation\Summit\Repositories\ISummitSponsorshipAddOnTypeRepository; use App\Services\Model\AbstractService; use App\Services\Model\ISummitSponsorshipService; use Illuminate\Support\Facades\Log; +use libs\utils\ITransactionService; use models\exceptions\EntityNotFoundException; +use models\exceptions\ValidationException; use models\summit\Summit; use models\summit\SummitSponsorship; use models\summit\SummitSponsorshipAddOn; +use models\summit\SummitSponsorshipAddOnType; /** * Class SummitSponsorshipService @@ -33,6 +37,20 @@ */ final class SummitSponsorshipService extends AbstractService implements ISummitSponsorshipService { + /** + * @var ISummitSponsorshipAddOnTypeRepository + */ + private $add_on_type_repository; + + public function __construct( + ISummitSponsorshipAddOnTypeRepository $add_on_type_repository, + ITransactionService $tx_service + ) + { + parent::__construct($tx_service); + $this->add_on_type_repository = $add_on_type_repository; + } + /** * @inheritDoc */ @@ -117,6 +135,10 @@ public function addNewAddOn(Summit $summit, int $sponsor_id, int $sponsorship_id throw new EntityNotFoundException("Sponsorship {$sponsorship_id} not found for sponsor {$sponsor_id}."); $add_on = SummitSponsorshipAddOnFactory::build($payload); + + $type = $this->resolveAddOnType($payload); + if (!is_null($type)) $add_on->setType($type); + $sponsorship->addAddOn($add_on); return $add_on; @@ -172,7 +194,12 @@ public function updateAddOn(Summit $summit, int $sponsor_id, int $sponsorship_id if (is_null($add_on)) throw new EntityNotFoundException("AddOn {$add_on_id} not found for sponsorship {$sponsorship_id}."); - return SummitSponsorshipAddOnFactory::populate($add_on, $payload); + SummitSponsorshipAddOnFactory::populate($add_on, $payload); + + $type = $this->resolveAddOnType($payload); + if (!is_null($type)) $add_on->setType($type); + + return $add_on; }); PublishSponsorServiceDomainEventsJob::dispatch( @@ -229,4 +256,23 @@ public function removeAddOn(Summit $summit, int $sponsor_id, int $sponsorship_id DeletedEventDTO::fromEntity($add_on)->serialize(), SponsorDomainEvents::SponsorshipAddOnRemoved); } + + private function resolveAddOnType(array $payload): ?SummitSponsorshipAddOnType + { + if (isset($payload['type_id'])) { + $type = $this->add_on_type_repository->getById(intval($payload['type_id'])); + if (is_null($type)) + throw new ValidationException(sprintf("Invalid AddOnType %s.", $payload['type_id'])); + return $type; + } + + if (isset($payload['type'])) { + $type = $this->add_on_type_repository->getByName(trim($payload['type'])); + if (is_null($type)) + throw new ValidationException(sprintf("Invalid AddOnType '%s'.", $payload['type'])); + return $type; + } + + return null; + } } diff --git a/app/Services/ModelServicesProvider.php b/app/Services/ModelServicesProvider.php index 90eea8be5..8483f68f3 100644 --- a/app/Services/ModelServicesProvider.php +++ b/app/Services/ModelServicesProvider.php @@ -58,6 +58,7 @@ use App\Services\Model\Imp\SummitScheduleSettingsService; use App\Services\Model\Imp\SummitSelectedPresentationListService; use App\Services\Model\Imp\SummitSignService; +use App\Services\Model\Imp\SummitSponsorshipAddOnTypeService; use App\Services\Model\Imp\SummitSponsorshipTypeService; use App\Services\Model\Imp\SummitSubmissionInvitationService; use App\Services\Model\Imp\TrackChairRankingService; @@ -99,6 +100,7 @@ use App\Services\Model\ISummitSelectedPresentationListService; use App\Services\Model\ISummitSelectionPlanService; use App\Services\Model\ISummitSignService; +use App\Services\Model\ISummitSponsorshipAddOnTypeService; use App\Services\Model\ISummitSponsorshipService; use App\Services\Model\ISummitSponsorshipTypeService; use App\Services\Model\ISummitSubmissionInvitationService; @@ -346,6 +348,11 @@ public function register() SummitSponsorshipService::class ); + App::singleton( + ISummitSponsorshipAddOnTypeService::class, + SummitSponsorshipAddOnTypeService::class + ); + App::singleton(ISummitOrderService::class, SummitOrderService::class); App::singleton(ISponsorUserInfoGrantService::class, @@ -602,6 +609,7 @@ public function provides() ISponsorUserSyncService::class, ISummitRSVPService::class, ISummitRSVPInvitationService::class, + ISummitSponsorshipAddOnTypeService::class, ]; } -} \ No newline at end of file +} diff --git a/database/migrations/config/Version20260615000000.php b/database/migrations/config/Version20260615000000.php new file mode 100644 index 000000000..ecd6b50e5 --- /dev/null +++ b/database/migrations/config/Version20260615000000.php @@ -0,0 +1,100 @@ +addSql($this->insertEndpoint(self::API_NAME, self::ENDPOINT_GET_ALL, self::ROUTE_COLLECTION, 'GET')); + $this->addSql($this->insertEndpoint(self::API_NAME, self::ENDPOINT_ADD, self::ROUTE_COLLECTION, 'POST')); + $this->addSql($this->insertEndpoint(self::API_NAME, self::ENDPOINT_GET_ONE, self::ROUTE_ITEM, 'GET')); + $this->addSql($this->insertEndpoint(self::API_NAME, self::ENDPOINT_UPDATE, self::ROUTE_ITEM, 'PUT')); + $this->addSql($this->insertEndpoint(self::API_NAME, self::ENDPOINT_DELETE, self::ROUTE_ITEM, 'DELETE')); + + // Scope associations + foreach (self::READ_ENDPOINTS as $endpoint) { + $this->addSql($this->insertEndpointScope(self::API_NAME, $endpoint, SummitScopes::ReadSummitData)); + $this->addSql($this->insertEndpointScope(self::API_NAME, $endpoint, SummitScopes::ReadAllSummitData)); + } + + foreach (self::WRITE_ENDPOINTS as $endpoint) { + $this->addSql($this->insertEndpointScope(self::API_NAME, $endpoint, SummitScopes::WriteSummitData)); + } + + // Authz group associations + foreach (self::ALL_ENDPOINTS as $endpoint) { + foreach (self::AUTHZ_GROUPS as $group) { + $this->addSql($this->insertEndpointAuthzGroup(self::API_NAME, $endpoint, $group)); + } + } + } + + public function down(Schema $schema): void + { + foreach (self::ALL_ENDPOINTS as $endpoint) { + $this->addSql($this->deleteEndpoint(self::API_NAME, $endpoint)); + } + } +} diff --git a/database/migrations/model/Version20260615000000.php b/database/migrations/model/Version20260615000000.php new file mode 100644 index 000000000..dce5882b7 --- /dev/null +++ b/database/migrations/model/Version20260615000000.php @@ -0,0 +1,78 @@ +hasTable(self::AddOnTypeTable)) { + $builder->create(self::AddOnTypeTable, function (Table $table) { + $table->integer('ID', true, false); + $table->primary('ID'); + + $table->timestamp('Created')->setNotnull(false); + $table->timestamp('LastEdited')->setNotnull(false); + + $table->string('Name', 255)->setNotnull(true); + $table->unique('Name', 'UNIQ_SummitSponsorshipAddOnType_Name'); + }); + } + + if (!$builder->hasColumn(self::AddOnTable, 'AddOnTypeID')) { + $builder->table(self::AddOnTable, function (Table $table) { + $table->integer('AddOnTypeID', false, false)->setNotnull(false); + $table->index('AddOnTypeID', 'IDX_SummitSponsorshipAddOn_AddOnTypeID'); + }); + } + } + + public function down(Schema $schema): void + { + $builder = new Builder($schema); + + // Drop the FK-carrying column before the referenced table. + // By the time this runs, Version20260615000001 down() will have + // already dropped the FK constraint. + if ($builder->hasColumn(self::AddOnTable, 'AddOnTypeID')) { + $this->addSql('ALTER TABLE `' . self::AddOnTable . '` DROP COLUMN `AddOnTypeID`'); + } + + // Builder: schema diff runs after addSql() — table is dropped last. + $builder->dropIfExists(self::AddOnTypeTable); + } +} diff --git a/database/migrations/model/Version20260615000001.php b/database/migrations/model/Version20260615000001.php new file mode 100644 index 000000000..492a5380a --- /dev/null +++ b/database/migrations/model/Version20260615000001.php @@ -0,0 +1,110 @@ +hasTable(self::AddOnTypeTable)) { + // Upstream DDL migration did not run — skip rather than crash. + return; + } + + // Seed the four types that correspond to the old string constants. + $this->addSql(<<addSql(<<addSql(<<hasColumn(self::AddOnTable, 'Type')) { + $builder->table(self::AddOnTable, function (Table $table) { + $table->dropColumn('Type'); + }); + } + } + + public function down(Schema $schema): void + { + $builder = new Builder($schema); + + // Restore the Type column so the backfill UPDATE below has somewhere to write. + if (!$builder->hasColumn(self::AddOnTable, 'Type')) { + $this->addSql( + 'ALTER TABLE `SummitSponsorshipAddOn` ADD COLUMN `Type` VARCHAR(255) CHARACTER SET utf8 NULL DEFAULT NULL' + ); + } + + // Backfill Type from AddOnTypeID while the lookup table still exists. + $this->addSql(<<addSql( + 'ALTER TABLE `SummitSponsorshipAddOn` DROP FOREIGN KEY `' . self::FkName . '`' + ); + } +} diff --git a/database/seeders/ApiEndpointsSeeder.php b/database/seeders/ApiEndpointsSeeder.php index fb94f015d..de19f19e1 100644 --- a/database/seeders/ApiEndpointsSeeder.php +++ b/database/seeders/ApiEndpointsSeeder.php @@ -2840,6 +2840,73 @@ private function seedSummitEndpoints() IGroup::SummitAdministrators, ] ], + [ + 'name' => 'get-sponsorship-add-on-types', + 'route' => '/api/v1/summits/all/add-on-types', + 'http_method' => 'GET', + 'scopes' => [ + SummitScopes::ReadSummitData, + SummitScopes::ReadAllSummitData, + ], + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + ] + ], + [ + 'name' => 'add-sponsorship-add-on-type', + 'route' => '/api/v1/summits/all/add-on-types', + 'http_method' => 'POST', + 'scopes' => [ + SummitScopes::WriteSummitData, + ], + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + ] + ], + [ + 'name' => 'get-sponsorship-add-on-type', + 'route' => '/api/v1/summits/all/add-on-types/{id}', + 'http_method' => 'GET', + 'scopes' => [ + SummitScopes::ReadSummitData, + SummitScopes::ReadAllSummitData, + ], + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + ] + ], + [ + 'name' => 'update-sponsorship-add-on-type', + 'route' => '/api/v1/summits/all/add-on-types/{id}', + 'http_method' => 'PUT', + 'scopes' => [ + SummitScopes::WriteSummitData, + ], + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + ] + ], + [ + 'name' => 'delete-sponsorship-add-on-type', + 'route' => '/api/v1/summits/all/add-on-types/{id}', + 'http_method' => 'DELETE', + 'scopes' => [ + SummitScopes::WriteSummitData, + ], + 'authz_groups' => [ + IGroup::SuperAdmins, + IGroup::Administrators, + IGroup::SummitAdministrators, + ] + ], // [ 'name' => 'add-sponsor-user', diff --git a/routes/api_v1.php b/routes/api_v1.php index fad69a60c..a3fe715fe 100644 --- a/routes/api_v1.php +++ b/routes/api_v1.php @@ -175,6 +175,17 @@ Route::group(['prefix' => 'all'], function () { Route::get('', 'OAuth2SummitApiController@getAllSummits'); + + Route::group(['prefix' => 'add-on-types'], function () { + Route::get('', ['middleware' => 'auth.user', 'uses' => 'OAuth2SummitSponsorshipAddOnTypesApiController@getAll']); + Route::post('', ['middleware' => 'auth.user', 'uses' => 'OAuth2SummitSponsorshipAddOnTypesApiController@add']); + Route::group(['prefix' => '{id}'], function () { + Route::get('', ['middleware' => 'auth.user', 'uses' => 'OAuth2SummitSponsorshipAddOnTypesApiController@get']); + Route::put('', ['middleware' => 'auth.user', 'uses' => 'OAuth2SummitSponsorshipAddOnTypesApiController@update']); + Route::delete('', ['middleware' => 'auth.user', 'uses' => 'OAuth2SummitSponsorshipAddOnTypesApiController@delete']); + }); + }); + Route::group(['prefix' => '{id}'], function () { Route::get('', 'OAuth2SummitApiController@getAllSummitByIdOrSlug'); Route::group(['prefix' => 'registration-stats'], function () { @@ -239,6 +250,7 @@ }); }); + }); Route::post('', ['middleware' => 'auth.user', 'uses' => 'OAuth2SummitApiController@addSummit']); diff --git a/tests/InsertSummitTestData.php b/tests/InsertSummitTestData.php index 8f1feec7e..640e195c4 100644 --- a/tests/InsertSummitTestData.php +++ b/tests/InsertSummitTestData.php @@ -68,6 +68,7 @@ use models\summit\SummitRegistrationPromoCode; use models\summit\SummitSponsorship; use models\summit\SummitSponsorshipAddOn; +use models\summit\SummitSponsorshipAddOnType; use models\summit\SummitSponsorshipType; use models\summit\SummitTicketType; use models\summit\SummitVenue; @@ -256,6 +257,16 @@ trait InsertSummitTestData */ static $default_summit_sponsor_type2; + /** + * @var SummitSponsorshipAddOnType + */ + static $default_add_on_type_booth; + + /** + * @var SummitSponsorshipAddOnType + */ + static $default_add_on_type_meeting_room; + /** * @var array | SummitTicketType */ @@ -837,6 +848,14 @@ protected static function insertSummitTestData(){ self::$default_summit_sponsor_type2->setType(self::$default_sponsor_ship_type2); self::$summit->addSponsorshipType(self::$default_summit_sponsor_type2); + self::$default_add_on_type_booth = new SummitSponsorshipAddOnType(); + self::$default_add_on_type_booth->setName('Booth_' . str_random(6)); + self::$em->persist(self::$default_add_on_type_booth); + + self::$default_add_on_type_meeting_room = new SummitSponsorshipAddOnType(); + self::$default_add_on_type_meeting_room->setName('Meeting_Room_' . str_random(6)); + self::$em->persist(self::$default_add_on_type_meeting_room); + self::$companies = []; self::$sponsors = []; for($i = 0 ; $i < 20; $i++){ @@ -876,7 +895,7 @@ protected static function insertSummitTestData(){ for($j = 0; $j < 5; $j ++){ $a = new SummitSponsorshipAddOn(); - $a->setType($j < 3 ? SummitSponsorshipAddOn::Booth_Type : SummitSponsorshipAddOn::MeetingRoom_Type); + $a->setType($j < 3 ? self::$default_add_on_type_booth : self::$default_add_on_type_meeting_room); $a->setName(sprintf("AddOn %s %s", $j, str_random(4))); $sps->addAddOn($a); self::$em->persist($a); @@ -1011,6 +1030,16 @@ protected static function clearSummitTestData(){ if (!is_null(self::$default_media_file_type)) { self::$em->remove(self::$default_media_file_type); } + if (!is_null(self::$default_add_on_type_booth)) { + self::$default_add_on_type_booth = self::$em->find(SummitSponsorshipAddOnType::class, self::$default_add_on_type_booth->getId()); + if (!is_null(self::$default_add_on_type_booth)) + self::$em->remove(self::$default_add_on_type_booth); + } + if (!is_null(self::$default_add_on_type_meeting_room)) { + self::$default_add_on_type_meeting_room = self::$em->find(SummitSponsorshipAddOnType::class, self::$default_add_on_type_meeting_room->getId()); + if (!is_null(self::$default_add_on_type_meeting_room)) + self::$em->remove(self::$default_add_on_type_meeting_room); + } self::$em->flush(); // reset static vars @@ -1018,6 +1047,8 @@ protected static function clearSummitTestData(){ self::$summit = null; self::$default_badge_type = null; self::$summit2 = null; + self::$default_add_on_type_booth = null; + self::$default_add_on_type_meeting_room = null; self::$mainVenue = null; self::$defaultTags = []; self::$ticket_types = []; diff --git a/tests/Unit/Entities/SummitSponsorshipTest.php b/tests/Unit/Entities/SummitSponsorshipTest.php index 562254727..5b963f593 100644 --- a/tests/Unit/Entities/SummitSponsorshipTest.php +++ b/tests/Unit/Entities/SummitSponsorshipTest.php @@ -17,6 +17,7 @@ use models\summit\SummitSponsorship; use models\summit\SummitSponsorshipAddOn; +use models\summit\SummitSponsorshipAddOnType; use models\summit\SummitSponsorshipType; use Tests\InsertSummitTestData; use Tests\TestCase; @@ -62,7 +63,7 @@ public function testAddSummitSponsorship(){ // Create a new add-on $add_on = new SummitSponsorshipAddOn(); $add_on->setName("Test Add-On " . str_random(5)); - $add_on->setType(SummitSponsorshipAddOn::Booth_Type); + $add_on->setType(self::$default_add_on_type_booth); self::$em->persist($add_on); // Add the add-on (OneToMany relationship) @@ -118,7 +119,7 @@ public function testDeleteSummitSponsorshipChildren(){ // Create a new add-on $add_on = new SummitSponsorshipAddOn(); $add_on->setName("Test Add-On " . str_random(5)); - $add_on->setType(SummitSponsorshipAddOn::Booth_Type); + $add_on->setType(self::$default_add_on_type_booth); self::$em->persist($add_on); // Add the add-on (OneToMany relationship) diff --git a/tests/oauth2/OAuth2SummitSponsorshipAddOnTypesApiControllerTest.php b/tests/oauth2/OAuth2SummitSponsorshipAddOnTypesApiControllerTest.php new file mode 100644 index 000000000..fd8dd5fce --- /dev/null +++ b/tests/oauth2/OAuth2SummitSponsorshipAddOnTypesApiControllerTest.php @@ -0,0 +1,299 @@ +setName('Test_Type_' . str_random(6)); + self::$em->persist(self::$default_type); + self::$em->flush(); + } + + protected function tearDown(): void + { + if (!is_null(self::$default_type)) { + $em = self::$em->isOpen() + ? self::$em + : Registry::resetManager(SilverstripeBaseModel::EntityManager); + + $type = $em->find(SummitSponsorshipAddOnType::class, self::$default_type->getId()); + if (!is_null($type)) { + $em->remove($type); + $em->flush(); + } + self::$default_type = null; + } + self::clearSummitTestData(); + parent::tearDown(); + } + + public function testGetAll(): void + { + $response = $this->action( + "GET", + "OAuth2SummitSponsorshipAddOnTypesApiController@getAll", + [], + [], + [], + [], + $this->getAuthHeaders() + ); + + $content = $response->getContent(); + $this->assertResponseStatus(200); + $page = json_decode($content); + $this->assertNotNull($page); + $this->assertNotEmpty($page->data); + } + + public function testGetAllFilterByName(): void + { + $params = [ + 'filter' => ['name==' . self::$default_type->getName()], + ]; + + $response = $this->action( + "GET", + "OAuth2SummitSponsorshipAddOnTypesApiController@getAll", + $params, + [], + [], + [], + $this->getAuthHeaders() + ); + + $content = $response->getContent(); + $this->assertResponseStatus(200); + $page = json_decode($content); + $this->assertNotNull($page); + $this->assertCount(1, $page->data); + $this->assertEquals(self::$default_type->getName(), $page->data[0]->name); + } + + public function testGet(): void + { + $params = ['id' => self::$default_type->getId()]; + + $response = $this->action( + "GET", + "OAuth2SummitSponsorshipAddOnTypesApiController@get", + $params, + [], + [], + [], + $this->getAuthHeaders() + ); + + $content = $response->getContent(); + $this->assertResponseStatus(200); + $type = json_decode($content); + $this->assertNotNull($type); + $this->assertEquals(self::$default_type->getId(), $type->id); + $this->assertEquals(self::$default_type->getName(), $type->name); + } + + public function testGetNotFound(): void + { + $params = ['id' => PHP_INT_MAX]; + + $this->action( + "GET", + "OAuth2SummitSponsorshipAddOnTypesApiController@get", + $params, + [], + [], + [], + $this->getAuthHeaders() + ); + + $this->assertResponseStatus(404); + } + + public function testAdd(): void + { + $data = [ + 'name' => 'New_Type_' . str_random(6), + ]; + + $response = $this->action( + "POST", + "OAuth2SummitSponsorshipAddOnTypesApiController@add", + [], + [], + [], + [], + $this->getAuthHeaders(), + json_encode($data) + ); + + $content = $response->getContent(); + $this->assertResponseStatus(201); + $type = json_decode($content); + $this->assertNotNull($type); + $this->assertEquals($data['name'], $type->name); + } + + public function testAddDuplicateName(): void + { + $data = [ + 'name' => self::$default_type->getName(), + ]; + + $this->action( + "POST", + "OAuth2SummitSponsorshipAddOnTypesApiController@add", + [], + [], + [], + [], + $this->getAuthHeaders(), + json_encode($data) + ); + + $this->assertResponseStatus(412); + } + + public function testAddMissingName(): void + { + $data = []; + + $this->action( + "POST", + "OAuth2SummitSponsorshipAddOnTypesApiController@add", + [], + [], + [], + [], + $this->getAuthHeaders(), + json_encode($data) + ); + + $this->assertResponseStatus(412); + } + + public function testUpdate(): void + { + $params = ['id' => self::$default_type->getId()]; + $new_name = 'Updated_Type_' . str_random(4); + + $data = [ + 'name' => $new_name, + ]; + + $response = $this->action( + "PUT", + "OAuth2SummitSponsorshipAddOnTypesApiController@update", + $params, + [], + [], + [], + $this->getAuthHeaders(), + json_encode($data) + ); + + $content = $response->getContent(); + $this->assertResponseStatus(201); + $type = json_decode($content); + $this->assertNotNull($type); + $this->assertEquals($new_name, $type->name); + } + + public function testUpdateNotFound(): void + { + $params = ['id' => PHP_INT_MAX]; + + $this->action( + "PUT", + "OAuth2SummitSponsorshipAddOnTypesApiController@update", + $params, + [], + [], + [], + $this->getAuthHeaders(), + json_encode(['name' => 'irrelevant']) + ); + + $this->assertResponseStatus(404); + } + + public function testDelete(): void + { + $params = ['id' => self::$default_type->getId()]; + + $this->action( + "DELETE", + "OAuth2SummitSponsorshipAddOnTypesApiController@delete", + $params, + [], + [], + [], + $this->getAuthHeaders() + ); + + $this->assertResponseStatus(204); + self::$default_type = null; + } + + public function testDeleteNotFound(): void + { + $params = ['id' => PHP_INT_MAX]; + + $this->action( + "DELETE", + "OAuth2SummitSponsorshipAddOnTypesApiController@delete", + $params, + [], + [], + [], + $this->getAuthHeaders() + ); + + $this->assertResponseStatus(404); + } + + public function testDeleteInUse(): void + { + $params = ['id' => self::$default_add_on_type_booth->getId()]; + + $this->action( + "DELETE", + "OAuth2SummitSponsorshipAddOnTypesApiController@delete", + $params, + [], + [], + [], + $this->getAuthHeaders() + ); + + $this->assertResponseStatus(412); + } +} diff --git a/tests/oauth2/OAuth2SummitSponsorshipApiControllerTest.php b/tests/oauth2/OAuth2SummitSponsorshipApiControllerTest.php index 0940ec94f..f150414d6 100644 --- a/tests/oauth2/OAuth2SummitSponsorshipApiControllerTest.php +++ b/tests/oauth2/OAuth2SummitSponsorshipApiControllerTest.php @@ -144,7 +144,8 @@ public function testGetAllAddOns(){ 'id' => self::$summit->getId(), 'sponsor_id' => self::$sponsors[0]->getId(), 'sponsorship_id' => self::$sponsors[0]->getSponsorships()[0]->getId(), - 'filter' => ['type==Booth'], + 'filter' => ['type=@Booth'], + 'expand' => 'type' ]; $response = $this->action( @@ -321,4 +322,5 @@ public function testGetAddsMetadata(){ $metadata = json_decode($content); $this->assertNotNull($metadata); } -} \ No newline at end of file + +}