Added comment scoring

This commit is contained in:
Marcin Kurczewski 2014-10-05 16:19:08 +02:00
parent 28e87dca93
commit 6bf8586735
15 changed files with 209 additions and 36 deletions

1
TODO
View file

@ -36,7 +36,6 @@ everything related to tags:
everything related to comments:
- markdown
- score
refactors:
- add enum validation in IValidatables (needs refactors of enums and

View file

@ -39,19 +39,30 @@
line-height: 16pt;
vertical-align: middle;
}
.comment .score,
.comment .date {
color: silver;
font-size: 80%;
font-size: 90%;
padding-left: 0.5em;
}
.comment .score-up.active,
.comment .score-down.active {
font-weight: bold;
}
.comment .header .ops a {
color: silver;
font-size: 80%;
cursor: pointer;
}
.comment .header .ops a:before {
.comment .header .ops a:first-of-type:before {
margin-left: 0.5em;
content: '[';
}
.comment .header .ops a:after {
.comment .header .ops a:not(:first-of-type):before {
content: '|';
margin: 0 0.3em;
}
.comment .header .ops a:last-of-type:after {
content: ']';
}

View file

@ -97,18 +97,31 @@ App.Presenters.PostCommentListPresenter = function(
comment: comment,
formatRelativeTime: util.formatRelativeTime,
formatMarkdown: util.formatMarkdown,
canVote: auth.isLoggedIn(),
canEditComment: auth.isLoggedIn(comment.user.name) ? privileges.editOwnComments : privileges.editAllComments,
canDeleteComment: auth.isLoggedIn(comment.user.name) ? privileges.deleteOwnComments : privileges.deleteAllComments,
}) + '</li>');
util.loadImagesNicely($item.find('img'));
$targetList.append($item);
$item.find('a.edit').click(function(e) {
e.preventDefault();
editCommentStart($item, comment);
});
$item.find('a.delete').click(function(e) {
e.preventDefault();
deleteComment($item, comment);
deleteComment(comment);
});
$item.find('a.score-up').click(function(e) {
e.preventDefault();
score(comment, jQuery(this).hasClass('active') ? 0 : 1);
});
$item.find('a.score-down').click(function(e) {
e.preventDefault();
score(comment, jQuery(this).hasClass('active') ? 0 : -1);
});
}
@ -132,6 +145,16 @@ App.Presenters.PostCommentListPresenter = function(
});
}
function updateComment(comment) {
comments = _.map(comments, function(c) { return c.id === comment.id ? comment : c; });
render();
}
function addComment(comment) {
comments.push(comment);
render();
}
function submitComment($form, commentToEdit) {
$form.find('.preview').slideUp();
var $textarea = $form.find('textarea');
@ -152,14 +175,11 @@ App.Presenters.PostCommentListPresenter = function(
$form.slideUp(function() {
$form.remove();
});
comments = _.map(comments, function(c) { return c.id === commentToEdit.id ? comment : c; });
updateComment(comment);
} else {
comments.push(comment);
addComment(comment);
}
render();
}).fail(function(response) {
window.alert(response.json && response.json.error || response);
});
}).fail(showGenericError);
}
function editCommentStart($item, comment) {
@ -171,7 +191,7 @@ App.Presenters.PostCommentListPresenter = function(
$item.find('form button[type=submit]').click(function(e) { commentFormSubmitted(e, comment); });
}
function deleteComment($item, comment) {
function deleteComment(comment) {
if (!window.confirm('Are you sure you want to delete this comment?')) {
return;
}
@ -179,9 +199,20 @@ App.Presenters.PostCommentListPresenter = function(
.then(function(response) {
comments = _.filter(comments, function(c) { return c.id !== comment.id; });
renderComments(comments, true);
}).fail(function(response) {
window.alert(response.json && response.json.error || response);
});
}).fail(showGenericError);
}
function score(comment, scoreValue) {
promise.wait(api.post('/comments/' + comment.id + '/score', {score: scoreValue}))
.then(function(response) {
comment.score = response.json.score;
comment.ownScore = parseInt(response.json.score);
updateComment(comment);
}).fail(showGenericError);
}
function showGenericError(response) {
window.alert(response.json && response.json.error || response);
}
return {

View file

@ -31,19 +31,32 @@
<%= formatRelativeTime(comment.creationTime) %>
</span>
<span class="ops">
<% if (canEditComment) { %>
<a class="edit" href="#"><!--
-->edit<!--
--></a>
<% } %>
<% if (canDeleteComment) { %>
<a class="delete" href="#"><!--
-->delete<!--
--></a>
<% } %>
<span class="score">
Score: <%= comment.score %>
</span>
<span class="ops"><!--
--><% if (canVote) { %><!--
--><a class="score-up <% print(comment.ownScore === 1 ? 'active' : '') %>"><!--
-->vote up<!--
--></a><!--
--><a class="score-down <% print(comment.ownScore === -1 ? 'active' : '') %>"><!--
-->vote down<!--
--></a><!--
--><% } %><!--
--><% if (canEditComment) { %><!--
--><a class="edit"><!--
-->edit<!--
--></a><!--
--><% } %><!--
--><% if (canDeleteComment) { %><!--
--><a class="delete"><!--
-->delete<!--
--></a><!--
--><% } %><!--
--></span>
</div>
<div class="content">

View file

@ -63,7 +63,10 @@ class CommentController extends AbstractController
{
$data[] = [
'post' => $this->postViewProxy->fromEntity($post),
'comments' => $this->commentViewProxy->fromArray($this->commentService->getByPost($post))];
'comments' => $this->commentViewProxy->fromArray(
$this->commentService->getByPost($post),
$this->getCommentsFetchConfig()),
];
}
return [
@ -88,7 +91,7 @@ class CommentController extends AbstractController
$filter->addRequirement($requirement);
$result = $this->commentService->getFiltered($filter);
$entities = $this->commentViewProxy->fromArray($result->getEntities());
$entities = $this->commentViewProxy->fromArray($result->getEntities(), $this->getCommentsFetchConfig());
return ['data' => $entities];
}
@ -98,7 +101,7 @@ class CommentController extends AbstractController
$post = $this->postService->getByNameOrId($postNameOrId);
$comment = $this->commentService->createComment($post, $this->inputReader->text);
return $this->commentViewProxy->fromEntity($comment);
return $this->commentViewProxy->fromEntity($comment, $this->getCommentsFetchConfig());
}
public function editComment($commentId)
@ -111,7 +114,7 @@ class CommentController extends AbstractController
: \Szurubooru\Privilege::EDIT_ALL_COMMENTS);
$comment = $this->commentService->updateComment($comment, $this->inputReader->text);
return $this->commentViewProxy->fromEntity($comment);
return $this->commentViewProxy->fromEntity($comment, $this->getCommentsFetchConfig());
}
public function deleteComment($commentId)
@ -125,4 +128,12 @@ class CommentController extends AbstractController
return $this->commentService->deleteComment($comment);
}
private function getCommentsFetchConfig()
{
return
[
\Szurubooru\Controllers\ViewProxies\CommentViewProxy::FETCH_OWN_SCORE => true,
];
}
}

