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#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'],
]

View file

@ -32,8 +32,10 @@ use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\IMapperException;
use OCP\IGroupManager;
use OCP\ILogger;
use OCP\IRequest;
use OCP\IUser;
use OCP\IUserManager;
@ -41,7 +43,6 @@ use OCP\Security\ISecureRandom;
use OCA\Forms\Db\Event;
use OCA\Forms\Db\EventMapper;
use OCA\Forms\Db\Vote;
use OCA\Forms\Db\VoteMapper;
use OCA\Forms\Db\Question;
@ -49,7 +50,7 @@ use OCA\Forms\Db\QuestionMapper;
use OCA\Forms\Db\Answer;
use OCA\Forms\Db\AnswerMapper;
use OCP\Util;
class ApiController extends Controller {
@ -60,6 +61,12 @@ class ApiController extends Controller {
private $questionMapper;
private $answerMapper;
/** @var ILogger */
private $logger;
/** @var string */
private $userId;
/**
* PageController constructor.
* @param string $appName
@ -81,7 +88,8 @@ class ApiController extends Controller {
EventMapper $eventMapper,
VoteMapper $voteMapper,
QuestionMapper $questionMapper,
AnswerMapper $answerMapper
AnswerMapper $answerMapper,
ILogger $logger
) {
parent::__construct($appName, $request);
$this->userId = $userId;
@ -91,6 +99,7 @@ class ApiController extends Controller {
$this->voteMapper = $voteMapper;
$this->questionMapper = $questionMapper;
$this->answerMapper = $answerMapper;
$this->logger = $logger;
}
/**
@ -472,7 +481,6 @@ class ApiController extends Controller {
$newEvent->setHash($oldForm->getHash());
$newEvent->setId($oldForm->getId());
$this->eventMapper->update($newEvent);
$this->questionMapper->deleteByForm($newEvent->getId());
} elseif ($mode === 'create') {
// Create new form
@ -488,27 +496,6 @@ class ApiController extends Controller {
$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(
'id' => $newEvent->getId(),
'hash' => $newEvent->getHash()
@ -531,9 +518,136 @@ class ApiController extends Controller {
));
$event->setTitle('New form');
$event->setDescription('');
$event->setAccess('public');
$this->eventMapper->insert($event);
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();
}
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 {
/**
* TextMapper constructor.
* @param IDBConnection $db
*/
public function __construct(IDBConnection $db) {
parent::__construct($db, 'forms_questions', Question::class);
}
@ -47,13 +43,13 @@ class QuestionMapper extends QBMapper {
public function findByForm(int $formId): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT))
);
$qb->select('*')
->from($this->getTableName())
->where(
$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 {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->getTableName())
->where(
$qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT))
);
$qb->delete($this->getTableName())
->where(
$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"
tag="ul"
class="form-table">
<li
is="text-form-item"
v-for="(ans, index) in formQuizAnswers"
:key="ans.id"
:option="ans"
@remove="emitRemoveAnswer(question, index)"
<TextFormItem
v-for="(answer, index) in answers"
:key="answer.id"
:option="answer"
@remove="emitRemoveAnswer(question, answer, index)"
@delete="question.answers.splice(index, 1)" />
</transitionGroup>
</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>
</li>
</template>
@ -58,25 +57,29 @@ export default {
question: {
type: Object,
default: undefined,
answers: [],
},
},
data() {
return {
formQuizAnswers: [],
nextQuizAnswerId: 1,
newQuizAnswer: '',
type: '',
}
},
computed: {
answers() {
return this.question.answers || []
},
},
methods: {
emitNewAnswer(question) {
this.$emit('add-answer', this, question)
},
emitRemoveAnswer(question, id) {
this.$emit('remove-answer', this, question, id)
emitRemoveAnswer(question, answer, index) {
this.$emit('remove-answer', question, answer, index)
},
},
}

View file

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