Insert Submission, new API
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
parent
7462be7bfe
commit
e0d41aef0c
|
@ -32,7 +32,6 @@ return [
|
||||||
['name' => 'page#getResult', 'url' => '/{hash}/results', 'verb' => 'GET'],
|
['name' => 'page#getResult', 'url' => '/{hash}/results', 'verb' => 'GET'],
|
||||||
|
|
||||||
['name' => 'page#goto_form', 'url' => '/{hash}', 'verb' => 'GET'],
|
['name' => 'page#goto_form', 'url' => '/{hash}', 'verb' => 'GET'],
|
||||||
['name' => 'page#insert_submission', 'url' => '/insert/submission', 'verb' => 'POST'],
|
|
||||||
|
|
||||||
// Forms
|
// Forms
|
||||||
['name' => 'api#getForms', 'url' => '/api/v1/forms', 'verb' => 'GET'],
|
['name' => 'api#getForms', 'url' => '/api/v1/forms', 'verb' => 'GET'],
|
||||||
|
@ -53,6 +52,7 @@ return [
|
||||||
['name' => 'api#deleteOption', 'url' => '/api/v1/option/{id}', 'verb' => 'DELETE'],
|
['name' => 'api#deleteOption', 'url' => '/api/v1/option/{id}', 'verb' => 'DELETE'],
|
||||||
|
|
||||||
['name' => 'api#getSubmissions', 'url' => '/api/v1/submissions/{hash}', 'verb' => 'GET'],
|
['name' => 'api#getSubmissions', 'url' => '/api/v1/submissions/{hash}', 'verb' => 'GET'],
|
||||||
|
['name' => 'api#insertSubmission', 'url' => '/api/v1/submissions/insert', 'verb' => 'POST'],
|
||||||
|
|
||||||
['name' => 'system#get_site_users_and_groups', 'url' => '/get/siteusers', 'verb' => 'POST'],
|
['name' => 'system#get_site_users_and_groups', 'url' => '/get/siteusers', 'verb' => 'POST'],
|
||||||
]
|
]
|
||||||
|
|
|
@ -38,6 +38,7 @@ use OCP\AppFramework\Db\IMapperException;
|
||||||
use OCP\ILogger;
|
use OCP\ILogger;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
use OCP\IUser;
|
use OCP\IUser;
|
||||||
|
use OCP\IUserSession;
|
||||||
use OCP\Security\ISecureRandom;
|
use OCP\Security\ISecureRandom;
|
||||||
|
|
||||||
use OCA\Forms\Db\Form;
|
use OCA\Forms\Db\Form;
|
||||||
|
@ -62,12 +63,13 @@ class ApiController extends Controller {
|
||||||
/** @var ILogger */
|
/** @var ILogger */
|
||||||
private $logger;
|
private $logger;
|
||||||
|
|
||||||
/** @var string */
|
/** @var IUserSession */
|
||||||
private $userId;
|
private $userSession;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
IRequest $request,
|
IRequest $request,
|
||||||
$userId,
|
$userId, // TODO remove & replace with userSession below.
|
||||||
|
IUserSession $userSession,
|
||||||
FormMapper $formMapper,
|
FormMapper $formMapper,
|
||||||
SubmissionMapper $submissionMapper,
|
SubmissionMapper $submissionMapper,
|
||||||
AnswerMapper $answerMapper,
|
AnswerMapper $answerMapper,
|
||||||
|
@ -77,6 +79,7 @@ class ApiController extends Controller {
|
||||||
) {
|
) {
|
||||||
parent::__construct(Application::APP_ID, $request);
|
parent::__construct(Application::APP_ID, $request);
|
||||||
$this->userId = $userId;
|
$this->userId = $userId;
|
||||||
|
$this->userSession = $userSession;
|
||||||
$this->formMapper = $formMapper;
|
$this->formMapper = $formMapper;
|
||||||
$this->submissionMapper = $submissionMapper;
|
$this->submissionMapper = $submissionMapper;
|
||||||
$this->answerMapper = $answerMapper;
|
$this->answerMapper = $answerMapper;
|
||||||
|
@ -603,4 +606,84 @@ class ApiController extends Controller {
|
||||||
|
|
||||||
return new Http\JSONResponse($result);
|
return new Http\JSONResponse($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @PublicPage
|
||||||
|
*
|
||||||
|
* Process a new submission
|
||||||
|
* @param int $formId
|
||||||
|
* @param array $answers [question_id => arrayOfString]
|
||||||
|
*/
|
||||||
|
public function insertSubmission(int $formId, array $answers) {
|
||||||
|
$this->logger->debug('Inserting submission: formId: {formId}, answers: {answers}', [
|
||||||
|
'formId' => $formId,
|
||||||
|
'answers' => $answers,
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$form = $this->formMapper->findById($formId);
|
||||||
|
$questions = $this->getQuestions($formId);
|
||||||
|
} catch (IMapperException $e) {
|
||||||
|
$this->logger->debug('Could not find form');
|
||||||
|
return new Http\JSONResponse(['message' => 'Could not find form'], Http::STATUS_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->userSession->getUser();
|
||||||
|
// TODO check again hasUserAccess?!
|
||||||
|
|
||||||
|
// Create Submission
|
||||||
|
$submission = new Submission();
|
||||||
|
$submission->setFormId($formId);
|
||||||
|
$submission->setTimestamp(time());
|
||||||
|
|
||||||
|
// If not logged in or anonymous use anonID
|
||||||
|
if (!$user || $form->getIsAnonymous()){
|
||||||
|
$anonID = "anon-user-". hash('md5', (time() + rand()));
|
||||||
|
$submission->setUserId($anonID);
|
||||||
|
} else {
|
||||||
|
$submission->setUserId($user->getUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert new submission
|
||||||
|
$this->submissionMapper->insert($submission);
|
||||||
|
$submissionId = $submission->getId();
|
||||||
|
|
||||||
|
// Process Answers
|
||||||
|
foreach($answers as $questionId => $answerArray) {
|
||||||
|
// Search corresponding Question, skip processing if not found
|
||||||
|
$questionIndex = array_search($questionId, array_column($questions, 'id'));
|
||||||
|
if ($questionIndex === false) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$question = $questions[$questionIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($answerArray as $answer) {
|
||||||
|
if($question['type'] === 'multiple' || $question['type'] === 'multiple_unique') {
|
||||||
|
// Search corresponding option, skip processing if not found
|
||||||
|
$optionIndex = array_search($answer, array_column($question['options'], 'id'));
|
||||||
|
if($optionIndex === false) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$option = $question['options'][$optionIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load option-text
|
||||||
|
$answerText = $option['text'];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$answerText = $answer; // Not a multiple-question, answerText is given answer
|
||||||
|
}
|
||||||
|
|
||||||
|
$answerEntity = new Answer();
|
||||||
|
$answerEntity->setSubmissionId($submissionId);
|
||||||
|
$answerEntity->setQuestionId($question['id']);
|
||||||
|
$answerEntity->setText($answerText);
|
||||||
|
$this->answerMapper->insert($answerEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Http\JSONResponse([]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,19 +29,16 @@
|
||||||
|
|
||||||
namespace OCA\Forms\Controller;
|
namespace OCA\Forms\Controller;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use OCA\Forms\Db\Form;
|
use OCA\Forms\Db\Form;
|
||||||
use OCA\Forms\Db\FormMapper;
|
use OCA\Forms\Db\FormMapper;
|
||||||
use OCA\Forms\Db\Submission;
|
use OCA\Forms\Db\Question;
|
||||||
use OCA\Forms\Db\SubmissionMapper;
|
|
||||||
use OCA\Forms\Db\Answer;
|
|
||||||
use OCA\Forms\Db\AnswerMapper;
|
|
||||||
|
|
||||||
use OCA\Forms\Db\OptionMapper;
|
|
||||||
use OCA\Forms\Db\QuestionMapper;
|
use OCA\Forms\Db\QuestionMapper;
|
||||||
|
use OCA\Forms\Db\Option;
|
||||||
|
use OCA\Forms\Db\OptionMapper;
|
||||||
|
|
||||||
use OCP\AppFramework\Controller;
|
use OCP\AppFramework\Controller;
|
||||||
use OCP\AppFramework\Db\DoesNotExistException;
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
use OCP\AppFramework\Http\RedirectResponse;
|
|
||||||
use OCP\AppFramework\Http\TemplateResponse;
|
use OCP\AppFramework\Http\TemplateResponse;
|
||||||
use OCP\IGroupManager;
|
use OCP\IGroupManager;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
|
@ -57,12 +54,6 @@ class PageController extends Controller {
|
||||||
/** @var FormMapper */
|
/** @var FormMapper */
|
||||||
private $formMapper;
|
private $formMapper;
|
||||||
|
|
||||||
/** @var SubmissionMapper */
|
|
||||||
private $submissionMapper;
|
|
||||||
|
|
||||||
/** @var AnswerMapper */
|
|
||||||
private $answerMapper;
|
|
||||||
|
|
||||||
/** @var IURLGenerator */
|
/** @var IURLGenerator */
|
||||||
private $urlGenerator;
|
private $urlGenerator;
|
||||||
|
|
||||||
|
@ -82,8 +73,6 @@ class PageController extends Controller {
|
||||||
FormMapper $formMapper,
|
FormMapper $formMapper,
|
||||||
QuestionMapper $questionMapper,
|
QuestionMapper $questionMapper,
|
||||||
OptionMapper $optionMapper,
|
OptionMapper $optionMapper,
|
||||||
SubmissionMapper $SubmissionMapper,
|
|
||||||
AnswerMapper $AnswerMapper,
|
|
||||||
IUserSession $userSession,
|
IUserSession $userSession,
|
||||||
IInitialStateService $initialStateService) {
|
IInitialStateService $initialStateService) {
|
||||||
parent::__construct($appName, $request);
|
parent::__construct($appName, $request);
|
||||||
|
@ -94,8 +83,6 @@ class PageController extends Controller {
|
||||||
$this->formMapper = $formMapper;
|
$this->formMapper = $formMapper;
|
||||||
$this->questionMapper = $questionMapper;
|
$this->questionMapper = $questionMapper;
|
||||||
$this->optionMapper = $optionMapper;
|
$this->optionMapper = $optionMapper;
|
||||||
$this->submissionMapper = $SubmissionMapper;
|
|
||||||
$this->answerMapper = $AnswerMapper;
|
|
||||||
$this->userSession = $userSession;
|
$this->userSession = $userSession;
|
||||||
$this->initialStateService = $initialStateService;
|
$this->initialStateService = $initialStateService;
|
||||||
}
|
}
|
||||||
|
@ -237,62 +224,6 @@ class PageController extends Controller {
|
||||||
return new TemplateResponse($this->appName, 'main', [], $renderAs);
|
return new TemplateResponse($this->appName, 'main', [], $renderAs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
* @PublicPage
|
|
||||||
*
|
|
||||||
* Process a new submission
|
|
||||||
* @param int $formId
|
|
||||||
* @param string $userId
|
|
||||||
* @param array $answers
|
|
||||||
* @param array $questions
|
|
||||||
* @return RedirectResponse
|
|
||||||
*/
|
|
||||||
public function insertSubmission(int $id, string $userId, array $answers, array $questions) {
|
|
||||||
|
|
||||||
$form = $this->formMapper->findById($id);
|
|
||||||
$anonID = "anon-user-". hash('md5', (time() + rand()));
|
|
||||||
|
|
||||||
// Insert Submission
|
|
||||||
$submission = new Submission();
|
|
||||||
$submission->setFormId($id);
|
|
||||||
if ($form->getIsAnonymous()){
|
|
||||||
$submission->setUserId($anonID);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$submission->setUserId($userId);
|
|
||||||
}
|
|
||||||
$submission->setTimestamp(time());
|
|
||||||
$this->submissionMapper->insert($submission);
|
|
||||||
$submissionId = $submission->getId();
|
|
||||||
|
|
||||||
//Insert Answers
|
|
||||||
foreach($questions as $question) {
|
|
||||||
// If question is answered, the questionText exists as key in $answers. Does not exist, when a (non-mandatory) question was not answered.
|
|
||||||
if (array_key_exists($question['text'], $answers)) {
|
|
||||||
if($question['type'] === "checkbox"){
|
|
||||||
foreach(($answers[$question['text']]) as $ansText) {
|
|
||||||
$answer = new Answer();
|
|
||||||
$answer->setSubmissionId($submissionId);
|
|
||||||
$answer->setQuestionId($question['id']);
|
|
||||||
$answer->setText($ansText);
|
|
||||||
$this->answerMapper->insert($answer);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$answer = new Answer();
|
|
||||||
$answer->setSubmissionId($submissionId);
|
|
||||||
$answer->setQuestionId($question['id']);
|
|
||||||
$answer->setText($answers[$question['text']]);
|
|
||||||
$this->answerMapper->insert($answer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$hash = $form->getHash();
|
|
||||||
$url = $this->urlGenerator->linkToRoute('forms.page.goto_form', ['hash' => $hash]);
|
|
||||||
return new RedirectResponse($url);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
* Check if user has access to this form
|
* Check if user has access to this form
|
||||||
|
|
|
@ -43,7 +43,8 @@
|
||||||
}"
|
}"
|
||||||
:name="`${id}-answer`"
|
:name="`${id}-answer`"
|
||||||
:required="true /* TODO: implement required option */"
|
:required="true /* TODO: implement required option */"
|
||||||
:type="isUnique ? 'radio' : 'checkbox'">
|
:type="isUnique ? 'radio' : 'checkbox'"
|
||||||
|
@change="onChange($event, answer.id)">
|
||||||
<label v-if="!edit"
|
<label v-if="!edit"
|
||||||
ref="label"
|
ref="label"
|
||||||
:for="`${id}-answer-${answer.id}`"
|
:for="`${id}-answer-${answer.id}`"
|
||||||
|
@ -126,6 +127,26 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
onChange(event, answerId) {
|
||||||
|
const isChecked = event.target.checked === true
|
||||||
|
let values = this.values.slice()
|
||||||
|
|
||||||
|
// Radio
|
||||||
|
if (this.isUnique) {
|
||||||
|
this.$emit('update:values', [answerId])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checkbox
|
||||||
|
if (isChecked) {
|
||||||
|
values.push(answerId)
|
||||||
|
} else {
|
||||||
|
values = values.filter(id => id !== answerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit values and remove duplicates
|
||||||
|
this.$emit('update:values', [...new Set(values)])
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the provided index checked
|
* Is the provided index checked
|
||||||
|
|
|
@ -392,7 +392,7 @@ export default {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding-left: 14px; // align with description (compensate font size diff)
|
padding-left: 14px; // align with description (compensate font size diff)
|
||||||
overflow: hidden;
|
overflow-x: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,9 +44,14 @@
|
||||||
:read-only="true"
|
:read-only="true"
|
||||||
:model="answerTypes[question.type]"
|
:model="answerTypes[question.type]"
|
||||||
:index="index + 1"
|
:index="index + 1"
|
||||||
v-bind="question" />
|
v-bind="question"
|
||||||
|
:values.sync="answers[question.id]" />
|
||||||
</ul>
|
</ul>
|
||||||
<input class="primary" type="submit" :value="t('forms', 'Submit')" :aria-label="t('forms', 'Submit form')">
|
<input class="primary"
|
||||||
|
type="submit"
|
||||||
|
:value="t('forms', 'Submit')"
|
||||||
|
:disabled="loading"
|
||||||
|
:aria-label="t('forms', 'Submit form')">
|
||||||
</form>
|
</form>
|
||||||
</AppContent>
|
</AppContent>
|
||||||
</Content>
|
</Content>
|
||||||
|
@ -54,12 +59,14 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { loadState } from '@nextcloud/initial-state'
|
import { loadState } from '@nextcloud/initial-state'
|
||||||
|
import { generateUrl } from '@nextcloud/router'
|
||||||
import answerTypes from '../models/AnswerTypes'
|
import { showError } from '@nextcloud/dialogs'
|
||||||
|
import axios from '@nextcloud/axios'
|
||||||
import AppContent from '@nextcloud/vue/dist/Components/AppContent'
|
import AppContent from '@nextcloud/vue/dist/Components/AppContent'
|
||||||
import Content from '@nextcloud/vue/dist/Components/Content'
|
import Content from '@nextcloud/vue/dist/Components/Content'
|
||||||
|
|
||||||
|
import answerTypes from '../models/AnswerTypes'
|
||||||
|
|
||||||
import Question from '../components/Questions/Question'
|
import Question from '../components/Questions/Question'
|
||||||
import QuestionLong from '../components/Questions/QuestionLong'
|
import QuestionLong from '../components/Questions/QuestionLong'
|
||||||
import QuestionShort from '../components/Questions/QuestionShort'
|
import QuestionShort from '../components/Questions/QuestionShort'
|
||||||
|
@ -81,6 +88,9 @@ export default {
|
||||||
return {
|
return {
|
||||||
form: loadState('forms', 'form'),
|
form: loadState('forms', 'form'),
|
||||||
answerTypes,
|
answerTypes,
|
||||||
|
answers: {},
|
||||||
|
loading: false,
|
||||||
|
success: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -102,8 +112,24 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
onSubmit(e) {
|
/**
|
||||||
console.info(e)
|
* Submit the form after the browser validated it 🚀
|
||||||
|
*/
|
||||||
|
async onSubmit() {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post(generateUrl('/apps/forms/api/v1/submissions/insert'), {
|
||||||
|
formId: this.form.id,
|
||||||
|
answers: this.answers,
|
||||||
|
})
|
||||||
|
this.success = true
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
showError(t('forms', 'There was an error submitting the form'))
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue