Skip to content

Commit

Permalink
feat: notification setting for all tasks (#139)
Browse files Browse the repository at this point in the history
  • Loading branch information
hudy9x authored Mar 13, 2024
1 parent 067030e commit 7d1eedd
Show file tree
Hide file tree
Showing 17 changed files with 486 additions and 16 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@radix-ui/react-popover": "^1.0.6",
"@radix-ui/react-scroll-area": "^1.0.4",
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@swc/helpers": "~0.5.0",
Expand Down
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 @@ -30,6 +30,7 @@ import { SchedulerController } from './scheduler/index.controller'
import TaskReorderController from './task/reorder.controller'
import { EventController } from './event/index.controller'
import { TestController } from './test'
import ProjectSetting from './project/setting.controller'

const router = Router()

Expand All @@ -46,6 +47,7 @@ router.use(
CommentRouer,
EventController,
ProjectViewController,
ProjectSetting,
PermissionController,
OrganizationController,
OrganizationStorageController,
Expand Down
6 changes: 6 additions & 0 deletions packages/be-gateway/src/routes/project/project.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ class Project extends BaseController {
res.status(error)
}
}

@Post('/toggle-tracker')
async toggleProjectTracker(@Body() body: { uid: string; projectId: string }) {
console.log(body)
return 1
}
}

export default Project
71 changes: 71 additions & 0 deletions packages/be-gateway/src/routes/project/setting.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {
BaseController,
Body,
Controller,
Get,
Put,
Req,
UseMiddleware
} from '../../core'
import { AuthRequest } from '../../types'
import { authMiddleware } from '../../middlewares'
import { ProjectSettingRepository } from '@shared/models'
import BadRequestException from '../../exceptions/BadRequestException'

@Controller('/project-setting')
@UseMiddleware([authMiddleware])
class ProjectSetting extends BaseController {
settingRepo: ProjectSettingRepository
constructor() {
super()
this.settingRepo = new ProjectSettingRepository()
}
@Get('/notification')
async getAllNotificationSetting(@Req() req: AuthRequest) {
const { id } = req.authen
const { projectId } = this.req.params as { projectId: string }
try {

const settings = await this.settingRepo.getMyNotifySettings({
uid: id,
projectId
})

return settings
} catch (error) {

throw new BadRequestException(error)
}
}
@Put('/notification')
async UpdateNotificationSetting(@Body() body, @Req() req: AuthRequest) {
const { id } = req.authen
const { projectId, taskChanges, remind, overdue } = body as {
projectId: string
taskChanges: boolean
remind: boolean
overdue: boolean
}

try {
console.log('body', body)
await this.settingRepo.updateOrCreateNotifySetting({
uid: id,
projectId,
taskChanges: !!taskChanges || false,
remind: !!remind || false,
overdue: !!overdue || false,
createdAt: new Date(),
createdBy: id
})

return 1
} catch (error) {
throw new BadRequestException(error)
}


}
}

export default ProjectSetting
34 changes: 31 additions & 3 deletions packages/be-gateway/src/routes/task/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
mdTaskDelete,
mdTaskStatusWithDoneType,
mdTaskStatusWithTodoType,
mdTaskUpdateMany
mdTaskUpdateMany,
ProjectSettingRepository
} from '@shared/models'

