Skip to content

Commit 717d6d1

Browse files
Merge pull request #58 from nashtech-garage/feat/upload-file-on-task
feat upload file on task
2 parents ad5b748 + 34bf441 commit 717d6d1

28 files changed

+604
-50
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package controllers
2+
3+
import play.api.mvc._
4+
import services.MediaService
5+
6+
import javax.inject.Inject
7+
import scala.concurrent.{ExecutionContext, Future}
8+
9+
class MediaController @Inject()(
10+
cc: ControllerComponents,
11+
mediaService: MediaService,
12+
authenticatedActionWithUser: AuthenticatedActionWithUser
13+
)(implicit ec: ExecutionContext) extends AbstractController(cc) {
14+
15+
def upload(taskId: Int) = authenticatedActionWithUser.async(parse.multipartFormData) { request =>
16+
val userId = request.userToken.userId
17+
request.body.file("file") match {
18+
case Some(filePart) =>
19+
mediaService.uploadMedia(userId, taskId, filePart).map(m => Ok(s"""{"id": ${m.id.get}}"""))
20+
21+
case None =>
22+
Future.successful(BadRequest("File missing"))
23+
}
24+
}
25+
26+
def get(id: Int) = Action.async {
27+
mediaService.getMedia(id).map {
28+
case Some(media) => Ok(play.api.libs.json.Json.toJson(media))
29+
case None => NotFound
30+
}
31+
}
32+
33+
def delete(id: Int) = Action.async {
34+
mediaService.deleteMedia(id).map {
35+
case true => NoContent
36+
case false => NotFound
37+
}
38+
}
39+
40+
def update(id: Int) = Action(parse.multipartFormData).async { request =>
41+
request.body.file("file") match {
42+
case Some(file) =>
43+
mediaService.updateMedia(id, file).map {
44+
case Some(updatedMedia) => Ok(play.api.libs.json.Json.toJson(updatedMedia))
45+
case None => NotFound
46+
}
47+
case None =>
48+
Future.successful(BadRequest("File missing"))
49+
}
50+
}
51+
}

