Fix questions display

Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
John Molakvoæ (skjnldsv) 2020-03-27 10:29:30 +01:00 committed by Roeland Jago Douma
parent b3570b81dc
commit 612af52f50
No known key found for this signature in database
GPG key ID: F941078878347C0C
6 changed files with 229 additions and 69 deletions

View file

@ -47,6 +47,10 @@ return [
['name' => 'api#newForm', 'url' => 'api/v1/form', 'verb' => 'POST'], ['name' => 'api#newForm', 'url' => 'api/v1/form', 'verb' => 'POST'],
['name' => 'api#deleteForm', 'url' => 'api/v1/form/{id}', 'verb' => 'DELETE'], ['name' => 'api#deleteForm', 'url' => 'api/v1/form/{id}', 'verb' => 'DELETE'],
['name' => 'api#newQuestion', 'url' => 'api/v1/question/', 'verb' => 'POST'],
['name' => 'api#deleteQuestion', 'url' => 'api/v1/question/{id}', 'verb' => 'DELETE'],
['name' => 'api#newAnswer', 'url' => 'api/v1/answer/', 'verb' => 'POST'],
['name' => 'api#deleteAnswer', 'url' => 'api/v1/answer/{id}', 'verb' => 'DELETE'],
['name' => 'system#get_site_users_and_groups', 'url' => '/get/siteusers', 'verb' => 'POST'], ['name' => 'system#get_site_users_and_groups', 'url' => '/get/siteusers', 'verb' => 'POST'],
] ]

View file

