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

feat: 5차 MVP 기능 개발 #41

Merged
merged 1 commit into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 3 additions & 12 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ plugins {
id("io.spring.dependency-management") version "1.1.5"
kotlin("jvm") version kotlinVersion
kotlin("plugin.spring") version kotlinVersion
// kotlin("plugin.jpa") version kotlinVersion
// kotlin("plugin.allopen") version kotlinVersion
kotlin("kapt") version kotlinVersion
id("nu.studer.jooq") version "9.0"
}
Expand All @@ -23,7 +21,7 @@ java {
}

jooq {
version.set("3.18.10")
version.set("3.19.0")
edition.set(JooqEdition.OSS)

configurations {
Expand Down Expand Up @@ -59,12 +57,6 @@ jooq {
}
}

//allOpen {
// annotation("jakarta.persistence.Entity")
// annotation("jakarta.persistence.MappedSuperclass")
// annotation("jakarta.persistence.Embeddable")
//}

repositories {
mavenCentral()
}
Expand All @@ -73,15 +65,14 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0")
// implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.jetbrains.kotlin:kotlin-reflect")
runtimeOnly("com.mysql:mysql-connector-j")
implementation("org.springframework.boot:spring-boot-starter-jooq")
jooqGenerator("com.mysql:mysql-connector-j")
jooqGenerator("org.jooq:jooq-meta:3.18.10")
jooqGenerator("org.jooq:jooq-codegen:3.18.10")
jooqGenerator("org.jooq:jooq-meta:3.19.0")
jooqGenerator("org.jooq:jooq-codegen:3.19.0")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,18 @@ import org.springframework.security.core.context.SecurityContextHolder
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 jwtUtil: JwtUtil
) {
@Tag(name = "1.4.0")
@Operation(
summary = "보드 생성", description = """
보드를 생성합니다.
userId는 추후 회원가입 기능이 추가될 것을 대비한 것입니다. 지금은 null로 주세요.

userId 데이터는 백에서 채울 것입니다.!
options 필드 추가했습니다. 폴라로이드 옵션과 동일하게 구성했습니다.
key : THEMA, value : 프론트에서 지정한 숫자 혹은 식별값
"""
)
@PostMapping
Expand All @@ -35,11 +34,11 @@ class BoardController(
return ApplicationResponse.ok(this.boardService.create(request))
}

@Tag(name = "1.3.0")
@Tag(name = "1.4.0")
@Operation(
summary = "보드 조회", description = """
보드를 조회합니다.
DTO 필드 수정했습니다. 스티커 리스트 추가했습니다.
DTO 필드 수정했습니다. 옵션이 추가되었습니다.

"""
)
Expand All @@ -51,6 +50,7 @@ class BoardController(
return ApplicationResponse.ok(this.boardService.getById(id, user))
}

@Tag(name = "1.0.0")
@Operation(
summary = "보드 누적 생성 수 조회", description = """
보드 누적 생성 수를 조회합니다.
Expand All @@ -59,6 +59,7 @@ class BoardController(
@GetMapping("/total-count")
fun getTotalCount() = ApplicationResponse.ok(this.boardService.getTotalCount())

@Tag(name = "1.0.0")
@Operation(
summary = "오늘 생성 가능한 보드 수 조회", description = """
오늘 생성 가능한 보드 수를 조회합니다.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package com.ddd.sonnypolabobe.domain.board.controller.dto

import com.ddd.sonnypolabobe.domain.polaroid.enumerate.ExtraOption
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Pattern
import java.util.*

data class BoardCreateRequest(
@field:Schema(description = "제목", example = "쏘니의 보드")
@field:NotBlank
@field:Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*()_+=-])(?=.*[ㄱ-ㅎㅏ-ㅣ가-힣]).{1,20}$", message = "제목은 국문, 영문, 숫자, 특수문자, 띄어쓰기를 포함한 20자 이내여야 합니다.")
val title: String,
@field:Schema(description = "작성자 아이디", example = "null", required = false)
var userId: Long? = null
var userId: Long? = null,
@field:Schema(description = "보드 옵션 - key 값으로 THEMA를 주세요. value로는 프론트에서 지정한 숫자 혹은 식별값을 주세요.", example = "{\"THEMA\":\"value3\"}")
val options : Map<ExtraOption, String>
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.ddd.sonnypolabobe.domain.board.controller.dto

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

data class BoardGetResponse(
Expand All @@ -9,5 +10,7 @@ data class BoardGetResponse(
@field:Schema(description = "폴라로이드")
val items: List<PolaroidGetResponse>,
@field:Schema(description = "작성자 여부", example = "true")
val isMine : Boolean
val isMine : Boolean,
@field:Schema(description = "옵션", example = "{\"THEMA\":\"value3\"}")
val options : Map<ExtraOption, String>?
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.*
@RequestMapping("/api/v1/my/boards")
class MyBoardController(private val myBoardService: MyBoardService) {

@Tag(name = "1.0.0")
@Operation(
summary = "내 보드 목록 조회", description = """
내 보드 목록을 조회합니다.
Expand All @@ -30,6 +31,7 @@ class MyBoardController(private val myBoardService: MyBoardService) {
}


@Tag(name = "1.0.0")
@Operation(
summary = "내 보드 이름 수정",
description = """
Expand All @@ -47,6 +49,7 @@ class MyBoardController(private val myBoardService: MyBoardService) {
return ApplicationResponse.ok()
}

@Tag(name = "1.1.0")
@Operation(
summary = "내 보드 삭제",
description = """
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
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
Expand All @@ -10,14 +9,14 @@ 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 com.ddd.sonnypolabobe.jooq.polabo.tables.User
import org.jooq.*
import com.fasterxml.jackson.databind.ObjectMapper
import org.jooq.Condition
import org.jooq.DSLContext
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
Expand All @@ -37,6 +36,7 @@ class BoardJooqRepositoryImpl(
this.yn = 1
this.activeyn = 1
this.userId = request.userId
this.options = request.options.let { ObjectMapper().writeValueAsString(it) }
}
val result = this.dslContext.insertInto(jBoard)
.set(insertValue)
Expand All @@ -51,16 +51,17 @@ class BoardJooqRepositoryImpl(

return this.dslContext
.select(
jBoard.ID.convertFrom { it?.let{UuidConverter.byteArrayToUUID(it) } },
jBoard.ID.convertFrom { it?.let { UuidConverter.byteArrayToUUID(it) } },
jBoard.TITLE,
jBoard.OPTIONS.`as`(BoardGetOneVo::options.name),
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.OPTIONS
jPolaroid.OPTIONS.`as`(BoardGetOneVo::polaroidOptions.name)
)
.from(jBoard)
.leftJoin(jPolaroid).on(
Expand Down Expand Up @@ -90,11 +91,21 @@ class BoardJooqRepositoryImpl(
.selectCount()
.from(jBoard)
.where(
jBoard.CREATED_AT.greaterOrEqual(DateConverter.convertToKst(LocalDateTime.now().withHour(0).withMinute(0).withSecond(0)))
.and(jBoard.CREATED_AT.lessThan(DateConverter.convertToKst(LocalDateTime.now().withHour(23).withMinute(59).withSecond(59)))
.and(jBoard.YN.eq(1))
.and(jBoard.ACTIVEYN.eq(1))
))
jBoard.CREATED_AT.greaterOrEqual(
DateConverter.convertToKst(
LocalDateTime.now().withHour(0).withMinute(0).withSecond(0)
)
)
.and(
jBoard.CREATED_AT.lessThan(
DateConverter.convertToKst(
LocalDateTime.now().withHour(23).withMinute(59).withSecond(59)
)
)
.and(jBoard.YN.eq(1))
.and(jBoard.ACTIVEYN.eq(1))
)
)
.fetchOne(0, Long::class.java) ?: 0L
}

Expand Down Expand Up @@ -173,9 +184,9 @@ class BoardJooqRepositoryImpl(
val jPolaroid = Polaroid.POLAROID
val boardSubQuery =
this.dslContext.select(jBoard.ID.`as`("id"))
.from(jBoard)
.where(jBoard.USER_ID.eq(userId).and(jBoard.YN.eq(1)).and(jBoard.ACTIVEYN.eq(1)))
.asTable()
.from(jBoard)
.where(jBoard.USER_ID.eq(userId).and(jBoard.YN.eq(1)).and(jBoard.ACTIVEYN.eq(1)))
.asTable()

val data = this.dslContext.select(
jBoard.ID,
Expand All @@ -187,22 +198,24 @@ class BoardJooqRepositoryImpl(
jBoard.ID.eq(jPolaroid.BOARD_ID).and(jPolaroid.USER_ID.eq(userId))
.and(jPolaroid.YN.eq(1)).and(jPolaroid.ACTIVEYN.eq(1))
)
.where(jBoard.ID.notIn(
this.dslContext.select(boardSubQuery.field("id", ByteArray::class.java))
.from(boardSubQuery)
))
.where(
jBoard.ID.notIn(
this.dslContext.select(boardSubQuery.field("id", ByteArray::class.java))
.from(boardSubQuery)
)
)
.orderBy(jBoard.CREATED_AT.desc())
.limit(size)
.offset(page)

return data
.map {
MyBoardDto.Companion.PageListRes(
id = UuidConverter.byteArrayToUUID(it.get("id", ByteArray::class.java)!!),
title = it.get("title", String::class.java)!!,
createdAt = it.get("created_at", LocalDateTime::class.java)!!,
)
}.distinct()
MyBoardDto.Companion.PageListRes(
id = UuidConverter.byteArrayToUUID(it.get("id", ByteArray::class.java)!!),
title = it.get("title", String::class.java)!!,
createdAt = it.get("created_at", LocalDateTime::class.java)!!,
)
}.distinct()
}

override fun selectTotalCountByParticipant(userId: Long): Long {
Expand All @@ -220,10 +233,12 @@ class BoardJooqRepositoryImpl(
jBoard.ID.eq(jPolaroid.BOARD_ID).and(jPolaroid.USER_ID.eq(userId))
.and(jPolaroid.YN.eq(1)).and(jPolaroid.ACTIVEYN.eq(1))
)
.where(jBoard.ID.notIn(
this.dslContext.select(boardSubQuery.field("id", ByteArray::class.java))
.from(boardSubQuery)
))
.where(
jBoard.ID.notIn(
this.dslContext.select(boardSubQuery.field("id", ByteArray::class.java))
.from(boardSubQuery)
)
)
.fetchOne(0, Long::class.java)
?: 0L
}
Expand All @@ -233,7 +248,7 @@ class BoardJooqRepositoryImpl(
val jUser = User.USER
val jPolaroid = Polaroid.POLAROID
// 현재 날짜 기준으로 연령대를 계산하는 로직
var userAgeGroup : String = "20-29세"
var userAgeGroup: String = "20-29세"
if (userBirth != null) {
val age = ChronoUnit.YEARS.between(userBirth, LocalDate.now())
userAgeGroup = if (age < 15) {
Expand Down Expand Up @@ -264,37 +279,43 @@ class BoardJooqRepositoryImpl(
.leftJoin(
this.dslContext.select(jPolaroid.BOARD_ID, count().`as`("polaroid_count"))
.from(jPolaroid)
.where(jPolaroid.YN.eq(1)
.and(jPolaroid.ACTIVEYN.eq(1)))
.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())
.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 {
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{
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);
.`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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import java.util.UUID
data class BoardGetOneVo(
val id: UUID?,
val title: String?,
val options: String?,
val ownerId: Long?,
val polaroidId : Long?,
val imageKey : String?,
val oneLineMessage: String?,
val createdAt: LocalDateTime?,
val userId : Long?,
val nickname: String?,
val options: String?
val polaroidOptions: String?
)
Loading