import { Task, TaskStatus } from '@prisma/client'
Expand Down Expand Up @@ -487,13 +488,15 @@ router.put('/project/task', async (req: AuthRequest, res) => {
const { id: userId } = req.authen

const activityService = new ActivityService()
const projectSettingRepo = new ProjectSettingRepository()

try {
// await pmClient.$transaction(async tx => {
const taskData = await mdTaskGetOne(id)
const oldTaskData = structuredClone(taskData)
const isDoneBefore = taskData.done
const oldStatusId = taskData.taskStatusId
const oldProgress = taskData.progress
const oldAssigneeId = taskData?.assigneeIds[0]

const key = [CKEY.TASK_QUERY, taskData.projectId]
Expand Down Expand Up @@ -577,6 +580,13 @@ router.put('/project/task', async (req: AuthRequest, res) => {

await Promise.allSettled(processes)

const getWatchers = async () => {
const watchers = await projectSettingRepo.getAllNotifySettings(result.projectId)
// merge watchers and make sure that do not send it to user who updated this task
const watcherList = [...result.assigneeIds, ...watchers].filter(uid => uid !== userId)
return watcherList
}

// send notification as status changed
if (oldStatusId !== result.taskStatusId) {
const newStatus = await serviceGetStatusById(result.taskStatusId)
Expand All @@ -585,13 +595,31 @@ router.put('/project/task', async (req: AuthRequest, res) => {
`${pinfo.organizationId}/project/${projectId}?mode=task&taskId=${result.id}`
)

notifyToWebUsers(result.assigneeIds, {
title: 'Task update',
const watcherList = await getWatchers()

notifyToWebUsers(watcherList, {
title: 'Status update',
body: `Status changed to ${newStatus.name} on "${result.title}"`,
deep_link: taskLink
})
}

if (oldProgress !== result.progress) {
const pinfo = await serviceGetProjectById(result.projectId)
const taskLink = genFrontendUrl(
`${pinfo.organizationId}/project/${projectId}?mode=task&taskId=${result.id}`
)

const watcherList = await getWatchers()

notifyToWebUsers(watcherList, {
title: 'Progress update',
body: `From ${oldProgress} => ${result.progress} on "${result.title}"`,
deep_link: taskLink
})

}

res.json({ status: 200, data: result })
// })
} catch (error) {
Expand Down
3 changes: 2 additions & 1 deletion packages/shared-models/src/lib/_prisma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { PrismaClient } from '@prisma/client'

import { Logtail } from '@logtail/node'

const logtail = new Logtail(process.env.LOGTAIL_SOURCE_TOKEN || "")
const logtail = new Logtail(process.env.LOGTAIL_SOURCE_TOKEN || '')

export const Log = logtail

export const pmClient = new PrismaClient()
export const pmTrans = pmClient.$transaction
export const projectModel = pmClient.project
export const projectViewModel = pmClient.projectView
export const projectNotifyModel = pmClient.projectSettingNotification
export const taskStatusModel = pmClient.taskStatus
export const taskPointModel = pmClient.taskPoint
export const tagModel = pmClient.tag
Expand Down
1 change: 1 addition & 0 deletions packages/shared-models/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './project'
export * from './projectPin'
export * from './projectView'
export * from './project.setting.repository'
export * from './taskPoint'
export * from './vision'
export * from './taskStatus'
Expand Down
66 changes: 66 additions & 0 deletions packages/shared-models/src/lib/project.setting.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { ProjectSettingNotification } from '@prisma/client'
import { projectNotifyModel } from './_prisma'

export class ProjectSettingRepository {
async getMyNotifySettings({
uid,
projectId
}: {
uid: string
projectId: string
}) {
return projectNotifyModel.findFirst({
where: {
uid,
projectId
}
})
}
async getAllNotifySettings(projectId: string) {
const settings = await projectNotifyModel.findMany({
where: {
projectId,
taskChanges: true
},
select: {
uid: true
}
})

if (settings.length) return settings.map(st => st.uid)


return []
}

async updateOrCreateNotifySetting(
data: Omit<ProjectSettingNotification, 'id'>
) {
const { uid, projectId } = data
const myNotifySetting = await projectNotifyModel.findFirst({
where: {
uid,
projectId
}
})

if (myNotifySetting) {
return projectNotifyModel.update({
where: {
id: myNotifySetting.id
},
data
})
} else {
return projectNotifyModel.create({
data
})
}
}

async createNotifySetting(data: Omit<ProjectSettingNotification, 'id'>) {
return projectNotifyModel.create({
data
})
}
}
33 changes: 23 additions & 10 deletions packages/shared-models/src/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,9 @@ model TaskAutomation {
}

model Scheduler {
id String @id @default(auto()) @map("_id") @db.ObjectId
organizationId String @db.ObjectId
projectId String @db.ObjectId
id String @id @default(auto()) @map("_id") @db.ObjectId
organizationId String @db.ObjectId
projectId String @db.ObjectId
cronId String?
trigger Json
Expand Down Expand Up @@ -282,6 +282,19 @@ model Project {
updatedAt DateTime?
}

model ProjectSettingNotification {
id String @id @default(auto()) @map("_id") @db.ObjectId
uid String @db.ObjectId
projectId String @db.ObjectId
taskChanges Boolean? @default(false)
remind Boolean? @default(false)
overdue Boolean? @default(false)
createdBy String?
createdAt DateTime?
}

enum ProjectViewType {
DASHBOARD
LIST
Expand Down Expand Up @@ -405,13 +418,13 @@ model Activity {
}

model Comment {
id String @id @default(auto()) @map("_id") @db.ObjectId
taskId String @db.ObjectId
projectId String @db.ObjectId
id String @id @default(auto()) @map("_id") @db.ObjectId
taskId String @db.ObjectId
projectId String @db.ObjectId
content String
content String
createdBy String
createdAt DateTime
updatedAt DateTime
createdBy String
createdAt DateTime
updatedAt DateTime
}
45 changes: 45 additions & 0 deletions packages/shared-ui/src/components/Switch/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as Switch from '@radix-ui/react-switch'
import './style.css'
import { useEffect, useState } from 'react'

interface SwitchProps {
title?: string
checked?: boolean
name?: string
onChange?: (checked: boolean) => void
desc?: string | React.ReactNode
className?: string
disabled?: boolean
}

export default function SwitchContainer({
className,
onChange,
checked
}: SwitchProps) {
const [isChecked, setIsChecked] = useState<boolean>(!!checked)

const handleChange = (checked: boolean) => {
if (onChange) {
onChange(checked)
return
}
}

useEffect(() => {
if (isChecked !== checked) {
setIsChecked(!!checked)
// onChange && onChange(!!checked)
}
// eslint-disable-next-line
}, [checked, isChecked])

return (
<Switch.Root
checked={isChecked}
onCheckedChange={handleChange}
className={`switch-root ${className}`}>
<Switch.Thumb className="switch-thumb" />
</Switch.Root>
)
}
Loading

0 comments on commit 7d1eedd

Please sign in to comment.