add voting/accepting

This commit is contained in:
Dennis Eichhorn 2022-03-22 16:32:41 +01:00
parent 0e1dd3e830
commit 9b0dc90e2a
10 changed files with 137 additions and 21 deletions

View File

@ -122,6 +122,13 @@
"foreignTable": "account", "foreignTable": "account",
"foreignKey": "account_id" "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": { "qa_question_vote_score": {
"name": "qa_question_vote_score", "name": "qa_question_vote_score",
"type": "TINYINT", "type": "TINYINT",
@ -263,6 +270,13 @@
"foreignTable": "account", "foreignTable": "account",
"foreignKey": "account_id" "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": { "qa_answer_vote_score": {
"name": "qa_answer_vote_score", "name": "qa_answer_vote_score",
"type": "TINYINT", "type": "TINYINT",

View File

@ -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,
],
],
],
]; ];

View File

@ -17,6 +17,7 @@ namespace Modules\QA\Controller;
use Modules\Admin\Models\NullAccount; use Modules\Admin\Models\NullAccount;
use Modules\Media\Models\NullMedia; use Modules\Media\Models\NullMedia;
use Modules\Profile\Models\Profile; use Modules\Profile\Models\Profile;
use Modules\QA\Models\NullQAAnswer;
use Modules\QA\Models\NullQAAnswerVote; use Modules\QA\Models\NullQAAnswerVote;
use Modules\QA\Models\NullQAApp; use Modules\QA\Models\NullQAApp;
use Modules\QA\Models\NullQAQuestion; use Modules\QA\Models\NullQAQuestion;
@ -331,9 +332,24 @@ final class ApiController extends Controller
*/ */
public function apiChangeAnsweredStatus(RequestAbstract $request, ResponseAbstract $response, $data = null) : void 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(); $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); $new = $this->updateAnsweredStatusFromRequest($request);
$this->updateModel($request->header->account, $old, $new, QAAnswerMapper::class, 'answer', $request->getOrigin()); $this->updateModel($request->header->account, $old, $new, QAAnswerMapper::class, 'answer', $request->getOrigin());
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Answer', 'Answer successfully updated.', $new); $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 public function updateAnsweredStatusFromRequest(RequestAbstract $request) : QAAnswer
{ {
$answer = QAAnswerMapper::get()->where('id', (int) $request->getData('id'))->execute(); $answer = QAAnswerMapper::get()->where('id', (int) $request->getData('id'))->execute();
$answer->isAccepted = $request->getData('accepted', 'bool') ?? false; $answer->isAccepted = !$answer->isAccepted;
return $answer; return $answer;
} }
@ -440,23 +456,30 @@ final class ApiController extends Controller
return; return;
} }
// @todo: check if is allowed to change
$questionVote = QAQuestionVoteMapper::get() $questionVote = QAQuestionVoteMapper::get()
->where('question', (int) $request->getData('id')) ->where('question', (int) $request->getData('id'))
->where('createdBy', $request->header->account) ->where('createdBy', $request->header->account)
->execute(); ->execute();
if ($questionVote === false || $questionVote instanceof NullQAQuestionVote || $questionVote === null) { if ($questionVote === false || $questionVote instanceof NullQAQuestionVote || $questionVote === null) {
$question = QAQuestionMapper::get()->where('id', (int) $request->getData('id'))->execute();
$new = new QAQuestionVote(); $new = new QAQuestionVote();
$new->score = (int) $request->getData('type'); $new->score = (int) $request->getData('type');
$new->question = (int) $request->getData('id'); $new->question = (int) $request->getData('id');
$new->createdBy = new NullAccount($request->header->account); $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->createModel($request->header->account, $new, QAQuestionVoteMapper::class, 'qa_question_vote', $request->getOrigin());
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Vote', 'Sucessfully voted.', $new); $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Vote', 'Sucessfully voted.', $new);
} else { } else {
/** @var QAQuestionVote $questionVote */ /** @var QAQuestionVote $questionVote */
$new = clone $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->updateModel($request->header->account, $questionVote, $new, QAQuestionVoteMapper::class, 'qa_question_vote', $request->getOrigin());
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Vote', 'Vote successfully changed.', $new); $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Vote', 'Vote successfully changed.', $new);
@ -506,23 +529,30 @@ final class ApiController extends Controller
return; return;
} }
// @todo: check if is allowed to change
$answerVote = QAAnswerVoteMapper::get() $answerVote = QAAnswerVoteMapper::get()
->where('answer', (int) $request->getData('id')) ->where('answer', (int) $request->getData('id'))
->where('createdBy', $request->header->account) ->where('createdBy', $request->header->account)
->execute(); ->execute();
if ($answerVote === false || $answerVote instanceof NullQAAnswerVote || $answerVote === null) { if ($answerVote === false || $answerVote instanceof NullQAAnswerVote || $answerVote === null) {
$answer = QAAnswerMapper::get()->where('id', (int) $request->getData('id'))->execute();
$new = new QAAnswerVote(); $new = new QAAnswerVote();
$new->score = (int) $request->getData('type'); $new->score = (int) $request->getData('type');
$new->answer = (int) $request->getData('id'); $new->answer = (int) $request->getData('id');
$new->createdBy = new NullAccount($request->header->account); $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->createModel($request->header->account, $new, QAAnswerVoteMapper::class, 'qa_answer_vote', $request->getOrigin());
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Vote', 'Sucessfully voted.', $new); $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Vote', 'Sucessfully voted.', $new);
} else { } else {
/** @var QAAnswerVote $answerVote */ /** @var QAAnswerVote $answerVote */
$new = clone $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->updateModel($request->header->account, $answerVote, $new, QAAnswerVoteMapper::class, 'qa_answer_vote', $request->getOrigin());
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Vote', 'Vote successfully changed.', $new); $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Vote', 'Vote successfully changed.', $new);

