Skip to content

Commit

Permalink
feat: pose layout feature
Browse files Browse the repository at this point in the history
  • Loading branch information
DongGeon0908 committed Aug 20, 2024
1 parent da3e1bf commit 3b99d67
Show file tree
Hide file tree
Showing 14 changed files with 314 additions and 0 deletions.
23 changes: 23 additions & 0 deletions sql/DDL.sql
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,26 @@ CREATE TABLE `pose_noti`
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT '자세 알림';
CREATE INDEX uidx__uid ON pose_noti (uid);

-- 포즈 레이아웃
CREATE TABLE `pose_layout`
(
`id` bigint NOT NULL AUTO_INCREMENT,
`uid` bigint NOT NULL COMMENT 'uid',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '생성일',
`modified_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT '포즈 레이아웃';
CREATE INDEX idx__uid ON pose_layout (uid);

-- 포즈 레이아웃 포인트
CREATE TABLE `pose_layout_point`
(
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'pose key point snapshot id',
`pose_layout_id` bigint NOT NULL COMMENT 'pose layout id',
`position` VARCHAR(32) NOT NULL COMMENT '스냅샷 위치',
`x` DECIMAL(20, 16) NOT NULL COMMENT 'x 좌표',
`y` DECIMAL(20, 16) NOT NULL COMMENT 'y 좌표',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='포즈 레이아웃 point';
CREATE INDEX uidx__pose_snapshot_id__position ON pose_layout_point (pose_layout_id, position);
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.hero.alignlab.domain.pose.application

import com.hero.alignlab.common.extension.executes
import com.hero.alignlab.config.database.TransactionTemplates
import com.hero.alignlab.domain.auth.model.AuthUser
import com.hero.alignlab.domain.pose.domain.PoseLayout
import com.hero.alignlab.domain.pose.domain.PoseLayoutPoint
import com.hero.alignlab.domain.pose.model.request.PoseLayoutRequest
import com.hero.alignlab.domain.pose.model.response.GetPoseLayoutResponse
import com.hero.alignlab.domain.pose.model.response.GetRecentPoseLayoutResponse
import com.hero.alignlab.domain.pose.model.response.PoseLayoutPointResponse
import com.hero.alignlab.domain.pose.model.response.RegisterPoseLayoutResponse
import org.springframework.stereotype.Component

@Component
class PoseLayoutFacade(
private val txTemplates: TransactionTemplates,
private val poseLayoutService: PoseLayoutService,
private val postLayoutPointService: PoseLayoutPointService,
) {
suspend fun getRecentPoseLayout(user: AuthUser): GetRecentPoseLayoutResponse {
val poseLayout = poseLayoutService.findTop1ByUidOrderByIdDesc(user.uid)
?: return GetRecentPoseLayoutResponse()

val points = postLayoutPointService.findAllByPoseLayoutId(poseLayout.id)
.map { point -> PoseLayoutPointResponse.from(point) }

return GetRecentPoseLayoutResponse(poseLayout.id, points)
}

suspend fun getPoseLayout(user: AuthUser, id: Long): GetPoseLayoutResponse {
val poseLayout = poseLayoutService.findByIdAndUidOrThrow(id, user.uid)

val points = postLayoutPointService.findAllByPoseLayoutId(poseLayout.id)
.map { point -> PoseLayoutPointResponse.from(point) }

return GetPoseLayoutResponse(poseLayout.id, points)
}

suspend fun register(user: AuthUser, request: PoseLayoutRequest): RegisterPoseLayoutResponse {
val createdPoseLayout = txTemplates.writer.executes {
val poseLayout = poseLayoutService.saveSync(PoseLayout(uid = user.uid))

request.points.map { point ->
PoseLayoutPoint(
poseLayoutId = poseLayout.id,
position = point.position,
x = point.x,
y = point.y,
)
}.run { postLayoutPointService.saveAllSync(this) }

poseLayout
}

return RegisterPoseLayoutResponse(createdPoseLayout.id)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.hero.alignlab.domain.pose.application

import com.hero.alignlab.domain.pose.domain.PoseLayoutPoint
import com.hero.alignlab.domain.pose.infrastructure.PoseLayoutPointRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
class PoseLayoutPointService(
private val poseLayoutPointRepository: PoseLayoutPointRepository,
) {
suspend fun findAllByPoseLayoutId(poseLayoutPointId: Long): List<PoseLayoutPoint> {
return withContext(Dispatchers.IO) {
poseLayoutPointRepository.findAllByPoseLayoutId(poseLayoutPointId)
}
}

@Transactional
fun saveAllSync(poseLayoutPoints: List<PoseLayoutPoint>): List<PoseLayoutPoint> {
return poseLayoutPointRepository.saveAll(poseLayoutPoints)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.hero.alignlab.domain.pose.application

import com.hero.alignlab.domain.pose.domain.PoseLayout
import com.hero.alignlab.domain.pose.infrastructure.PoseLayoutRepository
import com.hero.alignlab.exception.ErrorCode
import com.hero.alignlab.exception.NotFoundException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
class PoseLayoutService(
private val poseLayoutRepository: PoseLayoutRepository
) {
suspend fun findTop1ByUidOrderByIdDesc(uid: Long): PoseLayout? {
return withContext(Dispatchers.IO) {
poseLayoutRepository.findTop1ByUidOrderByIdDesc(uid)
}
}

suspend fun findByIdAndUidOrThrow(id: Long, uid: Long): PoseLayout {
return findByIdAndUidOrNull(id, uid) ?: throw NotFoundException(ErrorCode.NOT_FOUND_POSE_LAYOUT_ERROR)
}

suspend fun findByIdAndUidOrNull(id: Long, uid: Long): PoseLayout? {
return withContext(Dispatchers.IO) {
poseLayoutRepository.findByIdAndUid(id, uid)
}
}

@Transactional
fun saveSync(poseLayout: PoseLayout): PoseLayout {
return poseLayoutRepository.save(poseLayout)
}
}
16 changes: 16 additions & 0 deletions src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseLayout.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.hero.alignlab.domain.pose.domain

import com.hero.alignlab.domain.common.domain.BaseEntity
import jakarta.persistence.*

@Entity
@Table(name = "pose_layout")
class PoseLayout(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
val id: Long = -1,

@Column(name = "uid")
val uid: Long,
) : BaseEntity()
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.hero.alignlab.domain.pose.domain

import com.hero.alignlab.domain.pose.domain.vo.PosePosition
import jakarta.persistence.*
import java.math.BigDecimal

@Entity
@Table(name = "pose_layout_point")
class PoseLayoutPoint(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
val id: Long = -1,

@Column(name = "pose_layout_id")
val poseLayoutId: Long,

@Column(name = "position")
@Enumerated(EnumType.STRING)
val position: PosePosition,

@Column(name = "x")
val x: BigDecimal,

@Column(name = "y")
val y: BigDecimal,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.hero.alignlab.domain.pose.infrastructure

import com.hero.alignlab.domain.pose.domain.PoseLayoutPoint
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional

@Transactional(readOnly = true)
@Repository
interface PoseLayoutPointRepository : JpaRepository<PoseLayoutPoint, Long> {
fun findAllByPoseLayoutId(poseLayoutPointId: Long): List<PoseLayoutPoint>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.hero.alignlab.domain.pose.infrastructure

import com.hero.alignlab.domain.pose.domain.PoseLayout
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional

@Transactional(readOnly = true)
@Repository
interface PoseLayoutRepository : JpaRepository<PoseLayout, Long> {
fun findTop1ByUidOrderByIdDesc(uid: Long): PoseLayout?

fun findByIdAndUid(id: Long, uid: Long): PoseLayout?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.hero.alignlab.domain.pose.model.request

import com.hero.alignlab.domain.pose.domain.vo.PosePosition
import java.math.BigDecimal

data class PoseLayoutRequest(
val points: List<PoseLayoutPointRequest>
)

data class PoseLayoutPointRequest(
val y: BigDecimal,
val x: BigDecimal,
val position: PosePosition,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.hero.alignlab.domain.pose.model.response

data class GetPoseLayoutResponse(
/** id가 -1인 경우, 데이터가 없음을 의미 */
val id: Long = -1,
val points: List<PoseLayoutPointResponse> = emptyList(),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.hero.alignlab.domain.pose.model.response

import com.hero.alignlab.domain.pose.domain.PoseLayoutPoint
import com.hero.alignlab.domain.pose.domain.vo.PosePosition
import java.math.BigDecimal

data class GetRecentPoseLayoutResponse(
/** id가 -1인 경우, 데이터가 없음을 의미 */
val id: Long = -1,
val points: List<PoseLayoutPointResponse> = emptyList(),
)

data class PoseLayoutPointResponse(
val position: PosePosition,
val x: BigDecimal,
val y: BigDecimal,
) {
companion object {
fun from(poseLayoutPoint: PoseLayoutPoint): PoseLayoutPointResponse {
return PoseLayoutPointResponse(
position = poseLayoutPoint.position,
x = poseLayoutPoint.x,
y = poseLayoutPoint.y,
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.hero.alignlab.domain.pose.model.response

data class RegisterPoseLayoutResponse(
val id: Long,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.hero.alignlab.domain.pose.resource

import com.hero.alignlab.common.extension.wrapCreated
import com.hero.alignlab.common.extension.wrapOk
import com.hero.alignlab.domain.auth.model.AuthUser
import com.hero.alignlab.domain.pose.application.PoseLayoutFacade
import com.hero.alignlab.domain.pose.model.request.PoseLayoutRequest
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.*

@Tag(name = "Pose Layout API")
@RestController
@RequestMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
class PoseLayoutResource(
private val poseLayoutFacade: PoseLayoutFacade
) {
/**
* - 가장 최근에 생성된 레이아웃 데이터를 조회
* - 만약, 데이터가 없는 경우, 다음과 같이 데이터를 제공
* ```
* {
* "id" : -1,
* "points": []
* }
* ```
*/
@Operation(summary = "가장 최근 포즈 레이아웃 데이터 조회")
@GetMapping("/api/v1/pose-layouts/recent")
suspend fun getRecentPoseLayout(
user: AuthUser,
) = poseLayoutFacade.getRecentPoseLayout(user).wrapOk()

@Operation(summary = "포즈 레이아웃 조회")
@GetMapping("/api/v1/pose-layouts/{id}")
suspend fun getPoseLayout(
user: AuthUser,
@PathVariable id: Long,
) = poseLayoutFacade.getPoseLayout(user, id).wrapOk()

@Operation(summary = "포즈 레이아웃 생성")
@PostMapping("/api/v1/pose-layouts")
suspend fun register(
user: AuthUser,
@RequestBody request: PoseLayoutRequest,
) = poseLayoutFacade.register(user, request).wrapCreated()
}
3 changes: 3 additions & 0 deletions src/main/kotlin/com/hero/alignlab/exception/ErrorCode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,8 @@ enum class ErrorCode(val status: HttpStatus, val description: String) {

/** Pose Notification Error Code */
NOT_FOUND_POSE_NOTIFICATION_ERROR(HttpStatus.NOT_FOUND, "자세 알림 정보를 찾을 수 없습니다."),

/** Pose Layout Error Code */
NOT_FOUND_POSE_LAYOUT_ERROR(HttpStatus.NOT_FOUND, "포즈 레이아웃을 찾을 수 없습니다."),
;
}

0 comments on commit 3b99d67

Please sign in to comment.