@ -32,8 +32,10 @@ use OCP\AppFramework\Controller;
use OCP\AppFramework\Http; use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\IMapperException;
use OCP\IGroupManager; use OCP\IGroupManager;
use OCP\ILogger;
use OCP\IRequest; use OCP\IRequest;
use OCP\IUser; use OCP\IUser;
use OCP\IUserManager; use OCP\IUserManager;
@ -41,7 +43,6 @@ use OCP\Security\ISecureRandom;
use OCA\Forms\Db\Event; use OCA\Forms\Db\Event;
use OCA\Forms\Db\EventMapper; use OCA\Forms\Db\EventMapper;
use OCA\Forms\Db\Vote;
use OCA\Forms\Db\VoteMapper; use OCA\Forms\Db\VoteMapper;
use OCA\Forms\Db\Question; use OCA\Forms\Db\Question;
@ -49,7 +50,7 @@ use OCA\Forms\Db\QuestionMapper;
use OCA\Forms\Db\Answer; use OCA\Forms\Db\Answer;
use OCA\Forms\Db\AnswerMapper; use OCA\Forms\Db\AnswerMapper;
use OCP\Util;
class ApiController extends Controller { class ApiController extends Controller {
@ -60,6 +61,12 @@ class ApiController extends Controller {
private $questionMapper; private $questionMapper;
private $answerMapper; private $answerMapper;
/** @var ILogger */
private $logger;
/** @var string */
private $userId;
/** /**
* PageController constructor. * PageController constructor.
* @param string $appName * @param string $appName
@ -81,7 +88,8 @@ class ApiController extends Controller {
EventMapper $eventMapper, EventMapper $eventMapper,
VoteMapper $voteMapper, VoteMapper $voteMapper,
QuestionMapper $questionMapper, QuestionMapper $questionMapper,
AnswerMapper $answerMapper AnswerMapper $answerMapper,
ILogger $logger
) { ) {
parent::__construct($appName, $request); parent::__construct($appName, $request);
$this->userId = $userId; $this->userId = $userId;
@ -91,6 +99,7 @@ class ApiController extends Controller {
$this->voteMapper = $voteMapper; $this->voteMapper = $voteMapper;
$this->questionMapper = $questionMapper; $this->questionMapper = $questionMapper;
$this->answerMapper = $answerMapper; $this->answerMapper = $answerMapper;
$this->logger = $logger;
} }
/** /**
@ -472,7 +481,6 @@ class ApiController extends Controller {
$newEvent->setHash($oldForm->getHash()); $newEvent->setHash($oldForm->getHash());
$newEvent->setId($oldForm->getId()); $newEvent->setId($oldForm->getId());
$this->eventMapper->update($newEvent); $this->eventMapper->update($newEvent);
$this->questionMapper->deleteByForm($newEvent->getId());
} elseif ($mode === 'create') { } elseif ($mode === 'create') {
// Create new form // Create new form
@ -488,27 +496,6 @@ class ApiController extends Controller {
$newEvent = $this->eventMapper->insert($newEvent); $newEvent = $this->eventMapper->insert($newEvent);
} }
// Update options
foreach($options['formQuizQuestions'] as $questionElement){
$newQuestion = new Question();
$newQuestion->setFormId($newEvent->getId());
$newQuestion->setFormQuestionType($questionElement['type']);
$newQuestion->setFormQuestionText(trim(htmlspecialchars($questionElement['text'])));
$newQuestion = $this->questionMapper->insert($newQuestion);
foreach($questionElement['answers'] as $answer){
$newAnswer = new Answer();
$newAnswer->setFormId($newEvent->getId());
$newAnswer->setQuestionId($newQuestion->getId());
$newAnswer->setText($answer['text']);
$newAnswer = $this->answerMapper->insert($newAnswer);
}
}
return new DataResponse(array( return new DataResponse(array(
'id' => $newEvent->getId(), 'id' => $newEvent->getId(),
'hash' => $newEvent->getHash() 'hash' => $newEvent->getHash()
@ -531,9 +518,136 @@ class ApiController extends Controller {
)); ));
$event->setTitle('New form'); $event->setTitle('New form');
$event->setDescription(''); $event->setDescription('');
$event->setAccess('public');
$this->eventMapper->insert($event); $this->eventMapper->insert($event);
return new Http\JSONResponse($this->getForm($event->getHash())); return new Http\JSONResponse($this->getForm($event->getHash()));
} }
/**
* @NoAdminRequired
*/
public function newQuestion(int $formId, string $type, string $text): Http\JSONResponse {
$this->logger->debug('Adding new question: formId: {formId}, type: {type}, text: {text}', [
'formId' => $formId,
'type' => $type,
'text' => $text,
]);
try {
$form = $this->eventMapper->find($formId);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form');
return new Http\JSONResponse([], Http::STATUS_BAD_REQUEST);
}
if ($form->getOwner() !== $this->userId) {
$this->logger->debug('This form is not owned by the current user');
return new Http\JSONResponse([], Http::STATUS_FORBIDDEN);
}
$question = new Question();
$question->setFormId($formId);
$question->setFormQuestionType($type);
$question->setFormQuestionText($text);
$question = $this->questionMapper->insert($question);
return new Http\JSONResponse($question->getId());
}
/**
* @NoAdminRequired
*/
public function deleteQuestion(int $id): Http\JSONResponse {
$this->logger->debug('Delete question: {id}', [
'id' => $id,
]);
try {
$question = $this->questionMapper->findById($id);
$form = $this->eventMapper->find($question->getFormId());
} catch (IMapperException $e) {
$this->logger->debug('Could not find form or question of this answer');
return new Http\JSONResponse([], Http::STATUS_BAD_REQUEST);
}
if ($form->getOwner() !== $this->userId) {
$this->logger->debug('This form is not owned by the current user');
return new Http\JSONResponse([], Http::STATUS_FORBIDDEN);
}
$this->answerMapper->deleteByQuestion($id);
$this->questionMapper->delete($question);
return new Http\JSONResponse($id);
}
/**
* @NoAdminRequired
*/
public function newAnswer(int $formId, int $questionId, string $text): Http\JSONResponse {
$this->logger->debug('Adding new answer: formId: {formId}, questoinId: {questionId}, text: {text}', [
'formId' => $formId,
'questionId' => $questionId,
'text' => $text,
]);
try {
$form = $this->eventMapper->find($formId);
$question = $this->questionMapper->findById($questionId);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form or question so answer can\'t be added');
return new Http\JSONResponse([], Http::STATUS_BAD_REQUEST);
}
if ($form->getOwner() !== $this->userId) {
$this->logger->debug('This form is not owned by the current user');
return new Http\JSONResponse([], Http::STATUS_FORBIDDEN);
}
if ($question->getFormId() !== $formId) {
$this->logger->debug('This question is not owned by the current user');
return new Http\JSONResponse([], Http::STATUS_FORBIDDEN);
}
$answer = new Answer();
$answer->setFormId($formId);
$answer->setQuestionId($questionId);
$answer->setText($text);
$answer = $this->answerMapper->insert($answer);
return new Http\JSONResponse($answer->getId());
}
/**
* @NoAdminRequired
*/
public function deleteAnswer(int $id): Http\JSONResponse {
$this->logger->debug('Deleting answer: {id}', [
'id' => $id
]);
try {
$answer = $this->answerMapper->findById($id);
$form = $this->eventMapper->find($answer->getFormId());
} catch (IMapperException $e) {
$this->logger->debug('Could not find form or answer');
return new Http\JSONResponse([], Http::STATUS_BAD_REQUEST);
}
if ($form->getOwner() !== $this->userId) {
$this->logger->debug('This form is not owned by the current user');
return new Http\JSONResponse([], Http::STATUS_FORBIDDEN);
}
$this->answerMapper->delete($answer);
//TODO useful response
return new Http\JSONResponse($id);
}
} }

