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/122 sort with querydsl #125

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
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
45 changes: 41 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "3.1.0"
id("io.spring.dependency-management") version "1.1.0"
id("org.jlleitschuh.gradle.ktlint") version "11.5.0"
id("jacoco")
kotlin("jvm") version "1.8.21"
kotlin("plugin.spring") version "1.8.21"
kotlin("plugin.jpa") version "1.8.21"
kotlin("plugin.allopen") version "1.6.21"
kotlin("plugin.noarg") version "1.6.21"
id("org.jlleitschuh.gradle.ktlint") version "11.5.0"
id("jacoco")
kotlin("kapt") version "1.8.21"
idea
}

group = "com.example"
version = "0.0.1-SNAPSHOT"
val queryDslVersion = "5.0.0"

java {
sourceCompatibility = JavaVersion.VERSION_17
Expand Down Expand Up @@ -74,6 +77,11 @@ dependencies {
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("io.micrometer:micrometer-registry-prometheus")

implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta")
kapt("com.querydsl:querydsl-apt:${dependencyManagement.importedProperties["querydsl.version"]}:jakarta")
kapt("jakarta.annotation:jakarta.annotation-api")
kapt("jakarta.persistence:jakarta.persistence-api")
}

tasks.withType<KotlinCompile> {
Expand All @@ -88,6 +96,21 @@ tasks.withType<Test> {
finalizedBy(tasks.jacocoTestReport)
}

val querydslDir = "build/generated/source/kapt/main"
idea {
module {
val kaptMain = file(querydslDir)
sourceDirs.add(kaptMain)
generatedSourceDirs.add(kaptMain)
}
}

tasks.named("clean") {
doLast {
file(querydslDir).deleteRecursively()
}
}

allOpen {
annotation("jakarta.persistence.Entity")
annotation("jakarta.persistence.MappedSuperclass")
Expand Down Expand Up @@ -136,7 +159,7 @@ subprojects {
}

tasks.jacocoTestReport {
dependsOn(tasks.test, integrationTest)
dependsOn(tasks.test)

reports {
html.required.set(true)
Expand All @@ -145,6 +168,20 @@ tasks.jacocoTestReport {
html.outputLocation.set(layout.buildDirectory.dir("${rootProject.rootDir}/jacocoReport"))
}

val Qdomains = mutableListOf<String>()

for (qPattern in 'A'..'Z') {
Qdomains.add("**/Q$qPattern*")
}
classDirectories.setFrom(
files(
classDirectories.files.map {
fileTree(it) {
setExcludes(Qdomains)
}
}
)
)
finalizedBy(tasks.jacocoTestCoverageVerification)
}

Expand All @@ -164,7 +201,7 @@ tasks.jacocoTestCoverageVerification {
limit {
counter = "INSTRUCTION"
value = "COVEREDRATIO"
minimum = "0.8".toBigDecimal()
minimum = "0.7".toBigDecimal()
minjun3021 marked this conversation as resolved.
Show resolved Hide resolved
}
limit {
counter = "BRANCH"
Expand Down
29 changes: 22 additions & 7 deletions docs/open-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -188,16 +188,23 @@ paths:
- event-controller
operationId: getEvents
parameters:
- name: name
- name: sort
in: query
required: false
required: true
schema:
type: string
- name: pageable
- name: id
in: query
required: true
required: false
schema:
$ref: '#/components/schemas/Pageable'
type: integer
format: int32
- name: time
in: query
required: false
schema:
type: string
format: date-time
Comment on lines +196 to +207
Copy link
Member

@junha-ahn junha-ahn Oct 19, 2023

Choose a reason for hiding this comment

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

키 이름을 last_access_id, last_access_time 등 으로 변경할 필요가 있어보입니다.

  • 더 좋은 이름 있을 수 있으니 찾아보세요.
  • 당근마켓, 인스타, 트위터, 페이스북 등 참고
    • 무한스크롤 형태니까... Open API Docs 또는 개발자 도구 열고 직접 요청을 확인해보세요

responses:
"200":
description: OK
Expand Down Expand Up @@ -519,10 +526,10 @@ components:
pageSize:
type: integer
format: int32
paged:
type: boolean
unpaged:
type: boolean
paged:
type: boolean
Reservation:
required:
- address
Expand Down Expand Up @@ -653,13 +660,15 @@ components:
type: boolean
EventResponse:
required:
- createdAt
- endDate
- id
- maxAttendees
- name
- reservationEndTime
- reservationStartTime
- startDate
- updatedAt
type: object
properties:
id:
Expand All @@ -682,6 +691,12 @@ components:
maxAttendees:
type: integer
format: int32
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
PageBookmark:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.group4.ticketingservice.Event

import com.group4.ticketingservice.AbstractIntegrationTest
import com.group4.ticketingservice.entity.Event
import com.group4.ticketingservice.repository.EventRepository
import com.group4.ticketingservice.repository.EventRepositorySupport
import com.group4.ticketingservice.repository.UserRepository
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.springframework.beans.factory.annotation.Autowired
import java.time.OffsetDateTime
import java.time.ZoneOffset

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class EventRepositorySupportTest(
@Autowired val eventRepositorySupport: EventRepositorySupport,
@Autowired val eventRepository: EventRepository,
@Autowired val userRepository: UserRepository
) : AbstractIntegrationTest() {

val sampleEvents = mutableListOf<Event>()

@BeforeAll
fun addData() {
eventRepository!!.deleteAll()
for (i in 1..20) {
val event = Event(
name = i.toString(),
startDate = OffsetDateTime.now().plusDays((1..20).random().toLong()),
endDate = OffsetDateTime.now(),
reservationEndTime = OffsetDateTime.of(2024, 6, 30, 0, 0, 0, 0, ZoneOffset.UTC).plusDays((1..20).random().toLong()),
reservationStartTime = OffsetDateTime.now(),
maxAttendees = 10
)
sampleEvents.add(event)
}
eventRepository.saveAll(sampleEvents)
}

@Test
fun `eventRepositorySupportTest_getEvents should return sorted events by deadline`() {
// given
val sortBy = "deadline"
val sortedEvents = sampleEvents.sortedWith(
compareBy(
{ it.reservationEndTime },
{ -it.id!! }
)
)

// when
val firstResponse = eventRepositorySupport.getEvent(sortBy, null, null)
val responseWithLastAccess = eventRepositorySupport.getEvent(sortBy, firstResponse[9].id, firstResponse[9].reservationEndTime)

// then
for (i in 0..9) {
Assertions.assertThat(firstResponse[i].id).isEqualTo(sortedEvents[i].id)
}
for (i in 0..9) {
Assertions.assertThat(responseWithLastAccess[i].id).isEqualTo(sortedEvents[i + 10].id)
}
}

@Test
fun `eventRepositorySupportTest_getEvents should return sorted events by startDate`() {
// given
val sortBy = "startDate"
val sortedEvents = sampleEvents.sortedWith(
compareBy(
{ it.startDate },
{ -it.id!! }
)
)

// when
val firstResponse = eventRepositorySupport.getEvent(sortBy, null, null)
val responseWithLastAccess = eventRepositorySupport.getEvent(sortBy, firstResponse[9].id, firstResponse[9].startDate)

// then
for (i in 0..9) {
Assertions.assertThat(firstResponse[i].id).isEqualTo(sortedEvents[i].id)
}
for (i in 0..9) {
Assertions.assertThat(responseWithLastAccess[i].id).isEqualTo(sortedEvents[i + 10].id)
}
}

@Test
fun `eventRepositorySupportTest_getEvents should return sorted events by createdAt`() {
// given
val sortBy = "createdAt"
val sortedEvents = sampleEvents.toList().reversed()

// when
val firstResponse = eventRepositorySupport.getEvent(sortBy, null, null)
val responseWithLastAccess = eventRepositorySupport.getEvent(sortBy, firstResponse[9].id, firstResponse[9].createdAt)

// then
for (i in 0..9) {
Assertions.assertThat(firstResponse[i].id).isEqualTo(sortedEvents[i].id)
}
for (i in 0..9) {
Assertions.assertThat(responseWithLastAccess[i].id).isEqualTo(sortedEvents[i + 10].id)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.group4.ticketingservice.config

import com.querydsl.jpa.impl.JPAQueryFactory
import jakarta.persistence.EntityManager
import jakarta.persistence.PersistenceContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class QuerydslConfiguration(
@PersistenceContext

Check warning on line 11 in src/main/kotlin/com/group4/ticketingservice/config/QuerydslConfiguration.kt

View check run for this annotation

Codecov / codecov/patch

src/main/kotlin/com/group4/ticketingservice/config/QuerydslConfiguration.kt#L9-L11

Added lines #L9 - L11 were not covered by tests
private val entityManager: EntityManager
) {
@Bean
fun jpaQueryFactory(): JPAQueryFactory = JPAQueryFactory(entityManager)

Check warning on line 15 in src/main/kotlin/com/group4/ticketingservice/config/QuerydslConfiguration.kt

View check run for this annotation

Codecov / codecov/patch

src/main/kotlin/com/group4/ticketingservice/config/QuerydslConfiguration.kt#L15

Added line #L15 was not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import com.group4.ticketingservice.dto.EventCreateRequest
import com.group4.ticketingservice.dto.EventResponse
import com.group4.ticketingservice.entity.Event
import com.group4.ticketingservice.service.EventService
import com.group4.ticketingservice.utils.exception.CustomException
import com.group4.ticketingservice.utils.exception.ErrorCodes
import io.swagger.v3.oas.annotations.Hidden
import jakarta.servlet.http.HttpServletRequest
import jakarta.validation.Valid
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
import org.springframework.data.web.PageableDefault
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
Expand All @@ -22,12 +21,20 @@ import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import java.time.OffsetDateTime

@RestController
@RequestMapping("/events")
class EventController @Autowired constructor(
val eventService: EventService
) {
companion object {
const val DESCENDING = "desc"
const val ASCENDING = "asc"
const val SORT_BY_DEADLINE = "deadline"
const val SORT_BY_START_DATE = "startDate"
const val SORT_BY_CREATED_AT = "createdAt"
}

// TimeE2ETest를 위한 임시 EndPoint입니다.
@Hidden
Expand All @@ -51,7 +58,9 @@ class EventController @Autowired constructor(
endDate = event.endDate,
reservationStartTime = event.reservationStartTime,
reservationEndTime = event.reservationEndTime,
maxAttendees = event.maxAttendees
maxAttendees = event.maxAttendees,
createdAt = event.createdAt,
updatedAt = event.updatedAt
)

val headers = HttpHeaders()
Expand All @@ -70,7 +79,9 @@ class EventController @Autowired constructor(
endDate = it.endDate,
reservationStartTime = it.reservationStartTime,
reservationEndTime = it.reservationEndTime,
maxAttendees = it.maxAttendees
maxAttendees = it.maxAttendees,
createdAt = it.createdAt,
updatedAt = it.updatedAt
)
} ?: kotlin.run {
null
Expand All @@ -84,10 +95,28 @@ class EventController @Autowired constructor(
@GetMapping
fun getEvents(
request: HttpServletRequest,
@RequestParam(required = false) name: String?,
@PageableDefault(size = 10, sort = ["id"], direction = Sort.Direction.DESC) pageable: Pageable
@RequestParam sort: String,
@RequestParam id: Int?,
@RequestParam time: OffsetDateTime?
): ResponseEntity<Page<Event>> {
minjun3021 marked this conversation as resolved.
Show resolved Hide resolved
val page = eventService.getEvents(name, pageable)
val sortProperties = sort.split(",").toTypedArray()

val fieldName = sortProperties.getOrNull(0)
val direction = sortProperties.getOrNull(1)

when (Pair(fieldName, direction)) {
Pair(SORT_BY_DEADLINE, null),
Pair(SORT_BY_START_DATE, null),
Pair(SORT_BY_CREATED_AT, null),
Pair(SORT_BY_DEADLINE, ASCENDING),
Pair(SORT_BY_START_DATE, ASCENDING),
Pair(SORT_BY_CREATED_AT, DESCENDING) -> {
// Valid request
}
else -> throw CustomException(ErrorCodes.INVALID_SORT_FORMAT)
}

minjun3021 marked this conversation as resolved.
Show resolved Hide resolved
val page = eventService.getEvents(fieldName!!, id, time)

val headers = HttpHeaders()
headers.set("Content-Location", request.requestURI)
Expand Down
4 changes: 3 additions & 1 deletion src/main/kotlin/com/group4/ticketingservice/dto/EventDto.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,7 @@ data class EventResponse(
val endDate: OffsetDateTime,
val reservationStartTime: OffsetDateTime,
val reservationEndTime: OffsetDateTime,
val maxAttendees: Int
val maxAttendees: Int,
val createdAt: OffsetDateTime,
val updatedAt: OffsetDateTime
)
Loading
Loading