Skip to content

Commit bb69a3f

Browse files
Feat/search task fe (#20)
* search tasks * search tasks page * fix conflict --------- Co-authored-by: Luong Tran <tranthihienluong2003@gmail.com>
1 parent 3bd40c6 commit bb69a3f

File tree

25 files changed

+748
-192
lines changed

25 files changed

+748
-192
lines changed

backend/app/controllers/ProjectController.scala

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,7 @@ import dto.response.ApiResponse
55
import play.api.i18n.I18nSupport.RequestWithMessagesApi
66
import play.api.i18n.Messages
77
import play.api.libs.json.{JsValue, Json}
8-
import play.api.mvc.{
9-
Action,
10-
AnyContent,
11-
MessagesAbstractController,
12-
MessagesControllerComponents
13-
}
8+
import play.api.mvc.{Action, AnyContent, MessagesAbstractController, MessagesControllerComponents}
149
import services.ProjectService
1510
import utils.WritesExtras.unitWrites
1611
import validations.ValidationHandler
@@ -124,4 +119,14 @@ class ProjectController @Inject()(
124119
Ok(Json.toJson(apiResponse))
125120
}
126121
}
122+
123+
def getProjectsByUser: Action[AnyContent] =
124+
authenticatedActionWithUser.async { request =>
125+
val userId = request.userToken.userId
126+
projectService.getProjectsByUserId(userId).map { projects =>
127+
val apiResponse =
128+
ApiResponse.success("Projects retrieved", projects)
129+
Ok(Json.toJson(apiResponse))
130+
}
131+
}
127132
}

backend/app/dto/response/task/TaskSearchResponse.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package dto.response.task
22

3+
import models.Enums.TaskStatus.TaskStatus
34
import play.api.libs.json.{Format, Json}
45

56
import java.time.Instant
67

78
case class TaskSearchResponse(taskId: Int,
89
taskName: String,
910
taskDescription: Option[String],
11+
taskStatus: TaskStatus,
1012
projectId: Int,
1113
projectName: String,
1214
columnName: String,
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package mappers
2+
3+
object ProjectMapper {
4+
5+
def toProjectResponse(entity: models.entities.Project): dto.response.project.ProjectResponse =
6+
dto.response.project.ProjectResponse(
7+
id = entity.id.getOrElse(0),
8+
name = entity.name,
9+
status = entity.status
10+
)
11+
12+
}

backend/app/repositories/ProjectRepository.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,15 @@ class ProjectRepository @Inject()(
135135
val action = insertQuery ++= entries
136136
db.run(action)
137137
}
138+
139+
def getProjectsByUser(userId: Int): DBIO[Seq[Project]] = {
140+
val q = for {
141+
up <- userProjects
142+
if up.userId === userId && (up.role === UserProjectRole.owner || up.role === UserProjectRole.member)
143+
p <- projects if p.id === up.projectId && p.status =!= ProjectStatus.deleted
144+
w <- workspaces if w.id === p.workspaceId && !w.isDeleted
145+
} yield p
146+
147+
q.result
148+
}
138149
}

backend/app/repositories/TaskRepository.scala

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package repositories
22