View file

@ -6,6 +6,7 @@ class ScoreController extends AbstractController
private $privilegeService;
private $authService;
private $postService;
private $commentService;
private $scoreService;
private $inputReader;
@ -13,12 +14,14 @@ class ScoreController extends AbstractController
\Szurubooru\Services\PrivilegeService $privilegeService,
\Szurubooru\Services\AuthService $authService,
\Szurubooru\Services\PostService $postService,
\Szurubooru\Services\CommentService $commentService,
\Szurubooru\Services\ScoreService $scoreService,
\Szurubooru\Helpers\InputReader $inputReader)
{
$this->privilegeService = $privilegeService;
$this->authService = $authService;
$this->postService = $postService;
$this->commentService = $commentService;
$this->scoreService = $scoreService;
$this->inputReader = $inputReader;
}
@ -27,6 +30,8 @@ class ScoreController extends AbstractController
{
$router->get('/api/posts/:postNameOrId/score', [$this, 'getPostScore']);
$router->post('/api/posts/:postNameOrId/score', [$this, 'setPostScore']);
$router->get('/api/comments/:commentId/score', [$this, 'getCommentScore']);
$router->post('/api/comments/:commentId/score', [$this, 'setCommentScore']);
}
public function getPostScore($postNameOrId)
@ -41,6 +46,18 @@ class ScoreController extends AbstractController
return $this->setScore($post);
}
public function getCommentScore($commentId)
{
$comment = $this->commentService->getById($commentId);
return $this->getScore($comment);
}
public function setCommentScore($commentId)
{
$comment = $this->commentService->getById($commentId);
return $this->setScore($comment);
}
private function setScore(\Szurubooru\Entities\Entity $entity)
{
$this->privilegeService->assertLoggedIn();

View file

@ -3,14 +3,19 @@ namespace Szurubooru\Controllers\ViewProxies;
class CommentViewProxy extends AbstractViewProxy
{
private $postViewProxy;
private $authService;
private $scoreService;
private $userViewProxy;
const FETCH_OWN_SCORE = 'fetchOwnScore';
public function __construct(
PostViewProxy $postViewProxy,
\Szurubooru\Services\AuthService $authService,
\Szurubooru\Services\ScoreService $scoreService,
UserViewProxy $userViewProxy)
{
$this->postViewProxy = $postViewProxy;
$this->authService = $authService;
$this->scoreService = $scoreService;
$this->userViewProxy = $userViewProxy;
}
@ -25,8 +30,11 @@ class CommentViewProxy extends AbstractViewProxy
$result->text = $comment->getText();
$result->postId = $comment->getPostId();
$result->user = $this->userViewProxy->fromEntity($comment->getUser());
$result->score = $comment->getScore();
if (!empty($config[self::FETCH_OWN_SCORE]) and $this->authService->isLoggedIn())
$result->ownScore = $this->scoreService->getScoreValue($this->authService->getLoggedInUser(), $comment);
}
return $result;
}
}

