Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2b223a5
add: coupon doamin
vumrra Apr 24, 2025
90ea4ac
add: coupon service
vumrra Apr 24, 2025
24aae0a
add: query, use coupon service imple
vumrra Apr 24, 2025
6cf12f4
add: coupon controller
vumrra Apr 24, 2025
f5a4eff
update: random coupon minus point method
vumrra Apr 24, 2025
30902eb
Merge pull request #177 from team-gogo/feature/coupon
vumrra Apr 24, 2025
de29d6f
fix: coupon join column
vumrra Apr 24, 2025
d1dc3f8
Merge pull request #179 from team-gogo/fix/coupon-join-column
vumrra Apr 24, 2025
8b9785f
fix: coupon query valid
vumrra Apr 24, 2025
45a785e
Merge pull request #180 from team-gogo/fix/coupon-query-api-specs
vumrra Apr 24, 2025
be174a5
add: viewCount field add
Umjiseung Apr 26, 2025
111dff9
add: BoardView domain add
Umjiseung Apr 26, 2025
ebab8b7
add: saveBoardView method apply
Umjiseung Apr 26, 2025
4ea909d
update: create board viewCount 0 init
Umjiseung Apr 26, 2025
bdf561d
add: boardViewEvent publish
Umjiseung Apr 27, 2025
53775ed
update: saveBoardView method transactional
Umjiseung Apr 27, 2025
f8af4ee
add: EnableAsync annotation
Umjiseung Apr 27, 2025
6ceb4c8
add: publishEvent BoardViewEvent logic
Umjiseung Apr 27, 2025
22c10d0
add: AsyncConfig setting
Umjiseung Apr 28, 2025
2f9446f
delete: EnableAsync Annotation
Umjiseung Apr 28, 2025
cef1344
update: boardReader logic to processing within a transaction
Umjiseung Apr 28, 2025
c949c49
delete: getStageBoardInfo in communityProcessor logic
Umjiseung Apr 28, 2025
c350aab
update: asyncExecutor Bean name designation
Umjiseung Apr 28, 2025
ebc7ef8
update: AsyncExecutor designation
Umjiseung Apr 28, 2025
14f936e
Merge pull request #182 from team-gogo/feature/community-community-bo…
Umjiseung Apr 28, 2025
f3a8d85
add: BoardDto into viewCount field
Umjiseung Apr 28, 2025
10bfcaa
Merge pull request #183 from team-gogo/update/community-get-field
Umjiseung Apr 28, 2025
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
1 change: 1 addition & 0 deletions src/main/kotlin/gogo/gogostage/GogoStageApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gogo.gogostage

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.scheduling.annotation.EnableAsync
import org.springframework.scheduling.annotation.EnableScheduling

@EnableScheduling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class BoardProcessor(
isFiltered = false,
createdAt = LocalDateTime.now(),
imageUrl = writeCommunityBoardDto.imageUrl,
viewCount = 0
)

