From 9b0dc90e2ab851fe3170e8c1a1cfae291316279a Mon Sep 17 00:00:00 2001 From: Dennis Eichhorn Date: Tue, 22 Mar 2022 16:32:41 +0100 Subject: [PATCH] add voting/accepting --- Admin/Install/db.json | 14 ++++++++ Admin/Routes/Web/Api.php | 11 +++++++ Controller/ApiController.php | 36 ++++++++++++++++++-- Models/PermissionCategory.php | 2 ++ Models/QAAnswerVote.php | 8 +++++ Models/QAAnswerVoteMapper.php | 1 + Models/QAHelperMapper.php | 22 +++++-------- Models/QAQuestionVote.php | 8 +++++ Models/QAQuestionVoteMapper.php | 1 + Theme/Backend/qa-question.tpl.php | 55 ++++++++++++++++++++++++++++--- 10 files changed, 137 insertions(+), 21 deletions(-) diff --git a/Admin/Install/db.json b/Admin/Install/db.json index 15e0aa9..240e07b 100755 --- a/Admin/Install/db.json +++ b/Admin/Install/db.json @@ -122,6 +122,13 @@ "foreignTable": "account", "foreignKey": "account_id" }, + "qa_question_vote_created_for": { + "name": "qa_question_vote_created_for", + "type": "INT", + "null": false, + "foreignTable": "account", + "foreignKey": "account_id" + }, "qa_question_vote_score": { "name": "qa_question_vote_score", "type": "TINYINT", @@ -263,6 +270,13 @@ "foreignTable": "account", "foreignKey": "account_id" }, + "qa_answer_vote_created_for": { + "name": "qa_answer_vote_created_for", + "type": "INT", + "null": false, + "foreignTable": "account", + "foreignKey": "account_id" + }, "qa_answer_vote_score": { "name": "qa_answer_vote_score", "type": "TINYINT", diff --git a/Admin/Routes/Web/Api.php b/Admin/Routes/Web/Api.php index 53dfa57..8153576 100755 --- a/Admin/Routes/Web/Api.php +++ b/Admin/Routes/Web/Api.php @@ -100,4 +100,15 @@ return [ ], ], ], + '^.*/qa/answer/accept(\?.*|$)' => [ + [ + 'dest' => '\Modules\QA\Controller\ApiController:apiChangeAnsweredStatus', + 'verb' => RouteVerb::PUT | RouteVerb::SET, + 'permission' => [ + 'module' => ApiController::NAME, + 'type' => PermissionType::CREATE, + 'state' => PermissionCategory::ACCEPT, + ], + ], + ], ]; diff --git a/Controller/ApiController.php b/Controller/ApiController.php index 07418d0..f1b3f81 100755 --- a/Controller/ApiController.php +++ b/Controller/ApiController.php @@ -17,6 +17,7 @@ namespace Modules\QA\Controller; use Modules\Admin\Models\NullAccount; use Modules\Media\Models\NullMedia; use Modules\Profile\Models\Profile; +use Modules\QA\Models\NullQAAnswer; use Modules\QA\Models\NullQAAnswerVote; use Modules\QA\Models\NullQAApp; use Modules\QA\Models\NullQAQuestion; @@ -331,9 +332,24 @@ final class ApiController extends Controller */ public function apiChangeAnsweredStatus(RequestAbstract $request, ResponseAbstract $response, $data = null) : void { + // @todo: check if is allowed to change + $old = clone QAAnswerMapper::get()->where('id', (int) $request->getData('id'))->execute(); + $oldAccepted = QAAnswerMapper::get() + ->where('question', $old->question->getId()) + ->where('isAccepted', true) + ->execute(); + + if ($old->getId() !== $oldAccepted->getId()) { + $oldUnaccepted = clone $oldAccepted; + $oldUnaccepted->isAccepted = !$oldUnaccepted->isAccepted; + + $this->updateModel($request->header->account, $oldAccepted, $oldUnaccepted, QAAnswerMapper::class, 'answer', $request->getOrigin()); + } + $new = $this->updateAnsweredStatusFromRequest($request); $this->updateModel($request->header->account, $old, $new, QAAnswerMapper::class, 'answer', $request->getOrigin()); + $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Answer', 'Answer successfully updated.', $new); } @@ -349,7 +365,7 @@ final class ApiController extends Controller public function updateAnsweredStatusFromRequest(RequestAbstract $request) : QAAnswer { $answer = QAAnswerMapper::get()->where('id', (int) $request->getData('id'))->execute(); - $answer->isAccepted = $request->getData('accepted', 'bool') ?? false; + $answer->isAccepted = !$answer->isAccepted; return $answer; } @@ -440,23 +456,30 @@ final class ApiController extends Controller return; } + // @todo: check if is allowed to change + $questionVote = QAQuestionVoteMapper::get() ->where('question', (int) $request->getData('id')) ->where('createdBy', $request->header->account) ->execute(); if ($questionVote === false || $questionVote instanceof NullQAQuestionVote || $questionVote === null) { + $question = QAQuestionMapper::get()->where('id', (int) $request->getData('id'))->execute(); + $new = new QAQuestionVote(); $new->score = (int) $request->getData('type'); $new->question = (int) $request->getData('id'); $new->createdBy = new NullAccount($request->header->account); + $new->createdFor = $question->createdBy->getId(); $this->createModel($request->header->account, $new, QAQuestionVoteMapper::class, 'qa_question_vote', $request->getOrigin()); $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Vote', 'Sucessfully voted.', $new); } else { /** @var QAQuestionVote $questionVote */ $new = clone $questionVote; - $new->score = (int) $request->getData('type'); + $new->score = ((int) $request->getData('type')) === $new->score + ? 0 + : (int) $request->getData('type'); $this->updateModel($request->header->account, $questionVote, $new, QAQuestionVoteMapper::class, 'qa_question_vote', $request->getOrigin()); $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Vote', 'Vote successfully changed.', $new); @@ -506,23 +529,30 @@ final class ApiController extends Controller return; } + // @todo: check if is allowed to change + $answerVote = QAAnswerVoteMapper::get() ->where('answer', (int) $request->getData('id')) ->where('createdBy', $request->header->account) ->execute(); if ($answerVote === false || $answerVote instanceof NullQAAnswerVote || $answerVote === null) { + $answer = QAAnswerMapper::get()->where('id', (int) $request->getData('id'))->execute(); + $new = new QAAnswerVote(); $new->score = (int) $request->getData('type'); $new->answer = (int) $request->getData('id'); $new->createdBy = new NullAccount($request->header->account); + $new->createdFor = $answer->createdBy->getId(); $this->createModel($request->header->account, $new, QAAnswerVoteMapper::class, 'qa_answer_vote', $request->getOrigin()); $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Vote', 'Sucessfully voted.', $new); } else { /** @var QAAnswerVote $answerVote */ $new = clone $answerVote; - $new->score = (int) $request->getData('type'); + $new->score = ((int) $request->getData('type')) === $new->score + ? 0 + : (int) $request->getData('type'); $this->updateModel($request->header->account, $answerVote, $new, QAAnswerVoteMapper::class, 'qa_answer_vote', $request->getOrigin()); $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Vote', 'Vote successfully changed.', $new); diff --git a/Models/PermissionCategory.php b/Models/PermissionCategory.php index 436763a..22b8dc0 100755 --- a/Models/PermissionCategory.php +++ b/Models/PermissionCategory.php @@ -35,4 +35,6 @@ abstract class PermissionCategory extends Enum public const VOTE = 4; public const APP = 5; + + public const ACCEPT = 5; } diff --git a/Models/QAAnswerVote.php b/Models/QAAnswerVote.php index 36b4c2b..ecd4bd5 100755 --- a/Models/QAAnswerVote.php +++ b/Models/QAAnswerVote.php @@ -43,6 +43,14 @@ class QAAnswerVote */ public Account $createdBy; + /** + * Account + * + * @var int + * @since 1.0.0 + */ + public int $createdFor = 0; + /** * Created at * diff --git a/Models/QAAnswerVoteMapper.php b/Models/QAAnswerVoteMapper.php index 7b0dd90..c8026aa 100755 --- a/Models/QAAnswerVoteMapper.php +++ b/Models/QAAnswerVoteMapper.php @@ -38,6 +38,7 @@ final class QAAnswerVoteMapper extends DataMapperFactory 'qa_answer_vote_score' => ['name' => 'qa_answer_vote_score', 'type' => 'int', 'internal' => 'score'], 'qa_answer_vote_answer' => ['name' => 'qa_answer_vote_answer', 'type' => 'int', 'internal' => 'answer', 'readonly' => true], 'qa_answer_vote_created_by' => ['name' => 'qa_answer_vote_created_by', 'type' => 'int', 'internal' => 'createdBy', 'readonly' => true], + 'qa_answer_vote_created_for' => ['name' => 'qa_answer_vote_created_for', 'type' => 'int', 'internal' => 'createdFor', 'readonly' => true], 'qa_answer_vote_created_at' => ['name' => 'qa_answer_vote_created_at', 'type' => 'DateTimeImmutable', 'internal' => 'createdAt', 'readonly' => true], ]; diff --git a/Models/QAHelperMapper.php b/Models/QAHelperMapper.php index 7fa3ee4..2ff64b9 100755 --- a/Models/QAHelperMapper.php +++ b/Models/QAHelperMapper.php @@ -39,34 +39,30 @@ final class QAHelperMapper extends DataMapperFactory $scores = []; $query = new Builder(self::$db); - $questionScore = $query->select('qa_question_created_by') + $questionScore = $query->select('qa_question_vote_created_for') ->selectAs('SUM(qa_question_vote_score)', 'score') ->from(QAQuestionVoteMapper::TABLE) - ->leftJoin(QAQuestionMapper::TABLE) - ->on(QAQuestionVoteMapper::TABLE . '.qa_question_vote_question', '=', QAQuestionMapper::TABLE . '.qa_question_id') - ->where(QAQuestionMapper::TABLE . '.qa_question_created_by', 'in', $accounts) - ->groupBy('qa_question_created_by') + ->where(QAQuestionVoteMapper::TABLE . '.qa_question_vote_created_for', 'in', $accounts) + ->groupBy('qa_question_vote_created_for') ->execute() ->fetchAll(); foreach ($questionScore as $votes) { - $scores[(int) $votes['qa_question_created_by']] = (int) $votes['score']; + $scores[(int) $votes['qa_question_vote_created_for']] = (int) $votes['score']; } $query = new Builder(self::$db); - $answerScore = $query->select('qa_answer_created_by') + $answerScore = $query->select('qa_answer_vote_created_for') ->selectAs('SUM(qa_answer_vote_score)', 'score') ->from(QAAnswerVoteMapper::TABLE) - ->leftJoin(QAAnswerMapper::TABLE) - ->on(QAAnswerVoteMapper::TABLE . '.qa_answer_vote_answer', '=', QAAnswerMapper::TABLE . '.qa_answer_id') - ->where(QAAnswerMapper::TABLE . '.qa_answer_created_by', 'in', $accounts) - ->groupBy('qa_answer_created_by') + ->where(QAAnswerMapper::TABLE . '.qa_answer_vote_created_for', 'in', $accounts) + ->groupBy('qa_answer_vote_created_for') ->execute() ->fetchAll(); foreach ($answerScore as $votes) { - $scores[(int) $votes['qa_answer_created_by']] ??= 0; - $scores[(int) $votes['qa_answer_created_by']] += (int) $votes['score']; + $scores[(int) $votes['qa_answer_vote_created_for']] ??= 0; + $scores[(int) $votes['qa_answer_vote_created_for']] += (int) $votes['score']; } return $scores; diff --git a/Models/QAQuestionVote.php b/Models/QAQuestionVote.php index 9962e27..63092a6 100755 --- a/Models/QAQuestionVote.php +++ b/Models/QAQuestionVote.php @@ -43,6 +43,14 @@ class QAQuestionVote */ public Account $createdBy; + /** + * Account + * + * @var int + * @since 1.0.0 + */ + public int $createdFor = 0; + /** * Created at * diff --git a/Models/QAQuestionVoteMapper.php b/Models/QAQuestionVoteMapper.php index e4bb457..c013505 100755 --- a/Models/QAQuestionVoteMapper.php +++ b/Models/QAQuestionVoteMapper.php @@ -38,6 +38,7 @@ final class QAQuestionVoteMapper extends DataMapperFactory 'qa_question_vote_score' => ['name' => 'qa_question_vote_score', 'type' => 'int', 'internal' => 'score'], 'qa_question_vote_question' => ['name' => 'qa_question_vote_question', 'type' => 'int', 'internal' => 'question', 'readonly' => true], 'qa_question_vote_created_by' => ['name' => 'qa_question_vote_created_by', 'type' => 'int', 'internal' => 'createdBy', 'readonly' => true], + 'qa_question_vote_created_for' => ['name' => 'qa_question_vote_created_for', 'type' => 'int', 'internal' => 'createdFor', 'readonly' => true], 'qa_question_vote_created_at' => ['name' => 'qa_question_vote_created_at', 'type' => 'DateTimeImmutable', 'internal' => 'createdAt', 'readonly' => true], ]; diff --git a/Theme/Backend/qa-question.tpl.php b/Theme/Backend/qa-question.tpl.php index e366477..9db2a6c 100755 --- a/Theme/Backend/qa-question.tpl.php +++ b/Theme/Backend/qa-question.tpl.php @@ -33,10 +33,28 @@ echo $this->getData('nav')->render();
- + + + getVoteScore(); ?> Score - + + +
getAnswerCount(); ?> @@ -83,13 +101,40 @@ echo $this->getData('nav')->render();
- + + + getVoteScore(); ?> Score - + + +
- + + + printHtml($answer->isAccepted ? 'Accepted' : 'Accept'); ?>