diff --git a/app/Events/SummitVenueRoomInserted.php b/app/Events/SummitVenueRoomInserted.php new file mode 100644 index 00000000..be08f3b5 --- /dev/null +++ b/app/Events/SummitVenueRoomInserted.php @@ -0,0 +1,22 @@ +getCurrentUserExternalId(); if(is_null($owner_id)) $owner_id = 0; - $request = new AdminSummitLocationActionSyncWorkRequest(); - $location = $location_repository->getById($event->getLocationId()); - $request->setLocationId($location); + $request = new AdminSummitLocationActionSyncWorkRequest(); + $location = $location_repository->getById($event->getLocationId()); + + $request->setLocation($location); $request->Type = $type; if($owner_id > 0){ diff --git a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitLocationsApiController.php b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitLocationsApiController.php index fdba5ad9..a746a5f3 100644 --- a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitLocationsApiController.php +++ b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitLocationsApiController.php @@ -31,6 +31,7 @@ use models\summit\SummitAirport; use models\summit\SummitExternalLocation; use models\summit\SummitHotel; use models\summit\SummitVenue; +use models\summit\SummitVenueRoom; use ModelSerializers\SerializerRegistry; use services\model\ISummitService; use utils\Filter; @@ -794,7 +795,6 @@ final class OAuth2SummitLocationsApiController extends OAuth2ProtectedController $payload = Input::json()->all(); $summit = SummitFinderStrategyFactory::build($this->repository, $this->resource_server_context)->find($summit_id); if (is_null($summit)) return $this->error404(); - $payload['class_name'] = SummitAirport::ClassName; $rules = [ 'name' => 'required|string|max:50', 'number' => 'required|integer', @@ -831,6 +831,96 @@ final class OAuth2SummitLocationsApiController extends OAuth2ProtectedController } } + /** + * @param $summit_id + * @param $venue_id + * @return mixed + */ + public function addVenueRoom($summit_id, $venue_id){ + try { + if(!Request::isJson()) return $this->error403(); + $payload = Input::json()->all(); + $summit = SummitFinderStrategyFactory::build($this->repository, $this->resource_server_context)->find($summit_id); + if (is_null($summit)) return $this->error404(); + $payload['class_name'] = SummitVenueRoom::ClassName; + $rules = SummitLocationValidationRulesFactory::build($payload); + // Creates a Validator instance and validates the data. + $validation = Validator::make($payload, $rules); + + if ($validation->fails()) { + $messages = $validation->messages()->toArray(); + + return $this->error412 + ( + $messages + ); + } + + $room = $this->location_service->addVenueRoom($summit, $venue_id, $payload); + + return $this->created(SerializerRegistry::getInstance()->getSerializer($room)->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 $summit_id + * @param $venue_id + * @return mixed + */ + public function addVenueFloorRoom($summit_id, $venue_id, $floor_id){ + try { + if(!Request::isJson()) return $this->error403(); + $payload = Input::json()->all(); + $summit = SummitFinderStrategyFactory::build($this->repository, $this->resource_server_context)->find($summit_id); + if (is_null($summit)) return $this->error404(); + $payload['class_name'] = SummitVenueRoom::ClassName; + $rules = SummitLocationValidationRulesFactory::build($payload); + // Creates a Validator instance and validates the data. + $validation = Validator::make($payload, $rules); + + if ($validation->fails()) { + $messages = $validation->messages()->toArray(); + + return $this->error412 + ( + $messages + ); + } + + $payload['floor_id'] = intval($floor_id); + + $room = $this->location_service->addVenueRoom($summit, $venue_id, $payload); + + return $this->created(SerializerRegistry::getInstance()->getSerializer($room)->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); + } + } + /** * Update Location Endpoints */ diff --git a/app/Http/Controllers/Apis/Protected/Summit/SummitLocationValidationRulesFactory.php b/app/Http/Controllers/Apis/Protected/Summit/SummitLocationValidationRulesFactory.php index 6d28f2be..e98cd654 100644 --- a/app/Http/Controllers/Apis/Protected/Summit/SummitLocationValidationRulesFactory.php +++ b/app/Http/Controllers/Apis/Protected/Summit/SummitLocationValidationRulesFactory.php @@ -16,6 +16,7 @@ use models\summit\SummitAirport; use models\summit\SummitExternalLocation; use models\summit\SummitHotel; use models\summit\SummitVenue; +use models\summit\SummitVenueRoom; /** * Class SummitLocationValidationRulesFactory * @package App\Http\Controllers @@ -49,6 +50,9 @@ final class SummitLocationValidationRulesFactory case SummitExternalLocation::ClassName: { return SummitExternalLocationValidationRulesFactory::build($data, $update); } + case SummitVenueRoom::ClassName: { + return SummitVenueRoomValidationRulesFactory::build($data, $update); + } break; default:{ throw new ValidationException('invalid class_name param'); diff --git a/app/Http/Controllers/Apis/Protected/Summit/SummitVenueRoomValidationRulesFactory.php b/app/Http/Controllers/Apis/Protected/Summit/SummitVenueRoomValidationRulesFactory.php new file mode 100644 index 00000000..cc44b3b6 --- /dev/null +++ b/app/Http/Controllers/Apis/Protected/Summit/SummitVenueRoomValidationRulesFactory.php @@ -0,0 +1,42 @@ + 'sometimes|integer:min:0', + 'override_blackouts' => 'sometimes|boolean', + ], $rules); + } + + return array_merge([ + 'capacity' => 'sometimes|integer:min:0', + 'override_blackouts' => 'sometimes|boolean', + ], $rules); + } +} \ No newline at end of file diff --git a/app/Http/routes.php b/app/Http/routes.php index aea9531d..78de4812 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -286,16 +286,27 @@ Route::group([ Route::get('metadata', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitLocationsApiController@getMetadata']); Route::group(['prefix' => 'venues'], function () { + Route::get('', 'OAuth2SummitLocationsApiController@getVenues'); Route::post('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitLocationsApiController@addVenue']); Route::group(['prefix' => '{venue_id}'], function () { Route::put('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitLocationsApiController@updateVenue']); + Route::group(['prefix' => 'rooms'], function () { + Route::post('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitLocationsApiController@addVenueRoom']); + }); + Route::group(['prefix' => 'floors'], function () { Route::post('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitLocationsApiController@addVenueFloor']); Route::group(['prefix' => '{floor_id}'], function () { Route::put('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitLocationsApiController@updateVenueFloor']); Route::delete('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitLocationsApiController@deleteVenueFloor']); + Route::group(['prefix' => 'rooms'], function () { + Route::post('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitLocationsApiController@addVenueFloorRoom']); + Route::group(['prefix' => '{room_id}'], function () { + + }); + }); }); }); }); diff --git a/app/Models/Foundation/Summit/Factories/SummitLocationFactory.php b/app/Models/Foundation/Summit/Factories/SummitLocationFactory.php index 4c0d8c6e..c0066d91 100644 --- a/app/Models/Foundation/Summit/Factories/SummitLocationFactory.php +++ b/app/Models/Foundation/Summit/Factories/SummitLocationFactory.php @@ -18,6 +18,8 @@ use models\summit\SummitExternalLocation; use models\summit\SummitGeoLocatedLocation; use models\summit\SummitHotel; use models\summit\SummitVenue; +use models\summit\SummitVenueRoom; + /** * Class SummitLocationFactory * @package App\Models\Foundation\Summit\Factories @@ -48,6 +50,9 @@ final class SummitLocationFactory case SummitAirport::ClassName :{ $location = self::populateSummitAirport(new SummitAirport, $data); } + case SummitVenueRoom::ClassName :{ + $location = self::populateSummitVenueRoom(new SummitVenueRoom, $data); + } break; } return $location; @@ -202,6 +207,19 @@ final class SummitLocationFactory return $airport; } + public static function populateSummitVenueRoom(SummitVenueRoom $room, array $data){ + + self::populateSummitAbstractLocation($room, $data); + + if(isset($data['capacity'])) + $room->setCapacity(intval($data['capacity'])); + + if(isset($data['override_blackouts'])) + $room->setOverrideBlackouts(boolval($data['override_blackouts'])); + + return $room; + } + /** * @param SummitAbstractLocation $location * @param array $data @@ -220,6 +238,9 @@ final class SummitLocationFactory if($location instanceof SummitExternalLocation){ return self::populateSummitExternalLocation($location, $data); } + if($location instanceof SummitVenueRoom){ + return self::populateSummitVenueRoom($location, $data); + } return $location; } } \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Locations/SummitVenueFloor.php b/app/Models/Foundation/Summit/Locations/SummitVenueFloor.php index f6a5d91d..9d2916ba 100644 --- a/app/Models/Foundation/Summit/Locations/SummitVenueFloor.php +++ b/app/Models/Foundation/Summit/Locations/SummitVenueFloor.php @@ -172,4 +172,13 @@ class SummitVenueFloor extends SilverstripeBaseModel $this->image = $image; } + /** + * @param SummitVenueRoom $room + */ + public function addRoom(SummitVenueRoom $room){ + $this->rooms->add($room); + $room->setFloor($this); + } + + } \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Locations/SummitVenueRoom.php b/app/Models/Foundation/Summit/Locations/SummitVenueRoom.php index 04461fd0..ddf197ab 100644 --- a/app/Models/Foundation/Summit/Locations/SummitVenueRoom.php +++ b/app/Models/Foundation/Summit/Locations/SummitVenueRoom.php @@ -25,13 +25,46 @@ use Doctrine\ORM\Mapping AS ORM; */ class SummitVenueRoom extends SummitAbstractLocation { + /** + * @ORM\ManyToOne(targetEntity="models\summit\SummitVenue", inversedBy="rooms") + * @ORM\JoinColumn(name="VenueID", referencedColumnName="ID") + * @var SummitVenue + */ + private $venue; + + /** + * @ORM\ManyToOne(targetEntity="models\summit\SummitVenueFloor", inversedBy="rooms") + * @ORM\JoinColumn(name="FloorID", referencedColumnName="ID") + * @var SummitVenueFloor + */ + private $floor; + + /** + * @ORM\Column(name="Capacity", type="integer") + * @var int + */ + private $capacity; + + /** + * @ORM\Column(name="OverrideBlackouts", type="boolean") + * @var bool + */ + private $override_blackouts; + + /** + * @ORM\OneToMany(targetEntity="RoomMetricType", mappedBy="room", cascade={"persist"}) + */ + private $metrics; + /** * @return string */ public function getClassName(){ - return 'SummitVenueRoom'; + return self::ClassName; } + const ClassName = 'SummitVenueRoom'; + /** * @return SummitVenue */ @@ -45,7 +78,7 @@ class SummitVenueRoom extends SummitAbstractLocation */ public function getVenueId(){ try{ - return $this->venue->getId(); + return is_null($this->venue) ? 0 : $this->venue->getId(); } catch(\Exception $ex){ return 0; @@ -72,7 +105,7 @@ class SummitVenueRoom extends SummitAbstractLocation */ public function getFloorId(){ try{ - return $this->floor->getId(); + return is_null($this->floor) ? 0 : $this->floor->getId(); } catch(\Exception $ex){ return 0; @@ -117,36 +150,7 @@ class SummitVenueRoom extends SummitAbstractLocation { $this->override_blackouts = $override_blackouts; } - /** - * @ORM\ManyToOne(targetEntity="models\summit\SummitVenue", inversedBy="rooms") - * @ORM\JoinColumn(name="VenueID", referencedColumnName="ID") - * @var SummitVenue - */ - private $venue; - /** - * @ORM\ManyToOne(targetEntity="models\summit\SummitVenueFloor", inversedBy="rooms") - * @ORM\JoinColumn(name="FloorID", referencedColumnName="ID") - * @var SummitVenueFloor - */ - private $floor; - - /** - * @ORM\Column(name="Capacity", type="integer") - * @var int - */ - private $capacity; - - /** - * @ORM\Column(name="OverrideBlackouts", type="boolean") - * @var bool - */ - private $override_blackouts; - - /** - * @ORM\OneToMany(targetEntity="RoomMetricType", mappedBy="room", cascade={"persist"}) - */ - private $metrics; /** * @return ArrayCollection @@ -169,7 +173,11 @@ class SummitVenueRoom extends SummitAbstractLocation */ public function __construct() { - $this->metrics = new ArrayCollection(); + parent::__construct(); + $this->metrics = new ArrayCollection(); + $this->override_blackouts = false; + $this->capacity = 0; + $this->type = self::TypeInternal; } /** diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index f183bd43..dcbd87c9 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -181,6 +181,11 @@ final class EventServiceProvider extends ServiceProvider EntityEventPersister::persist(LocationActionEntityEventFactory::build($event, 'INSERT')); }); + Event::listen(\App\Events\SummitVenueRoomInserted::class, function($event) + { + EntityEventPersister::persist(LocationActionEntityEventFactory::build($event, 'INSERT')); + }); + Event::listen(\App\Events\LocationUpdated::class, function($event) { EntityEventPersister::persist(LocationActionEntityEventFactory::build($event, 'UPDATE')); diff --git a/app/Services/Model/ILocationService.php b/app/Services/Model/ILocationService.php index 247623c8..88d3808d 100644 --- a/app/Services/Model/ILocationService.php +++ b/app/Services/Model/ILocationService.php @@ -11,6 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ +use models\summit\SummitVenueRoom; use models\exceptions\EntityNotFoundException; use models\exceptions\ValidationException; use models\summit\Summit; @@ -80,4 +81,14 @@ interface ILocationService * @throws ValidationException */ public function deleteVenueFloor(Summit $summit, $venue_id, $floor_id); + + /** + * @param Summit $summit + * @param $venue_id + * @param array $data + * @return SummitVenueRoom + * @throws EntityNotFoundException + * @throws ValidationException + */ + public function addVenueRoom(Summit $summit, $venue_id, array $data); } \ No newline at end of file diff --git a/app/Services/Model/LocationService.php b/app/Services/Model/LocationService.php index 68498191..7bcb4086 100644 --- a/app/Services/Model/LocationService.php +++ b/app/Services/Model/LocationService.php @@ -18,6 +18,7 @@ use App\Events\FloorUpdated; use App\Events\LocationDeleted; use App\Events\LocationInserted; use App\Events\LocationUpdated; +use App\Events\SummitVenueRoomInserted; use App\Models\Foundation\Summit\Factories\SummitLocationFactory; use App\Models\Foundation\Summit\Factories\SummitVenueFloorFactory; use App\Models\Foundation\Summit\Repositories\ISummitLocationRepository; @@ -34,6 +35,8 @@ use models\summit\SummitAbstractLocation; use models\summit\SummitGeoLocatedLocation; use models\summit\SummitVenue; use models\summit\SummitVenueFloor; +use models\summit\SummitVenueRoom; + /** * Class LocationService * @package App\Services\Model @@ -575,4 +578,116 @@ final class LocationService implements ILocationService $venue->removeFloor($floor); }); } + + /** + * @param Summit $summit + * @param $venue_id + * @param array $data + * @return SummitVenueRoom + * @throws EntityNotFoundException + * @throws ValidationException + */ + public function addVenueRoom(Summit $summit, $venue_id, array $data) + { + $room = $this->tx_service->transaction(function () use ($summit, $venue_id, $data) { + + if (isset($data['name'])) { + $old_location = $summit->getLocationByName(trim($data['name'])); + + if (!is_null($old_location)) { + throw new ValidationException + ( + trans + ( + 'validation_errors.LocationService.addVenueRoom.LocationNameAlreadyExists', + [ + 'summit_id' => $summit->getId() + ] + ) + ); + } + } + + $venue = $summit->getLocation($venue_id); + + if(is_null($venue)){ + throw new EntityNotFoundException + ( + trans + ( + 'not_found_errors.LocationService.addVenueRoom.VenueNotFound', + [ + 'summit_id' => $summit->getId(), + 'venue_id' => $venue_id, + ] + ) + ); + } + + if(!$venue instanceof SummitVenue){ + throw new ValidationException + ( + trans + ( + 'not_found_errors.LocationService.addVenueRoom.VenueNotFound', + [ + 'summit_id' => $summit->getId(), + 'venue_id' => $venue_id, + ] + ) + ); + } + + $data['class_name'] = SummitVenueRoom::ClassName; + $room = SummitLocationFactory::build($data); + + if (is_null($room)) { + throw new ValidationException + ( + trans + ( + 'validation_errors.LocationService.addVenueRoom.InvalidClassName' + ) + ); + } + + if(isset($data['floor_id'])){ + $floor_id = intval($data['floor_id']); + $floor = $venue->getFloor($floor_id); + + if(is_null($floor)){ + throw new EntityNotFoundException + ( + trans + ( + 'not_found_errors.LocationService.addVenueRoom.FloorNotFound', + [ + 'floor_id' => $floor_id, + 'venue_id' => $venue_id + ] + ) + ); + } + + $floor->addRoom($room); + } + + $summit->addLocation($room); + $venue->addRoom($room); + + return $room; + }); + + Event::fire + ( + new SummitVenueRoomInserted + ( + $room->getSummitId(), + $room->getId(), + $room->getClassName() + ) + ); + + return $room; + } } \ No newline at end of file diff --git a/database/seeds/ApiEndpointsSeeder.php b/database/seeds/ApiEndpointsSeeder.php index 35737eef..2642c155 100644 --- a/database/seeds/ApiEndpointsSeeder.php +++ b/database/seeds/ApiEndpointsSeeder.php @@ -545,6 +545,24 @@ class ApiEndpointsSeeder extends Seeder sprintf(SummitScopes::WriteLocationsData, $current_realm) ], ], + [ + 'name' => 'add-venue-room', + 'route' => '/api/v1/summits/{id}/locations/venues/{venue_id}/rooms', + 'http_method' => 'POST', + 'scopes' => [ + sprintf(SummitScopes::WriteSummitData, $current_realm), + sprintf(SummitScopes::WriteLocationsData, $current_realm) + ], + ], + [ + 'name' => 'add-venue-floor-room', + 'route' => '/api/v1/summits/{id}/locations/venues/{venue_id}/floors/{floor_id}/rooms', + 'http_method' => 'POST', + 'scopes' => [ + sprintf(SummitScopes::WriteSummitData, $current_realm), + sprintf(SummitScopes::WriteLocationsData, $current_realm) + ], + ], [ 'name' => 'update-venue-floor', 'route' => '/api/v1/summits/{id}/locations/venues/{venue_id}/floors/{floor_id}', diff --git a/resources/lang/en/not_found_errors.php b/resources/lang/en/not_found_errors.php index 7f074b25..c0fbc7b8 100644 --- a/resources/lang/en/not_found_errors.php +++ b/resources/lang/en/not_found_errors.php @@ -22,4 +22,6 @@ return [ 'LocationService.updateVenueFloor.VenueNotFound' => 'venue :venue_id not found on summit :summit_id', 'LocationService.deleteVenueFloor.FloorNotFound' => 'floor :floor_id does not belongs to venue :venue_id', 'LocationService.deleteVenueFloor.VenueNotFound' => 'venue :venue_id not found on summit :summit_id', + 'LocationService.addVenueRoom.FloorNotFound' => 'floor :floor_id does not belongs to venue :venue_id', + 'LocationService.addVenueRoom.VenueNotFound' => 'venue :venue_id not found on summit :summit_id', ]; \ No newline at end of file diff --git a/resources/lang/en/validation_errors.php b/resources/lang/en/validation_errors.php index 2020bfed..ff99db82 100644 --- a/resources/lang/en/validation_errors.php +++ b/resources/lang/en/validation_errors.php @@ -39,4 +39,6 @@ return [ 'LocationService.addVenueFloor.FloorNumberAlreadyExists' => 'floor number :floor_number already belongs to another floor on venue :venue_id', 'LocationService.updateVenueFloor.FloorNameAlreadyExists' => 'floor name :floor_name already belongs to another floor on venue :venue_id', 'LocationService.updateVenueFloor.FloorNumberAlreadyExists' => 'floor number :floor_number already belongs to another floor on venue :venue_id', + 'LocationService.addVenueRoom.InvalidClassName' => 'invalid class name', + 'LocationService.addVenueRoom.LocationNameAlreadyExists' => 'there is already another location with same name for summit :summit_id', ]; \ No newline at end of file diff --git a/tests/OAuth2SummitLocationsApiTest.php b/tests/OAuth2SummitLocationsApiTest.php index 048a278c..f582616f 100644 --- a/tests/OAuth2SummitLocationsApiTest.php +++ b/tests/OAuth2SummitLocationsApiTest.php @@ -812,4 +812,95 @@ final class OAuth2SummitLocationsApiTest extends ProtectedApiTest $content = $response->getContent(); $this->assertResponseStatus(204); } + + /** + * @param int $summit_id + * @param int $venue_id + * @return mixed + */ + public function testAddVenueRoom($summit_id = 23, $venue_id = 292){ + + + $params = [ + 'id' => $summit_id, + 'venue_id' => $venue_id, + ]; + + $name = str_random(16).'_room'; + + $data = [ + 'name' => $name, + 'description' => 'test room', + ]; + + + $headers = [ + "HTTP_Authorization" => " Bearer " . $this->access_token, + "CONTENT_TYPE" => "application/json" + ]; + + $response = $this->action( + "POST", + "OAuth2SummitLocationsApiController@addVenueRoom", + $params, + [], + [], + [], + $headers, + json_encode($data) + ); + + $content = $response->getContent(); + $this->assertResponseStatus(201); + $room = json_decode($content); + $this->assertTrue(!is_null($room)); + return $room; + } + + + /** + * @param int $summit_id + * @param int $venue_id + * @return mixed + */ + public function testAddVenueRoomWithFloor($summit_id = 23, $venue_id = 292){ + + $floor = $this->testAddVenueFloor($summit_id, $venue_id, rand(0,1000)); + + $params = [ + 'id' => $summit_id, + 'venue_id' => $venue_id, + 'floor_id' => $floor->id + ]; + + $name = str_random(16).'_room'; + + $data = [ + 'name' => $name, + 'description' => 'test room', + ]; + + + $headers = [ + "HTTP_Authorization" => " Bearer " . $this->access_token, + "CONTENT_TYPE" => "application/json" + ]; + + $response = $this->action( + "POST", + "OAuth2SummitLocationsApiController@addVenueFloorRoom", + $params, + [], + [], + [], + $headers, + json_encode($data) + ); + + $content = $response->getContent(); + $this->assertResponseStatus(201); + $room = json_decode($content); + $this->assertTrue(!is_null($room)); + return $room; + } } \ No newline at end of file