View File

@ -35,4 +35,6 @@ abstract class PermissionCategory extends Enum
public const VOTE = 4; public const VOTE = 4;
public const APP = 5; public const APP = 5;
public const ACCEPT = 5;
} }

View File

@ -43,6 +43,14 @@ class QAAnswerVote
*/ */
public Account $createdBy; public Account $createdBy;
/**
* Account
*
* @var int
* @since 1.0.0
*/
public int $createdFor = 0;
/** /**
* Created at * Created at
* *

View File

@ -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_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_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_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], 'qa_answer_vote_created_at' => ['name' => 'qa_answer_vote_created_at', 'type' => 'DateTimeImmutable', 'internal' => 'createdAt', 'readonly' => true],
]; ];

View File

@ -39,34 +39,30 @@ final class QAHelperMapper extends DataMapperFactory
$scores = []; $scores = [];
$query = new Builder(self::$db); $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') ->selectAs('SUM(qa_question_vote_score)', 'score')
->from(QAQuestionVoteMapper::TABLE) ->from(QAQuestionVoteMapper::TABLE)
->leftJoin(QAQuestionMapper::TABLE) ->where(QAQuestionVoteMapper::TABLE . '.qa_question_vote_created_for', 'in', $accounts)
->on(QAQuestionVoteMapper::TABLE . '.qa_question_vote_question', '=', QAQuestionMapper::TABLE . '.qa_question_id') ->groupBy('qa_question_vote_created_for')
->where(QAQuestionMapper::TABLE . '.qa_question_created_by', 'in', $accounts)
->groupBy('qa_question_created_by')
->execute() ->execute()
->fetchAll(); ->fetchAll();
foreach ($questionScore as $votes) { 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); $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') ->selectAs('SUM(qa_answer_vote_score)', 'score')
->from(QAAnswerVoteMapper::TABLE) ->from(QAAnswerVoteMapper::TABLE)
->leftJoin(QAAnswerMapper::TABLE) ->where(QAAnswerMapper::TABLE . '.qa_answer_vote_created_for', 'in', $accounts)
->on(QAAnswerVoteMapper::TABLE . '.qa_answer_vote_answer', '=', QAAnswerMapper::TABLE . '.qa_answer_id') ->groupBy('qa_answer_vote_created_for')
->where(QAAnswerMapper::TABLE . '.qa_answer_created_by', 'in', $accounts)
->groupBy('qa_answer_created_by')
->execute() ->execute()
->fetchAll(); ->fetchAll();
foreach ($answerScore as $votes) { foreach ($answerScore as $votes) {
$scores[(int) $votes['qa_answer_created_by']] ??= 0; $scores[(int) $votes['qa_answer_vote_created_for']] ??= 0;
$scores[(int) $votes['qa_answer_created_by']] += (int) $votes['score']; $scores[(int) $votes['qa_answer_vote_created_for']] += (int) $votes['score'];
} }
return $scores; return $scores;

View File

@ -43,6 +43,14 @@ class QAQuestionVote
*/ */
public Account $createdBy; public Account $createdBy;
/**
* Account
*
* @var int
* @since 1.0.0
*/
public int $createdFor = 0;
/** /**
* Created at * Created at
* *

View File

@ -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_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_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_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], 'qa_question_vote_created_at' => ['name' => 'qa_question_vote_created_at', 'type' => 'DateTimeImmutable', 'internal' => 'createdAt', 'readonly' => true],
]; ];

View File

@ -33,10 +33,28 @@ echo $this->getData('nav')->render();
<div class="score"> <div class="score">
<div class="counter-area"> <div class="counter-area">
<div class="counter-container"> <div class="counter-container">
<i class="fa fa-chevron-up qa-vote<?= $this->printHtml($question->getAccountVoteScore($this->request->header->account) > 0 ? ' voted' : ' open'); ?>"></i> <a id="qa-question-upvote" data-action='[
{
"key": 1, "listener": "click", "action": [
{"key": 1, "type": "event.prevent"},
{"key": 2, "type": "message.request", "uri": "<?= UriFactory::build('{/base}/{/lang}/{/api}qa/question/vote?id=' . $question->getId());?>&type=1", "method": "PUT", "request_type": "json"}
]
}
]' href="#">
<i class="fa fa-chevron-up qa-vote<?= $this->printHtml($question->getAccountVoteScore($this->request->header->account) > 0 ? ' voted' : ' open'); ?>"></i>
</a>
<span class="counter"><?= $question->getVoteScore(); ?></span> <span class="counter"><?= $question->getVoteScore(); ?></span>
<span class="text">Score</span> <span class="text">Score</span>
<i class="fa fa-chevron-down qa-vote<?= $this->printHtml($question->getAccountVoteScore($this->request->header->account) < 0 ? ' voted' : ' open'); ?>"></i> <a id="qa-question-downvote" data-action='[
{
"key": 1, "listener": "click", "action": [
{"key": 1, "type": "event.prevent"},
{"key": 2, "type": "message.request", "uri": "<?= UriFactory::build('{/base}/{/lang}/{/api}qa/question/vote?id=' . $question->getId());?>&type=-1", "method": "PUT", "request_type": "json"}
]
}
]' href="#">
<i class="fa fa-chevron-down qa-vote<?= $this->printHtml($question->getAccountVoteScore($this->request->header->account) < 0 ? ' voted' : ' open'); ?>"></i>
</a>
</div> </div>
<div class="counter-container"> <div class="counter-container">
<span class="counter score<?= $this->printHtml($question->hasAccepted() ? ' done' : ' open'); ?>"><?= $question->getAnswerCount(); ?></span> <span class="counter score<?= $this->printHtml($question->hasAccepted() ? ' done' : ' open'); ?>"><?= $question->getAnswerCount(); ?></span>
@ -83,13 +101,40 @@ echo $this->getData('nav')->render();
<div class="score"> <div class="score">
<div class="counter-area"> <div class="counter-area">
<div class="counter-container"> <div class="counter-container">
<i class="fa fa-chevron-up qa-vote<?= $this->printHtml($answer->getAccountVoteScore($this->request->header->account) > 0 ? ' voted' : ' open'); ?>"></i> <a id="qa-answer-upvote-<?= $answer->getId() ?>" data-action='[
{
"key": 1, "listener": "click", "action": [
{"key": 1, "type": "event.prevent"},
{"key": 2, "type": "message.request", "uri": "<?= UriFactory::build('{/base}/{/lang}/{/api}qa/answer/vote?id=' . $answer->getId());?>&type=1", "method": "PUT", "request_type": "json"}
]
}
]' href="#">
<i class="fa fa-chevron-up qa-vote<?= $this->printHtml($answer->getAccountVoteScore($this->request->header->account) > 0 ? ' voted' : ' open'); ?>"></i>
</a>
<span class="counter"><?= $answer->getVoteScore(); ?></span> <span class="counter"><?= $answer->getVoteScore(); ?></span>
<span class="text">Score</span> <span class="text">Score</span>
<i class="fa fa-chevron-down qa-vote<?= $this->printHtml($answer->getAccountVoteScore($this->request->header->account) < 0 ? ' voted' : ' open'); ?>"></i> <a id="qa-answer-downvote-<?= $answer->getId() ?>" data-action='[
{
"key": 1, "listener": "click", "action": [
{"key": 1, "type": "event.prevent"},
{"key": 2, "type": "message.request", "uri": "<?= UriFactory::build('{/base}/{/lang}/{/api}qa/answer/vote?id=' . $answer->getId());?>&type=-1", "method": "PUT", "request_type": "json"}
]
}
]' href="#">
<i class="fa fa-chevron-down qa-vote<?= $this->printHtml($answer->getAccountVoteScore($this->request->header->account) < 0 ? ' voted' : ' open'); ?>"></i>
</a>
</div> </div>
<div class="counter-container"> <div class="counter-container">
<i class="fa fa-check qa-accept"></i> <a id="qa-answer-accept-<?= $answer->getId() ?>" data-action='[
{
"key": 1, "listener": "click", "action": [
{"key": 1, "type": "event.prevent"},
{"key": 2, "type": "message.request", "uri": "<?= UriFactory::build('{/base}/{/lang}/{/api}qa/answer/accept?id=' . $answer->getId());?>&type=1", "method": "PUT", "request_type": "json"}
]
}
]' href="#">
<i class="fa fa-check qa-accept"></i>
</a>
<span class="text"><?= $this->printHtml($answer->isAccepted ? 'Accepted' : 'Accept'); ?></span> <span class="text"><?= $this->printHtml($answer->isAccepted ? 'Accepted' : 'Accept'); ?></span>
</div> </div>
</div> </div>