Skip to content

Commit 481e7f7

Browse files
authored
Merge pull request #39 from DDD-Community/dev
4차 mvp 릴리즈
2 parents 69b6f28 + b6f4384 commit 481e7f7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+479
-520
lines changed

.github/workflows/healthCheck.yml

+24-24
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
1-
name: "[운영] 헬스체크"
2-
3-
on:
4-
schedule:
5-
- cron: "0 0 */3 * *"
6-
7-
jobs:
8-
healthcheck:
9-
runs-on: ubuntu-latest
10-
steps:
11-
- name: API Health Check
12-
id: health_check
13-
uses: jtalk/url-health-check-action@v3
14-
with:
15-
url: ${{ secrets.BASE_URI_PROD }}
16-
max-attempts: 3
17-
retry-delay: 1s
18-
19-
- name: Discord Webhook Action
20-
if: always()
21-
uses: tsickert/[email protected]
22-
with:
23-
webhook-url: ${{ secrets.WEBHOOK_URL }}
24-
content: ${{ job.status }}
1+
#name: "[운영] 헬스체크"
2+
#
3+
#on:
4+
# schedule:
5+
# - cron: "0 0 */3 * *"
6+
#
7+
#jobs:
8+
# healthcheck:
9+
# runs-on: ubuntu-latest
10+
# steps:
11+
# - name: API Health Check
12+
# id: health_check
13+
# uses: jtalk/url-health-check-action@v3
14+
# with:
15+
# url: ${{ secrets.BASE_URI_PROD }}
16+
# max-attempts: 3
17+
# retry-delay: 1s
18+
#
19+
# - name: Discord Webhook Action
20+
# if: always()
21+
# uses: tsickert/[email protected]
22+
# with:
23+
# webhook-url: ${{ secrets.WEBHOOK_URL }}
24+
# content: ${{ job.status }}

src/main/kotlin/com/ddd/sonnypolabobe/domain/board/controller/BoardController.kt

+27-13
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,19 @@ import com.ddd.sonnypolabobe.domain.board.controller.dto.BoardGetResponse
55
import com.ddd.sonnypolabobe.domain.board.service.BoardService
66
import com.ddd.sonnypolabobe.domain.user.dto.UserDto
77
import com.ddd.sonnypolabobe.global.response.ApplicationResponse
8-
import com.ddd.sonnypolabobe.logger
8+
import com.ddd.sonnypolabobe.global.security.JwtUtil
99
import io.swagger.v3.oas.annotations.Operation
1010
import io.swagger.v3.oas.annotations.tags.Tag
1111
import org.springframework.security.core.context.SecurityContextHolder
12-
import org.springframework.web.bind.annotation.GetMapping
13-
import org.springframework.web.bind.annotation.PathVariable
14-
import org.springframework.web.bind.annotation.PostMapping
15-
import org.springframework.web.bind.annotation.RequestBody
16-
import org.springframework.web.bind.annotation.RequestMapping
17-
import org.springframework.web.bind.annotation.RestController
12+
import org.springframework.web.bind.annotation.*
1813
import java.util.UUID
1914