backend/app/dto/response/project/ProjectDetailResponse.scala

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ object ProjectDetailResponse {
2424
status: ProjectStatus,
2525
columns: Seq[Column],
2626
members: Seq[UserInProjectResponse],
27-
rankedTasks: Seq[(Int, String, Int, Int, java.time.Instant, Int, Int)],
27+
rankedTasks: Seq[(Int, String, Int, Int, java.time.Instant, Int, Int, String)],
2828
allUserTasks: Seq[UserTask]
2929
): ProjectDetailResponse = {
3030
val memberIdsByTask = allUserTasks
@@ -34,8 +34,8 @@ object ProjectDetailResponse {
3434
.toMap
3535

3636
val allTasks = rankedTasks.map {
37-
case (taskId, taskName, pos, colId, updatedAt, _, totalTasksInColumn) =>
38-
(taskId, taskName, pos, colId, updatedAt, totalTasksInColumn)
37+
case (taskId, taskName, pos, colId, updatedAt, _, totalTasksInColumn, mediaPreviewPath) =>
38+
(taskId, taskName, pos, colId, updatedAt, totalTasksInColumn, mediaPreviewPath)
3939
}
4040

4141
val totalTasksByColumn =
@@ -44,14 +44,15 @@ object ProjectDetailResponse {
4444
val tasksByColumn =
4545
allTasks.groupBy(_._4).view.mapValues { tasks =>
4646
tasks.map {
47-
case (taskId, taskName, pos, colId, updatedAt, _) =>
47+
case (taskId, taskName, pos, colId, updatedAt, _, mediaPreviewPath) =>
4848
TaskSummaryResponse(
4949
taskId,
5050
taskName,
5151
pos,
5252
colId,
5353
memberIdsByTask.getOrElse(taskId, Seq.empty),
54-
updatedAt
54+
updatedAt,
55+
Some(mediaPreviewPath)
5556
)
5657
}
5758
}.toMap

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dto.response.task
22

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

56
import java.time.Instant
@@ -16,7 +17,8 @@ case class TaskDetailResponse(id: Int,
1617
isCompleted: Boolean,
1718
createdAt: Instant,
1819
updatedAt: Instant,
19-
assignedMembers: Seq[AssignedMemberResponse] = Seq.empty
20+
assignedMembers: Seq[AssignedMemberResponse] = Seq.empty,
21+
media: Seq[Media] = Seq.empty
2022
)
2123

2224
object TaskDetailResponse {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ case class TaskSummaryResponse(id: Int,
99
position: Int,
1010
columnId: Int,
1111
memberIds: Seq[Int],
12-
updatedAt: Instant)
12+
updatedAt: Instant,
13+
mediaPreviewPath: Option[String] = None)
1314

1415
object TaskSummaryResponse {
1516
implicit val taskSummaryFmt: OFormat[TaskSummaryResponse] =

backend/app/mappers/TaskMapper.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package mappers
22

33
import dto.response.task.{AssignedMemberResponse, TaskDetailResponse}
4-
import models.entities.Task
4+
import models.entities.{Media, Task}
55

66
object TaskMapper {
77

8-
def toDetailResponse(entity: Task): TaskDetailResponse = {
8+
def toDetailResponse(entity: Task, media: Seq[Media] = Seq.empty): TaskDetailResponse = {
99
TaskDetailResponse(
1010
id = entity.id.getOrElse(0),
1111
name = entity.name,
@@ -18,10 +18,11 @@ object TaskMapper {
1818
columnId = entity.columnId,
1919
isCompleted = entity.isCompleted,
2020
createdAt = entity.createdAt,
21-
updatedAt = entity.updatedAt
21+
updatedAt = entity.updatedAt,
22+
media = media
2223
)
2324
}
24-
def toDetailWithAssignMembersResponse(entity: Task,assignedMembers: Seq[AssignedMemberResponse]): TaskDetailResponse = {
25+
def toDetailWithAssignMembersResponse(entity: Task, assignedMembers: Seq[AssignedMemberResponse], media: Seq[Media] = Seq.empty): TaskDetailResponse = {
2526
TaskDetailResponse(
2627
id = entity.id.getOrElse(0),
2728
name = entity.name,
@@ -35,7 +36,8 @@ object TaskMapper {
3536
isCompleted = entity.isCompleted,
3637
createdAt = entity.createdAt,
3738
updatedAt = entity.updatedAt,
38-
assignedMembers = assignedMembers
39+
assignedMembers = assignedMembers,
40+
media = media
3941
)
4042
}
4143

backend/app/models/entities/Task.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,17 @@ case class Tag(id: Option[Int] = None,
6565
case class TaskTag(id: Option[Int] = None,
6666
taskId: Option[Int] = None,
6767
tagId: Option[Int] = None)
68+
69+
case class Media(id: Option[Int] = None,
70+
userId: Int,
71+
taskId: Int,
72+
filePath: String,
73+
fileName: String,
74+
fileType: String,
75+
fileSize: Long,
76+
createdAt: Instant = Instant.now(),
77+
updatedAt: Instant = Instant.now())
78+
79+
object Media {
80+
implicit val mediaFormat: OFormat[Media] = Json.format[Media]
81+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package models.tables
2+
3+
import slick.lifted.Tag
4+
import models.entities.Media
5+
import db.MyPostgresProfile.api._
6+
7+
import java.time.Instant
8+
9+
class MediaTable(tag: Tag) extends Table[Media](tag, "media") {
10+
11+
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
12+
def userId = column[Int]("user_id")
13+
def taskId = column[Int]("task_id")
14+
def filePath = column[String]("file_path")
15+
def fileName = column[String]("file_name")
16+
def fileType = column[String]("file_type")
17+
def fileSize = column[Long]("file_size")
18+
def createdAt = column[Instant]("created_at")
19+
def updatedAt = column[Instant]("updated_at")
20+
21+
def * = (id.?, userId, taskId, filePath, fileName, fileType, fileSize, createdAt, updatedAt) <> ((Media.apply _).tupled, Media.unapply)
22+
23+
// Foreign keys
24+
def user = foreignKey("media_user_fk", userId, TableQuery[UserTable])(_.id, onDelete = ForeignKeyAction.Cascade)
25+
def task = foreignKey("media_task_fk", taskId, TableQuery[TaskTable])(_.id, onDelete = ForeignKeyAction.Cascade)
26+
}

backend/app/models/tables/TableRegistry.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ object TableRegistry {
2020
lazy val activityLogs = TableQuery[ActivityLogTable]
2121
lazy val userTasks = TableQuery[UserTaskTable]
2222
lazy val userProfiles = TableQuery[UserProfileTable]
23+
lazy val medias = TableQuery[MediaTable]
2324

2425
// All tables for schema creation/evolution
2526
val allTables = Seq(
2627
roles, users, workspaces, userWorkspaces, projects, columns,
2728
tasks, checklists, checklistItems, taskComments, tags, taskTags,
28-
notifications, activityLogs
29+
notifications, activityLogs, userTasks, userProfiles, medias
2930
)
3031
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package modules
2+
3+
import com.cloudinary.Cloudinary
4+
import play.api.{Configuration, Environment}
5+
import play.api.inject._
6+
7+
import javax.inject.{Inject, Provider}
8+
import scala.jdk.CollectionConverters._
9+
10+
class CloudinaryModule extends Module {
11+
override def bindings(environment: Environment, config: Configuration) = {
12+
Seq(
13+
bind[Cloudinary].toProvider[CloudinaryProvider].eagerly()
14+
)
15+
}
16+
}
17+
18+
class CloudinaryProvider @Inject()(config: Configuration) extends Provider[Cloudinary] {
19+
override def get(): Cloudinary = {
20+
val cloudName = config.get[String]("cloudinary.cloud-name")
21+
val apiKey = config.get[String]("cloudinary.api-key")
22+
val apiSecret = config.get[String]("cloudinary.api-secret")
23+
24+
new Cloudinary(Map(
25+
"cloud_name" -> cloudName,
26+
"api_key" -> apiKey,
27+
"api_secret" -> apiSecret
28+
).asJava)
29+
}
30+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package repositories
2+
3+
import javax.inject._
4+
import db.MyPostgresProfile.api._
5+
import models.entities.Media
6+
import models.tables.MediaTable
7+
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
8+
import slick.jdbc.JdbcProfile
9+
10+
import scala.concurrent.{ExecutionContext, Future}
11+
12+
class MediaRepository @Inject()(
13+
protected val dbConfigProvider: DatabaseConfigProvider
14+
)(implicit ec: ExecutionContext) extends HasDatabaseConfigProvider[JdbcProfile] {
15+
private val mediaTable = TableQuery[MediaTable]
16+
17+
def insert(media: Media): Future[Media] =
18+
db.run((mediaTable returning mediaTable.map(_.id)
19+
into ((data, id) => data.copy(id = Some(id))
20+
) += media))
21+
22+
def findById(id: Int): Future[Option[Media]] =
23+
db.run(mediaTable.filter(_.id === id).result.headOption)
24+
25+
def delete(id: Int): Future[Int] =
26+
db.run(mediaTable.filter(_.id === id).delete)
27+
28+
def update(media: Media): Future[Int] =
29+
db.run(mediaTable.filter(_.id === media.id).update(media))
30+
31+
def findByTaskId(taskId: Int): Future[Seq[Media]] =
32+
db.run(mediaTable.filter(_.taskId === taskId).result)
33+
}

0 commit comments

Comments
 (0)