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",
"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",

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\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);

View File

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

View File

@ -43,6 +43,14 @@ class QAAnswerVote
*/
public Account $createdBy;
/**
* Account
*
* @var int
* @since 1.0.0
*/
public int $createdFor = 0;
/**
* 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_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],
];

View File

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

View File

@ -43,6 +43,14 @@ class QAQuestionVote
*/
public Account $createdBy;
/**
* Account
*
* @var int
* @since 1.0.0
*/
public int $createdFor = 0;
/**
* 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_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],
];

View File

@ -33,10 +33,28 @@ echo $this->getData('nav')->render();
<div class="score">
<div class="counter-area">
<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="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 class="counter-container">
<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="counter-area">
<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="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 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>
</div>
</div>