Skip to content

Commit

Permalink
Merge pull request #82 from grida-diary/main
Browse files Browse the repository at this point in the history
Prod
  • Loading branch information
wwan13 authored Jul 29, 2024
2 parents 01a0d7e + 3479566 commit 8a3df79
Show file tree
Hide file tree
Showing 20 changed files with 245 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ package org.grida.presentation.v1.diary
import io.wwan13.wintersecurity.resolve.RequestUserId
import org.grida.api.ApiResponse
import org.grida.api.dto.IdResponse
import org.grida.domain.diary.DiaryScope
import org.grida.domain.diary.DiaryService
import org.grida.presentation.v1.diary.dto.DiaryModifyRequest
import org.grida.presentation.v1.diary.dto.DiaryRequest
import org.grida.presentation.v1.diary.dto.DiaryResponse
import org.grida.presentation.v1.diary.dto.DiaryScopeRequest
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PatchMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
Expand Down Expand Up @@ -38,4 +42,35 @@ class DiaryController(
val response = DiaryResponse.from(diary)
return ApiResponse.success(response)
}

@PatchMapping("/{diaryId}")
fun modifyDiary(
@RequestUserId userId: Long,
@PathVariable diaryId: Long,
@RequestBody request: DiaryModifyRequest
): ApiResponse<IdResponse> {
val modifiedDiaryId = diaryService.modify(
diaryId,
userId,
request.content,
DiaryScope.valueOf(request.scope)
)
val response = IdResponse(modifiedDiaryId)
return ApiResponse.success(response)
}

