diff --git a/packages/common/src/IComment.ts b/packages/common/src/IComment.ts index 9251630..5773b8d 100644 --- a/packages/common/src/IComment.ts +++ b/packages/common/src/IComment.ts @@ -2,7 +2,9 @@ export interface IComment { id: number storyId: number message: string - score: number + votes: number + spams: number parentId: number - children: IComment[] + userId: number + childrenIds?: number[] } diff --git a/packages/server/src/entities/Comment.ts b/packages/server/src/entities/Comment.ts index 484b130..dc03cd0 100644 --- a/packages/server/src/entities/Comment.ts +++ b/packages/server/src/entities/Comment.ts @@ -1,4 +1,4 @@ -import {Column, Entity, ManyToOne} from 'typeorm' +import {Column, Entity, Index, ManyToOne} from 'typeorm' import {User} from './User' import {Story} from './Story' import {BaseEntity} from './BaseEntity' @@ -12,6 +12,7 @@ export class Comment extends BaseEntity { story?: Story @Column() + @Index() storyId!: number @ManyToOne(type => User, user => user.comments) diff --git a/packages/server/src/services/CommentService.ts b/packages/server/src/services/CommentService.ts index 249b399..b3d2ca8 100644 --- a/packages/server/src/services/CommentService.ts +++ b/packages/server/src/services/CommentService.ts @@ -1,34 +1,176 @@ import {BaseService} from './BaseService' +import {Comment} from '../entities/Comment' +import {ICommentService, ICommentTree} from './ICommentService' import {IComment} from '@rondo/common' -import {ICommentService} from'./ICommentService' +import {Spam} from '../entities/Spam' +import {Validator} from '../validator' +import {Vote} from '../entities/Vote' export class CommentService extends BaseService implements ICommentService { async find(storyId: number) { + const comments = await this.getRepository(Comment).find({ + where: { + storyId, + }, + }) + + const commentTree: ICommentTree = { + rootIds: [], + commentsById: {}, + } + comments.reduce((obj, comment) => { + obj[comment.id] = comment + const {parentId} = comment + if (!parentId) { + commentTree.rootIds.push(comment.id) + return obj + } + const children = + obj[parentId].childrenIds = + obj[parentId].childrenIds || [] + children.push(comment.id) + return obj + }, commentTree.commentsById) + + return commentTree + } + + async findOne(commentId: number) { + return this.getRepository(Comment).findOne(commentId) } async saveRoot(comment: IComment, userId: number) { + new Validator(comment) + .ensure('storyId') + .throw() + + comment.parentId = 0 + comment.userId = userId + comment.votes = 0 + comment.spams = 0 + return this.getRepository(Comment).save(comment) } async save(comment: IComment, userId: number) { + + comment.votes = 0 + comment.spams = 0 + comment.userId = userId + + new Validator(comment) + .ensure('message') + .ensure('userId') + .ensure('storyId') + .ensure('parentId') + .throw() + + return this.getRepository(Comment).save(comment) } async edit(comment: IComment, userId: number) { + new Validator(comment) + .ensure('id') + .ensure('message') + .throw() + + await this.getRepository(Comment) + .update(comment.id, { + message: comment.message, + userId, + }) + const editedComment = await this.findOne(comment.id) + + if (!editedComment) { + throw new Error('Comment not found') + } + return editedComment } async delete(comment: IComment, userId: number) { + new Validator(comment) + .ensure('id') + .throw() + + await this.getRepository(Comment) + .update({ + id: comment.id, + userId, + }, { + message: '(this message has been removed by the user)', + }) + + return this.findOne(comment.id) } async upVote(commentId: number, userId: number) { + await this.getRepository(Vote) + .save({ + commentId, + userId, + }) + + await this.getRepository(Comment) + .createQueryBuilder() + .update() + .where({ id: commentId }) + .set({ + score: () => 'score + 1', + }) + .execute() } async deleteVote(commentId: number, userId: number) { + const result = await this.getRepository(Vote) + .delete({ + commentId, + userId, + }) + + if (result.affected && result.affected === 1) { + await this.getRepository(Comment) + .createQueryBuilder() + .update() + .where({ id: commentId }) + .set({ + score: () => 'score - 1', + }) + } } async markAsSpam(commentId: number, userId: number) { + await this.getRepository(Spam) + .save({ + commentId, + userId, + }) + + await this.getRepository(Comment) + .createQueryBuilder() + .update() + .where({ id: commentId }) + .set({ + spams: () => 'spams + 1', + }) + .execute() } async unmarkAsSpam(commentId: number, userId: number) { + const result = await this.getRepository(Spam) + .delete({ + commentId, + userId, + }) + + if (result.affected && result.affected === 1) { + await this.getRepository(Comment) + .createQueryBuilder() + .update() + .where({ id: commentId }) + .set({ + spams: () => 'spams - 1', + }) + } } } diff --git a/packages/server/src/services/ICommentService.ts b/packages/server/src/services/ICommentService.ts index 3655b3c..ba1bed0 100644 --- a/packages/server/src/services/ICommentService.ts +++ b/packages/server/src/services/ICommentService.ts @@ -1,7 +1,16 @@ import {IComment} from '@rondo/common' +export interface ICommentTree { + rootIds: number[], + commentsById: { + [key: number]: IComment + } +} + export interface ICommentService { - find(storyId: number): Promise + find(storyId: number): Promise + + findOne(commentId: number): Promise saveRoot(comment: IComment, userId: number): Promise @@ -9,7 +18,7 @@ export interface ICommentService { edit(comment: IComment, userId: number): Promise - delete(comment: IComment, userId: number): Promise + delete(comment: IComment, userId: number): Promise upVote(commentId: number, userId: number): Promise