-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Chat] 채팅방에 초대할 때 사용할 코드 생성 API 구현 #149
Changes from 27 commits
7d7cc9b
0f52c3d
9a93d03
edd40ed
66ffbc6
b0b114c
5b70b4b
9826a24
0a934ba
7dfae34
a5a16e1
d67ff43
18b7f84
47038d4
a9c1453
406d11d
a797346
7ecfec9
46e80af
18b7cb6
ac747fb
18c44ef
1854afd
6020df3
2383b40
be2f961
640d3ac
7804e42
0449e6f
df2ecfd
c583e10
dde725d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package kpring.chat.chatroom.repository | ||
|
||
import kpring.chat.global.config.ChatRoomProperty | ||
import org.springframework.data.redis.core.RedisTemplate | ||
import org.springframework.data.redis.core.ValueOperations | ||
import org.springframework.stereotype.Component | ||
import java.nio.charset.StandardCharsets | ||
import java.security.MessageDigest | ||
import java.util.* | ||
|
||
@Component | ||
class InvitationRepository( | ||
private val redisTemplate: RedisTemplate<String, String>, | ||
private val chatRoomProperty: ChatRoomProperty, | ||
) { | ||
fun getInvitationCode( | ||
userId: String, | ||
chatRoomId: String, | ||
): String { | ||
val key = generateKey(userId, chatRoomId) | ||
var value = redisTemplate.opsForValue().get(key) | ||
if (value == null) { | ||
value = setInvitation(key, chatRoomId) | ||
} | ||
return generateCode(key, value) | ||
} | ||
|
||
fun setInvitation( | ||
key: String, | ||
chatRoomId: String, | ||
): String { | ||
val value = generateValue() | ||
val ops: ValueOperations<String, String> = redisTemplate.opsForValue() | ||
ops.set(key, value, chatRoomProperty.getExpiration()) | ||
return value | ||
} | ||
|
||
fun getExpiration( | ||
userId: String, | ||
chatRoomId: String, | ||
): Long { | ||
val key = generateKey(userId, chatRoomId) | ||
val expiration = redisTemplate.getExpire(key) | ||
return expiration | ||
} | ||
|
||
fun generateCode( | ||
key: String, | ||
value: String, | ||
): String { | ||
val combine = "$key,$value" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. combine 동사이기도 하고 무슨 의미인지 직관적으로 파악이 힘든 것 같아요!, key, value의 의미가 무엇인지 파악이 힘들어서 주석으로 무슨 의미인지를 좀 더 설명을 해주면 좋을 것 같아요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네~! 변수명이 가독성이 떨어지는거 같긴 하네요 |
||
val digest = MessageDigest.getInstance("SHA-256") | ||
val hash = digest.digest(combine.toByteArray(StandardCharsets.UTF_8)) | ||
return Base64.getEncoder().encodeToString(hash) | ||
} | ||
|
||
private fun generateKey( | ||
userId: String, | ||
chatRoomId: String, | ||
): String { | ||
return "$userId:$chatRoomId" | ||
} | ||
|
||
private fun generateValue(): String { | ||
return UUID.randomUUID().toString() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package kpring.chat.global.config | ||
|
||
import org.springframework.boot.context.properties.ConfigurationProperties | ||
import org.springframework.context.annotation.Configuration | ||
import java.time.Duration | ||
|
||
@Configuration | ||
@ConfigurationProperties(prefix = "chatroom") | ||
class ChatRoomProperty { | ||
private lateinit var expiration: Duration | ||
|
||
fun getExpiration(): Duration { | ||
return expiration | ||
} | ||
|
||
fun setExpiration(expiration: Duration) { | ||
this.expiration = expiration | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package kpring.chat.global.config | ||
|
||
import org.springframework.context.annotation.Bean | ||
import org.springframework.context.annotation.Configuration | ||
import org.springframework.data.redis.connection.RedisConnectionFactory | ||
import org.springframework.data.redis.core.RedisTemplate | ||
|
||
@Configuration | ||
class RedisConfig { | ||
@Bean | ||
fun redisTemplate(connectionFactory: RedisConnectionFactory): RedisTemplate<*, *> { | ||
val template = RedisTemplate<ByteArray, ByteArray>() | ||
template.connectionFactory = connectionFactory | ||
return template | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,4 +12,7 @@ enum class ErrorCode(val httpStatus: Int, val message: String) { | |
|
||
// 404 | ||
CHATROOM_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "해당 id로 chatroom을 찾을 수 없습니다"), | ||
|
||
// 500 | ||
INVITATION_LINK_SAVE_FAILURE(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Invitation Code가 저장되지 않았습니다"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. client로 노출할 메시지라서 에러 메시지가 너무 상세하지 않은가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋은 지적이네요 다시 생각해볼게요ㅕ |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,12 +11,21 @@ spring: | |
authentication-database: admin | ||
authSource: admin | ||
|
||
redis: | ||
host: localhost | ||
port: 6379 | ||
password: testpassword1234 | ||
|
||
server: | ||
port: 8081 | ||
|
||
auth: | ||
url: "http://localhost:30000/" | ||
url: "http://localhost:30001/" | ||
url: | ||
server: "http://localhost:8080/" | ||
server: "http://localhost:8080/" | ||
|
||
page: | ||
size: 100 | ||
chatroom: | ||
expiration: 1d | ||
baseurl: "http://localhost:8081/" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. baseurl을 사용한 곳이 있나요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지금은 사용하지 않고 있어요 빼겠습니다! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package kpring.chat.chat.api.v1 | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper | ||
import com.ninjasquad.springmockk.MockkBean | ||
import io.kotest.core.spec.style.DescribeSpec | ||
import io.mockk.every | ||
import io.mockk.junit5.MockKExtension | ||
import kpring.chat.chatroom.api.v1.ChatRoomController | ||
import kpring.chat.chatroom.service.ChatRoomService | ||
import kpring.chat.global.ChatRoomTest | ||
import kpring.chat.global.CommonTest | ||
import kpring.core.auth.client.AuthClient | ||
import kpring.core.auth.dto.response.TokenInfo | ||
import kpring.core.auth.enums.TokenType | ||
import kpring.core.chat.chat.dto.response.InvitationResponse | ||
import kpring.core.global.dto.response.ApiResponse | ||
import kpring.test.restdoc.dsl.restDoc | ||
import kpring.test.restdoc.json.JsonDataType | ||
import org.junit.jupiter.api.extension.ExtendWith | ||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest | ||
import org.springframework.restdocs.ManualRestDocumentation | ||
import org.springframework.restdocs.RestDocumentationExtension | ||
import org.springframework.restdocs.operation.preprocess.Preprocessors | ||
import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation | ||
import org.springframework.test.context.junit.jupiter.SpringExtension | ||
import org.springframework.test.web.reactive.server.WebTestClient | ||
import org.springframework.test.web.servlet.client.MockMvcWebTestClient | ||
import org.springframework.web.context.WebApplicationContext | ||
|
||
@WebMvcTest(controllers = [ChatRoomController::class]) | ||
@ExtendWith(RestDocumentationExtension::class) | ||
@ExtendWith(SpringExtension::class) | ||
@ExtendWith(MockKExtension::class) | ||
class ChatRoomControllerTest( | ||
private val om: ObjectMapper, | ||
webContext: WebApplicationContext, | ||
@MockkBean val chatRoomService: ChatRoomService, | ||
@MockkBean val authClient: AuthClient, | ||
) : DescribeSpec({ | ||
|
||
val restDocument = ManualRestDocumentation() | ||
val webTestClient: WebTestClient = | ||
MockMvcWebTestClient.bindToApplicationContext(webContext).configureClient().baseUrl("http://localhost:8081").filter( | ||
WebTestClientRestDocumentation.documentationConfiguration(restDocument).operationPreprocessors() | ||
.withRequestDefaults(Preprocessors.prettyPrint()).withResponseDefaults(Preprocessors.prettyPrint()), | ||
).build() | ||
|
||
beforeSpec { restDocument.beforeTest(this.javaClass, "chat controller") } | ||
|
||
afterSpec { restDocument.afterTest() } | ||
|
||
describe("GET /api/v1/chatroom/{chatRoomId}/invite : getChatRoomInvitation api test") { | ||
|
||
val url = "/api/v1/chatroom/{chatRoomId}/invite" | ||
it("getChatRoomInvitation api test") { | ||
|
||
// Given | ||
val chatRoomId = ChatRoomTest.TEST_ROOM_ID | ||
val userId = CommonTest.TEST_USER_ID | ||
val key = "62e9df6b-13cb-4673-a6fe-8566451b7f15" | ||
val data = InvitationResponse(key) | ||
|
||
every { authClient.getTokenInfo(any()) } returns | ||
ApiResponse( | ||
data = | ||
TokenInfo( | ||
type = TokenType.ACCESS, userId = CommonTest.TEST_USER_ID, | ||
), | ||
) | ||
|
||
every { | ||
chatRoomService.getChatRoomInvitation( | ||
chatRoomId, | ||
userId, | ||
) | ||
} returns data | ||
|
||
// When | ||
val result = webTestClient.get().uri(url, chatRoomId).header("Authorization", "Bearer mock_token").exchange() | ||
|
||
val docs = result.expectStatus().isOk.expectBody().json(om.writeValueAsString(ApiResponse(data = data))) | ||
|
||
// Then | ||
docs.restDoc( | ||
identifier = "getChatRoomInvitation_200", | ||
description = "채팅방 참여코드를 위한 key값을 반환하는 api", | ||
) { | ||
request { | ||
path { | ||
"chatRoomId" mean "채팅방 참여코드를 발급할 채팅방 Id" | ||
} | ||
} | ||
|
||
response { | ||
body { | ||
"data.code" type JsonDataType.Strings mean "참여 코드" | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package kpring.core.chat.chat.dto.response | ||
|
||
data class InvitationResponse( | ||
val code: String, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
초대 저장소가 하는 역할이 무엇인가요? 서비스 layer에 더 어울리는 역할을 수행하는 것 같은 느낌?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
재사용할 수 있는 컴포넌트인가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
findbyId 나 save등만 있는게 아니라서 재사용이 불가능할 것 같아요
chatService랑은 좀 동떨어진 일을 하는 것 같아서 저 repository로 초대/참여 관련 로직을 분리했었는데 생각해보면 다른 Service로 분리하는게 더 나아보이네요