Skip to content

Commit bb2efe5

Browse files
committed
feat(be) : controller,service,repository 팀 스타일에 맞게 수정
1 parent 62a2ef6 commit bb2efe5

File tree

7 files changed

+94
-17
lines changed

7 files changed

+94
-17
lines changed

src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatmessage/entity/ChatMessage.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ data class ChatMessage(
2121
val roomId: Long,
2222
@Column(name = "sender_id", nullable = false)
2323
val senderId: Long,
24-
@Column(columnDefinition = "text", nullable = false)
24+
@Column(nullable = false, columnDefinition = "text")
2525
val content: String,
2626
@Column(name = "created_at", nullable = false)
2727
val createdAt: Instant = Instant.now(),

src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatmessage/repository/ChatMessageRepository.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ interface ChatMessageRepository : JpaRepository<ChatMessage, Long> {
1212
roomId: Long,
1313
afterId: Long,
1414
): List<ChatMessage>
15+
16+
fun deleteByRoomId(roomId: Long)
1517
}

src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatmessage/service/ChatMessageService.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ class ChatMessageService(
2323
afterId: Long,
2424
): List<ChatMessage> = msgRepository.findByRoomIdAndIdGreaterThanOrderByIdAsc(roomId, afterId)
2525

26+
@Transactional
27+
fun deleteByRoom(roomId: Long) {
28+
msgRepository.deleteByRoomId(roomId)
29+
}
30+
2631
@Transactional
2732
fun send(
2833
roomId: Long,

src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatroom/controller/ChatRoomController.kt

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package com.back.koreaTravelGuide.domain.userChat.chatroom.controller
22

3+
import com.back.koreaTravelGuide.common.ApiResponse
34
import com.back.koreaTravelGuide.domain.userChat.UserChatSseEvents
45
import com.back.koreaTravelGuide.domain.userChat.chatmessage.service.ChatMessageService
56
import com.back.koreaTravelGuide.domain.userChat.chatroom.service.ChatRoomService
67
import org.springframework.http.MediaType
8+
import org.springframework.http.ResponseEntity
9+
import org.springframework.web.bind.annotation.DeleteMapping
710
import org.springframework.web.bind.annotation.GetMapping
811
import org.springframework.web.bind.annotation.PathVariable
912
import org.springframework.web.bind.annotation.PostMapping
@@ -13,41 +16,68 @@ import org.springframework.web.bind.annotation.RequestParam
1316
import org.springframework.web.bind.annotation.RestController
1417
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
1518

19+
// 컨트롤러는 임시로 강사님 스타일 따라서 통합해놓았음. 추후 리팩토링 예정
1620
@RestController
1721
@RequestMapping("/api/userchat/rooms")
1822
class ChatRoomController(
1923
private val roomSvc: ChatRoomService,
2024
private val msgSvc: ChatMessageService,
2125
private val events: UserChatSseEvents,
2226
) {
23-
// Room API
24-
@PostMapping
25-
fun create(
26-
@RequestBody req: ChatRoomService.CreateRoomReq,
27-
) = roomSvc.create(req)
27+
data class StartChatReq(val guideId: Long, val userId: Long)
28+
29+
data class DeleteChatReq(val userId: Long)
30+
31+
// MVP: 같은 페어는 방 재사용
32+
@PostMapping("/start")
33+
fun startChat(
34+
@RequestBody req: StartChatReq,
35+
): ResponseEntity<ApiResponse<Map<String, Long>>> {
36+
val roomId = roomSvc.exceptOneToOneRoom(req.guideId, req.userId).id!!
37+
return ResponseEntity.ok(ApiResponse(msg = "채팅방 시작", data = mapOf("roomId" to roomId)))
38+
}
39+
40+
@DeleteMapping("/{roomId}")
41+
fun deleteRoom(
42+
@PathVariable roomId: Long,
43+
@RequestBody req: DeleteChatReq,
44+
): ResponseEntity<ApiResponse<Unit>> {
45+
roomSvc.deleteByOwner(roomId, req.userId)
46+
return ResponseEntity.ok(ApiResponse("채팅방 삭제 완료"))
47+
}
2848

2949
@GetMapping("/{roomId}")
3050
fun get(
3151
@PathVariable roomId: Long,
32-
) = roomSvc.get(roomId)
52+
) = ResponseEntity.ok(ApiResponse(msg = "채팅방 조회", data = roomSvc.get(roomId)))
3353

34-
// Message API (룸 하위 리소스)
3554
@GetMapping("/{roomId}/messages")
3655
fun listMessages(
3756
@PathVariable roomId: Long,
3857
@RequestParam(required = false) after: Long?,
3958
@RequestParam(defaultValue = "50") limit: Int,
40-
) = if (after == null) msgSvc.getlistbefore(roomId, limit) else msgSvc.getlistafter(roomId, after)
59+
): ResponseEntity<ApiResponse<Any>> {
60+
val messages =
61+
if (after == null) {
62+
msgSvc.getlistbefore(roomId, limit)
63+
} else {
64+
msgSvc.getlistafter(roomId, after)
65+
}
66+
return ResponseEntity.ok(ApiResponse(msg = "메시지 조회", data = messages))
67+
}
4168

