Skip to content

Commit

Permalink
feat(comment): allow user write comments on task (#125)
Browse files Browse the repository at this point in the history
  • Loading branch information
huypl53 authored Mar 5, 2024
1 parent ad147b4 commit 736464e
Show file tree
Hide file tree
Showing 25 changed files with 1,206 additions and 108 deletions.
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,14 @@
"@radix-ui/react-tooltip": "^1.0.7",
"@swc/helpers": "~0.5.0",
"@tailwindcss/forms": "^0.5.3",
"@tiptap/extension-image": "^2.2.4",
"@tiptap/extension-link": "^2.0.3",
"@tiptap/extension-mention": "^2.2.1",
"@tiptap/extension-underline": "^2.2.4",
"@tiptap/pm": "^2.0.3",
"@tiptap/react": "^2.0.3",
"@tiptap/starter-kit": "^2.0.3",
"@tiptap/suggestion": "^2.2.1",
"apexcharts": "^3.41.0",
"axios": "^1.4.0",
"bcryptjs": "^2.4.3",
Expand All @@ -59,6 +63,7 @@
"firebase": "^10.7.1",
"firebase-admin": "^12.0.0",
"formik": "^2.4.1",
"fuse.js": "^7.0.0",
"immer": "^10.0.2",
"ioredis": "^5.3.2",
"jsonwebtoken": "^9.0.0",
Expand All @@ -81,6 +86,7 @@
"react-icons": "^4.9.0",
"read-excel-file": "^5.6.1",
"resend": "^1.0.0",
"tippy.js": "^6.3.7",
"tslib": "^2.3.0",
"zod": "^3.21.4",
"zustand": "^4.3.8"
Expand Down
129 changes: 129 additions & 0 deletions packages/be-gateway/src/routes/comment/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { Request, Response } from 'express'
import { CommentRepository } from '@shared/models'
import { Comment } from '@prisma/client'
import {
BaseController,
Controller,
Res,
Req,
Body,
Next,
ExpressResponse,
Get,
Post,
Put,
Delete
} from '../../core'
import { pusherServer } from '../../lib/pusher-server'
import { AuthRequest } from '../../types'

@Controller('/comment')
export default class TaskComment extends BaseController {
name: string
commentRepo: CommentRepository
constructor() {
super()
this.name = 'comment'
this.commentRepo = new CommentRepository()
}

@Get('')
async getCommentByObjectId(@Res() res: Response, @Req() req: Request) {
const { taskId } = req.query as { taskId: string }

try {
const results = await this.commentRepo.mdCommentGetAllByTask(taskId)
// results.sort((a, b) => (a.createdAt < b.createdAt ? 1 : 0))
res.json({ status: 200, data: results })
} catch (error) {
res.json({
status: 500,
err: error,
data: []
})
}
}

@Post('')
createComment(
@Body() body: Omit<Comment, 'id'>,
@Res() res: ExpressResponse,
@Req() req: AuthRequest
) {
this.commentRepo
.mdCommentAdd(body)
.then(result => {
const { taskId } = body as Comment
const eventName = `event-send-task-comment-${taskId}`

console.log(`trigger event ${eventName} `, body)

pusherServer.trigger('team-collab', eventName, {
...result
})

res.json({ status: 200, data: result })
})
.catch(error => {
console.log({ error })
res.json({
status: 500,
err: error
})
})
}

@Put('')
updateComment(@Res() res: Response, @Req() req: AuthRequest, @Next() next) {
const body = req.body as Comment
const { id, ...rest } = body
this.commentRepo
.mdCommentUpdate(id, rest)
.then(result => {
const { taskId } = result as Comment
const eventName = `event-update-task-comment-${taskId}`

console.log(`trigger event ${eventName} `, body)

pusherServer.trigger('team-collab', eventName, {
...result
})

res.json({ status: 200, data: result })
})
.catch(error => {
console.log({ error })
res.json({
status: 500,
err: error
})
})
}

@Delete('')
async commentDelete(@Req() req: Request, @Res() res: Response) {
try {
const { id, taskId, updatedBy } = req.query as {
id: string
taskId: string
updatedBy: string
}
const result = await this.commentRepo.mdCommentDel(id)
const eventName = `event-delete-task-comment-${taskId}`

console.log(`trigger event ${eventName} `, id)

pusherServer.trigger('team-collab', eventName, {
id,
triggerBy: updatedBy
})

res.json({ status: 200, data: result })
} catch (error) {
res.json({
status: 500,
err: error
})
}
}
}
2 changes: 2 additions & 0 deletions packages/be-gateway/src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import buzzerRouter from './buzzer'
import meetingRouter from './meeting'
import { authMiddleware } from '../middlewares'
import ActivityRouter from './activity'
import CommentRouer from './comment'

// import "./test";
import ProjectController from './project/project.controller'
Expand Down Expand Up @@ -42,6 +43,7 @@ router.use(
TestController,
ProjectController,
ActivityRouter,
CommentRouer,
EventController,
ProjectViewController,
PermissionController,
Expand Down
1 change: 1 addition & 0 deletions packages/shared-models/src/lib/_prisma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export const taskAutomation = pmClient.taskAutomation
export const fileStorageModel = pmClient.fileStorage
export const visionModel = pmClient.vision
export const activityModel = pmClient.activity
export const commentModel = pmClient.comment
50 changes: 50 additions & 0 deletions packages/shared-models/src/lib/comment.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Comment } from '@prisma/client'
import { pmClient } from './_prisma'

const mdComment = pmClient.comment
export class CommentRepository {
async mdCommentAdd(data: Omit<Comment, 'id'>) {
return mdComment.create({
data
})
}

async mdCommentAddMany(data: Omit<Comment, 'id'>[]) {
return mdComment.createMany({
data
})
}

async mdCommentDel(id: string) {
return mdComment.delete({
where: {
id
}
})
}

async mdCommentUpdate(id: string, data: Omit<Comment, 'id'>) {
return mdComment.update({
where: {
id
},
data: data
})
}

async mdCommentGetAllByTask(taskId: string) {
return mdComment.findMany({
where: {
taskId: taskId
}
})
}

async mdCommentGetAllByProject(projectId: string) {
return mdComment.findMany({
where: {
projectId: projectId
}
})
}
}
1 change: 1 addition & 0 deletions packages/shared-models/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export * from './taskAutomation'
export * from './storage'
export * from './activity'
export * from './scheduler.repository'
export * from './comment.repository'
12 changes: 12 additions & 0 deletions packages/shared-models/src/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -403,3 +403,15 @@ model Activity {
updatedAt DateTime?
updatedBy String?
}

model Comment {
id String @id @default(auto()) @map("_id") @db.ObjectId
taskId String @db.ObjectId
projectId String @db.ObjectId
content String
createdBy String
createdAt DateTime
updatedAt DateTime
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { SuggestionProps } from '@tiptap/suggestion'
import {
ReactElement,
Ref,
forwardRef,
useEffect,
useImperativeHandle,
useState
} from 'react'

import MemberAvatar from '@/components/MemberAvatar'

export type TItemBase = {
id: string
label: string
}
type TMemberMentionProps<I> = SuggestionProps<I>
type TMemberMentionRef = Ref<{ onKeyDown: ({ event }: { event: any }) => void }>

const Mention = <I,>(
props: TMemberMentionProps<I & TItemBase>,
ref: TMemberMentionRef
) => {
const [selectedIndex, setSelectedIndex] = useState(0)

const selectItem = (index: number) => {
const item = props.items[index]

if (item) {
const { id, label } = item
props.command({ id, label })
}
}

const upHandler = () => {
setSelectedIndex(
(selectedIndex + props.items.length - 1) % props.items.length
)
}

const downHandler = () => {
setSelectedIndex((selectedIndex + 1) % props.items.length)
}

const enterHandler = () => {
selectItem(selectedIndex)
}

useEffect(() => setSelectedIndex(0), [props.items])

useImperativeHandle(ref, () => ({
onKeyDown: ({ event }) => {
if (event.key === 'ArrowUp') {
upHandler()
return true
}

if (event.key === 'ArrowDown') {
downHandler()
return true
}

if (event.key === 'Enter') {
enterHandler()
return true
}

return false
}
}))

return (
<div className="items border-gray-200">
{props.items?.length ? (
props.items?.map((item, index) => (
<button
className={`item ${index === selectedIndex ? 'is-selected' : ''}`}
key={index}
onClick={() => {
selectItem(index)
}}>
<div className="flex gap-3 items-start">
<MemberAvatar uid={item.id || ''} noName={true} />
<div className="flex flex-col">
{item.label}
<span className="italic text-gray-600 text-xs">
{item?.email}
</span>
</div>
</div>
</button>
))
) : (
<div className="item">No result</div>
)}
</div>
)
}

export default forwardRef(Mention) as <I>(
p: TMemberMentionProps<I & TItemBase> & { r: TMemberMentionRef }
) => ReactElement
Loading

0 comments on commit 736464e

Please sign in to comment.