View file

@ -95,4 +95,27 @@ class AnswerMapper extends QBMapper {
$qb->execute(); $qb->execute();
} }
public function findById(int $answerId): Answer {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('id', $qb->createNamedParameter($answerId))
);
return $this->findEntity($qb);
}
public function deleteByQuestion(int $questionId): void {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->getTableName())
->where(
$qb->expr()->eq('question_id', $qb->createNamedParameter($questionId))
);
$qb->execute();
}
} }

View file

@ -30,10 +30,6 @@ use OCP\AppFramework\Db\QBMapper;
class QuestionMapper extends QBMapper { class QuestionMapper extends QBMapper {
/**
* TextMapper constructor.
* @param IDBConnection $db
*/
public function __construct(IDBConnection $db) { public function __construct(IDBConnection $db) {
parent::__construct($db, 'forms_questions', Question::class); parent::__construct($db, 'forms_questions', Question::class);
} }
@ -47,13 +43,13 @@ class QuestionMapper extends QBMapper {
public function findByForm(int $formId): array { public function findByForm(int $formId): array {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('*') $qb->select('*')
->from($this->getTableName()) ->from($this->getTableName())
->where( ->where(
$qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT)) $qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT))
); );
return $this->findEntities($qb); return $this->findEntities($qb);
} }
/** /**
@ -62,12 +58,24 @@ class QuestionMapper extends QBMapper {
public function deleteByForm(int $formId): void { public function deleteByForm(int $formId): void {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->delete($this->getTableName()) $qb->delete($this->getTableName())
->where( ->where(
$qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT)) $qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT))
); );
$qb->execute(); $qb->execute();
}
public function findById(int $questionId): Question {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('id', $qb->createNamedParameter($questionId))
);
return $this->findEntity($qb);
} }
} }

View file

@ -33,17 +33,16 @@
name="list" name="list"
tag="ul" tag="ul"
class="form-table"> class="form-table">
<li <TextFormItem
is="text-form-item" v-for="(answer, index) in answers"
v-for="(ans, index) in formQuizAnswers" :key="answer.id"
:key="ans.id" :option="answer"
:option="ans" @remove="emitRemoveAnswer(question, answer, index)"
@remove="emitRemoveAnswer(question, index)"
@delete="question.answers.splice(index, 1)" /> @delete="question.answers.splice(index, 1)" />
</transitionGroup> </transitionGroup>
</div> </div>
<div> <div>
<a class="icon icon-delete svg delete-form" @click="$emit('remove'), $emit('delete')" /> <a class="icon icon-delete svg delete-form" @click="$emit('deleteQuestion')" />
</div> </div>
</li> </li>
</template> </template>
@ -58,25 +57,29 @@ export default {
question: { question: {
type: Object, type: Object,
default: undefined, default: undefined,
answers: [],
}, },
}, },
data() { data() {
return { return {
formQuizAnswers: [],
nextQuizAnswerId: 1, nextQuizAnswerId: 1,
newQuizAnswer: '', newQuizAnswer: '',
type: '', type: '',
} }
}, },
computed: {
answers() {
return this.question.answers || []
},
},
methods: { methods: {
emitNewAnswer(question) { emitNewAnswer(question) {
this.$emit('add-answer', this, question) this.$emit('add-answer', this, question)
}, },
emitRemoveAnswer(question, id) { emitRemoveAnswer(question, answer, index) {
this.$emit('remove-answer', this, question, id) this.$emit('remove-answer', question, answer, index)
}, },
}, },
} }

View file

@ -66,15 +66,14 @@
name="list" name="list"
tag="ul" tag="ul"
class="form-table"> class="form-table">
<li <QuizFormItem
is="quiz-form-item"
v-for="(question, index) in form.options.formQuizQuestions" v-for="(question, index) in form.options.formQuizQuestions"
:key="question.id" :key="question.id"
:question="question" :question="question"
:type="question.type" :type="question.type"
@add-answer="addAnswer" @add-answer="addAnswer"
@remove-answer="removeAnswer" @remove-answer="deleteAnswer"
@remove="form.options.formQuizQuestions.splice(index, 1)" /> @deleteQuestion="deleteQuestion(question, index)" />
</transitionGroup> </transitionGroup>
</div> </div>
</div> </div>
@ -82,6 +81,7 @@
</template> </template>
<script> <script>
import { generateUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import moment from '@nextcloud/moment' import moment from '@nextcloud/moment'
import debounce from 'debounce' import debounce from 'debounce'
@ -198,7 +198,7 @@ export default {
}) })
}, },
addQuestion() { async addQuestion() {
this.checkNames() this.checkNames()
if (this.selected === '') { if (this.selected === '') {
showError(t('forms', 'Select a question type!'), { duration: 3000 }) showError(t('forms', 'Select a question type!'), { duration: 3000 })
@ -206,8 +206,11 @@ export default {
showError(t('forms', 'Cannot have the same question!')) showError(t('forms', 'Cannot have the same question!'))
} else { } else {
if (this.newQuizQuestion !== null & this.newQuizQuestion !== '' & (/\S/.test(this.newQuizQuestion))) { if (this.newQuizQuestion !== null & this.newQuizQuestion !== '' & (/\S/.test(this.newQuizQuestion))) {
const response = await axios.post(generateUrl('/apps/forms/api/v1/question/'), { formId: this.form.id, type: this.selected, text: this.newQuizQuestion })
const questionId = response.data
this.form.options.formQuizQuestions.push({ this.form.options.formQuizQuestions.push({
id: this.nextQuizQuestionId++, id: questionId,
text: this.newQuizQuestion, text: this.newQuizQuestion,
type: this.selected, type: this.selected,
answers: [], answers: [],
@ -217,6 +220,12 @@ export default {
} }
}, },
async deleteQuestion(question, index) {
await axios.delete(generateUrl('/apps/forms/api/v1/question/{id}', { id: question.id }))
// TODO catch Error
this.form.options.formQuizQuestions.splice(index, 1)
},
checkAnsNames(item, question) { checkAnsNames(item, question) {
this.uniqueAnsName = true this.uniqueAnsName = true
question.answers.forEach(q => { question.answers.forEach(q => {
@ -226,31 +235,30 @@ export default {
}) })
}, },
removeAnswer(item, question, index) { async addAnswer(item, question) {
item.formQuizAnswers.splice(index, 1)
question.answers.splice(index, 1)
},
addAnswer(item, question) {
this.checkAnsNames(item, question) this.checkAnsNames(item, question)
if (!this.uniqueAnsName) { if (!this.uniqueAnsName) {
showError(t('forms', 'Two answers cannot be the same!'), { duration: 3000 }) showError(t('forms', 'Two answers cannot be the same!'), { duration: 3000 })
} else { } else {
if (item.newQuizAnswer !== null & item.newQuizAnswer !== '' & (/\S/.test(item.newQuizAnswer))) { if (item.newQuizAnswer !== null & item.newQuizAnswer !== '' & (/\S/.test(item.newQuizAnswer))) {
item.formQuizAnswers.push({ const response = await axios.post(generateUrl('/apps/forms/api/v1/answer/'), { formId: this.form.id, questionId: question.id, text: item.newQuizAnswer })
id: item.nextQuizAnswerId, const answerId = response.data
text: item.newQuizAnswer,
})
question.answers.push({ question.answers.push({
id: item.nextQuizAnswerId, id: answerId,
text: item.newQuizAnswer, text: item.newQuizAnswer,
}) })
item.nextQuizAnswerId++
} }
item.newQuizAnswer = '' item.newQuizAnswer = ''
} }
}, },
async deleteAnswer(question, answer, index) {
await axios.delete(generateUrl('/apps/forms/api/v1/answer/{id}', { id: answer.id }))
// TODO catch errors
question.answers.splice(index, 1)
},
allHaveAns() { allHaveAns() {
this.haveAns = true this.haveAns = true
this.form.options.formQuizQuestions.forEach(q => { this.form.options.formQuizQuestions.forEach(q => {