return boardRepository.save(board)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class Board(
@Column(name = "like_count", nullable = false)
var likeCount: Int,

@Column(name = "view_count", nullable = false)
var viewCount: Int,

@Column(name = "is_filtered", nullable = false)
var isFiltered: Boolean,

Expand All @@ -57,4 +60,8 @@ class Board(
isFiltered = true
}

fun plusViewCount() {
viewCount += 1
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package gogo.gogostage.domain.community.boardview.persistence

import gogo.gogostage.domain.community.board.persistence.Board
import jakarta.persistence.*

@Entity
@Table(name = "tbl_board_view")
class BoardView(

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
val id: Long = 0,

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "board_id", nullable = false)
val board: Board,

@Column(name = "student_id", nullable = false)
val studentId: Long,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package gogo.gogostage.domain.community.boardview.persistence

import org.springframework.data.jpa.repository.JpaRepository

interface BoardViewRepository: JpaRepository<BoardView, Long> {

fun existsByBoardIdAndStudentId(boardId: Long, studentId: Long): Boolean
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package gogo.gogostage.domain.community.root.application

import gogo.gogostage.domain.community.board.application.BoardReader
import gogo.gogostage.domain.community.board.persistence.Board
import gogo.gogostage.domain.community.board.persistence.BoardRepository
import gogo.gogostage.domain.community.boardlike.persistence.BoardLike
import gogo.gogostage.domain.community.boardlike.persistence.BoardLikeRepository
import gogo.gogostage.domain.community.boardview.persistence.BoardView
import gogo.gogostage.domain.community.boardview.persistence.BoardViewRepository
import gogo.gogostage.domain.community.comment.persistence.Comment
import gogo.gogostage.domain.community.comment.persistence.CommentRepository
import gogo.gogostage.domain.community.commentlike.persistence.CommentLike
import gogo.gogostage.domain.community.commentlike.persistence.CommentLikeRepository
import gogo.gogostage.domain.community.root.application.dto.*
import gogo.gogostage.domain.community.root.event.BoardViewEvent
import gogo.gogostage.global.error.StageException
import gogo.gogostage.global.internal.student.stub.StudentByIdStub
import org.springframework.data.repository.findByIdOrNull
Expand All @@ -24,6 +28,8 @@ class CommunityProcessor(
private val commentLikeRepository: CommentLikeRepository,
private val commentMapper: CommunityMapper,
private val boardRepository: BoardRepository,
private val boardViewRepository: BoardViewRepository,
private val boardReader: BoardReader
) {

fun likeBoard(studentId: Long, board: Board): LikeResDto {
Expand Down Expand Up @@ -130,4 +136,22 @@ class CommunityProcessor(

commentRepository.save(comment)
}

@Transactional
fun saveBoardView(event: BoardViewEvent) {
val board = boardReader.read(event.boardId)

if (!boardViewRepository.existsByBoardIdAndStudentId(board.id, board.studentId)) {
val newBoardView = BoardView(
board = board,
studentId = board.studentId,
)

boardViewRepository.save(newBoardView)

board.plusViewCount()

boardRepository.save(board)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import gogo.gogostage.domain.community.board.application.BoardProcessor
import gogo.gogostage.domain.community.board.application.BoardReader
import gogo.gogostage.domain.community.root.application.dto.*
import gogo.gogostage.domain.community.root.event.BoardCreateEvent
import gogo.gogostage.domain.community.root.event.BoardViewEvent
import gogo.gogostage.domain.community.root.event.CommentCreateEvent
import gogo.gogostage.domain.community.root.persistence.SortType
import gogo.gogostage.domain.game.persistence.GameCategory
Expand Down Expand Up @@ -55,7 +56,16 @@ class CommunityServiceImpl(
val board = boardReader.read(boardId)
stageValidator.validStage(student, board.community.stage.id)
stageValidator.validProfanityFilter(student, board)
return communityReader.readBoardInfo(isFiltered, board, student)
val response = communityReader.readBoardInfo(isFiltered, board, student)

applicationEventPublisher.publishEvent(
BoardViewEvent(
boardId = boardId,
studentId = student.studentId,
)
)

return response
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ data class BoardDto(
val gameCategory: GameCategory,
val title: String,
val likeCount: Int,
val viewCount: Int,
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
val createdAt: LocalDateTime,
val stageType: StageType,
Expand All @@ -50,6 +51,7 @@ data class GetCommunityBoardInfoResDto(
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
val createdAt: LocalDateTime,
val imageUrl: String?,
val viewCount: Int,
val stage: StageDto,
val commentCount: Int,
val comment: List<CommentDto>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package gogo.gogostage.domain.community.root.event

data class BoardViewEvent(
val boardId: Long,
val studentId: Long
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package gogo.gogostage.domain.community.root.event.handler

import gogo.gogostage.domain.community.root.application.CommunityProcessor
import gogo.gogostage.domain.community.root.event.BoardViewEvent
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Component
import org.springframework.transaction.event.TransactionPhase
import org.springframework.transaction.event.TransactionalEventListener

@Component
class BoardViewEventHandler(
private val communityProcessor: CommunityProcessor,
) {

@Async("asyncExecutor")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
fun eventHandler(event: BoardViewEvent) {
communityProcessor.saveBoardView(event)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class CommunityCustomRepositoryImpl(
createdAt = board.createdAt,
stageType = board.community.stage.type,
commentCount = board.commentCount,
viewCount = board.viewCount,
)
}.toList()

Expand Down Expand Up @@ -124,7 +125,8 @@ class CommunityCustomRepositoryImpl(
stage = stageDto,
commentCount = board.commentCount,
comment = commentDto,
imageUrl = board.imageUrl
imageUrl = board.imageUrl,
viewCount = board.viewCount,
)

return response
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package gogo.gogostage.domain.coupon.result.persistence

import gogo.gogostage.domain.coupon.root.persistence.Coupon
import jakarta.persistence.*
import java.time.LocalDateTime

@Entity
@Table(name = "tbl_coupon_result")
class CouponResult (

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
val id: Long = 0,

@JoinColumn(name = "coupon_id", nullable = false)
@OneToOne(cascade = [(CascadeType.ALL)], fetch = FetchType.LAZY)
val coupon: Coupon,

@Column(name = "student_id", nullable = false)
val studentId: Long,

@Column(name = "is_gain", nullable = false)
val isGain: Boolean,

@Column(name = "before_point", nullable = false)
val beforePoint: Long,

@Column(name = "after_point", nullable = false)
val afterPoint: Long,

@Column(name = "create_at", nullable = false)
val createAt: LocalDateTime = LocalDateTime.now(),

) {

companion object {

fun of(coupon: Coupon, studentId: Long, isGain: Boolean, beforePoint: Long, afterPoint: Long) = CouponResult(
coupon = coupon,
studentId = studentId,
isGain = isGain,
beforePoint = beforePoint,
afterPoint = afterPoint
)

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package gogo.gogostage.domain.coupon.result.persistence

import org.springframework.data.repository.CrudRepository

interface CouponResultRepository: CrudRepository<CouponResult, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package gogo.gogostage.domain.coupon.root.application

import gogo.gogostage.domain.coupon.result.persistence.CouponResult
import gogo.gogostage.domain.coupon.root.application.dto.QueryCouponDto
import gogo.gogostage.domain.coupon.root.application.dto.UseCouponDto
import gogo.gogostage.domain.coupon.root.persistence.Coupon
import gogo.gogostage.domain.coupon.root.persistence.CouponType
import org.springframework.stereotype.Component

@Component
class CouponMapper {

fun map(coupon: Coupon) = QueryCouponDto(
isUsed = coupon.isUsed,
stageId = coupon.stage.id,
stageName = coupon.stage.name,
couponType = coupon.type,
point = if (coupon.type == CouponType.NORMAL) coupon.earnPoint else null,
)

fun mapResult(coupon: Coupon, couponResult: CouponResult) = UseCouponDto(
isGain = couponResult.isGain,
earnedPoint = if (couponResult.isGain) coupon.earnPoint else null,
lostedPoint = if (couponResult.isGain.not()) coupon.lostPoint else null,
beforePoint = couponResult.beforePoint,
afterPoint = couponResult.afterPoint,
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package gogo.gogostage.domain.coupon.root.application

import gogo.gogostage.domain.coupon.result.persistence.CouponResult
import gogo.gogostage.domain.coupon.result.persistence.CouponResultRepository
import gogo.gogostage.domain.coupon.root.persistence.Coupon
import gogo.gogostage.domain.coupon.root.persistence.CouponRepository
import gogo.gogostage.domain.coupon.root.persistence.CouponType
import gogo.gogostage.domain.stage.participant.root.persistence.StageParticipant
import gogo.gogostage.domain.stage.participant.root.persistence.StageParticipantRepository
import gogo.gogostage.global.error.StageException
import gogo.gogostage.global.internal.student.stub.StudentByIdStub
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Component
import kotlin.random.Random

@Component
class CouponProcessor(
private val couponRepository: CouponRepository,
private val couponResultRepository: CouponResultRepository,
private val stageParticipantRepository: StageParticipantRepository
) {

fun use(student: StudentByIdStub, coupon: Coupon): CouponResult {
coupon.used()
couponRepository.save(coupon)

val isGain =
if (coupon.type == CouponType.RANDOM) Random.nextBoolean()
else true

val stageParticipant = getStageParticipant(coupon, student)

val beforePoint = stageParticipant.point
updatePoints(stageParticipant, coupon, isGain)
stageParticipantRepository.save(stageParticipant)
val afterPoint = stageParticipant.point

val couponResult = CouponResult.of(
coupon = coupon,
studentId = student.studentId,
isGain = isGain,
beforePoint = beforePoint,
afterPoint = afterPoint
)
return couponResultRepository.save(couponResult)
}

private fun getStageParticipant(coupon: Coupon, student: StudentByIdStub) =
stageParticipantRepository.queryStageParticipantByStageIdAndStudentId(coupon.stage.id, student.studentId)
?: throw StageException(
"Stage Participant Not Found, Stage Id = ${coupon.stage.id}, Student Id = ${student.studentId}",
HttpStatus.NOT_FOUND.value()
)

private fun updatePoints(participant: StageParticipant, coupon: Coupon, isGain: Boolean) {
if (isGain) {
participant.plusPoint(coupon.earnPoint)
} else {
participant.minusPointMust(coupon.lostPoint!!)
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package gogo.gogostage.domain.coupon.root.application

import gogo.gogostage.domain.coupon.root.persistence.Coupon
import gogo.gogostage.domain.coupon.root.persistence.CouponRepository
import gogo.gogostage.global.error.StageException
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Component

@Component
class CouponReader(
private val couponRepository: CouponRepository
) {

fun read(couponId: String): Coupon =
couponRepository.findByIdOrNull(couponId)
?: throw StageException("쿠폰을 찾을 수 없습니다. coupon id = $couponId", HttpStatus.NOT_FOUND.value())

fun readForUpdate(couponId: String): Coupon =
couponRepository.findByIdOrNullForUpdate(couponId)
?: throw StageException("쿠폰을 찾을 수 없습니다. coupon id = $couponId", HttpStatus.NOT_FOUND.value())

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package gogo.gogostage.domain.coupon.root.application

import gogo.gogostage.domain.coupon.root.application.dto.QueryCouponDto
import gogo.gogostage.domain.coupon.root.application.dto.UseCouponDto

interface CouponService {
fun query(couponId: String): QueryCouponDto
fun use(couponId: String): UseCouponDto
}
Loading
Loading