Skip to content

Commit

Permalink
feat: 포즈 알림 API
Browse files Browse the repository at this point in the history
  • Loading branch information
DongGeon0908 committed Aug 16, 2024
1 parent cd721cf commit f80826c
Show file tree
Hide file tree
Showing 12 changed files with 273 additions and 0 deletions.
13 changes: 13 additions & 0 deletions sql/DDL.sql
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,16 @@ CREATE TABLE `pose_count`
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE INDEX idx__uid__date ON pose_count (uid, date);
CREATE INDEX idx__date ON pose_count (date);

-- 자세 알림
CREATE TABLE `pose_noti`
(
`id` bigint NOT NULL AUTO_INCREMENT,
`uid` bigint NOT NULL COMMENT 'uid',
`is_active` tinyint NOT NULL DEFAULT 0 COMMENT '활성화 여부',
`duration` varchar(32) NOT NULL COMMENT '알림 주기',
`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 uidx__uid ON pose_noti (uid);
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.hero.alignlab.domain.notification.application

import com.hero.alignlab.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.notification.domain.PoseNotification
import com.hero.alignlab.domain.notification.infrastructure.PoseNotificationRepository
import com.hero.alignlab.domain.notification.model.request.PatchPoseNotificationRequest
import com.hero.alignlab.domain.notification.model.request.RegisterPoseNotificationRequest
import com.hero.alignlab.domain.notification.model.response.GetPoseNotificationResponse
import com.hero.alignlab.domain.notification.model.response.PatchPoseNotificationResponse
import com.hero.alignlab.domain.notification.model.response.RegisterPoseNotificationResponse
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

@Service
class PoseNotificationService(
private val poseNotificationRepository: PoseNotificationRepository,
private val txTemplates: TransactionTemplates,
private val application: Application,
) {
suspend fun getNotification(user: AuthUser): GetPoseNotificationResponse? {
val poseNotification = findByUidAndIsActiveOrNull(user.uid, true) ?: return null
return GetPoseNotificationResponse.from(poseNotification)
}

suspend fun findByUidAndIsActiveOrNull(uid: Long, isActive: Boolean): PoseNotification? {
return withContext(Dispatchers.IO) {
poseNotificationRepository.findByUidAndIsActive(uid, isActive)
}
}

suspend fun registerNotification(
user: AuthUser,
request: RegisterPoseNotificationRequest
): RegisterPoseNotificationResponse {
val poseNotification = findByUidOrNull(user.uid)

val registeredPoseNotification = txTemplates.writer.executes {
poseNotification?.apply {
this.isActive = request.isActive
this.duration = request.duration
} ?: PoseNotification(
uid = user.uid,
isActive = request.isActive,
duration = request.duration
).run { poseNotificationRepository.save(this) }
}

return RegisterPoseNotificationResponse.from(registeredPoseNotification)
}

suspend fun findByUidOrNull(uid: Long): PoseNotification? {
return withContext(Dispatchers.IO) {
poseNotificationRepository.findByUid(uid)
}
}

suspend fun findByUidOrThrow(uid: Long): PoseNotification {
return findByUidOrNull(uid) ?: throw NotFoundException(ErrorCode.NOT_FOUND_POSE_NOTIFICATION_ERROR)
}

suspend fun patch(user: AuthUser, request: PatchPoseNotificationRequest): PatchPoseNotificationResponse {
val poseNotification = findByUidOrThrow(user.uid)

val updatedPoseNotification = txTemplates.writer.executes {
poseNotification.apply {
request.isActive?.let { isActive -> this.isActive = isActive }
request.duration?.let { duration -> this.duration = duration }
}.run { poseNotificationRepository.save(this) }
}

return PatchPoseNotificationResponse.from(updatedPoseNotification)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.hero.alignlab.domain.notification.domain

import com.hero.alignlab.domain.common.domain.BaseEntity
import com.hero.alignlab.domain.notification.domain.vo.PoseNotificationDuration
import jakarta.persistence.*

/** 자세 알림 */
@Entity
@Table(name = "pose_noti")
class PoseNotification(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = -1,

@Column(name = "uid")
val uid: Long = -1,

@Column(name = "is_active")
var isActive: Boolean = true,

@Column(name = "duration")
@Enumerated(EnumType.STRING)
var duration: PoseNotificationDuration,
) : BaseEntity()
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.hero.alignlab.domain.notification.domain.vo

enum class PoseNotificationDuration {
/** 즉시 */
IMMEDIATELY,

/** 15분 */
MIN_15,

/** 30분 */
MIN_30,

/** 45분 */
MIN_45,

/** 60분 */
MIN_60,
;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.hero.alignlab.domain.notification.infrastructure

import com.hero.alignlab.domain.notification.domain.PoseNotification
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.transaction.annotation.Transactional

@Transactional(readOnly = true)
interface PoseNotificationRepository : JpaRepository<PoseNotification, Long> {
fun findByUidAndIsActive(uid: Long, isActive: Boolean): PoseNotification?

fun findByUid(uid: Long): PoseNotification?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.hero.alignlab.domain.notification.model.request

import com.hero.alignlab.domain.notification.domain.vo.PoseNotificationDuration

data class PatchPoseNotificationRequest(
val isActive: Boolean?,
val duration: PoseNotificationDuration?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.hero.alignlab.domain.notification.model.request

import com.hero.alignlab.domain.notification.domain.vo.PoseNotificationDuration

data class RegisterPoseNotificationRequest(
val isActive: Boolean,
val duration: PoseNotificationDuration,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.hero.alignlab.domain.notification.model.response

import com.hero.alignlab.domain.notification.domain.PoseNotification
import com.hero.alignlab.domain.notification.domain.vo.PoseNotificationDuration

data class GetPoseNotificationResponse(
val id: Long,
val duration: PoseNotificationDuration,
) {
companion object {
fun from(poseNotification: PoseNotification): GetPoseNotificationResponse {
return GetPoseNotificationResponse(
id = poseNotification.id,
duration = poseNotification.duration
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.hero.alignlab.domain.notification.model.response

import com.hero.alignlab.domain.notification.domain.PoseNotification
import com.hero.alignlab.domain.notification.domain.vo.PoseNotificationDuration

data class PatchPoseNotificationResponse(
val id: Long,
val isActive: Boolean,
val duration: PoseNotificationDuration,
) {
companion object {
fun from(poseNotification: PoseNotification): PatchPoseNotificationResponse {
return PatchPoseNotificationResponse(
id = poseNotification.id,
isActive = poseNotification.isActive,
duration = poseNotification.duration
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.hero.alignlab.domain.notification.model.response

import com.hero.alignlab.domain.notification.domain.PoseNotification
import com.hero.alignlab.domain.notification.domain.vo.PoseNotificationDuration

data class RegisterPoseNotificationResponse(
val id: Long,
val duration: PoseNotificationDuration,
) {
companion object {
fun from(poseNotification: PoseNotification): RegisterPoseNotificationResponse {
return RegisterPoseNotificationResponse(
id = poseNotification.id,
duration = poseNotification.duration
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.hero.alignlab.domain.notification.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.notification.application.PoseNotificationService
import com.hero.alignlab.domain.notification.model.request.PatchPoseNotificationRequest
import com.hero.alignlab.domain.notification.model.request.RegisterPoseNotificationRequest
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 Noti API")
@RestController
@RequestMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
class PoseNotificationResource(
private val poseNotificationService: PoseNotificationService,
) {
/**
* **저장된 자세 알림 정보를 조회**
* - 활성화된 데이터가 있는 경우에만, 데이터를 제공
* - 비활성화된 경우에는, 빈 데이터를 제공
* - duration
* - IMMEDIATELY : 즉시
* - MIN_15 : 15분
* - MIN_30 : 30분
* - MIN_45 : 45분
* - MIN_60 : 60분
*/
@Operation(summary = "자세 알림 조회")
@GetMapping(path = ["/api/v1/pose-notifications"])
suspend fun getPoseNotification(
user: AuthUser,
) = poseNotificationService.getNotification(user).wrapOk()

@Operation(summary = "자세 알림 등록 또는 수정")
@PostMapping(path = ["/api/v1/pose-notifications"])
suspend fun registerPoseNotification(
user: AuthUser,
@RequestBody request: RegisterPoseNotificationRequest,
) = poseNotificationService.registerNotification(user, request).wrapCreated()

/** 변경이 필요한 항목만 Request로 입력 */
@Operation(summary = "자세 알림 활성화, 주기 정보 변경")
@PatchMapping(path = ["/api/v1/pose-notifications/{id}"])
suspend fun patchPoseNotification(
user: AuthUser,
@RequestBody request: PatchPoseNotificationRequest,
) = poseNotificationService.patch(user, request).wrapOk()
}
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 @@ -45,5 +45,8 @@ enum class ErrorCode(val status: HttpStatus, val description: String) {

/** Image Client Error Code */
IMAGE_CLIENT_UPLOAD_ERROR(HttpStatus.BAD_REQUEST, "이미지 업로드 중 오류가 발생했습니다."),

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

0 comments on commit f80826c

Please sign in to comment.