From 6d5e0d637b5402b8fecb2d766e5c27eb6ab831d6 Mon Sep 17 00:00:00 2001 From: Sebastian Marcet Date: Tue, 20 Mar 2018 18:05:48 -0300 Subject: [PATCH] Added endpoint POST /api/v1/summits/{id}/rsvp-templates/{template_id}/questions/{question_id}/values Payload * label (required|string) * value (required|string) Change-Id: I80818505f2877b38241416bd211610593a300267 --- app/Console/Commands/SummitJsonGenerator.php | 2 +- ...TemplateQuestionValidationRulesFactory.php | 2 +- ...ateQuestionValueValidationRulesFactory.php | 34 +++++++ ...OAuth2SummitRSVPTemplatesApiController.php | 45 +++++++++ app/Http/routes.php | 2 +- app/Models/Foundation/Main/IOrderable.php | 26 ++++++ .../Foundation/Main/OrderableChilds.php | 61 +++++++++++++ .../RSVPMultiValueQuestionTemplate.php | 84 +++++++++++++++++ .../RSVP/Templates/RSVPQuestionTemplate.php | 11 ++- .../Templates/RSVPQuestionValueTemplate.php | 4 + .../Events/RSVP/Templates/RSVPTemplate.php | 14 ++- ...SummitRSVPTemplateQuestionValueFactory.php | 44 +++++++++ app/Services/Model/IRSVPTemplateService.php | 12 +++ app/Services/Model/RSVPTemplateService.php | 91 +++++++++++++++++++ config/cache_api_response.php | 2 +- resources/lang/en/not_found_errors.php | 2 + resources/lang/en/validation_errors.php | 1 + tests/OAuth2SummitRSVPTemplateApiTest.php | 39 ++++++++ 18 files changed, 466 insertions(+), 10 deletions(-) create mode 100644 app/Http/Controllers/Apis/Protected/Summit/Factories/SummitRSVPTemplateQuestionValueValidationRulesFactory.php create mode 100644 app/Models/Foundation/Main/IOrderable.php create mode 100644 app/Models/Foundation/Main/OrderableChilds.php create mode 100644 app/Models/Foundation/Summit/Factories/SummitRSVPTemplateQuestionValueFactory.php diff --git a/app/Console/Commands/SummitJsonGenerator.php b/app/Console/Commands/SummitJsonGenerator.php index c255d1c7..7ebfd375 100644 --- a/app/Console/Commands/SummitJsonGenerator.php +++ b/app/Console/Commands/SummitJsonGenerator.php @@ -109,7 +109,7 @@ final class SummitJsonGenerator extends Command { $key_current = sprintf('/api/v1/summits/%s.expand=%s','current', urlencode($expand)); $key_id = sprintf('/api/v1/summits/%s.expand=%s', $summit->getIdentifier(), urlencode($expand)); - $cache_lifetime = intval(Config::get('server.response_cache_lifetime', 300)); + $cache_lifetime = intval(Config::get('cache_api_response.get_summit_response_lifetime', 300)); if($summit->isActive()) { diff --git a/app/Http/Controllers/Apis/Protected/Summit/Factories/SummitRSVPTemplateQuestionValidationRulesFactory.php b/app/Http/Controllers/Apis/Protected/Summit/Factories/SummitRSVPTemplateQuestionValidationRulesFactory.php index 1ca010fe..69921dff 100644 --- a/app/Http/Controllers/Apis/Protected/Summit/Factories/SummitRSVPTemplateQuestionValidationRulesFactory.php +++ b/app/Http/Controllers/Apis/Protected/Summit/Factories/SummitRSVPTemplateQuestionValidationRulesFactory.php @@ -48,7 +48,7 @@ final class SummitRSVPTemplateQuestionValidationRulesFactory 'name' => 'sometimes|alpha_dash|max:255', 'label' => 'sometimes|string', 'is_mandatory' => 'sometimes|boolean', - 'order' => 'sometimes|int|min:1', + 'order' => 'sometimes|integer|min:1', 'is_read_only;' => 'sometimes|boolean', ]); } diff --git a/app/Http/Controllers/Apis/Protected/Summit/Factories/SummitRSVPTemplateQuestionValueValidationRulesFactory.php b/app/Http/Controllers/Apis/Protected/Summit/Factories/SummitRSVPTemplateQuestionValueValidationRulesFactory.php new file mode 100644 index 00000000..6caa2fca --- /dev/null +++ b/app/Http/Controllers/Apis/Protected/Summit/Factories/SummitRSVPTemplateQuestionValueValidationRulesFactory.php @@ -0,0 +1,34 @@ + 'sometimes|string|max:255', + 'label' => 'sometimes|string', + 'order' => 'sometimes|integer|min:1', + ]; + } + return [ + 'value' => 'required|string|max:255', + 'label' => 'required|string', + ]; + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitRSVPTemplatesApiController.php b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitRSVPTemplatesApiController.php index f85b756b..f56c5ea7 100644 --- a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitRSVPTemplatesApiController.php +++ b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitRSVPTemplatesApiController.php @@ -396,4 +396,49 @@ final class OAuth2SummitRSVPTemplatesApiController extends OAuth2ProtectedContro return $this->error500($ex); } } + + /** + * values endpoints + */ + + public function addRSVPTemplateQuestionValue($summit_id, $template_id, $question_id){ + try { + + if(!Request::isJson()) return $this->error400(); + $payload = Input::json()->all(); + + $summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->resource_server_context)->find($summit_id); + if (is_null($summit)) return $this->error404(); + + $rules = SummitRSVPTemplateQuestionValueValidationRulesFactory::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 + ); + } + + $value = $this->rsvp_template_service->addQuestionValue($summit, $template_id, $question_id, $payload); + + return $this->created(SerializerRegistry::getInstance()->getSerializer($value)->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); + } + } } \ No newline at end of file diff --git a/app/Http/routes.php b/app/Http/routes.php index 22388b79..ea913030 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -178,7 +178,7 @@ Route::group([ Route::delete('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitRSVPTemplatesApiController@deleteRSVPTemplateQuestion']); // multi values questions Route::group(['prefix' => 'values'], function () { - + Route::post('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitRSVPTemplatesApiController@addRSVPTemplateQuestionValue']); Route::group(['prefix' => '{value_id}'], function () { }); diff --git a/app/Models/Foundation/Main/IOrderable.php b/app/Models/Foundation/Main/IOrderable.php new file mode 100644 index 00000000..5e2eb010 --- /dev/null +++ b/app/Models/Foundation/Main/IOrderable.php @@ -0,0 +1,26 @@ +orderBy(['order'=> 'ASC']); + + $elements = $collection->matching($criteria)->toArray(); + $elements = array_slice($elements,0, count($elements), false); + $max_order = count($elements); + $former_order = 1; + + foreach ($elements as $e){ + if($e->getId() == $element->getId()) break; + $former_order++; + } + + if($new_order > $max_order) + throw new ValidationException(sprintf("max order is %s", $max_order)); + + unset($elements[$former_order - 1]); + + $elements = array_merge + ( + array_slice($elements, 0, $new_order -1 , true) , + [$elements] , + array_slice($elements, $new_order -1 , count($elements), true) + ); + + $order = 1; + foreach($elements as $q){ + $q->setOrder($order); + $order++; + } + } +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/RSVP/Templates/RSVPMultiValueQuestionTemplate.php b/app/Models/Foundation/Summit/Events/RSVP/Templates/RSVPMultiValueQuestionTemplate.php index f8ae2e76..295c3715 100644 --- a/app/Models/Foundation/Summit/Events/RSVP/Templates/RSVPMultiValueQuestionTemplate.php +++ b/app/Models/Foundation/Summit/Events/RSVP/Templates/RSVPMultiValueQuestionTemplate.php @@ -14,6 +14,8 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Mapping AS ORM; +use models\exceptions\ValidationException; + /** * @ORM\Table(name="RSVPMultiValueQuestionTemplate") * @ORM\Entity @@ -123,4 +125,86 @@ class RSVPMultiValueQuestionTemplate extends RSVPQuestionTemplate } } + /** + * @param int $id + * @return RSVPQuestionValueTemplate + */ + public function getValueById($id){ + $criteria = Criteria::create(); + $criteria->where(Criteria::expr()->eq('id', intval($id))); + $res = $this->values->matching($criteria)->first(); + return $res ? $res : null; + } + + /** + * @param string $value + * @return RSVPQuestionValueTemplate + */ + public function getValueByValue($value){ + $criteria = Criteria::create(); + $criteria->where(Criteria::expr()->eq('value', trim($value))); + $res = $this->values->matching($criteria)->first(); + return $res ? $res : null; + } + + /** + * @param RSVPQuestionValueTemplate $value + * @return $this + */ + public function addValue(RSVPQuestionValueTemplate $value){ + $values = $this->getValues(); + $this->values->add($value); + $value->setOwner($this); + $value->setOrder(count($values) + 1); + return $this; + } + + /** + * @param RSVPQuestionValueTemplate $value + * @param int $new_order + * @throws ValidationException + */ + public function recalculateValueOrder(RSVPQuestionValueTemplate $value, $new_order){ + + $criteria = Criteria::create(); + $criteria->orderBy(['order'=> 'ASC']); + + $values = $this->values->matching($criteria)->toArray(); + $values = array_slice($values,0, count($values), false); + $max_order = count($values); + $former_order = 1; + + foreach ($values as $v){ + if($v->getId() == $value->getId()) break; + $former_order++; + } + + if($new_order > $max_order) + throw new ValidationException(sprintf("max order is %s", $max_order)); + + unset($values[$former_order - 1]); + + $values = array_merge + ( + array_slice($values, 0, $new_order -1 , true) , + [$values] , + array_slice($values, $new_order -1 , count($values), true) + ); + + $order = 1; + foreach($values as $v){ + $v->setOrder($order); + $order++; + } + } + + /** + * @param RSVPQuestionValueTemplate $value + * @return $this + */ + public function removeValue(RSVPQuestionValueTemplate $value){ + $this->values->removeElement($value); + $value->clearOwner(); + return $this; + } } \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/RSVP/Templates/RSVPQuestionTemplate.php b/app/Models/Foundation/Summit/Events/RSVP/Templates/RSVPQuestionTemplate.php index bf1ea746..07883ce9 100644 --- a/app/Models/Foundation/Summit/Events/RSVP/Templates/RSVPQuestionTemplate.php +++ b/app/Models/Foundation/Summit/Events/RSVP/Templates/RSVPQuestionTemplate.php @@ -11,6 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ +use App\Models\Foundation\Main\IOrderable; use Doctrine\ORM\Mapping AS ORM; use models\utils\SilverstripeBaseModel; /** @@ -35,7 +36,7 @@ use models\utils\SilverstripeBaseModel; * Class RSVPQuestionTemplate * @package App\Models\Foundation\Summit\Events\RSVP */ -class RSVPQuestionTemplate extends SilverstripeBaseModel +class RSVPQuestionTemplate extends SilverstripeBaseModel implements IOrderable { /** @@ -57,8 +58,8 @@ class RSVPQuestionTemplate extends SilverstripeBaseModel protected $is_mandatory; /** - * @ORM\Column(name="`Order`", type="string") - * @var string + * @ORM\Column(name="`Order`", type="integer") + * @var int */ protected $order; @@ -130,7 +131,7 @@ class RSVPQuestionTemplate extends SilverstripeBaseModel } /** - * @return string + * @return int */ public function getOrder() { @@ -138,7 +139,7 @@ class RSVPQuestionTemplate extends SilverstripeBaseModel } /** - * @param string $order + * @param int $order */ public function setOrder($order) { diff --git a/app/Models/Foundation/Summit/Events/RSVP/Templates/RSVPQuestionValueTemplate.php b/app/Models/Foundation/Summit/Events/RSVP/Templates/RSVPQuestionValueTemplate.php index 800c958e..b7f94e12 100644 --- a/app/Models/Foundation/Summit/Events/RSVP/Templates/RSVPQuestionValueTemplate.php +++ b/app/Models/Foundation/Summit/Events/RSVP/Templates/RSVPQuestionValueTemplate.php @@ -121,4 +121,8 @@ class RSVPQuestionValueTemplate extends SilverstripeBaseModel return 0; } } + + public function clearOwner(){ + $this->owner = null; + } } \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/RSVP/Templates/RSVPTemplate.php b/app/Models/Foundation/Summit/Events/RSVP/Templates/RSVPTemplate.php index 90f605d9..13412733 100644 --- a/app/Models/Foundation/Summit/Events/RSVP/Templates/RSVPTemplate.php +++ b/app/Models/Foundation/Summit/Events/RSVP/Templates/RSVPTemplate.php @@ -11,8 +11,10 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ +use App\Models\Foundation\Main\OrderableChilds; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Criteria; +use models\exceptions\ValidationException; use models\main\Member; use models\summit\SummitOwned; use models\utils\SilverstripeBaseModel; @@ -177,6 +179,17 @@ class RSVPTemplate extends SilverstripeBaseModel return $this; } + use OrderableChilds; + + /** + * @param RSVPQuestionTemplate $question + * @param int $new_order + * @throws ValidationException + */ + public function recalculateQuestionOrder(RSVPQuestionTemplate $question, $new_order){ + self::recalculateOrderFor($this->questions, $question, $new_order); + } + /** * @param RSVPQuestionTemplate $question * @return $this @@ -186,5 +199,4 @@ class RSVPTemplate extends SilverstripeBaseModel $question->clearTemplate(); return $this; } - } \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Factories/SummitRSVPTemplateQuestionValueFactory.php b/app/Models/Foundation/Summit/Factories/SummitRSVPTemplateQuestionValueFactory.php new file mode 100644 index 00000000..0620623c --- /dev/null +++ b/app/Models/Foundation/Summit/Factories/SummitRSVPTemplateQuestionValueFactory.php @@ -0,0 +1,44 @@ +setValue(trim($data['value'])); + + if(isset($data['label'])) + $value->setLabel(trim($data['label'])); + + return $value; + } +} \ No newline at end of file diff --git a/app/Services/Model/IRSVPTemplateService.php b/app/Services/Model/IRSVPTemplateService.php index ba57d454..32bcb3e9 100644 --- a/app/Services/Model/IRSVPTemplateService.php +++ b/app/Services/Model/IRSVPTemplateService.php @@ -12,6 +12,7 @@ * limitations under the License. **/ use App\Models\Foundation\Summit\Events\RSVP\RSVPQuestionTemplate; +use App\Models\Foundation\Summit\Events\RSVP\RSVPQuestionValueTemplate; use models\exceptions\EntityNotFoundException; use models\exceptions\ValidationException; use models\summit\Summit; @@ -61,4 +62,15 @@ interface IRSVPTemplateService */ public function deleteQuestion(Summit $summit, $template_id, $question_id); + /** + * @param Summit $summit + * @param int $template_id + * @param int $question_id + * @param array $payload + * @return RSVPQuestionValueTemplate + * @throws EntityNotFoundException + * @throws ValidationException + */ + public function addQuestionValue($summit, $template_id, $question_id, $payload); + } \ No newline at end of file diff --git a/app/Services/Model/RSVPTemplateService.php b/app/Services/Model/RSVPTemplateService.php index ec9a26c9..0c3ed1d6 100644 --- a/app/Services/Model/RSVPTemplateService.php +++ b/app/Services/Model/RSVPTemplateService.php @@ -11,8 +11,11 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ +use App\Models\Foundation\Summit\Events\RSVP\RSVPMultiValueQuestionTemplate; use App\Models\Foundation\Summit\Events\RSVP\RSVPQuestionTemplate; +use App\Models\Foundation\Summit\Events\RSVP\RSVPQuestionValueTemplate; use App\Models\Foundation\Summit\Factories\SummitRSVPTemplateQuestionFactory; +use App\Models\Foundation\Summit\Factories\SummitRSVPTemplateQuestionValueFactory; use App\Models\Foundation\Summit\Repositories\IRSVPTemplateRepository; use libs\utils\ITransactionService; use models\exceptions\EntityNotFoundException; @@ -183,6 +186,11 @@ final class RSVPTemplateService implements IRSVPTemplateService } } + if (isset($data['order']) && intval($data['order']) != $question->getOrder()) { + // request to update order + $template->recalculateQuestionOrder($question, intval($data['order'])); + } + return SummitRSVPTemplateQuestionFactory::populate($question, $data); }); @@ -233,4 +241,87 @@ final class RSVPTemplateService implements IRSVPTemplateService $template->removeQuestion($question); }); } + + /** + * @param Summit $summit + * @param int $template_id + * @param int $question_id + * @param array $data + * @return RSVPQuestionValueTemplate + * @throws EntityNotFoundException + * @throws ValidationException + */ + public function addQuestionValue($summit, $template_id, $question_id, $data) + { + return $this->tx_service->transaction(function() use($summit, $template_id, $question_id, $data){ + + $template = $summit->getRSVPTemplateById($template_id); + + if(is_null($template)) + throw new EntityNotFoundException + ( + trans + ( + 'not_found_errors.RSVPTemplateService.addQuestionValue.TemplateNotFound', + [ + 'summit_id' => $summit->getId(), + 'template_id' => $template_id, + ] + ) + ); + + $question = $template->getQuestionById($question_id); + if(is_null($question)) + throw new EntityNotFoundException + ( + trans + ( + 'not_found_errors.RSVPTemplateService.addQuestionValue.QuestionNotFound', + [ + 'summit_id' => $summit->getId(), + 'template_id' => $template_id, + 'question_id' => $question_id, + ] + ) + ); + + if(!$question instanceof RSVPMultiValueQuestionTemplate){ + throw new EntityNotFoundException + ( + trans + ( + 'not_found_errors.RSVPTemplateService.addQuestionValue.QuestionNotFound', + [ + 'summit_id' => $summit->getId(), + 'template_id' => $template_id, + 'question_id' => $question_id, + ] + ) + ); + } + + $former_value = $question->getValueByValue($data['value']); + if(!is_null($former_value)){ + throw new ValidationException + ( + trans + ( + 'validation_errors.RSVPTemplateService.addQuestionValue.ValueAlreadyExist', + [ + 'summit_id' => $summit->getId(), + 'template_id' => $template_id, + 'question_id' => $question_id, + 'value' => $data['value'] + ] + ) + ); + } + + $value = SummitRSVPTemplateQuestionValueFactory::build($data); + + $question->addValue($value); + + return $value; + }); + } } \ No newline at end of file diff --git a/config/cache_api_response.php b/config/cache_api_response.php index 7245796d..949e0a42 100644 --- a/config/cache_api_response.php +++ b/config/cache_api_response.php @@ -13,7 +13,7 @@ **/ return [ - 'get_summit_response_lifetime' => env('CACHE_API_RESPONSE_GET_SUMMIT_LIFETIME', 300), + 'get_summit_response_lifetime' => env('CACHE_API_RESPONSE_GET_SUMMIT_LIFETIME', 600), 'get_event_feedback_response_lifetime' => env('CACHE_API_RESPONSE_GET_EVENT_FEEDBACK_LIFETIME', 300), 'get_published_event_response_lifetime' => env('CACHE_API_RESPONSE_GET_PUBLISHED_EVENT_LIFETIME', 300), 'get_summits_response_lifetime' => env('CACHE_API_RESPONSE_GET_SUMMITS_LIFETIME', 600), diff --git a/resources/lang/en/not_found_errors.php b/resources/lang/en/not_found_errors.php index 6d9a9beb..161431e8 100644 --- a/resources/lang/en/not_found_errors.php +++ b/resources/lang/en/not_found_errors.php @@ -48,4 +48,6 @@ return [ 'RSVPTemplateService.updateQuestion.QuestionNotFound' => 'question :question_id not found on template :template_id', 'RSVPTemplateService.deleteQuestion.TemplateNotFound' => 'template :template_id not found on summit :summit_id', 'RSVPTemplateService.deleteQuestion.QuestionNotFound' => 'question :question_id not found on template :template_id', + 'RSVPTemplateService.addQuestionValue.TemplateNotFound' => 'template :template_id not found on summit :summit_id', + 'RSVPTemplateService.addQuestionValue.QuestionNotFound' => 'question :question_id not found on template :template_id', ]; \ No newline at end of file diff --git a/resources/lang/en/validation_errors.php b/resources/lang/en/validation_errors.php index 4993194f..9d63e23c 100644 --- a/resources/lang/en/validation_errors.php +++ b/resources/lang/en/validation_errors.php @@ -54,4 +54,5 @@ return [ // RSVP Template Service 'RSVPTemplateService.addQuestion.QuestionNameAlreadyExists' => 'question name :name already exists for template :template_id', 'RSVPTemplateService.updateQuestion.QuestionNameAlreadyExists' => 'question name :name already exists for template :template_id', + 'RSVPTemplateService.addQuestionValue.ValueAlreadyExist' => 'value :value already exists on question :question_id', ]; \ No newline at end of file diff --git a/tests/OAuth2SummitRSVPTemplateApiTest.php b/tests/OAuth2SummitRSVPTemplateApiTest.php index 23913567..11636099 100644 --- a/tests/OAuth2SummitRSVPTemplateApiTest.php +++ b/tests/OAuth2SummitRSVPTemplateApiTest.php @@ -269,4 +269,43 @@ final class OAuth2SummitRSVPTemplateApiTest extends ProtectedApiTest $content = $response->getContent(); $this->assertResponseStatus(204); } + + public function testAddRSVPQuestionValue($summit_id = 24, $template_id = 13, $question_id = 86){ + + $params = [ + 'id' => $summit_id, + 'template_id' => $template_id, + 'question_id' => $question_id + ]; + + $value = str_random(16).'_value'; + $label = str_random(16).'_label'; + + $data = [ + 'value' => $value, + 'label' => $label, + ]; + + $headers = [ + "HTTP_Authorization" => " Bearer " . $this->access_token, + "CONTENT_TYPE" => "application/json" + ]; + + $response = $this->action( + "POST", + "OAuth2SummitRSVPTemplatesApiController@addRSVPTemplateQuestionValue", + $params, + [], + [], + [], + $headers, + json_encode($data) + ); + + $content = $response->getContent(); + $this->assertResponseStatus(201); + $value = json_decode($content); + $this->assertTrue(!is_null($value)); + return $value; + } } \ No newline at end of file