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

4차 mvp 릴리즈 #39

Merged
merged 11 commits into from
Sep 28, 2024
48 changes: 24 additions & 24 deletions .github/workflows/healthCheck.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
name: "[운영] 헬스체크"

on:
schedule:
- cron: "0 0 */3 * *"

jobs:
healthcheck:
runs-on: ubuntu-latest
steps:
- name: API Health Check
id: health_check
uses: jtalk/url-health-check-action@v3
with:
url: ${{ secrets.BASE_URI_PROD }}
max-attempts: 3
retry-delay: 1s

- name: Discord Webhook Action
if: always()
uses: tsickert/[email protected]
with:
webhook-url: ${{ secrets.WEBHOOK_URL }}
content: ${{ job.status }}
#name: "[운영] 헬스체크"
#
#on:
# schedule:
# - cron: "0 0 */3 * *"
#
#jobs:
# healthcheck:
# runs-on: ubuntu-latest
# steps:
# - name: API Health Check
# id: health_check
# uses: jtalk/url-health-check-action@v3
# with:
# url: ${{ secrets.BASE_URI_PROD }}
# max-attempts: 3
# retry-delay: 1s
#
# - name: Discord Webhook Action
# if: always()
# uses: tsickert/[email protected]
# with:
# webhook-url: ${{ secrets.WEBHOOK_URL }}
# content: ${{ job.status }}
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,19 @@ import com.ddd.sonnypolabobe.domain.board.controller.dto.BoardGetResponse
import com.ddd.sonnypolabobe.domain.board.service.BoardService
import com.ddd.sonnypolabobe.domain.user.dto.UserDto
import com.ddd.sonnypolabobe.global.response.ApplicationResponse
import com.ddd.sonnypolabobe.logger
import com.ddd.sonnypolabobe.global.security.JwtUtil
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.bind.annotation.*
import java.util.UUID

