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

[🌎 Feature] νƒ€μž„ 라인용 λͺ©ν‘œ 쑰회 API 개발 - Pagination 방식 offset 기반으둜 λ³€κ²½ #243

Merged
merged 18 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
18 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
4f8d56b
refactor: λ―Έμ‚¬μš© μ½”λ“œ 및 의미 μ—†λŠ” 쀑볡 μ½”λ“œ μ‚­μ œ
egg528 Mar 27, 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
Expand Up @@ -8,15 +8,15 @@ 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 +46,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 +89,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
@@ -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 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

μ—¬κΈ° λ³΅λΆ™ν•˜λ‹€κ°€ μ•„λž˜ λ°˜ν™˜μͺ½μ΄λž‘ μ€‘λ³΅μ½”λ“œ λœλ“―~~

Copy link
Member Author

Choose a reason for hiding this comment

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

μ•—!!! γ…‹γ…‹γ…‹γ…‹γ…‹γ…‹
κ°μ‚¬ν•©λ‹ˆλ‹€ πŸ‘

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
Expand Up @@ -10,6 +10,6 @@ data class CheererQueryResult(
val userNickName: String?,
val userImageUrl: String?,
val cheeringAt: LocalDateTime,
) : CursorExtractable<Long> {
) : CursorExtractable {
override fun cursorId(): Long = cheererId
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package io.raemian.storage.db.core.common.pagination

interface CursorExtractable<CursorType> {
fun cursorId(): CursorType
interface CursorExtractable {
fun cursorId(): Long
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ package io.raemian.storage.db.core.common.pagination
import kotlin.math.min

object CursorPaginationTemplate {
fun <CursorType, T : CursorExtractable<CursorType>> execute(
fun <T : CursorExtractable> execute(
id: Long,
cursorId: CursorType,
cursorId: Long,
size: Int,
query: TriFunction<Long, CursorType, Int, List<T>>,
): CursorPaginationResult<CursorType, T> {
query: TriFunction<Long, Long, Int, List<T>>,
): PaginationResult<T> {
val data = query.apply(id, cursorId, size + 1)
val isLast = data.size != size + 1
val nextCursor = if (isLast) null else data[size].cursorId()

return CursorPaginationResult(
return PaginationResult(
data.subList(0, min(data.size, size)),
nextCursor,
isLast,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package io.raemian.storage.db.core.common.pagination

import java.util.function.Function

data class CursorPaginationResult<CursorType, T> internal constructor(
data class PaginationResult<T> internal constructor(
val contents: List<T>,
val nextCursor: CursorType?,
val nextCursor: Long?,
Copy link
Collaborator

Choose a reason for hiding this comment

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

nextκ°€ μ—†λŠ” κ²½μš°λŠ” μ²«νŽ˜μ΄μ§€λ₯Ό λœ»ν•˜λŠ”κ±΄κ°€?

λ§Œμ•½ μ²«νŽ˜μ΄μ§€λΌλ©΄ LONG MAX(MIN) VALUE 을 ν• λ‹Ήν•˜λŠ”κ±΄ μ–΄λ–¨κΉŒ?

μ΅œλŒ€ν•œ null 을 ν—ˆμš©ν•˜μ§€ μ•ŠλŠ” λ°©ν–₯μœΌλ‘œκ°€λŠ”κ²Œ 쒋을것같아~~

null 이 λ“€μ–΄κ°€λŠ” 경우라면 μ–΄μ©”μˆ˜μ—†κ³  γ…Žγ…Ž

Copy link
Member Author

Choose a reason for hiding this comment

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

ν˜„μž¬ pagination μš”μ²­μ˜ κ²°κ³Όκ°€ λ§ˆμ§€λ§‰ νŽ˜μ΄μ§€μΌ 경우(isLast = true) nextCursorλ₯Ό null둜 보내고 μžˆλŠ” μƒν™©μž…λ‹ˆλ‹€!
λ§Œμ•½ null이 μ•„λ‹Œ 값을 λ„£λŠ”λ‹€λ©΄ -1? κ³Ό 같은 κ°’μœΌλ‘œ nextCursorκ°€ μ—†λ‹€λŠ” κ±Έ λ‚˜νƒ€λ‚Ό 수 μžˆμ„ 것 κ°™μŠ΅λ‹ˆλ‹€.
nullμ΄λ‚˜ -1 ν˜Ήμ€ λ‹€λ₯Έ κ°’ 쀑에 ν•˜λ‚˜ μ‚¬μš©ν•˜λ©΄ 쒋을 것 같은데 의견 λΆ€νƒλ“œλ¦½λ‹ˆλ‹€~!

val isLast: Boolean,
) {
fun <R> transform(transformer: Function<T, R>): CursorPaginationResult<CursorType, R> {
return CursorPaginationResult(
fun <R> transform(transformer: Function<T, R>): PaginationResult<R> {
return PaginationResult(
contents.stream().map(transformer).toList(),
nextCursor,
isLast,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import java.time.LocalDateTime
class GoalJdbcQueryRepository(
private val jdbcTemplate: NamedParameterJdbcTemplate,
) {
fun findAllByLifeMapWithCursor(lifeMapId: Long, cursor: LocalDateTime, size: Int): List<GoalQueryResult> {
fun findAllByLifeMapWithOffset(lifeMapId: Long, page: Int, size: Int): List<GoalQueryResult> {
val sql =
"""
SELECT
Expand All @@ -26,14 +26,14 @@ class GoalJdbcQueryRepository(
LEFT OUTER JOIN stickers s ON g.STICKER_ID = s.ID
WHERE 1 = 1
AND g.LIFE_MAP_ID = :lifeMapId
AND g.DEADLINE <= :cursor
ORDER BY g.DEADLINE DESC
LIMIT :size
OFFSET :page
""".trimIndent()

val namedParameter = MapSqlParameterSource()
.addValue("lifeMapId", lifeMapId)
.addValue("cursor", cursor)
.addValue("page", page)
.addValue("size", size)

return jdbcTemplate.query(sql, namedParameter) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import org.springframework.data.repository.query.Param
import java.time.LocalDateTime

interface GoalRepository : JpaRepository<Goal, Long> {

fun countByLifeMapId(lifeMapId: Long): Long

fun findUserByCreatedAtGreaterThanEqual(createdAt: LocalDateTime): List<Goal>

override fun getById(id: Long): Goal =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.raemian.storage.db.core.goal.model

import io.raemian.storage.db.core.common.pagination.CursorExtractable
import java.time.LocalDateTime

data class GoalQueryResult(
Expand All @@ -11,6 +10,4 @@ data class GoalQueryResult(
val stickerUrl: String,
val tag: String,
val createdAt: LocalDateTime,
) : CursorExtractable<LocalDateTime> {
override fun cursorId(): LocalDateTime = deadline
}
)
Loading