Add initial comments setup

This commit is contained in:
Jerko Steiner 2019-01-21 01:04:56 +01:00
parent 6ee5077c88
commit 10e5775bf4
15 changed files with 351 additions and 1 deletions

View File

@ -0,0 +1,102 @@
import {IHTTPClient} from '../http'
import {IComment, IAPIDef} from '@rondo/common'
export enum ICommentActionTypes {
VOTE_UP = 'VOTE_UP',
VOTE_DOWN = 'VOTE_DOWN',
COMMENTS_RETRIEVE = 'COMMENTS_RETRIEVE',
COMMENT_ADD = 'COMMENT_ADD',
COMMENT_EDIT = 'COMMENT_EDIT',
COMMENT_REMOVE = 'COMMENT_DELETE',
SPAM_MARK = 'SPAM_MARK',
SPAM_UNMARK = 'SPAM_UNMARK',
}
export class CommentActions {
constructor(protected readonly http: IHTTPClient<IAPIDef>) {}
voteUp(comment: IComment) {
return {
payload: this.http.post('/comments/:commentId/vote', null, {
commentId: comment.id,
}),
type: ICommentActionTypes.VOTE_UP,
}
}
voteDown(comment: IComment) {
return {
payload: this.http.delete('/comments/:commentId/vote', null, {
commentId: comment.id,
}),
type: ICommentActionTypes.VOTE_DOWN,
}
}
getComments(storyId: number) {
return {
payload: this.http.get('/story/:storyId/comments', null, {
storyId,
}),
type: ICommentActionTypes.COMMENTS_RETRIEVE,
}
}
addComment(comment: IComment) {
if (!comment.parentId) {
// root comment
return {
payload: this.http.post('/story/:storyId/comments', comment, {
storyId: comment.storyId,
}),
type: ICommentActionTypes.COMMENT_ADD,
}
}
// nested comment
return {
payload: this.http.post('/comments/:parentId', comment, {
parentId: comment.parentId,
}),
type: ICommentActionTypes.COMMENT_ADD,
}
}
editComment(comment: IComment) {
return {
payload: this.http.put('/comments/:commentId', comment, {
commentId: comment.id,
}),
type: ICommentActionTypes.COMMENT_EDIT,
}
}
removeComment(comment: IComment) {
return {
payload: this.http.delete('/comments/:commentId', null, {
commentId: comment.id,
}),
type: ICommentActionTypes.COMMENT_REMOVE,
}
}
markAsSpam(comment: IComment) {
return {
payload: this.http.post('/comments/:commentId/spam', comment, {
commentId: comment.id,
}),
type: ICommentActionTypes.SPAM_MARK,
}
}
unmarkAsSpam(comment: IComment) {
return {
payload: this.http.delete('/comments/:commentId/spam', null, {
commentId: comment.id,
}),
type: ICommentActionTypes.SPAM_UNMARK,
}
}
}

View File

@ -0,0 +1,49 @@
import React from 'react'
import {IComment} from '@rondo/common'
export interface ICommentProps {
comment: IComment
}
export class CommentVote extends React.PureComponent {
render() {
return (
)
}
}
export class CommentButtons extends React.PureComponent {
render() {
}
}
export class Comment extends React.PureComponent<ICommentProps> {
render() {
const {comment} = this.props
return (
<div className='comment'>
<span className='score'>{comment.score}</span>
<p>{comment.message}</p>
<Comments comments={comment.children} />
</div>
)
}
}
export interface ICommentsProps {
comments: IComment[]
}
export class Comments extends React.PureComponent<ICommentsProps> {
render() {
const {comments} = this.props
return (
<div className='comments'>
{comments.map(comment => <Comment comment={comment} />)}
</div>
)
}
}

View File

@ -1,2 +1,6 @@
export * from './HTTPClient' export * from './HTTPClient'
export * from './IHTTPClient'
export * from './IHeader' export * from './IHeader'
export * from './IRequest'
export * from './IResponse'
export * from './ITypedRequestParams'

View File