View file

@ -24,6 +24,7 @@ class CommentEntityConverter extends AbstractEntityConverter implements IEntityC
$entity->setText($array['text']);
$entity->setCreationTime($this->dbTimeToEntityTime($array['creationTime']));
$entity->setLastEditTime($this->dbTimeToEntityTime($array['lastEditTime']));
$entity->setMeta(\Szurubooru\Entities\Comment::META_SCORE, intval($array['score']));
return $entity;
}
}

View file

@ -10,6 +10,7 @@ class ScoreEntityConverter extends AbstractEntityConverter implements IEntityCon
'id' => $entity->getId(),
'userId' => $entity->getUserId(),
'postId' => $entity->getPostId(),
'commentId' => $entity->getCommentId(),
'time' => $this->entityTimeToDbTime($entity->getTime()),
'score' => $entity->getScore(),
];
@ -20,6 +21,7 @@ class ScoreEntityConverter extends AbstractEntityConverter implements IEntityCon
$entity = new \Szurubooru\Entities\Score($array['id']);
$entity->setUserId($array['userId']);
$entity->setPostId($array['postId']);
$entity->setCommentId($array['commentId']);
$entity->setTime($this->dbTimeToEntityTime($array['time']));
$entity->setScore(intval($array['score']));
return $entity;

View file

@ -23,6 +23,8 @@ class ScoreDao extends AbstractDao implements ICrudDao
if ($entity instanceof \Szurubooru\Entities\Post)
$query->where('postId', $entity->getId());
elseif ($entity instanceof \Szurubooru\Entities\Comment)
$query->where('commentId', $entity->getId());
else
throw new \InvalidArgumentException();
@ -42,6 +44,8 @@ class ScoreDao extends AbstractDao implements ICrudDao
if ($entity instanceof \Szurubooru\Entities\Post)
$score->setPostId($entity->getId());
elseif ($entity instanceof \Szurubooru\Entities\Comment)
$score->setCommentId($entity->getId());
else
throw new \InvalidArgumentException();
}

