diff --git a/appinfo/routes.php b/appinfo/routes.php index f188b0d..e051902 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -54,8 +54,12 @@ return [ ['name' => 'api#updateOption', 'url' => '/api/v1/option/update', 'verb' => 'POST'], ['name' => 'api#deleteOption', 'url' => '/api/v1/option/{id}', 'verb' => 'DELETE'], + // Submissions ['name' => 'api#getSubmissions', 'url' => '/api/v1/submissions/{hash}', 'verb' => 'GET'], - ['name' => 'api#insertSubmission', 'url' => '/api/v1/submissions/insert', 'verb' => 'POST'], + ['name' => 'api#deleteAllSubmissions', 'url' => '/api/v1/submissions/{formId}', 'verb' => 'DELETE'], + + ['name' => 'api#insertSubmission', 'url' => '/api/v1/submission/insert', 'verb' => 'POST'], + ['name' => 'api#deleteSubmission', 'url' => '/api/v1/submission/{id}', 'verb' => 'DELETE'], ['name' => 'system#get_site_users_and_groups', 'url' => '/get/siteusers', 'verb' => 'POST'], ] diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php index 6e8c494..13fef69 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -42,8 +42,11 @@ use OCP\AppFramework\Controller; use OCP\AppFramework\Db\IMapperException; use OCP\AppFramework\Http; use OCP\ILogger; +use OCP\IL10N; use OCP\IRequest; +use OCP\IUser; use OCP\IUserSession; +use OCP\IUserManager; use OCP\Security\ISecureRandom; class ApiController extends Controller { @@ -67,9 +70,15 @@ class ApiController extends Controller { /** @var ILogger */ private $logger; + /** @var IL10N */ + private $l10n; + /** @var IUserSession */ private $userSession; - + + /** @var IUserManager */ + private $userManager; + /** @var FormsService */ private $formsService; @@ -77,17 +86,20 @@ class ApiController extends Controller { IRequest $request, $userId, // TODO remove & replace with userSession below. IUserSession $userSession, + IUserManager $userManager, FormMapper $formMapper, SubmissionMapper $submissionMapper, AnswerMapper $answerMapper, QuestionMapper $questionMapper, OptionMapper $optionMapper, ILogger $logger, + IL10N $l10n, FormsService $formsService) { parent::__construct($appName, $request); $this->appName = $appName; $this->userId = $userId; $this->userSession = $userSession; + $this->userManager = $userManager; $this->formMapper = $formMapper; $this->questionMapper = $questionMapper; $this->optionMapper = $optionMapper; @@ -96,6 +108,7 @@ class ApiController extends Controller { $this->questionMapper = $questionMapper; $this->optionMapper = $optionMapper; $this->logger = $logger; + $this->l10n = $l10n; $this->formsService = $formsService; } @@ -553,6 +566,25 @@ class ApiController extends Controller { return new Http\JSONResponse($id); } + /** + * @NoAdminRequired + */ + private function getAnswers(int $submissionId): array { + try { + $answerEntities = $this->answerMapper->findBySubmission($submissionId); + } catch (DoesNotExistException $e) { + //Just ignore, if no Data. Returns empty Answers-Array + } + + // Load Answer-Data + $answers = []; + foreach ($answerEntities as $answerEntity) { + $answers[] = $answerEntity->read(); + } + + return $answers; + } + /** * @NoAdminRequired */ @@ -569,24 +601,54 @@ class ApiController extends Controller { return new Http\JSONResponse([], Http::STATUS_FORBIDDEN); } - $result = []; - $submissionList = $this->submissionMapper->findByForm($form->getId()); - foreach ($submissionList as $submissionEntity) { - $answerList = $this->answerMapper->findBySubmission($submissionEntity->id); - foreach ($answerList as $answerEntity) { - $answer = $answerEntity->read(); - //Temporary Adapt Data to be usable by old Results-View - $answer['userId'] = $submissionEntity->getUserId(); - - $question = $this->questionMapper->findById($answer['questionId']); - $answer['questionText'] = $question->getText(); - $answer['questionType'] = $question->getType(); - - $result[] = $answer; - } + try { + $submissionEntities = $this->submissionMapper->findByForm($form->getId()); + } catch (DoesNotExistException $e) { + //Just ignore, if no Data. Returns empty Submissions-Array } - return new Http\JSONResponse($result); + $submissions = []; + foreach ($submissionEntities as $submissionEntity) { + // Load Submission-Data & corresponding Answers + $submission = $submissionEntity->read(); + $submission['answers'] = $this->getAnswers($submission['id']); + + // Append Display Name + if (substr($submission['userId'], 0, 10) === 'anon-user-') { + // Anonymous User + $submission['userDisplayName'] = $this->l10n->t('anonymous user'); + } else { + $userEntity = $this->userManager->get($submission['userId']); + + if ($userEntity instanceof IUser) { + $submission['userDisplayName'] = $userEntity->getDisplayName(); + } else { + // Fallback, should not occur regularly. + $submission['userDisplayName'] = $submission['userId']; + } + } + + // Add to returned List of Submissions + $submissions[] = $submission; + } + + // Load question-texts, including deleted ones. + try { + $questionEntities = $this->questionMapper->findByForm($form->getId()); + } catch (DoesNotExistException $e) { + //handle silently + } + $questions = []; + foreach ($questionEntities as $questionEntity) { + $questions[] = $questionEntity->read(); + } + + $response = [ + 'submissions' => $submissions, + 'questions' => $questions, + ]; + + return new Http\JSONResponse($response); } /** @@ -680,4 +742,57 @@ class ApiController extends Controller { return new Http\JSONResponse([]); } + + /** + * @NoAdminRequired + */ + public function deleteSubmission(int $id): Http\JSONResponse { + $this->logger->debug('Delete Submission: {id}', [ + 'id' => $id, + ]); + + try { + $submission = $this->submissionMapper->findById($id); + $form = $this->formMapper->findById($submission->getFormId()); + } catch (IMapperException $e) { + $this->logger->debug('Could not find form or submission'); + return new Http\JSONResponse([], Http::STATUS_BAD_REQUEST); + } + + if ($form->getOwnerId() !== $this->userId) { + $this->logger->debug('This form is not owned by the current user'); + return new Http\JSONResponse([], Http::STATUS_FORBIDDEN); + } + + // Delete submission (incl. Answers) + $this->submissionMapper->delete($submission); + + return new Http\JSONResponse($id); + } + + /** + * @NoAdminRequired + */ + public function deleteAllSubmissions(int $formId): Http\JSONResponse { + $this->logger->debug('Delete all submissions to form: {formId}', [ + 'formId' => $formId, + ]); + + try { + $form = $this->formMapper->findById($formId); + } catch (IMapperException $e) { + $this->logger->debug('Could not find form'); + return new Http\JSONResponse([], Http::STATUS_BAD_REQUEST); + } + + if ($form->getOwnerId() !== $this->userId) { + $this->logger->debug('This form is not owned by the current user'); + return new Http\JSONResponse([], Http::STATUS_FORBIDDEN); + } + + // Delete all submissions (incl. Answers) + $this->submissionMapper->deleteByForm($formId); + + return new Http\JSONResponse($id); + } } diff --git a/lib/Db/SubmissionMapper.php b/lib/Db/SubmissionMapper.php index a8131b4..85ac5f9 100644 --- a/lib/Db/SubmissionMapper.php +++ b/lib/Db/SubmissionMapper.php @@ -57,11 +57,31 @@ class SubmissionMapper extends QBMapper { ->from($this->getTableName()) ->where( $qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT)) - ); + ) + //Newest submissions first + ->orderBy('timestamp', 'DESC'); return $this->findEntities($qb); } + /** + * @param Integer $id + * @return Submission + * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + */ + public function findById(int $id): Submission { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*') + ->from($this->getTableName()) + ->where( + $qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)) + ); + + return $this->findEntity($qb); + } + /** * @param int $formId * @throws DoesNotExistException if not found diff --git a/src/components/Results/Answer.vue b/src/components/Results/Answer.vue new file mode 100644 index 0000000..3352ac5 --- /dev/null +++ b/src/components/Results/Answer.vue @@ -0,0 +1,59 @@ + + + + + + + diff --git a/src/components/Results/Submission.vue b/src/components/Results/Submission.vue new file mode 100644 index 0000000..89d3369 --- /dev/null +++ b/src/components/Results/Submission.vue @@ -0,0 +1,129 @@ + + + + + + + diff --git a/src/components/TopBar.vue b/src/components/TopBar.vue index 83859cf..711ddb8 100644 --- a/src/components/TopBar.vue +++ b/src/components/TopBar.vue @@ -51,7 +51,15 @@ $top-bar-height: 60px; button { cursor: pointer; - &:not(:first-child) { + min-height: 44px; + + // Fix button having too little spacing left and right of text + &:not(.button-small) { + padding-left: 16px; + padding-right: 16px; + } + + &.button-small { width: 44px; height: 44px; border: none; diff --git a/src/components/resultItem.vue b/src/components/resultItem.vue deleted file mode 100644 index 03959d3..0000000 --- a/src/components/resultItem.vue +++ /dev/null @@ -1,354 +0,0 @@ - - - - - - -x diff --git a/src/views/Create.vue b/src/views/Create.vue index a247944..ad6d277 100644 --- a/src/views/Create.vue +++ b/src/views/Create.vue @@ -38,6 +38,7 @@ diff --git a/src/views/Results.vue b/src/views/Results.vue index e40f52f..b1efe60 100644 --- a/src/views/Results.vue +++ b/src/views/Results.vue @@ -39,8 +39,18 @@

{{ t('forms', 'Responses for {title}', { title: form.title }) }}

-
- {{ sum }} +
+ + + + {{ t('forms', 'Delete all responses') }} + +
@@ -56,35 +66,28 @@
- - - - - +
diff --git a/src/views/Submit.vue b/src/views/Submit.vue index cbd1e0a..d4a5600 100644 --- a/src/views/Submit.vue +++ b/src/views/Submit.vue @@ -131,7 +131,7 @@ export default { this.loading = true try { - await axios.post(generateUrl('/apps/forms/api/v1/submissions/insert'), { + await axios.post(generateUrl('/apps/forms/api/v1/submission/insert'), { formId: this.form.id, answers: this.answers, })