Skip to content
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

[🚌 Issue] dev api 로그인 요청 위치에 따라 redirect url 변환 #245

Merged
merged 25 commits into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b99367c
delete: 유저 정보를 활용한 타임라인 조회 API 삭제
egg528 Mar 24, 2024
4249f81
refactor: PaginationResult.transform() 사용 방식 lamda 형태로 통일
egg528 Mar 24, 2024
854f4a2
refactor: 메서드 로직에 따라 get, find 명칭 구분
egg528 Mar 24, 2024
23aa5f9
refactor: lamda body 내 생략 가능한 parameter 생략
egg528 Mar 24, 2024
1c7d46f
refactor: 메서드명 내 부정확한 단어 제거(findGoalTimelineCountMap -> findGoalCount…
egg528 Mar 24, 2024
74b372b
refactor: associateWith 메서드 활용
egg528 Mar 24, 2024
54c8b81
refactor: PaginationResult total값 Long으로 다시 변경
egg528 Mar 24, 2024
ef9f69d
Merge branch 'develop' of github.com:depromeet/amazing3-be into featu…
egg528 Mar 26, 2024
30b557b
fix: 목표 피드 조회 API deadline 역순으로 정렬 조회
egg528 Mar 26, 2024
89264c7
Merge branch 'develop' of github.com:depromeet/amazing3-be into featu…
egg528 Mar 26, 2024
5425558
chore: apply lint
egg528 Mar 26, 2024
48e2709
fix: goals 테이블의 index 수정
egg528 Mar 26, 2024
a9135a1
refactor: pagination cusorId type Any -> Generic으로 변경
egg528 Mar 26, 2024
63bb7e9
Merge branch 'develop' of github.com:depromeet/amazing3-be into featu…
egg528 Mar 27, 2024
4c110bc
fix: 타임 라인 목표 조회 API paging 처리 방식 offset으로 변경
egg528 Mar 27, 2024
bb151c4
refactor: cursor 기반 pagination 공통 모듈 cursor type long으로 통일
egg528 Mar 27, 2024
0a00a11
refactor: pagination 공통 모듈 cursor와 offset 구분
egg528 Mar 27, 2024
c94c5b5
feat(#244): dev api 로그인 요청 위치에 따라 redirect url 변환
egg528 Mar 27, 2024
e97bc4e
chore: apply lint
egg528 Mar 27, 2024
583201f
fix: @Value(spring.profiles.active) default값 추가
egg528 Mar 27, 2024
e307c2d
refactor: 코드 리뷰 반영(#245)
egg528 Mar 28, 2024
1073e18
chore: encrypt하던 데이터 삭제
egg528 Mar 28, 2024
39ce4cc
chore: resolve conflict
egg528 Mar 28, 2024
128357d
chore: encrypted test properties 삭제
egg528 Mar 28, 2024
e27bb5b
chore: referer 저장/조회 logging 추가
egg528 Mar 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import io.raemian.api.cheer.model.CheererResult
import io.raemian.api.cheer.model.CheeringCountResult
import io.raemian.api.cheer.service.CheeringService
import io.raemian.api.support.response.ApiResponse
import io.raemian.api.support.response.PaginationResult
import io.raemian.api.support.response.CursorPaginationResult
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
Expand All @@ -25,7 +25,7 @@ class CheeringController(
fun findCheeringSquad(
@PathVariable("lifeMapId") lifeMapId: Long,
request: CheeringSquadPageRequest,
): ResponseEntity<ApiResponse<PaginationResult<Long, CheererResult>>> =
): ResponseEntity<ApiResponse<CursorPaginationResult<CheererResult>>> =
ResponseEntity.ok().body(ApiResponse.success(cheeringService.findCheeringSquad(lifeMapId, request)))

@GetMapping("/count/{userName}")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.raemian.api.support.limiter
package io.raemian.api.cheer.service

import com.github.benmanes.caffeine.cache.Caffeine
import org.springframework.stereotype.Component
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@ import io.raemian.api.cheer.model.CheeringCountResult
import io.raemian.api.event.model.CheeredEvent
import io.raemian.api.support.exception.CoreApiException
import io.raemian.api.support.exception.ErrorInfo
import io.raemian.api.support.limiter.CheeringLimiter
import io.raemian.api.support.response.PaginationResult
import io.raemian.api.support.response.CursorPaginationResult
import io.raemian.storage.db.core.cheer.CheerJdbcQueryRepository
import io.raemian.storage.db.core.cheer.Cheerer
import io.raemian.storage.db.core.cheer.CheererRepository
import io.raemian.storage.db.core.cheer.Cheering
import io.raemian.storage.db.core.cheer.CheeringRepository
import io.raemian.storage.db.core.cheer.model.CheererQueryResult
import io.raemian.storage.db.core.common.pagination.CursorPaginationResult
import io.raemian.storage.db.core.common.pagination.CursorPaginationTemplate
import io.raemian.storage.db.core.common.pagination.PaginationResult
import io.raemian.storage.db.core.lifemap.LifeMapRepository
import io.raemian.storage.db.core.user.UserRepository
import org.springframework.context.ApplicationEventPublisher
Expand Down Expand Up @@ -46,15 +45,15 @@ class CheeringService(
}

@Transactional(readOnly = true)
fun findCheeringSquad(lifeMapId: Long, request: CheeringSquadPageRequest): PaginationResult<Long, CheererResult> {
fun findCheeringSquad(lifeMapId: Long, request: CheeringSquadPageRequest): CursorPaginationResult<CheererResult> {
val cheering = cheeringRepository.findByLifeMapId(lifeMapId)
?: Cheering(0, lifeMapId)

val cheeringSquad = findCheeringSquadWithCursor(lifeMapId, request)

val filteredCheeringSquad = cheeringSquad.transform { CheererResult.from(it) }

return PaginationResult.from(cheering.count, filteredCheeringSquad)
return CursorPaginationResult.from(cheering.count, filteredCheeringSquad)
}

@Transactional(readOnly = true)
Expand Down Expand Up @@ -89,7 +88,7 @@ class CheeringService(
)
}

private fun findCheeringSquadWithCursor(lifeMapId: Long, request: CheeringSquadPageRequest): CursorPaginationResult<Long, CheererQueryResult> {
private fun findCheeringSquadWithCursor(lifeMapId: Long, request: CheeringSquadPageRequest): PaginationResult<CheererQueryResult> {
return CursorPaginationTemplate.execute(lifeMapId, request.cursor ?: Long.MAX_VALUE, request.size) {
id, cursor, size ->
cheererJdbcQueryRepository.findAllByLifeMapWithCursor(id, cursor, size)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import io.raemian.api.auth.converter.TokenRequestEntityConverter
import io.raemian.api.auth.model.CurrentUser
import io.raemian.api.auth.service.OAuth2UserService
import io.raemian.api.support.constant.WebSecurityConstant
import io.raemian.api.support.security.LoginRedirector
import io.raemian.api.support.security.StateOAuth2AuthorizationRequestRepository
import io.raemian.api.support.security.TokenProvider
import jakarta.servlet.http.HttpServletResponse
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.security.config.annotation.SecurityConfigurerAdapter
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
Expand All @@ -25,16 +24,14 @@ import org.springframework.security.web.DefaultSecurityFilterChain
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.web.filter.CorsFilter
import java.nio.charset.StandardCharsets

@Configuration
@EnableWebSecurity
class WebSecurityConfig(
private val corsFilter: CorsFilter,
private val tokenProvider: TokenProvider,
private val oAuth2UserService: OAuth2UserService,
@Value("\${spring.profiles.active:local}")
private val profile: String,
private val loginRedirector: LoginRedirector,
private val tokenRequestEntityConverter: TokenRequestEntityConverter,
private val httpCookieOAuth2AuthorizationRequestRepository: StateOAuth2AuthorizationRequestRepository,
) : SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>() {
Expand Down Expand Up @@ -66,23 +63,9 @@ class WebSecurityConfig(
it.authorizationEndpoint { it.authorizationRequestRepository(httpCookieOAuth2AuthorizationRequestRepository) }
it.successHandler { request, response, authentication ->
val user = authentication.principal as CurrentUser
response.contentType = MediaType.APPLICATION_JSON_VALUE
response.characterEncoding = StandardCharsets.UTF_8.name()

val tokenDTO = tokenProvider.generateTokenDto(user)
response.setHeader("x-token", tokenDTO.accessToken)
log.info("x-token access ${tokenDTO.accessToken}")

// TODO edit redirect url
val referer = request.getHeader("referer")
val redirectUrl =
if (profile == "live") {
"https://bandiboodi.com/oauth2/token"
} else {
"http://localhost:3000/oauth2/token"
}

response.sendRedirect("$redirectUrl?token=${tokenDTO.accessToken}&refresh=${tokenDTO.refreshToken}")
val token = tokenProvider.generateTokenDto(user)
val redirectUrl = loginRedirector.getUrl(request.getParameter("state"), token)
response.sendRedirect(redirectUrl)
}
it.failureHandler { request, response, exception ->
log.error("x-token error ${exception.message}")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package io.raemian.api.goal.controller.request

import java.time.LocalDateTime

data class TimelinePageRequest(
val cursor: LocalDateTime?,
val size: Int,
val page: Int = 0,
val size: Int = 20,
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,46 @@ import io.raemian.api.goal.controller.request.TimelinePageRequest
import io.raemian.api.goal.model.GoalTimelineCountSubset
import io.raemian.api.goal.model.GoalTimelinePageResult
import io.raemian.api.lifemap.service.LifeMapService
import io.raemian.api.support.constant.LocalDateTimeConstant
import io.raemian.api.support.response.PaginationResult
import io.raemian.api.support.response.OffsetPaginationResult
import io.raemian.api.task.service.TaskService
import io.raemian.storage.db.core.common.pagination.CursorPaginationResult
import io.raemian.storage.db.core.common.pagination.CursorPaginationTemplate
import io.raemian.storage.db.core.goal.GoalJdbcQueryRepository
import io.raemian.storage.db.core.goal.model.GoalQueryResult
import io.raemian.storage.db.core.goal.GoalRepository
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime

@Service
class GoalQueryService(
private val lifeMapService: LifeMapService,
private val goalJdbcQueryRepository: GoalJdbcQueryRepository,
private val goalRepository: GoalRepository,
private val emojiService: EmojiService,
private val taskService: TaskService,
private val commentService: CommentService,
) {
@Transactional(readOnly = true)
fun findAllByUsernameWithCursor(username: String, request: TimelinePageRequest): PaginationResult<LocalDateTime, GoalTimelinePageResult> {
fun findAllByUsernameWithOffset(username: String, request: TimelinePageRequest): OffsetPaginationResult<GoalTimelinePageResult> {
val lifeMap = lifeMapService.getFirstByUserName(username)
val goals = findAllByLifeMapIdWithCursor(lifeMap.lifeMapId, request)
val goals = goalJdbcQueryRepository.findAllByLifeMapWithOffset(lifeMap.lifeMapId, request.page, request.size)
val total = goalRepository.countByLifeMapId(lifeMap.lifeMapId)

val goalIds = goals.contents.map { it.goalId }
val goalIds = goals.map { it.goalId }

val goalCountMap = findGoalCountMap(goalIds)
val reactedEmojiMap = emojiService.findAllByGoalIds(goalIds, lifeMap.user.id)

return PaginationResult.from(
lifeMap.goals.size,
goals.transform {
goals.map {
GoalTimelinePageResult.from(
goal = it,
counts = goalCountMap[it.goalId],
reactedEmojisResult = reactedEmojiMap[it.goalId],
)
}

return OffsetPaginationResult.of(
request.page,
request.size,
total,
goals.map {
GoalTimelinePageResult.from(
goal = it,
counts = goalCountMap[it.goalId],
Expand All @@ -58,11 +66,4 @@ class GoalQueryService(
)
}
}

private fun findAllByLifeMapIdWithCursor(lifeMapId: Long, request: TimelinePageRequest): CursorPaginationResult<LocalDateTime, GoalQueryResult> {
return CursorPaginationTemplate.execute(lifeMapId, request.cursor ?: LocalDateTimeConstant.MAX, request.size) {
id, cursor, size ->
goalJdbcQueryRepository.findAllByLifeMapWithCursor(id, cursor, size)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ import io.raemian.api.goal.service.GoalQueryService
import io.raemian.api.lifemap.model.LifeMapResponse
import io.raemian.api.lifemap.service.LifeMapService
import io.raemian.api.support.response.ApiResponse
import io.raemian.api.support.response.PaginationResult
import io.raemian.api.support.response.OffsetPaginationResult
import io.swagger.v3.oas.annotations.Operation
import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import java.time.LocalDateTime

@RestController
@RequestMapping("/open/life-map")
Expand Down Expand Up @@ -51,8 +50,8 @@ class OpenLifeMapController(
fun getTimeline(
@PathVariable("username") username: String,
request: TimelinePageRequest,
): ResponseEntity<ApiResponse<PaginationResult<LocalDateTime, GoalTimelinePageResult>>> {
val goalTimeline = goalQueryService.findAllByUsernameWithCursor(username, request)
): ResponseEntity<ApiResponse<OffsetPaginationResult<GoalTimelinePageResult>>> {
val goalTimeline = goalQueryService.findAllByUsernameWithOffset(username, request)
return ResponseEntity.ok(ApiResponse.success(goalTimeline))
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
package io.raemian.api.support.response

import io.raemian.storage.db.core.common.pagination.CursorPaginationResult
import io.raemian.storage.db.core.common.pagination.PaginationResult

data class PaginationResult<CursorType, T>(
data class CursorPaginationResult<T>(
val total: Long,
val contents: List<T>,
val isLast: Boolean,
val nextCursor: CursorType?,
val nextCursor: Long?,
) {
companion object {
fun <CursorType, T> from(total: Long, result: CursorPaginationResult<CursorType, T>): PaginationResult<CursorType, T> {
return PaginationResult(
fun <T> from(total: Long, result: PaginationResult<T>): CursorPaginationResult<T> {
return CursorPaginationResult(
total = total,
contents = result.contents,
isLast = result.isLast,
nextCursor = result.nextCursor,
)
}

fun <CursorType, T> from(total: Int, result: CursorPaginationResult<CursorType, T>): PaginationResult<CursorType, T> {
return PaginationResult(
fun <T> from(total: Int, result: PaginationResult<T>): CursorPaginationResult<T> {
return CursorPaginationResult(
total = total.toLong(),
contents = result.contents,
isLast = result.isLast,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.raemian.api.support.response

data class OffsetPaginationResult<T>(
val total: Long,
val page: Int,
val size: Int,
val contents: List<T>,
) {
companion object {
fun <T> of(page: Int, size: Int, total: Long, contents: List<T>): OffsetPaginationResult<T> {
return OffsetPaginationResult(total, page, size, contents)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.raemian.api.support.security

import io.raemian.api.auth.model.Token
import org.springframework.stereotype.Component

@Component
class LoginRedirector(
val profilePlaceHolder: ProfileHolder,
val redirectUrlHolder: RedirectUrlHolder,
val loginRequestRefererStorage: LoginRequestRefererStorage,
) {
fun getUrl(state: String, token: Token): String {
if (profilePlaceHolder.isLive()) {
return "${redirectUrlHolder.live}?token=${token.accessToken}&refresh=${token.refreshToken}"
}

val referer = loginRequestRefererStorage.get(state)

if (profilePlaceHolder.isDev() && referer.contains("dev-bandiboodi")) {
return "${redirectUrlHolder.dev}?token=${token.accessToken}&refresh=${token.refreshToken}"
}

return "${redirectUrlHolder.local}?token=${token.accessToken}&refresh=${token.refreshToken}"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.raemian.api.support.security

import com.github.benmanes.caffeine.cache.Caffeine
import org.springframework.stereotype.Component
import java.util.concurrent.TimeUnit

@Component
class LoginRequestRefererStorage {
private val timedStorage = Caffeine.newBuilder()
.expireAfterWrite(60L, TimeUnit.SECONDS)
.build<String, String>()

fun put(state: String, referer: String?) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[[ 100% 취향 제안 ]]
저도 자바에 익숙해서 우석님이랑 항상 같은 방식으로 코드를 작성했는데요?
요즘 코틀린 코드들 다른데서 보면서 아래와 같은 코드도 조금 이뻐 보이이기 시작했는데 어떠신가요..

put() = if (referer.isNullOrBlank()) return
else timedStorage.put(state, referer)

혹은.. 확장함수 활용해서 isNotNullOrBlank() 구현한 다음..

fun put() {
  if (referer.isNotNullOrBlank()) 
    timedStorage.put(state, referer)
}

완전 취향이고 지금 얼리 리턴 방식도 너무 좋아요 저도 보통 그렇게 작성해왔던 것 같아요.
어떤지 한번 얘기해보고 싶어서 올려봐요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 얼리 리턴을 위한 조건문은 명시적으로 작성하는 편이 잘 읽힌다고 생각하는 편이였는데요.
진호님이 제시해주신 첫번째 코드 (if문 Expression으로 사용)를 보니 한번 사용해보고 싶다는 생각이 들었습니다 ㅎㅎ
첫번째 방식으로 수정 한번 해보겠습니다!
제안 감사합니다 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@binary-ho
제안 감사합니다 👍

추가 제안 ㅆㄱㄴ

if (referer.isNullOrBlank()) {
return
} else {
timedStorage.put(state, referer)
}
}

fun get(state: String): String {
return timedStorage.getIfPresent(state) ?: ""
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.raemian.api.support.security

import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component

@Component
class ProfileHolder(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@binary-ho
제안 감사합니다 👍

추가 제안 ㅆㄱㄴ

@Value("\${spring.profiles.active:local}")
private val profile: String,
) {
fun isLive(): Boolean {
return ProfileType.fromString(profile) == ProfileType.LIVE
}

fun isDev(): Boolean {
return ProfileType.fromString(profile) == ProfileType.DEV
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.raemian.api.support.security

enum class ProfileType(
Copy link
Member Author

@egg528 egg528 Mar 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ManHyuk
제안 감사합니다 👍

추가 제안 ㅆㄱㄴ

value: String,
) {
LIVE("live"),
DEV("dev"),
LOCAL("local"), ;
companion object {
fun fromString(value: String): ProfileType =
when (value) {
"live" -> LIVE
"dev" -> DEV
else -> LOCAL
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.raemian.api.support.security

import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties(prefix = "redirect-url")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@binary-ho
2개의 제안을 합쳐서 RedirectUrlHolder에 ConfigurationProperties를 적용해봤습니다!
제안 감사합니다 👍

추가 제안 ㅆㄱㄴ

data class RedirectUrlHolder(
val live: String,
val dev: String,
val local: String,
)
Loading