2015
@Tag(name = "Board API", description = "보드 관련 API")
2116
@RestController
2217
@RequestMapping("/api/v1/boards")
2318
class BoardController(
24-
private val boardService: BoardService
19+
private val boardService: BoardService,
20+
private val jwtUtil: JwtUtil
2521
) {
2622
@Operation(
2723
summary = "보드 생성", description = """
@@ -32,23 +28,28 @@ class BoardController(
3228
"""
3329
)
3430
@PostMapping
35-
fun create(@RequestBody request: BoardCreateRequest)
36-
: ApplicationResponse<UUID> {
31+
fun create(@RequestBody request: BoardCreateRequest) : ApplicationResponse<UUID> {
3732
val user =
3833
SecurityContextHolder.getContext().authentication.principal as UserDto.Companion.Res
3934
request.userId = user.id
4035
return ApplicationResponse.ok(this.boardService.create(request))
4136
}
4237

43-
@Tag(name = "1.1.0")
38+
@Tag(name = "1.3.0")
4439
@Operation(
4540
summary = "보드 조회", description = """
4641
보드를 조회합니다.
47-
DTO 필드 수정했습니다. 폴라로이드에 닉네임 필드 추가
42+
DTO 필드 수정했습니다. 스티커 리스트 추가했습니다.
43+
4844
"""
4945
)
5046
@GetMapping("/{id}")
51-
fun get(@PathVariable id: String) = ApplicationResponse.ok(this.boardService.getById(id))
47+
fun get(@PathVariable id: String,
48+
@RequestHeader("Authorization") token: String?
49+
) : ApplicationResponse<List<BoardGetResponse>> {
50+
val user = token?.let { this.jwtUtil.getAuthenticatedMemberFromToken(it) }
51+
return ApplicationResponse.ok(this.boardService.getById(id, user))
52+
}
5253

5354
@Operation(
5455
summary = "보드 누적 생성 수 조회", description = """
@@ -65,4 +66,17 @@ class BoardController(
6566
)
6667
@GetMapping("/create-available")
6768
fun createAvailable() = ApplicationResponse.ok(this.boardService.createAvailable())
69+
70+
@Tag(name = "1.2.0")
71+
@Operation(
72+
summary = "보드명 주제 추천", description = """
73+
보드명 주제를 추천합니다.
74+
"""
75+
)
76+
@GetMapping("/recommend-title")
77+
fun recommendTitle() : ApplicationResponse<List<String>> {
78+
val user =
79+
SecurityContextHolder.getContext().authentication.principal as UserDto.Companion.Res
80+
return ApplicationResponse.ok(this.boardService.recommendTitle(user))
81+
}
6882
}

src/main/kotlin/com/ddd/sonnypolabobe/domain/board/controller/dto/BoardCreateRequest.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import jakarta.validation.constraints.Pattern
66
import java.util.*
77

88
data class BoardCreateRequest(
9-
@Schema(description = "제목", example = "쏘니의 보드")
9+
@field:Schema(description = "제목", example = "쏘니의 보드")
1010
@field:NotBlank
1111
@field:Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*()_+=-])(?=.*[ㄱ-ㅎㅏ-ㅣ가-힣]).{1,20}$", message = "제목은 국문, 영문, 숫자, 특수문자, 띄어쓰기를 포함한 20자 이내여야 합니다.")
1212
val title: String,
13-
@Schema(description = "작성자 아이디", example = "null", required = false)
13+
@field:Schema(description = "작성자 아이디", example = "null", required = false)
1414
var userId: Long? = null
1515
)
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package com.ddd.sonnypolabobe.domain.board.controller.dto
22

3-
import com.ddd.sonnypolabobe.domain.polaroid.controller.dto.PolaroidGetResponse
3+
import com.ddd.sonnypolabobe.domain.polaroid.dto.PolaroidGetResponse
44
import io.swagger.v3.oas.annotations.media.Schema
55

66
data class BoardGetResponse(
7-
@Schema(description = "제목", example = "쏘니의 보드")
7+
@field:Schema(description = "제목", example = "쏘니의 보드")
88
val title: String,
9-
@Schema(description = "작성자", example = "작성자입니다.")
10-
val items: List<PolaroidGetResponse>
9+
@field:Schema(description = "폴라로이드")
10+
val items: List<PolaroidGetResponse>,
11+
@field:Schema(description = "작성자 여부", example = "true")
12+
val isMine : Boolean
1113
)

src/main/kotlin/com/ddd/sonnypolabobe/domain/board/entity/BoardEntity.kt

-15
This file was deleted.

src/main/kotlin/com/ddd/sonnypolabobe/domain/board/my/dto/MyBoardDto.kt

+11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.ddd.sonnypolabobe.domain.board.my.dto
22

33
import com.fasterxml.jackson.annotation.JsonProperty
4+
import io.swagger.v3.oas.annotations.media.Schema
45
import org.springframework.format.annotation.DateTimeFormat
56
import java.time.LocalDateTime
67
import java.util.UUID
@@ -9,26 +10,36 @@ class MyBoardDto {
910
companion object {
1011
data class MBUpdateReq(
1112
@JsonProperty("title")
13+
@field:Schema(description = "제목", example = "쏘니의 보드")
1214
val title: String
1315
)
1416

1517
data class PageListRes(
18+
@field:Schema(description = "보드 아이디", example = "01906259-94b2-74ef-8c13-554385c42943")
1619
val id: UUID,
20+
@field:Schema(description = "제목", example = "쏘니의 보드")
1721
val title: String,
1822
@DateTimeFormat(pattern = "yyyy-MM-dd", iso = DateTimeFormat.ISO.DATE)
23+
@field:Schema(description = "생성일", example = "2021-07-01")
1924
val createdAt: LocalDateTime,
2025
)
2126

2227
data class GetOneRes(
28+
@field:Schema(description = "보드 아이디", example = "01906259-94b2-74ef-8c13-554385c42943")
2329
val id: UUID,
30+
@field:Schema(description = "제목", example = "쏘니의 보드")
2431
val title: String,
2532
@DateTimeFormat(pattern = "yyyy-MM-dd", iso = DateTimeFormat.ISO.DATE)
33+
@field:Schema(description = "생성일", example = "2021-07-01")
2634
val createdAt: LocalDateTime,
35+
@field:Schema(description = "작성자 아이디", example = "null", required = false)
2736
val userId: Long?
2837
)
2938

3039
data class TotalCountRes(
40+
@field:Schema(description = "총 보드 생성 수", example = "100")
3141
val totalCreateCount: Long,
42+
@field:Schema(description = "총 참여자 수", example = "1000")
3243
val totalParticipantCount: Long
3344
)
3445

src/main/kotlin/com/ddd/sonnypolabobe/domain/board/repository/BoardJooqRepository.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ package com.ddd.sonnypolabobe.domain.board.repository
22

33
import com.ddd.sonnypolabobe.domain.board.controller.dto.BoardCreateRequest
44
import com.ddd.sonnypolabobe.domain.board.my.dto.MyBoardDto
5+
import com.ddd.sonnypolabobe.domain.board.repository.vo.BoardGetOneVo
6+
import com.ddd.sonnypolabobe.domain.user.dto.GenderType
57
import com.ddd.sonnypolabobe.jooq.polabo.tables.Board
68
import org.jooq.Record6
79
import org.jooq.Record7
10+
import java.time.LocalDate
811
import java.time.LocalDateTime
912
import java.util.*
1013

1114
interface BoardJooqRepository {
1215
fun insertOne(request: BoardCreateRequest): ByteArray?
13-
fun selectOneById(id: UUID) : Array<out Record7<String?, Long?, String?, String?, LocalDateTime?, Long?, String?>>
16+
fun selectOneById(id: UUID) : List<BoardGetOneVo>
1417
fun selectTotalCount(): Long
1518
fun selectTodayTotalCount(): Long
1619
fun findById(id: UUID): MyBoardDto.Companion.GetOneRes?
@@ -25,4 +28,5 @@ interface BoardJooqRepository {
2528
): List<MyBoardDto.Companion.PageListRes>
2629

2730
fun selectTotalCountByParticipant(userId: Long): Long
31+
fun selectRecommendTitle(userBirth: LocalDate?, userGender: GenderType): List<String>
2832
}

src/main/kotlin/com/ddd/sonnypolabobe/domain/board/repository/BoardJooqRepositoryImpl.kt

+88-7
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,24 @@ package com.ddd.sonnypolabobe.domain.board.repository
33
import com.ddd.sonnypolabobe.domain.board.controller.dto.BoardCreateRequest
44
import com.ddd.sonnypolabobe.domain.board.controller.dto.BoardGetResponse
55
import com.ddd.sonnypolabobe.domain.board.my.dto.MyBoardDto
6+
import com.ddd.sonnypolabobe.domain.board.repository.vo.BoardGetOneVo
7+
import com.ddd.sonnypolabobe.domain.user.dto.GenderType
68
import com.ddd.sonnypolabobe.global.util.DateConverter
79
import com.ddd.sonnypolabobe.global.util.UuidConverter
810
import com.ddd.sonnypolabobe.global.util.UuidGenerator
11+
import com.ddd.sonnypolabobe.jooq.polabo.enums.UserGender
912
import com.ddd.sonnypolabobe.jooq.polabo.tables.Board
13+
import com.ddd.sonnypolabobe.jooq.polabo.tables.BoardSticker
1014
import com.ddd.sonnypolabobe.jooq.polabo.tables.Polaroid
11-
import org.jooq.DSLContext
12-
import org.jooq.Record6
13-
import org.jooq.Record7
15+
import com.ddd.sonnypolabobe.jooq.polabo.tables.User
16+
import org.jooq.*
1417
import org.jooq.impl.DSL
18+
import org.jooq.impl.DSL.*
1519
import org.springframework.stereotype.Repository
20+
import java.sql.Timestamp
21+
import java.time.LocalDate
1622
import java.time.LocalDateTime
23+
import java.time.temporal.ChronoUnit
1724
import java.util.*
1825

1926
@Repository
@@ -38,18 +45,22 @@ class BoardJooqRepositoryImpl(
3845
return if (result == 1) id else null
3946
}
4047

41-
override fun selectOneById(id: UUID): Array<out Record7<String?, Long?, String?, String?, LocalDateTime?, Long?, String?>> {
48+
override fun selectOneById(id: UUID): List<BoardGetOneVo> {
4249
val jBoard = Board.BOARD
4350
val jPolaroid = Polaroid.POLAROID
51+
4452
return this.dslContext
4553
.select(
54+
jBoard.ID.convertFrom { it?.let{UuidConverter.byteArrayToUUID(it) } },
4655
jBoard.TITLE,
47-
jPolaroid.ID,
56+
jBoard.USER_ID.`as`(BoardGetOneVo::ownerId.name),
57+
jPolaroid.ID.`as`(BoardGetOneVo::polaroidId.name),
4858
jPolaroid.IMAGE_KEY,
4959
jPolaroid.ONE_LINE_MESSAGE,
5060
jPolaroid.CREATED_AT,
5161
jPolaroid.USER_ID,
52-
jPolaroid.NICKNAME
62+
jPolaroid.NICKNAME,
63+
jPolaroid.OPTIONS
5364
)
5465
.from(jBoard)
5566
.leftJoin(jPolaroid).on(
@@ -61,7 +72,7 @@ class BoardJooqRepositoryImpl(
6172
.and(jBoard.ACTIVEYN.eq(1))
6273
)
6374
.orderBy(jPolaroid.CREATED_AT.desc())
64-
.fetchArray()
75+
.fetchInto(BoardGetOneVo::class.java)
6576

6677
}
6778

@@ -216,4 +227,74 @@ class BoardJooqRepositoryImpl(
216227
.fetchOne(0, Long::class.java)
217228
?: 0L
218229
}
230+
231+
override fun selectRecommendTitle(userBirth: LocalDate?, userGender: GenderType): List<String> {
232+
val jBoard = Board.BOARD
233+
val jUser = User.USER
234+
val jPolaroid = Polaroid.POLAROID
235+
// 현재 날짜 기준으로 연령대를 계산하는 로직
236+
var userAgeGroup : String = "20-29세"
237+
if (userBirth != null) {
238+
val age = ChronoUnit.YEARS.between(userBirth, LocalDate.now())
239+
userAgeGroup = if (age < 15) {
240+
"15세 미만"
241+
} else if (age < 20) {
242+
"15-19세"
243+
} else if (age < 30) {
244+
"20-29세"
245+
} else if (age < 40) {
246+
"30-39세"
247+
} else if (age < 50) {
248+
"40-49세"
249+
} else if (age < 60) {
250+
"50-59세"
251+
} else {
252+
"60대 이상"
253+
}
254+
}
255+
256+
// 기준일 (30일 전)
257+
val thirtyDaysAgo = LocalDateTime.now().minusDays(30)
258+
259+
// 쿼리 작성
260+
return this.dslContext.select(jBoard.TITLE)
261+
.from(jBoard)
262+
.join(jUser)
263+
.on(jBoard.USER_ID.eq(jUser.ID))
264+
.leftJoin(
265+
this.dslContext.select(jPolaroid.BOARD_ID, count().`as`("polaroid_count"))
266+
.from(jPolaroid)
267+
.where(jPolaroid.YN.eq(1)
268+
.and(jPolaroid.ACTIVEYN.eq(1)))
269+
.groupBy(jPolaroid.BOARD_ID)
270+
.asTable("sub_query")
271+
)
272+
.on(jBoard.ID.eq(field(name("sub_query", "board_id"), jBoard.ID.dataType)))
273+
.where(jBoard.YN.eq(1)
274+
.and(jBoard.ACTIVEYN.eq(1))
275+
.and(jBoard.CREATED_AT.greaterOrEqual(thirtyDaysAgo))
276+
.and(genderAndAgeGroupMatch(userGender, userAgeGroup))
277+
)
278+
.orderBy(field("sub_query.polaroid_count", Int::class.java).desc(), jBoard.CREATED_AT.desc())
279+
.limit(16)
280+
.fetchInto(String::class.java)
281+
}
282+
283+
// 성별 및 연령대 일치 조건을 위한 메서드
284+
private fun genderAndAgeGroupMatch( userGender : GenderType, userAgeGroup: String?): Condition {
285+
return User.USER.GENDER.eq(UserGender.valueOf(userGender.name))
286+
.or(User.USER.BIRTH_DT.isNotNull().and(ageGroupCondition(userAgeGroup)))
287+
}
288+
289+
// 연령대 계산 로직에 따른 조건을 처리하는 메서드
290+
private fun ageGroupCondition(ageGroup: String?) : Condition{
291+
return `when`(User.USER.BIRTH_DT.ge(LocalDate.now().minusYears(15)), "15세 미만")
292+
.`when`(User.USER.BIRTH_DT.ge(LocalDate.now().minusYears(19)), "15-19세")
293+
.`when`(User.USER.BIRTH_DT.ge(LocalDate.now().minusYears(29)), "20-29세")
294+
.`when`(User.USER.BIRTH_DT.ge(LocalDate.now().minusYears(39)), "30-39세")
295+
.`when`(User.USER.BIRTH_DT.ge(LocalDate.now().minusYears(49)), "40-49세")
296+
.`when`(User.USER.BIRTH_DT.ge(LocalDate.now().minusYears(59)), "50-59세")
297+
.otherwise("60대 이상").eq(ageGroup);
298+
}
299+
219300
}

0 commit comments

Comments
 (0)