Skip to content

Commit

Permalink
Merge branch 'dev' into feat/edit-chat
Browse files Browse the repository at this point in the history
  • Loading branch information
minisundev authored Jun 10, 2024
2 parents 3aa0306 + 712ea7b commit 39ff4bf
Show file tree
Hide file tree
Showing 63 changed files with 1,052 additions and 252 deletions.
2 changes: 1 addition & 1 deletion auth/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ jib {
image = "eclipse-temurin:21-jre"
}
to {
image = "localhost:5000/auth-application"
image = "youdong98/kpring-auth-application"
setAllowInsecureRegistries(true)
tags = setOf("latest")
}
Expand Down
3 changes: 3 additions & 0 deletions chat/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ dependencies {
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")

// non-blocking redis
implementation("org.springframework.boot:spring-boot-starter-data-redis")

// test
testImplementation(project(":test"))
testImplementation("org.springframework.boot:spring-boot-starter-test")
Expand Down
17 changes: 13 additions & 4 deletions chat/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,17 @@ services:
image: mongo:latest
container_name: mongo
ports:
- "27017:27017"
- "27018:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: 58155815
MONGO_INITDB_DATABASE: mongodb
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: testpassword1234
MONGO_INITDB_DATABASE: mongodb

redis:
image: redis:alpine
container_name: redis_link
ports:
- "6379:6379"
environment:
- REDIS_PASSWORD = "testpassword1234"
command: [ "redis-server","--requirepass","${REDIS_PASSWORD}" ]
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package kpring.chat.chatroom.api.v1
import kpring.chat.chatroom.service.ChatRoomService
import kpring.core.auth.client.AuthClient
import kpring.core.chat.chatroom.dto.request.CreateChatRoomRequest
import kpring.core.global.dto.response.ApiResponse
import org.springframework.http.ResponseEntity
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.*
Expand Down Expand Up @@ -34,4 +35,14 @@ class ChatRoomController(
val result = chatRoomService.exitChatRoom(chatRoomId, userId)
return ResponseEntity.ok().body(result)
}

@GetMapping("/chatroom/{chatRoomId}/invite")
fun getChatRoomInvitation(
@PathVariable("chatRoomId") chatRoomId: String,
@RequestHeader("Authorization") token: String,
): ResponseEntity<*> {
val userId = authClient.getTokenInfo(token).data!!.userId
val result = chatRoomService.getChatRoomInvitation(chatRoomId, userId)
return ResponseEntity.ok().body(ApiResponse(data = result))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package kpring.chat.chatroom.repository

import org.springframework.data.redis.core.RedisTemplate
import org.springframework.data.redis.core.ValueOperations
import org.springframework.stereotype.Component
import java.time.Duration

@Component
class InvitationRepository(
private val redisTemplate: RedisTemplate<String, String>,
) {
fun getValue(key: String): String? {
return redisTemplate.opsForValue().get(key)
}

fun setValueAndExpiration(
key: String,
value: String,
expiration: Duration,
) {
val ops: ValueOperations<String, String> = redisTemplate.opsForValue()
ops.set(key, value, expiration)
}

fun getExpiration(key: String): Long {
return redisTemplate.getExpire(key)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import kpring.chat.chatroom.model.ChatRoom
import kpring.chat.chatroom.repository.ChatRoomRepository
import kpring.chat.global.exception.ErrorCode
import kpring.chat.global.exception.GlobalException
import kpring.core.chat.chat.dto.response.InvitationResponse
import kpring.core.chat.chatroom.dto.request.CreateChatRoomRequest
import org.springframework.stereotype.Service

@Service
class ChatRoomService(
private val chatRoomRepository: ChatRoomRepository,
private val invitationService: InvitationService,
) {
fun createChatRoom(
request: CreateChatRoomRequest,
Expand All @@ -30,6 +32,19 @@ class ChatRoomService(
chatRoomRepository.save(chatRoom)
}

fun getChatRoomInvitation(
chatRoomId: String,
userId: String,
): InvitationResponse {
verifyChatRoomAccess(chatRoomId, userId)
var code = invitationService.getInvitation(userId, chatRoomId)
if (code == null) {
code = invitationService.setInvitation(userId, chatRoomId)
}
val encodedCode = invitationService.generateKeyAndCode(userId, chatRoomId, code)
return InvitationResponse(encodedCode)
}

fun verifyChatRoomAccess(
chatRoomId: String,
userId: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package kpring.chat.chatroom.service

import kpring.chat.chatroom.repository.InvitationRepository
import kpring.chat.global.config.ChatRoomProperty
import org.springframework.stereotype.Service
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.util.*

@Service
class InvitationService(
private val invitationRepository: InvitationRepository,
private val chatRoomProperty: ChatRoomProperty,
) {
fun getInvitation(
userId: String,
chatRoomId: String,
): String? {
val key = generateKey(userId, chatRoomId)
return invitationRepository.getValue(key)
}

fun setInvitation(
userId: String,
chatRoomId: String,
): String {
val key = generateKey(userId, chatRoomId)
val value = generateValue()
invitationRepository.setValueAndExpiration(key, value, chatRoomProperty.getExpiration())
return value
}

fun generateKeyAndCode(
userId: String,
chatRoomId: String,
code: String,
): String {
val key = generateKey(userId, chatRoomId)
return generateCode(key, code)
}

private fun generateCode(
key: String,
value: String,
): String {
val combinedString = "$key,$value"
val digest = MessageDigest.getInstance("SHA-256")
val hash = digest.digest(combinedString.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()
}
}
19 changes: 19 additions & 0 deletions chat/src/main/kotlin/kpring/chat/global/config/ChatRoomProperty.kt
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
}
}
16 changes: 16 additions & 0 deletions chat/src/main/kotlin/kpring/chat/global/config/RedisConfig.kt
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
Expand Up @@ -14,4 +14,7 @@ enum class ErrorCode(val httpStatus: Int, val message: String) {
// 404
CHATROOM_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "해당 id로 chatroom을 찾을 수 없습니다"),
CHAT_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "해당 id로 채팅을 찾을 수 없습니다"),

// 500
INVITATION_LINK_SAVE_FAILURE(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Invitation Code가 저장되지 않았습니다"),
}
7 changes: 6 additions & 1 deletion chat/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,16 @@ spring:
port: 6379
password: testpassword1234

redis:
host: localhost
port: 6379
password: testpassword1234

server:
port: 8081

auth:
url: "http://localhost:30000/"
url: "http://localhost:30001/"
url:
server: "http://localhost:8080/"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.mockk.verify
import kpring.chat.chatroom.model.ChatRoom
import kpring.chat.chatroom.repository.ChatRoomRepository
import kpring.chat.chatroom.service.ChatRoomService
import kpring.chat.chatroom.service.InvitationService
import kpring.chat.global.ChatRoomTest
import kpring.chat.global.CommonTest
import kpring.core.chat.chatroom.dto.request.CreateChatRoomRequest
Expand All @@ -16,7 +17,8 @@ import java.util.*
class ChatRoomServiceTest : FunSpec({

val chatRoomRepository = mockk<ChatRoomRepository>()
val chatRoomService = ChatRoomService(chatRoomRepository)
val invitationService = mockk<InvitationService>()
val chatRoomService = ChatRoomService(chatRoomRepository, invitationService)

test("createChatRoom 는 새 ChatRoom을 저장해야 한다") {
// Given
Expand Down
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,
)
1 change: 1 addition & 0 deletions front/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
Chart.lock
Loading

0 comments on commit 39ff4bf

Please sign in to comment.