View file

@ -12,6 +12,8 @@ class Comment extends Entity
const LAZY_LOADER_USER = 'user';
const LAZY_LOADER_POST = 'post';
const META_SCORE = 'score';
public function getUserId()
{
return $this->userId;
@ -83,4 +85,9 @@ class Comment extends Entity
$this->lazySave(self::LAZY_LOADER_POST, $post);
$this->postId = $post->getId();
}
public function getScore()
{
return $this->getMeta(self::META_SCORE, 0);
}
}

View file

@ -5,6 +5,7 @@ class Score extends Entity
{
private $postId;
private $userId;
private $commentId;
private $time;
private $score;
@ -28,6 +29,16 @@ class Score extends Entity
$this->postId = $postId;
}
public function getCommentId()
{
return $this->commentId;
}
public function setCommentId($commentId)
{
$this->commentId = $commentId;
}
public function getTime()
{
return $this->time;

View file

@ -47,7 +47,7 @@ class ScoreService
$transactionFunc = function() use ($user, $entity, $scoreValue)
{
if ($scoreValue !== 1)
if (($scoreValue !== 1) and ($entity instanceof \Szurubooru\Entities\Post))
$this->favoritesDao->delete($user, $entity);
return $this->scoreDao->setScore($user, $entity, $scoreValue);

View file

@ -0,0 +1,57 @@
<?php
namespace Szurubooru\Upgrades;
class Upgrade16 implements IUpgrade
{
public function run(\Szurubooru\DatabaseConnection $databaseConnection)
{
$pdo = $databaseConnection->getPDO();
$pdo->exec('ALTER TABLE scores ADD COLUMN commentId INTEGER');
$pdo->exec('ALTER TABLE comments ADD COLUMN score INTEGER NOT NULL DEFAULT 0');
$pdo->exec('DROP TRIGGER IF EXISTS scoresDelete');
$pdo->exec('DROP TRIGGER IF EXISTS scoresInsert');
$pdo->exec('DROP TRIGGER IF EXISTS scoresUpdate');
$pdo->exec('
CREATE TRIGGER scoresDelete AFTER DELETE ON scores
FOR EACH ROW
BEGIN
UPDATE posts SET
score = (SELECT SUM(score) FROM scores WHERE scores.postId = posts.id)
WHERE posts.id = OLD.postId;
UPDATE comments SET
score = (SELECT SUM(score) FROM scores WHERE scores.commentId = comments.id)
WHERE comments.id = OLD.commentId;
END');
$pdo->exec('
CREATE TRIGGER scoresInsert AFTER INSERT ON scores
FOR EACH ROW
BEGIN
UPDATE posts SET
score = (SELECT SUM(score) FROM scores WHERE scores.postId = posts.id)
WHERE posts.id = NEW.postId;
UPDATE comments SET
score = (SELECT SUM(score) FROM scores WHERE scores.commentId = comments.id)
WHERE comments.id = NEW.commentId;
END');
$pdo->exec('
CREATE TRIGGER scoresUpdate AFTER UPDATE ON scores
FOR EACH ROW
BEGIN
UPDATE posts SET
score = (SELECT SUM(score) FROM scores WHERE scores.postId = posts.id)
WHERE posts.id IN (OLD.postId, NEW.postId);
UPDATE comments SET
score = (SELECT SUM(score) FROM scores WHERE scores.commentId = comments.id)
WHERE comments.id IN (OLD.commentId, NEW.commentId);
END');
}
}

View file

@ -31,6 +31,7 @@ return [
$container->get(\Szurubooru\Upgrades\Upgrade13::class),
$container->get(\Szurubooru\Upgrades\Upgrade14::class),
$container->get(\Szurubooru\Upgrades\Upgrade15::class),
$container->get(\Szurubooru\Upgrades\Upgrade16::class),
];
}),