@ -1,4 +1,5 @@
import {ICredentials} from './ICredentials' import {ICredentials} from './ICredentials'
import {IComment} from './IComment'
import {IUser} from './IUser' import {IUser} from './IUser'
export interface IAPIDef { export interface IAPIDef {
@ -15,7 +16,7 @@ export interface IAPIDef {
} }
'/auth/logout': { '/auth/logout': {
'get': {} 'get': {}
}, }
'/users/password': { '/users/password': {
'post': { 'post': {
body: { body: {
@ -31,4 +32,65 @@ export interface IAPIDef {
} }
} }
} }
'/story/:storyId/comments': {
'get': {
response: IComment[],
params: {
storyId: number // TODO might have to change to string (url)
}
}
'post': {
response: IComment,
params: {
storyId: number
}
}
}
'/comments/:parentId': {
'post': {
response: IComment,
params: {
parentId: number
},
body: IComment,
}
}
'/comments/:commentId': {
'put': {
response: IComment,
body: IComment,
params: {
commentId: number
}
}
'delete': {
params: {
commentId: number
}
}
}
'/comments/:commentId/vote': {
'post': {
params: {
commentId: number
}
}
'delete': {
params: {
commentId: number
}
}
}
'/comments/:commentId/spam': {
'post': {
params: {
commentId: number
}
}
'delete': {
params: {
commentId: number
}
}
}
} }

View File

@ -0,0 +1,8 @@
export interface IComment {
id: number
storyId: number
message: string
score: number
parentId: number
children: IComment[]
}

View File

@ -1,4 +1,5 @@
export * from './IAPIDef' export * from './IAPIDef'
export * from './IComment'
export * from './ICredentials' export * from './ICredentials'
export * from './IRoutes' export * from './IRoutes'
export * from './IUser' export * from './IUser'

View File

View File

View File

View File

View File

View File

@ -0,0 +1,68 @@
import {AsyncRouter} from '../router'
import {BaseRoute} from './BaseRoute'
import {IAPIDef} from '@rondo/common'
import {ensureLoggedInApi} from '../middleware'
export class CommentRoutes extends BaseRoute<IAPIDef> {
constructor(
protected readonly commentService: ICommentService,
protected readonly t: AsyncRouter<IAPIDef>,
) {
super(t)
}
setup(t: AsyncRouter<IAPIDef>) {
t.get('/story/:storyId/comments', async req => {
const {storyId} = req.params
// TODO retrieve comments from story
return []
})
t.use(ensureLoggedInApi)
t.post('/story/:storyId/comments', async req => {
const comment = req.body
// TODO save a comment
return comment
})
t.post('/comments/:parentId', async req => {
const comment = req.body
// TODO save a comment
return comment
})
t.put('/comments/:commentId', async req => {
const comment = req.body
// TODO edit a comment
return comment
})
t.delete('/comments/:commentId', async req => {
const {commentId} = req.params
// TODO delete a comment
})
t.post('/comments/:commentId/vote', async req => {
const {commentId} = req.params
// TODO upvote a comment
})
t.delete('/comments/:commentId/vote', async req => {
const {commentId} = req.params
// TODO delete a vote
})
t.post('/comments/:commentId/spam', async req => {
const {commentId} = req.params
// TODO report comment as spam
})
t.delete('/comments/:commentId/spam', async req => {
const {commentId} = req.params
// TODO delete spam report
})
}
}

View File

@ -33,6 +33,7 @@ export class LoginRoutes extends BaseRoute<IAPIDef> {
} }
await req.logInPromise(user) await req.logInPromise(user)
res.redirect(req.baseUrl) res.redirect(req.baseUrl)
// TODO return user
}) })
t.get('/auth/logout', async (req, res) => { t.get('/auth/logout', async (req, res) => {

View File

@ -0,0 +1,34 @@
import {BaseService} from './BaseService'
import {IComment} from '@rondo/common'
import {ICommentService} from'./ICommentService'
export class CommentService extends BaseService implements ICommentService {
async find(storyId: number) {
}
async saveRoot(comment: IComment, userId: number) {
}
async save(comment: IComment, userId: number) {
}
async edit(comment: IComment, userId: number) {
}
async delete(comment: IComment, userId: number) {
}
async upVote(commentId: number, userId: number) {
}
async deleteVote(commentId: number, userId: number) {
}
async markAsSpam(commentId: number, userId: number) {
}
async unmarkAsSpam(commentId: number, userId: number) {
}
}

View File

@ -0,0 +1,21 @@
import {IComment} from '@rondo/common'
export interface ICommentService {
find(storyId: number): Promise<IComment>
saveRoot(comment: IComment, userId: number): Promise<IComment>
save(comment: IComment, userId: number): Promise<IComment>
edit(comment: IComment, userId: number): Promise<IComment>
delete(comment: IComment, userId: number): Promise<IComment>
upVote(commentId: number, userId: number): Promise<void>
deleteVote(commentId: number, userId: number): Promise<void>
markAsSpam(commentId: number, userId: number): Promise<void>
unmarkAsSpam(commentId: number, userId: number): Promise<void>
}