From d17943947ea402e726d5c205866d0be99beb4f9e Mon Sep 17 00:00:00 2001 From: Christian Hartmann Date: Thu, 2 Feb 2023 20:23:51 +0100 Subject: [PATCH] Add lastUpdated property to Form Signed-off-by: Christian Hartmann --- lib/Controller/ApiController.php | 28 ++++++++- lib/Controller/ShareApiController.php | 6 ++ lib/Db/Form.php | 7 ++- lib/Db/FormMapper.php | 10 ++-- lib/FormsMigrator.php | 1 + .../Version030100Date20230202175747.php | 60 +++++++++++++++++++ lib/Service/FormsService.php | 12 ++++ src/Forms.vue | 28 ++++++++- src/components/Questions/QuestionDropdown.vue | 4 +- src/components/Questions/QuestionMultiple.vue | 4 +- src/mixins/QuestionMixin.js | 4 +- src/mixins/ViewsMixin.js | 2 + src/views/Create.vue | 5 ++ src/views/Results.vue | 3 + src/views/Sidebar.vue | 4 ++ src/views/Submit.vue | 2 + tests/Integration/Api/ApiV2Test.php | 42 ++++++++++++- tests/Unit/Controller/ApiControllerTest.php | 26 +++++++- tests/Unit/FormsMigratorTest.php | 5 +- tests/Unit/Service/FormsServiceTest.php | 8 +++ 20 files changed, 244 insertions(+), 17 deletions(-) create mode 100644 lib/Migration/Version030100Date20230202175747.php diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php index cf9c408f5..befcbea9d 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -269,6 +269,7 @@ public function newForm(): DataResponse { $form->setShowExpiration(false); $form->setExpires(0); $form->setIsAnonymous(false); + $form->setLastUpdated(time()); $this->formMapper->insert($form); @@ -315,6 +316,7 @@ public function cloneForm(int $id): DataResponse { $formData = $oldForm->read(); unset($formData['id']); $formData['created'] = time(); + $formData['lastUpdated'] = time(); $formData['hash'] = $this->formsService->generateFormHash(); // TRANSLATORS Appendix to the form Title of a duplicated/copied form. $formData['title'] .= ' - ' . $this->l10n->t('Copy'); @@ -384,9 +386,10 @@ public function updateForm(int $id, array $keyValuePairs): DataResponse { throw new OCSForbiddenException(); } - // Don't allow to change params id, hash, ownerId, created + // Don't allow to change params id, hash, ownerId, created, lastUpdated if (key_exists('id', $keyValuePairs) || key_exists('hash', $keyValuePairs) || - key_exists('ownerId', $keyValuePairs) || key_exists('created', $keyValuePairs)) { + key_exists('ownerId', $keyValuePairs) || key_exists('created', $keyValuePairs) || + key_exists('lastUpdated', $keyValuePairs)) { $this->logger->info('Not allowed to update id, hash, ownerId or created'); throw new OCSForbiddenException(); } @@ -397,6 +400,7 @@ public function updateForm(int $id, array $keyValuePairs): DataResponse { // Update changed Columns in Db. $this->formMapper->update($form); + $this->formsService->setLastUpdatedTimestamp($id); return new DataResponse($form->getId()); } @@ -501,6 +505,8 @@ public function newQuestion(int $formId, string $type, string $text = ''): DataR $response = $question->read(); $response['options'] = []; + $this->formsService->setLastUpdatedTimestamp($formId); + return new DataResponse($response); } @@ -594,6 +600,8 @@ public function reorderQuestions(int $formId, array $newOrder): DataResponse { ]; } + $this->formsService->setLastUpdatedTimestamp($formId); + return new DataResponse($response); } @@ -654,6 +662,8 @@ public function updateQuestion(int $id, array $keyValuePairs): DataResponse { // Update changed Columns in Db. $this->questionMapper->update($question); + $this->formsService->setLastUpdatedTimestamp($form->getId()); + return new DataResponse($question->getId()); } @@ -703,6 +713,8 @@ public function deleteQuestion(int $id): DataResponse { } } + $this->formsService->setLastUpdatedTimestamp($form->getId()); + return new DataResponse($id); } @@ -744,6 +756,8 @@ public function newOption(int $questionId, string $text): DataResponse { $option = $this->optionMapper->insert($option); + $this->formsService->setLastUpdatedTimestamp($form->getId()); + return new DataResponse($option->read()); } @@ -798,6 +812,8 @@ public function updateOption(int $id, array $keyValuePairs): DataResponse { // Update changed Columns in Db. $this->optionMapper->update($option); + $this->formsService->setLastUpdatedTimestamp($form->getId()); + return new DataResponse($option->getId()); } @@ -833,6 +849,8 @@ public function deleteOption(int $id): DataResponse { $this->optionMapper->delete($option); + $this->formsService->setLastUpdatedTimestamp($form->getId()); + return new DataResponse($id); } @@ -1013,6 +1031,8 @@ public function insertSubmission(int $formId, array $answers, string $shareHash } } + $this->formsService->setLastUpdatedTimestamp($formId); + //Create Activity $this->activityManager->publishNewSubmission($form, $submission->getUserId()); @@ -1051,6 +1071,8 @@ public function deleteSubmission(int $id): DataResponse { // Delete submission (incl. Answers) $this->submissionMapper->deleteById($id); + $this->formsService->setLastUpdatedTimestamp($form->getId()); + return new DataResponse($id); } @@ -1085,6 +1107,8 @@ public function deleteAllSubmissions(int $formId): DataResponse { // Delete all submissions (incl. Answers) $this->submissionMapper->deleteByForm($formId); + $this->formsService->setLastUpdatedTimestamp($formId); + return new DataResponse($formId); } diff --git a/lib/Controller/ShareApiController.php b/lib/Controller/ShareApiController.php index df78dc941..8404022f1 100644 --- a/lib/Controller/ShareApiController.php +++ b/lib/Controller/ShareApiController.php @@ -211,6 +211,8 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar // Create share-notifications (activity) $this->formsService->notifyNewShares($form, $share); + + $this->formsService->setLastUpdatedTimestamp($formId); // Append displayName for Frontend $shareData = $share->read(); @@ -250,6 +252,8 @@ public function deleteShare(int $id): DataResponse { $this->shareMapper->deleteById($id); + $this->formsService->setLastUpdatedTimestamp($form->getId()); + return new DataResponse($id); } @@ -303,6 +307,8 @@ public function updateShare(int $id, array $keyValuePairs): DataResponse { $share->setPermissions($keyValuePairs['permissions']); $share = $this->shareMapper->update($share); + $this->formsService->setLastUpdatedTimestamp($form->getId()); + return new DataResponse($share->getId()); } diff --git a/lib/Db/Form.php b/lib/Db/Form.php index cb0dc4459..3f0b7d0af 100644 --- a/lib/Db/Form.php +++ b/lib/Db/Form.php @@ -50,6 +50,8 @@ * @method void setSubmitMultiple(bool $value) * @method integer getShowExpiration() * @method void setShowExpiration(bool $value) + * @method integer getLastUpdated() + * @method void setLastUpdated(integer $value) */ class Form extends Entity { protected $hash; @@ -62,6 +64,7 @@ class Form extends Entity { protected $isAnonymous; protected $submitMultiple; protected $showExpiration; + protected $lastUpdated; /** * Form constructor. @@ -72,6 +75,7 @@ public function __construct() { $this->addType('isAnonymous', 'bool'); $this->addType('submitMultiple', 'bool'); $this->addType('showExpiration', 'bool'); + $this->addType('lastUpdated', 'integer'); } // JSON-Decoding of access-column. @@ -97,7 +101,8 @@ public function read() { 'expires' => (int)$this->getExpires(), 'isAnonymous' => (bool)$this->getIsAnonymous(), 'submitMultiple' => (bool)$this->getSubmitMultiple(), - 'showExpiration' => (bool)$this->getShowExpiration() + 'showExpiration' => (bool)$this->getShowExpiration(), + 'lastUpdated' => (int)$this->getLastUpdated() ]; } } diff --git a/lib/Db/FormMapper.php b/lib/Db/FormMapper.php index 044b3590c..dd18de7ed 100644 --- a/lib/Db/FormMapper.php +++ b/lib/Db/FormMapper.php @@ -99,8 +99,9 @@ public function findAll(): array { $qb->select('*') ->from($this->getTableName()) - //Newest forms first - ->orderBy('created', 'DESC'); + //Last updated forms first, then newest forms first + ->addOrderBy('last_updated', 'DESC') + ->addOrderBy('created', 'DESC'); return $this->findEntities($qb); } @@ -116,8 +117,9 @@ public function findAllByOwnerId(string $ownerId): array { ->where( $qb->expr()->eq('owner_id', $qb->createNamedParameter($ownerId)) ) - //Newest forms first - ->orderBy('created', 'DESC'); + //Last updated forms first, then newest forms first + ->addOrderBy('last_updated', 'DESC') + ->addOrderBy('created', 'DESC'); return $this->findEntities($qb); } diff --git a/lib/FormsMigrator.php b/lib/FormsMigrator.php index ba366e779..1ddb2ec25 100644 --- a/lib/FormsMigrator.php +++ b/lib/FormsMigrator.php @@ -199,6 +199,7 @@ public function import(IUser $user, IImportSource $importSource, OutputInterface $form->setIsAnonymous($formData['isAnonymous']); $form->setSubmitMultiple($formData['submitMultiple']); $form->setShowExpiration($formData['showExpiration']); + $form->setLastUpdated($formData['lastUpdated']); $this->formMapper->insert($form); diff --git a/lib/Migration/Version030100Date20230202175747.php b/lib/Migration/Version030100Date20230202175747.php new file mode 100644 index 000000000..a21736b50 --- /dev/null +++ b/lib/Migration/Version030100Date20230202175747.php @@ -0,0 +1,60 @@ + + * + * @author Christian Hartmann + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Forms\Migration; + +use Closure; +use OCP\DB\ISchemaWrapper; +use OCP\DB\Types; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + +class Version030100Date20230202175747 extends SimpleMigrationStep { + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + $table = $schema->getTable('forms_v2_forms'); + + if (!$table->hasColumn('last_updated')) { + $table->addColumn('last_updated', Types::INTEGER, [ + 'notnull' => false, + 'default' => 0, + 'comment' => 'unix-timestamp', + ]); + + return $schema; + } + + return null; + } +} diff --git a/lib/Service/FormsService.php b/lib/Service/FormsService.php index 5fc2ef83c..ee587beab 100644 --- a/lib/Service/FormsService.php +++ b/lib/Service/FormsService.php @@ -226,6 +226,7 @@ public function getPartialFormArray(int $id): array { 'hash' => $form->getHash(), 'title' => $form->getTitle(), 'expires' => $form->getExpires(), + 'lastUpdated' => $form->getLastUpdated(), 'permissions' => $this->getPermissions($form->getId()), 'partial' => true ]; @@ -527,4 +528,15 @@ protected function getSharesWithUser(int $formId, string $userId): array { } }); } + + /** + * Update lastUpdated timestamp for the given form + * + * @param int $formId The form to update + */ + public function setLastUpdatedTimestamp(int $formId): void { + $form = $this->formMapper->findById($formId); + $form->setLastUpdated(time()); + $this->formMapper->update($form); + } } diff --git a/src/Forms.vue b/src/Forms.vue index 85c14e5b8..3ec470bbd 100644 --- a/src/Forms.vue +++ b/src/Forms.vue @@ -103,11 +103,12 @@ diff --git a/src/components/Questions/QuestionDropdown.vue b/src/components/Questions/QuestionDropdown.vue index b9febfeb3..cb1fc43ed 100644 --- a/src/components/Questions/QuestionDropdown.vue +++ b/src/components/Questions/QuestionDropdown.vue @@ -82,8 +82,9 @@