diff --git a/MANIFEST b/MANIFEST index 0456a33..eeab4c2 100644 --- a/MANIFEST +++ b/MANIFEST @@ -33,7 +33,7 @@ lib/TPS/Questionnaire/Schema/Result/QuestionnaireAnswer.pm lib/TPS/Questionnaire/Schema/Result/QuestionnaireQuestion.pm lib/TPS/Questionnaire/View/JSON.pm Makefile.PL -MANIFEST This list of files +MANIFEST META.yml questionnaire.conf questionnaire.psgi diff --git a/lib/TPS/Questionnaire/Controller/API.pm b/lib/TPS/Questionnaire/Controller/API.pm index 49bc053..54a5365 100644 --- a/lib/TPS/Questionnaire/Controller/API.pm +++ b/lib/TPS/Questionnaire/Controller/API.pm @@ -85,6 +85,67 @@ sub get_questionnaire :Path('questionnaire') GET CaptureArgs(1) { } +=head2 put_questionnaire + +Handles PUT action to /api/questionnaire/{id} + +The ID is required, this is an "update" action to edit existing data on an unpublished questionnaire. +That is, you can edit the title (and in the future, the questions) associated with an unpublished questionnaire, +or you can publish a questionnaire. + +Once a questionnaire is published it can accept responses, but none of its questions or metadata can be further modified. + +Once published, a questionnaire cannot be unpublished. (In the future, we may want to be able to unpublish a +questionnaire. For example, if you want to revise an existing questionnaire, you'd publish a revised version and +unpublish the old version. But this would have to be done in such a way that no previously published questionnaires +could be modified. Previous responses to old questionnaires would be preserved.) + +=cut + +sub put_questionnaire :Path('questionnaire') PUT CaptureArgs(1) Consumes(JSON) { + my ($self, $c, $id) = (shift, @_); + + ## input validation? + ## a. is the id valid, i.e., does it exist in the DB + ## b. is this item (id) in an unpublished state? + ## + my (%posted, $q, $qa, ); + + # %posted = %{$c->request->body_data}; + + if ($id) { + $q = TPS::Questionnaire::Model::Questionnaire->from_id($c->schema, $id); + + if ( $q->is_published() ) { + $c->stash->{'status'} = 'error'; + $c->stash->{'error'} = 'Not allowed to update an already published questionnaire.'; + $c->response->status(400); + } + else { + $q = 'TPS::Questionnaire::Model::Questionnaire'->from_hashref($c->request->body_data); + $q->id( $id ); + $q->update($c->schema); + + $c->stash->{'status'} = 'ok'; + $c->stash->{'result'} = $q->to_hashref; + $c->forward('View::JSON'); + } + } + else { + ## if $id not provided, should we do a "create" like POST does? + ## + my $q = 'TPS::Questionnaire::Model::Questionnaire'->from_hashref($c->request->body_data); + $q->save($c->schema); + $c->stash->{'status'} = 'ok'; + $c->stash->{'result'} = $q->to_hashref; + $c->forward('View::JSON'); + } + +}# end put_questionnaire + + + + __PACKAGE__->meta->make_immutable; 1; diff --git a/lib/TPS/Questionnaire/Model/Questionnaire.pm b/lib/TPS/Questionnaire/Model/Questionnaire.pm index ed91d0a..dbec1bc 100644 --- a/lib/TPS/Questionnaire/Model/Questionnaire.pm +++ b/lib/TPS/Questionnaire/Model/Questionnaire.pm @@ -243,6 +243,53 @@ sub summary_list { } + +=head2 update($schema) + +Based on save, but expects that the ID exists. + +Saves the questionnaire (title and is_published) using this object's ID. + +Future improvement is to also save the associated questions to the database. + +=cut + +sub update { + my ($self, $schema) = (shift, @_); + + # Even though this object has 'rw' attributes, questionnaires are + # conceptually write-once. or not. + unless ($self->has_id) { + croak 'Updating questionnaire requires that one already exists', $self; + return; + } + + my $input = { + title => $self->title, + is_published => $self->is_published, + }; + + + my $result = $schema + ->resultset('Questionnaire') + ->search( {questionnaire_id => $self->id} ) + ->update( $input ); + + + ## updated: 21 Oct 2021 by SJS + ## comment: update() works above. need to update() the questions? am I into scope creep, yes. maybe. + ## so the method works for the Questionnaire, and does allow changes to the two fields, and + ## overall, only works for existing Qs. But it does not save/update the list of questions, which + ## really bugs me. But I've run out of development time. bummer. + # my $rank = 0; + # for my $q (@{$self->questions}) { + # $q->_save($self, ++$rank, @_); + # } + + return $self->id; +} + + __PACKAGE__->meta->make_immutable; 1; diff --git a/t/unit/TPS/Questionnaire/Model/Questionnaire.t b/t/unit/TPS/Questionnaire/Model/Questionnaire.t index 7958348..6beaee6 100644 --- a/t/unit/TPS/Questionnaire/Model/Questionnaire.t +++ b/t/unit/TPS/Questionnaire/Model/Questionnaire.t @@ -244,6 +244,138 @@ tests save => sub { ) or diag explain($object); }; + +=begin comment + +Step 1. +Manual test plan using POSTMAN: + do a GET to know active Q's. + do a PUT w/o ID to create new record: + +``` +{ + "title": "Steve Test PUT without id", + "is_published": false, + "questions": [ + { + "question_type": "text", + "question_text": "New question here, does not have an id." + } + ] +} +``` + +get a 200 status ok + +Step 2. +In Pycharm (or other database tool) verify that the new Q was created. Since we are NOT dealing with +questions in the PUT, their presence in the submitted data is optional. We could use POST to create a +new Q (that would include the questions), being sure to set the is_published to 'false'. But, the +result does not include the QID, so there's no real difference. + +Step 3. +Back to POSTMAN, update the url to include the QID created above. + do a PUT w/ID to update the record with: + +``` +{ + "title": "Steve Test PUT with id", + "is_published": true, +} +``` + +get a 200 status ok + +verify in database that the id_published changes from false to true (0 to 1). +verify with GET that the Q is listed. + +Step 4. +Back to POSTMAN, send the same data to the same URL as in Step 3. + +get a 400 status error + +This verifies that the PUT will not update a published Questionnaire. +end. + +=end comment + +=cut + +## updated: 21 Oct 2021 by SJS +## comment: first pass at testing the put method. Need to save hash, but then find the QID, not from GET. +## + +tests update => sub { + plan(2); + + my $object = $CLASS->from_hashref({ + title => 'Steve Test', + is_published => 0, + questions => [ + { + question_type => 'text', + question_text => 'New Question without id.' + } + ], + }); + + require TPS::Questionnaire::Schema; + my $schema = TPS::Questionnaire::Schema->connect( + sub { make_database('schema.sql') }, + ); + + # Save and reload from database + my $id = $object->save($schema); + $object = $CLASS->from_id($schema, $id); + + is( + $object, + object { + prop 'isa' => 'TPS::Questionnaire::Model::Questionnaire'; + call 'title' => string 'Steve Test'; + call 'is_published' => bool !!0; + call 'questions' => array { + item object { + prop 'isa' => 'TPS::Questionnaire::Model::Question::Text'; + call 'question_text' => 'New Question without id.'; + }; + + end(); + }; + }, + 'Correct object loaded from database', + ) or diag explain($object); + + # update the saved object + # + $object->is_published( 1 ); + my $up_id = $object->update($schema); + $object = $CLASS->from_id($schema, $up_id); + + is( + $object, + object { + prop 'isa' => 'TPS::Questionnaire::Model::Questionnaire'; + call 'title' => string 'Steve Test'; + call 'is_published' => bool !!1; + call 'questions' => array { + item object { + prop 'isa' => 'TPS::Questionnaire::Model::Question::Text'; + call 'question_text' => 'New Question without id.'; + }; + + end(); + }; + }, + 'Updated the object properly', + ) + or diag explain($object); +}; + + + + + done_testing(); 1;