@PatchMapping("/{diaryId}/scope")
fun modifyDiaryScope(
@RequestUserId userId: Long,
@PathVariable diaryId: Long,
@RequestBody request: DiaryScopeRequest
): ApiResponse<IdResponse> {
val modifiedDiaryId = diaryService.modifyScope(
diaryId,
userId,
DiaryScope.valueOf(request.scope)
)
val response = IdResponse(modifiedDiaryId)
return ApiResponse.success(response)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.grida.presentation.v1.diary.dto

data class DiaryModifyRequest(
val content: String,
val scope: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.grida.presentation.v1.diary.dto

data class DiaryScopeRequest(
val scope: String
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.grida.support.requestlogger

import com.fasterxml.jackson.databind.ObjectMapper
import mu.KotlinLogging
import org.springframework.web.filter.OncePerRequestFilter
import org.springframework.web.util.ContentCachingRequestWrapper
Expand All @@ -10,7 +11,9 @@ import javax.servlet.http.HttpServletResponse

private val log = KotlinLogging.logger {}

class LogFilter : OncePerRequestFilter() {
class LogFilter(
private val objectMapper: ObjectMapper
) : OncePerRequestFilter() {

override fun doFilterInternal(
request: HttpServletRequest,
Expand All @@ -26,7 +29,7 @@ class LogFilter : OncePerRequestFilter() {
val requestElapsed = (requestCompleted - requestOccurred) / 1000.0

val logContext = RequestLogContext.of(requestWrapper, responseWrapper, requestElapsed)
log.info(logContext.toLogMessage())
log.info(logContext.toLogMessage(objectMapper))
responseWrapper.copyBodyToResponse()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package org.grida.support.requestlogger

import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.boot.web.servlet.FilterRegistrationBean
import org.springframework.context.annotation.Bean

class LogFilterRegistrar {

@Bean
fun logFilter(): FilterRegistrationBean<LogFilter> {
fun logFilter(
objectMapper: ObjectMapper
): FilterRegistrationBean<LogFilter> {
val filterRegistration = FilterRegistrationBean<LogFilter>()
filterRegistration.filter = LogFilter()
filterRegistration.filter = LogFilter(objectMapper)
filterRegistration.order = 0
return filterRegistration
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,49 +1,34 @@
package org.grida.support.requestlogger

import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.http.HttpStatus
import org.springframework.web.util.ContentCachingRequestWrapper
import org.springframework.web.util.ContentCachingResponseWrapper

fun ContentCachingRequestWrapper.getRequestHeaders(): String {
val headers = this.headerNames.toList().map { "\"$it\":\"${this.getHeader(it)}\"" }
val joinedHeaders = headers.joinToString(", ")
return "[$joinedHeaders]"
}

fun ContentCachingRequestWrapper.getRequestParams(): String {
val params = this.parameterNames.toList().map { "\"$it\":\"${this.getParameter(it)}\"" }
val joinedHeaders = params.joinToString(", ")
return "[$joinedHeaders]"
}

fun String.trimSpaceAndNewLine(): String {
return this.replace("\\n".toRegex(), "").replace(" ", "")
}
import java.util.Enumeration

data class RequestLogContext(
val method: String,
val uri: String,
val status: HttpStatus,
val elapsed: Double,
val requestHeaders: String,
val requestParams: String,
val requestHeaders: Map<String, String>,
val requestParams: Map<String, String>,
val requestBody: String,
val responseBody: String
) {

fun toLogMessage(): String {
fun toLogMessage(objectMapper: ObjectMapper): String {
return """
|
|$method $uri - $status ($elapsed s)
|>> REQUEST HEADERS : $requestHeaders
|>> REQUEST PARAMS : $requestParams
|>> REQUEST BODY : $requestBody
|>> RESPONSE BODY : $responseBody
|>> REQUEST BODY : ${objectMapper.readTree(requestBody.ifBlank { "{}" })}
|>> RESPONSE BODY : ${objectMapper.readTree(responseBody)}
""".trimMargin()
}

companion object {

fun of(
request: ContentCachingRequestWrapper,
response: ContentCachingResponseWrapper,
Expand All @@ -54,11 +39,22 @@ data class RequestLogContext(
uri = request.requestURI,
status = HttpStatus.valueOf(response.status),
elapsed = elapsed,
requestHeaders = request.getRequestHeaders(),
requestParams = request.getRequestParams(),
requestBody = String(request.contentAsByteArray).trimSpaceAndNewLine(),
responseBody = String(response.contentAsByteArray).trimSpaceAndNewLine(),
requestHeaders = extractAsMap(request, request.headerNames),
requestParams = extractAsMap(request, request.parameterNames),
requestBody = String(request.contentAsByteArray),
responseBody = String(response.contentAsByteArray),
)
}

private fun extractAsMap(
request: ContentCachingRequestWrapper,
names: Enumeration<String>
): Map<String, String> {
val result = mutableMapOf<String, String>()
names.asIterator().forEach {
result.put(it, request.getHeader(it))
}
return result
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import org.grida.domain.diary.Diary
import org.grida.domain.diary.DiaryScope
import org.grida.domain.diary.DiaryService
import org.grida.presentation.v1.diary.DiaryController
import org.grida.presentation.v1.diary.dto.DiaryModifyRequest
import org.grida.presentation.v1.diary.dto.DiaryRequest
import org.grida.presentation.v1.diary.dto.DiaryScopeRequest
import org.junit.jupiter.api.Test
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import java.time.LocalDate
Expand Down Expand Up @@ -90,4 +92,60 @@ class DiaryApiDocsTest(
)
}
}

@Test
fun `일기 수정 API`() {
every { diaryService.modify(any(), any(), any(), any()) } returns 1L

val api = api.patch("/api/v1/diary/{diaryId}", 1L) {
withBearerToken()
requestBody(
DiaryModifyRequest(
content = "수정할 일기 콘텐츠",
scope = "PUBLIC"
)
)
}

documentFor(api, "modify-diary") {
summary("일기 생성 API")
requestHeaders(
"Authorization" whichMeans "인증 토큰"
)
requestFields(
"content" isTypeOf STRING whichMeans "수정할 일기 콘텐츠",
"scope" isTypeOf ENUM(DiaryScope::class) whichMeans "일기 공개 범위"
)
responseFields(
"data.id" isTypeOf NUMBER whichMeans "수정돤 일기 ID"
)
}
}

@Test
fun `일기 공개 범위 수정 API`() {
every { diaryService.modifyScope(any(), any(), any()) } returns 1L

val api = api.patch("/api/v1/diary/{diaryId}/scope", 1L) {
withBearerToken()
requestBody(
DiaryScopeRequest(
scope = "PUBLIC"
)
)
}

documentFor(api, "modify-diary") {
summary("일기 생성 API")
requestHeaders(
"Authorization" whichMeans "인증 토큰"
)
requestFields(
"scope" isTypeOf ENUM(DiaryScope::class) whichMeans "일기 공개 범위"
)
responseFields(
"data.id" isTypeOf NUMBER whichMeans "수정돤 일기 ID"
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package org.grida.domain.diary

import org.grida.domain.user.UserReader
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional

@Component
class DiaryAppender(
private val diaryRepository: DiaryRepository,
private val userReader: UserReader
) {

@Transactional
fun append(
diary: Diary
): Long {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.grida.domain.diary

import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional

@Component
class DiaryModifier(
private val diaryRepository: DiaryRepository,
private val diaryReader: DiaryReader,
private val diaryValidator: DiaryValidator
) {

@Transactional
fun modify(
diaryId: Long,
userId: Long,
content: String,
scope: DiaryScope
) {
val diary = diaryReader.read(diaryId)
diaryValidator.validateIsOwner(diary, userId)

diaryRepository.updateContent(diaryId, content)
diaryRepository.updateScope(diaryId, scope)
}

@Transactional
fun modifyScope(
diaryId: Long,
userId: Long,
scope: DiaryScope
) {
val diary = diaryReader.read(diaryId)
diaryValidator.validateIsOwner(diary, userId)

diaryRepository.updateScope(diaryId, scope)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class DiaryReader(
) {

@Transactional(readOnly = true)
fun read(diaryId: Long, userId: Long): Diary {
fun read(diaryId: Long): Diary {
return diaryRepository.findById(diaryId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ interface DiaryRepository {
fun findById(id: Long): Diary

fun existsByUserIdAndTargetDate(userId: Long, targetDate: LocalDate): Boolean

fun updateContent(diaryId: Long, content: String): Long

fun updateScope(diaryId: Long, scope: DiaryScope): Long
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.springframework.stereotype.Service
class DiaryService(
private val diaryAppender: DiaryAppender,
private val diaryReader: DiaryReader,
private val diaryModifier: DiaryModifier,
private val diaryValidator: DiaryValidator
) {

Expand All @@ -16,10 +17,32 @@ class DiaryService(
return diaryAppender.append(diary)
}

fun readDiary(diaryId: Long, userId: Long): Diary {
val diary = diaryReader.read(diaryId, userId)
fun readDiary(
diaryId: Long,
userId: Long
): Diary {
val diary = diaryReader.read(diaryId)
diaryValidator.validateCanAccess(diary, userId)

return diary
}

fun modify(
diaryId: Long,
userId: Long,
content: String,
scope: DiaryScope
): Long {
diaryModifier.modify(diaryId, userId, content, scope)
return diaryId
}

fun modifyScope(
diaryId: Long,
userId: Long,
scope: DiaryScope
): Long {
diaryModifier.modifyScope(diaryId, userId, scope)
return diaryId
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class DiaryValidator(
throw GridaException(AccessFailed)
}

private fun validateIsOwner(diary: Diary, userId: Long) {
fun validateIsOwner(diary: Diary, userId: Long) {
if (!diary.isOwner(userId)) {
throw GridaException(AccessFailed)
}
Expand Down
Loading

0 comments on commit 8a3df79

Please sign in to comment.