33
import db.MyPostgresProfile.api.{columnStatusTypeMapper, projectStatusTypeMapper, taskStatusTypeMapper}
44
import dto.response.task.{AssignMemberToTaskResponse, AssignedMemberResponse, TaskSummaryResponse}
5+
import dto.response.task.AssignMemberToTaskResponse
6+
import dto.response.task.TaskSummaryResponse
7+
import models.Enums.TaskStatus.TaskStatus
58
import models.Enums.{ColumnStatus, ProjectStatus, TaskStatus}
69
import models.entities.{Task, UserTask}
710
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
@@ -131,11 +134,12 @@ class TaskRepository@Inject()(
131134
): Query[(Rep[Int],
132135
Rep[String],
133136
Rep[Option[String]],
137+
Rep[TaskStatus],
134138
Rep[Int],
135139
Rep[String],
136140
Rep[String],
137141
Rep[Instant]),
138-
(Int, String, Option[String], Int, String, String, Instant),
142+
(Int, String, Option[String], TaskStatus, Int, String, String, Instant),
139143
Seq] = {
140144

141145
val baseQuery =
@@ -147,7 +151,7 @@ class TaskRepository@Inject()(
147151
.on(_._2.projectId === _.id)
148152
.join(userProjects)
149153
.on(_._2.id === _.projectId)
150-
if up.userId === userId
154+
if up.userId === userId && t.status =!= TaskStatus.deleted && p.status =!= ProjectStatus.deleted && c.status =!= ColumnStatus.deleted
151155
} yield (t, c, p)
152156

153157
val filtered = baseQuery
@@ -164,7 +168,7 @@ class TaskRepository@Inject()(
164168
.sortBy { case (t, _, _) => t.updatedAt.desc }
165169
.map {
166170
case (t, c, p) =>
167-
(t.id, t.name, t.description, p.id, p.name, c.name, t.updatedAt)
171+
(t.id, t.name, t.description, t.status, p.id, p.name, c.name, t.updatedAt)
168172
}
169173
}
170174

backend/app/services/ProjectService.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import dto.request.project.CreateProjectRequest
44
import dto.response.project.{ProjectResponse, ProjectSummariesResponse}
55
import dto.response.user.UserInProjectResponse
66
import exception.AppException
7+
import mappers.ProjectMapper
78
import models.Enums.ProjectStatus.ProjectStatus
89
import models.Enums.{ProjectStatus, ProjectVisibility}
910
import models.entities.Project
@@ -207,4 +208,10 @@ class ProjectService @Inject()(
207208
db.run(projectRepository.isUserInActiveProject(userId, projectId))
208209
}
209210

211+
def getProjectsByUserId(userId: Int): Future[Seq[ProjectResponse]] = {
212+
db.run(projectRepository.getProjectsByUser(userId)).map {
213+
_.map(ProjectMapper.toProjectResponse)
214+
}
215+
}
216+
210217
}

backend/app/services/TaskService.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,8 @@ class TaskService @Inject()(taskRepository: TaskRepository,
279279
.take(size)
280280

281281
db.run(query.result).map(_.map {
282-
case (taskId, taskName, taskDesc, projectId, projectName, columnName, updatedAt) =>
283-
TaskSearchResponse(taskId, taskName, taskDesc, projectId, projectName, columnName, updatedAt)
282+
case (taskId, taskName, taskDesc, taskStatus, projectId, projectName, columnName, updatedAt) =>
283+
TaskSearchResponse(taskId, taskName, taskDesc, taskStatus, projectId, projectName, columnName, updatedAt)
284284
})
285285
}
286286

backend/conf/routes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ PATCH /api/projects/:projectId/reopen controllers.ProjectController.reopen
3636
GET /api/projects/completed controllers.ProjectController.getCompletedProjectsByUser
3737
GET /api/projects/:projectId/members controllers.ProjectController.getAllMembersInProject(projectId: Int)
3838
GET /api/projects/:projectId controllers.ProjectController.getProjectById(projectId: Int)
39+
GET /api/projects controllers.ProjectController.getProjectsByUser
3940

4041
# Column routes
4142
POST /api/projects/:projectId/columns controllers.ColumnController.create(projectId: Int)

backend/test/controllers/ProjectControllerSpec.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,5 +222,14 @@ class ProjectControllerSpec
222222
status(result) mustBe OK
223223
(contentAsJson(result) \ "message").as[String] mustBe "Project deleted successfully"
224224
}
225+
226+
"get all projects by user successfully" in {
227+
val request = FakeRequest(GET, s"/api/projects")
228+
.withCookies(Cookie(cookieName, fakeToken))
229+
val result = route(app, request).get
230+
231+
status(result) mustBe OK
232+
(contentAsJson(result) \ "message").as[String] mustBe "Projects retrieved"
233+
}
225234
}
226235
}

frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="UTF-8" />
55
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<title>Vite + React + TS</title>
7+
<title>Smart Taskhub</title>
88
</head>
99
<body>
1010
<div id="root"></div>

0 commit comments

Comments
 (0)