@Tag(name = "Board API", description = "보드 관련 API")
@RestController
@RequestMapping("/api/v1/boards")
class BoardController(
private val boardService: BoardService
private val boardService: BoardService,
private val jwtUtil: JwtUtil
) {
@Operation(
summary = "보드 생성", description = """
Expand All @@ -32,23 +28,28 @@ class BoardController(
"""
)
@PostMapping
fun create(@RequestBody request: BoardCreateRequest)
: ApplicationResponse<UUID> {
fun create(@RequestBody request: BoardCreateRequest) : ApplicationResponse<UUID> {
val user =
SecurityContextHolder.getContext().authentication.principal as UserDto.Companion.Res
request.userId = user.id
return ApplicationResponse.ok(this.boardService.create(request))
}

@Tag(name = "1.1.0")
@Tag(name = "1.3.0")
@Operation(
summary = "보드 조회", description = """
보드를 조회합니다.
DTO 필드 수정했습니다. 폴라로이드에 닉네임 필드 추가
DTO 필드 수정했습니다. 스티커 리스트 추가했습니다.

"""
)
@GetMapping("/{id}")
fun get(@PathVariable id: String) = ApplicationResponse.ok(this.boardService.getById(id))
fun get(@PathVariable id: String,
@RequestHeader("Authorization") token: String?
) : ApplicationResponse<List<BoardGetResponse>> {
val user = token?.let { this.jwtUtil.getAuthenticatedMemberFromToken(it) }
return ApplicationResponse.ok(this.boardService.getById(id, user))
}

@Operation(
summary = "보드 누적 생성 수 조회", description = """
Expand All @@ -65,4 +66,17 @@ class BoardController(
)
@GetMapping("/create-available")
fun createAvailable() = ApplicationResponse.ok(this.boardService.createAvailable())

@Tag(name = "1.2.0")
@Operation(
summary = "보드명 주제 추천", description = """
보드명 주제를 추천합니다.
"""
)
@GetMapping("/recommend-title")
fun recommendTitle() : ApplicationResponse<List<String>> {
val user =
SecurityContextHolder.getContext().authentication.principal as UserDto.Companion.Res
return ApplicationResponse.ok(this.boardService.recommendTitle(user))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import jakarta.validation.constraints.Pattern
import java.util.*

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

import com.ddd.sonnypolabobe.domain.polaroid.controller.dto.PolaroidGetResponse
import com.ddd.sonnypolabobe.domain.polaroid.dto.PolaroidGetResponse
import io.swagger.v3.oas.annotations.media.Schema

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

This file was deleted.

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

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

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

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

data class TotalCountRes(
@field:Schema(description = "총 보드 생성 수", example = "100")
val totalCreateCount: Long,
@field:Schema(description = "총 참여자 수", example = "1000")
val totalParticipantCount: Long
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ package com.ddd.sonnypolabobe.domain.board.repository

import com.ddd.sonnypolabobe.domain.board.controller.dto.BoardCreateRequest
import com.ddd.sonnypolabobe.domain.board.my.dto.MyBoardDto
import com.ddd.sonnypolabobe.domain.board.repository.vo.BoardGetOneVo
import com.ddd.sonnypolabobe.domain.user.dto.GenderType
import com.ddd.sonnypolabobe.jooq.polabo.tables.Board
import org.jooq.Record6
import org.jooq.Record7
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.*

interface BoardJooqRepository {
fun insertOne(request: BoardCreateRequest): ByteArray?
fun selectOneById(id: UUID) : Array<out Record7<String?, Long?, String?, String?, LocalDateTime?, Long?, String?>>
fun selectOneById(id: UUID) : List<BoardGetOneVo>
fun selectTotalCount(): Long
fun selectTodayTotalCount(): Long
fun findById(id: UUID): MyBoardDto.Companion.GetOneRes?
Expand All @@ -25,4 +28,5 @@ interface BoardJooqRepository {
): List<MyBoardDto.Companion.PageListRes>

fun selectTotalCountByParticipant(userId: Long): Long
fun selectRecommendTitle(userBirth: LocalDate?, userGender: GenderType): List<String>
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,24 @@ package com.ddd.sonnypolabobe.domain.board.repository
import com.ddd.sonnypolabobe.domain.board.controller.dto.BoardCreateRequest
import com.ddd.sonnypolabobe.domain.board.controller.dto.BoardGetResponse
import com.ddd.sonnypolabobe.domain.board.my.dto.MyBoardDto
import com.ddd.sonnypolabobe.domain.board.repository.vo.BoardGetOneVo
import com.ddd.sonnypolabobe.domain.user.dto.GenderType
import com.ddd.sonnypolabobe.global.util.DateConverter
import com.ddd.sonnypolabobe.global.util.UuidConverter
import com.ddd.sonnypolabobe.global.util.UuidGenerator
import com.ddd.sonnypolabobe.jooq.polabo.enums.UserGender
import com.ddd.sonnypolabobe.jooq.polabo.tables.Board
import com.ddd.sonnypolabobe.jooq.polabo.tables.BoardSticker
import com.ddd.sonnypolabobe.jooq.polabo.tables.Polaroid
import org.jooq.DSLContext
import org.jooq.Record6
import org.jooq.Record7
import com.ddd.sonnypolabobe.jooq.polabo.tables.User
import org.jooq.*
import org.jooq.impl.DSL
import org.jooq.impl.DSL.*
import org.springframework.stereotype.Repository
import java.sql.Timestamp
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
import java.util.*

@Repository
Expand All @@ -38,18 +45,22 @@ class BoardJooqRepositoryImpl(
return if (result == 1) id else null
}

override fun selectOneById(id: UUID): Array<out Record7<String?, Long?, String?, String?, LocalDateTime?, Long?, String?>> {
override fun selectOneById(id: UUID): List<BoardGetOneVo> {
val jBoard = Board.BOARD
val jPolaroid = Polaroid.POLAROID

return this.dslContext
.select(
jBoard.ID.convertFrom { it?.let{UuidConverter.byteArrayToUUID(it) } },
jBoard.TITLE,
jPolaroid.ID,
jBoard.USER_ID.`as`(BoardGetOneVo::ownerId.name),
jPolaroid.ID.`as`(BoardGetOneVo::polaroidId.name),
jPolaroid.IMAGE_KEY,
jPolaroid.ONE_LINE_MESSAGE,
jPolaroid.CREATED_AT,
jPolaroid.USER_ID,
jPolaroid.NICKNAME
jPolaroid.NICKNAME,
jPolaroid.OPTIONS
)
.from(jBoard)
.leftJoin(jPolaroid).on(
Expand All @@ -61,7 +72,7 @@ class BoardJooqRepositoryImpl(
.and(jBoard.ACTIVEYN.eq(1))
)
.orderBy(jPolaroid.CREATED_AT.desc())
.fetchArray()
.fetchInto(BoardGetOneVo::class.java)

}

Expand Down Expand Up @@ -216,4 +227,74 @@ class BoardJooqRepositoryImpl(
.fetchOne(0, Long::class.java)
?: 0L
}

override fun selectRecommendTitle(userBirth: LocalDate?, userGender: GenderType): List<String> {
val jBoard = Board.BOARD
val jUser = User.USER
val jPolaroid = Polaroid.POLAROID
// 현재 날짜 기준으로 연령대를 계산하는 로직
var userAgeGroup : String = "20-29세"
if (userBirth != null) {
val age = ChronoUnit.YEARS.between(userBirth, LocalDate.now())
userAgeGroup = if (age < 15) {
"15세 미만"
} else if (age < 20) {
"15-19세"
} else if (age < 30) {
"20-29세"
} else if (age < 40) {
"30-39세"
} else if (age < 50) {
"40-49세"
} else if (age < 60) {
"50-59세"
} else {
"60대 이상"
}
}

// 기준일 (30일 전)
val thirtyDaysAgo = LocalDateTime.now().minusDays(30)

// 쿼리 작성
return this.dslContext.select(jBoard.TITLE)
.from(jBoard)
.join(jUser)
.on(jBoard.USER_ID.eq(jUser.ID))
.leftJoin(
this.dslContext.select(jPolaroid.BOARD_ID, count().`as`("polaroid_count"))
.from(jPolaroid)
.where(jPolaroid.YN.eq(1)
.and(jPolaroid.ACTIVEYN.eq(1)))
.groupBy(jPolaroid.BOARD_ID)
.asTable("sub_query")
)
.on(jBoard.ID.eq(field(name("sub_query", "board_id"), jBoard.ID.dataType)))
.where(jBoard.YN.eq(1)
.and(jBoard.ACTIVEYN.eq(1))
.and(jBoard.CREATED_AT.greaterOrEqual(thirtyDaysAgo))
.and(genderAndAgeGroupMatch(userGender, userAgeGroup))
)
.orderBy(field("sub_query.polaroid_count", Int::class.java).desc(), jBoard.CREATED_AT.desc())
.limit(16)
.fetchInto(String::class.java)
}

// 성별 및 연령대 일치 조건을 위한 메서드
private fun genderAndAgeGroupMatch( userGender : GenderType, userAgeGroup: String?): Condition {
return User.USER.GENDER.eq(UserGender.valueOf(userGender.name))
.or(User.USER.BIRTH_DT.isNotNull().and(ageGroupCondition(userAgeGroup)))
}

// 연령대 계산 로직에 따른 조건을 처리하는 메서드
private fun ageGroupCondition(ageGroup: String?) : Condition{
return `when`(User.USER.BIRTH_DT.ge(LocalDate.now().minusYears(15)), "15세 미만")
.`when`(User.USER.BIRTH_DT.ge(LocalDate.now().minusYears(19)), "15-19세")
.`when`(User.USER.BIRTH_DT.ge(LocalDate.now().minusYears(29)), "20-29세")
.`when`(User.USER.BIRTH_DT.ge(LocalDate.now().minusYears(39)), "30-39세")
.`when`(User.USER.BIRTH_DT.ge(LocalDate.now().minusYears(49)), "40-49세")
.`when`(User.USER.BIRTH_DT.ge(LocalDate.now().minusYears(59)), "50-59세")
.otherwise("60대 이상").eq(ageGroup);
}

}
Loading
Loading