4269
@PostMapping("/{roomId}/messages")
4370
fun sendMessage(
4471
@PathVariable roomId: Long,
4572
@RequestBody req: ChatMessageService.SendMessageReq,
46-
) = msgSvc.send(roomId, req).also { saved ->
73+
): ResponseEntity<ApiResponse<Any>> {
74+
val saved = msgSvc.send(roomId, req)
4775
events.publishNew(roomId, saved.id!!)
76+
return ResponseEntity.status(201).body(ApiResponse(msg = "메시지 전송", data = saved))
4877
}
4978

50-
// SSE 구독도 여기 포함
79+
// SSE는 스트림이여서 ApiResponse로 감싸지 않았음
80+
// WebSocket,Stomp 적용되면 바로 삭제 예정
5181
@GetMapping("/{roomId}/events", produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
5282
fun subscribe(
5383
@PathVariable roomId: Long,

src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatroom/entity/ChatRoom.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ data class ChatRoom(
1919
val id: Long? = null,
2020
@Column(nullable = false)
2121
val title: String,
22-
@Column(name = "owner_id", nullable = false)
23-
val ownerId: Long,
22+
@Column(name = "guide_id", nullable = false)
23+
val guideId: Long,
24+
@Column(name = "user_id", nullable = false)
25+
val userId: Long,
2426
@Column(name = "updated_at", nullable = false)
2527
val updatedAt: Instant = Instant.now(),
2628
@Column(name = "last_message_id")

src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatroom/repository/ChatRoomRepository.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,21 @@ package com.back.koreaTravelGuide.domain.userChat.chatroom.repository
22

33
import com.back.koreaTravelGuide.domain.userChat.chatroom.entity.ChatRoom
44
import org.springframework.data.jpa.repository.JpaRepository
5+
import org.springframework.data.jpa.repository.Query
56
import org.springframework.stereotype.Repository
67

78
@Repository
8-
interface ChatRoomRepository : JpaRepository<ChatRoom, Long>
9+
interface ChatRoomRepository : JpaRepository<ChatRoom, Long> {
10+
// 가이드,유저 방 생성시 중복 생성 방지
11+
@Query(
12+
"""
13+
select r from ChatRoom r
14+
where (r.guideId = :guideId and r.userId = :userId)
15+
or (r.guideId = :userId and r.userId = :guideId)
16+
""",
17+
)
18+
fun findOneToOneRoom(
19+
guideId: Long,
20+
userId: Long,
21+
): ChatRoom?
22+
}

src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatroom/service/ChatRoomService.kt

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,35 @@ import java.time.Instant
1010
class ChatRoomService(
1111
private val roomRepository: ChatRoomRepository,
1212
) {
13-
data class CreateRoomReq(val title: String, val ownerId: Long)
13+
data class CreateRoomReq(val title: String, val guideId: Long, val userId: Long)
1414

1515
@Transactional
16-
fun create(req: CreateRoomReq): ChatRoom =
17-
roomRepository.save(ChatRoom(title = req.title, ownerId = req.ownerId, updatedAt = Instant.now()))
16+
fun exceptOneToOneRoom(
17+
guideId: Long,
18+
userId: Long,
19+
): ChatRoom {
20+
// 1) 기존 방 재사용
21+
roomRepository.findOneToOneRoom(guideId, userId)?.let { return it }
22+
23+
// 2) 없으면 생성 (동시요청은 DB 유니크 인덱스로 가드)
24+
val title = "Guide-$guideId · User-$userId"
25+
return roomRepository.save(
26+
ChatRoom(title = title, guideId = guideId, userId = userId, updatedAt = Instant.now()),
27+
)
28+
}
1829

1930
fun get(roomId: Long): ChatRoom = roomRepository.findById(roomId).orElseThrow { NoSuchElementException("room not found: $roomId") }
31+
32+
@Transactional
33+
fun deleteByOwner(
34+
roomId: Long,
35+
requesterId: Long,
36+
) {
37+
val room = get(roomId)
38+
if (room.userId != requesterId) {
39+
// 예외처리 임시
40+
throw IllegalArgumentException("채팅방 생성자만 삭제할 수 있습니다.")
41+
}
42+
roomRepository.deleteById(roomId)
43+
}
2044
}

0 commit comments

Comments
 (0)