Skip to content
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
9 changes: 7 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
plugins {
kotlin("jvm") version "1.9.25"
kotlin("plugin.spring") version "1.9.25"
kotlin("jvm") version "2.1.0"
kotlin("plugin.spring") version "2.1.0"
id("org.springframework.boot") version "3.4.1"
id("io.spring.dependency-management") version "1.1.7"
kotlin("plugin.jpa") version "1.9.25"
kotlin("kapt") version "1.9.20"
}

group = "gogo"
Expand Down Expand Up @@ -37,6 +38,10 @@ dependencies {
testImplementation("org.springframework.kafka:spring-kafka-test")
testImplementation("org.springframework.security:spring-security-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta")
kapt("com.querydsl:querydsl-apt:5.0.0:jakarta")
kapt("jakarta.annotation:jakarta.annotation-api")
kapt("jakarta.persistence:jakarta.persistence-api")
}

dependencyManagement {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package gogo.gogosp.domain.customer.root.application

import gogo.gogosp.domain.customer.root.application.dto.WriteCustomerInquiryReqDto
import gogo.gogosp.domain.customer.root.persistence.CustomerInquiry
import gogo.gogosp.domain.customer.root.persistence.CustomerInquiryRepository
import gogo.gogosp.global.internal.student.stub.StudentByIdStub
import org.springframework.stereotype.Component

@Component
class CustomerInquiryProcessor(
private val customerInquiryRepository: CustomerInquiryRepository
) {

fun saveCustomerInquiry(dto: WriteCustomerInquiryReqDto, student: StudentByIdStub) {
val customerInquiry = CustomerInquiry(
studentId = student.studentId,
title = dto.title,
content = dto.content,
isReading = false,
isOpening = dto.isOpening,
)

customerInquiryRepository.save(customerInquiry)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package gogo.gogosp.domain.customer.root.application

import gogo.gogosp.domain.customer.root.application.dto.GetCustomerInquiryListResDto
import gogo.gogosp.domain.customer.root.persistence.CustomerInquiryRepository
import org.springframework.data.domain.PageRequest

import org.springframework.stereotype.Component
@Component
class CustomerInquiryReader(
private val customerInquiryRepository: CustomerInquiryRepository
) {

fun readCustomerInquiryPage(page: Int, size: Int): GetCustomerInquiryListResDto {
val pageRequest = PageRequest.of(page, size)
return customerInquiryRepository.queryCustomerInquiry(pageRequest, size)
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package gogo.gogosp.domain.customer.root.application

import gogo.gogosp.domain.customer.root.application.dto.GetCustomerInquiryListResDto
import gogo.gogosp.domain.customer.root.application.dto.WriteCustomerInquiryReqDto

interface CustomerInquiryService {
fun writeInquiry(dto: WriteCustomerInquiryReqDto)
fun getInquiryList(page: Int, size: Int): GetCustomerInquiryListResDto
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package gogo.gogosp.domain.customer.root.application

import gogo.gogosp.domain.customer.root.application.dto.GetCustomerInquiryListResDto
import gogo.gogosp.domain.customer.root.application.dto.WriteCustomerInquiryReqDto
import gogo.gogosp.global.util.UserContextUtil
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
class CustomerInquiryServiceImpl(
private val userUtil: UserContextUtil,
private val customerInquiryProcessor: CustomerInquiryProcessor,
private val customerInquiryReader: CustomerInquiryReader,
private val customerInquiryValidator: CustomerInquiryValidator
): CustomerInquiryService {

@Transactional
override fun writeInquiry(dto: WriteCustomerInquiryReqDto) {
val student = userUtil.getCurrentStudent()
customerInquiryProcessor.saveCustomerInquiry(dto, student)
}

@Transactional(readOnly = true)
override fun getInquiryList(page: Int, size: Int): GetCustomerInquiryListResDto {
customerInquiryValidator.validPageAndSize(page, size)
return customerInquiryReader.readCustomerInquiryPage(page, size)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package gogo.gogosp.domain.customer.root.application

import gogo.gogosp.global.error.SpException
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Component

@Component
class CustomerInquiryValidator {

fun validPageAndSize(page: Int, size: Int) {
if (page < 0 || size < 0) {
throw SpException("page와 size는 음수일 수 없습니다.", HttpStatus.BAD_REQUEST.value())
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package gogo.gogosp.domain.customer.root.application.dto

import jakarta.validation.constraints.Size
import org.aspectj.weaver.IntMap
import org.jetbrains.annotations.NotNull

data class WriteCustomerInquiryReqDto(
@NotNull
@Size(min = 1, max = 10)
val title: String,
@NotNull
val content: String,
@NotNull
val isOpening: Boolean,
)

data class GetCustomerInquiryListResDto(
val info: PagingDto,
val inquiries: List<CustomerInquiryDto>
)

data class CustomerInquiryDto(
val customerInquiryId: Long,
val title: String,
val content: String,
val isReading: Boolean,
val isOpening: Boolean,
val authorDto: AuthorDto
)

data class PagingDto(
val page: Int,
val size: Int
)

data class AuthorDto(
val studentId: Long,
val name: String,
val classNumber: Int,
val studentNumber: Int,
)
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class CustomerInquiry(
@Column(name = "customer_inquiry_id", nullable = false)
val customerInquiryId: Long = 0,

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

@Column(name = "title", nullable = false)
Expand All @@ -21,5 +21,8 @@ class CustomerInquiry(

@Column(name = "is_reading", nullable = false)
val isReading: Boolean,

@Column(name = "is_opening", nullable = false)
val isOpening: Boolean,
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package gogo.gogosp.domain.customer.root.persistence

import gogo.gogosp.domain.customer.root.application.dto.GetCustomerInquiryListResDto
import org.springframework.data.domain.Pageable

interface CustomerInquiryCustomRepository {
fun queryCustomerInquiry(pageable: Pageable, size: Int): GetCustomerInquiryListResDto
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package gogo.gogosp.domain.customer.root.persistence

import com.querydsl.jpa.impl.JPAQueryFactory
import gogo.gogosp.domain.customer.root.application.dto.AuthorDto
import gogo.gogosp.domain.customer.root.application.dto.CustomerInquiryDto
import gogo.gogosp.domain.customer.root.application.dto.GetCustomerInquiryListResDto
import gogo.gogosp.domain.customer.root.application.dto.PagingDto
import gogo.gogosp.domain.customer.root.persistence.QCustomerInquiry.customerInquiry
import gogo.gogosp.global.internal.student.api.StudentApi
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Repository

@Repository
class CustomerInquiryCustomRepositoryImpl(
private val queryFactory: JPAQueryFactory,
private val studentApi: StudentApi
): CustomerInquiryCustomRepository {

override fun queryCustomerInquiry(pageable: Pageable, size: Int): GetCustomerInquiryListResDto {
val customerInquiryList = queryFactory
.selectFrom(customerInquiry)
.orderBy(
customerInquiry.customerInquiryId.desc()
)
.offset(pageable.offset)
.limit(pageable.pageSize.toLong())
.fetch()

val studentIds = customerInquiryList.map {it.studentId}.toSet().toList()

val students = studentApi.queryStudentsByStudentIds(studentIds).students

val customerInquiryDto = customerInquiryList.map { customerInquiry ->
val student = students.find { it.studentId == customerInquiry.studentId }!!

CustomerInquiryDto(
customerInquiryId = customerInquiry.customerInquiryId,
title = customerInquiry.title,
content = customerInquiry.content,
isReading = customerInquiry.isReading,
isOpening = customerInquiry.isOpening,
authorDto = AuthorDto(
studentId = student.studentId,
name = student.name,
classNumber = student.classNumber,
studentNumber = student.studentNumber
)
)
}

val totalElement = customerInquiryList.size

val totalPage = if (totalElement % size == 0) {
totalElement / size
} else {
totalElement / size + 1
}

val pagingDto = PagingDto(totalPage, totalElement)

return GetCustomerInquiryListResDto(pagingDto, customerInquiryDto)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ package gogo.gogosp.domain.customer.root.persistence

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

interface CustomerInquiryRepository: JpaRepository<CustomerInquiry, Long> {
interface CustomerInquiryRepository: JpaRepository<CustomerInquiry, Long>, CustomerInquiryCustomRepository {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package gogo.gogosp.domain.customer.root.presentation

import gogo.gogosp.domain.customer.root.application.CustomerInquiryService
import gogo.gogosp.domain.customer.root.application.dto.GetCustomerInquiryListResDto
import gogo.gogosp.domain.customer.root.application.dto.WriteCustomerInquiryReqDto
import jakarta.validation.Valid
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/sp")
class CustomerInquiryController(
private val customerInquiryService: CustomerInquiryService
) {

@PostMapping("/customer/inquiry")
fun writeInquiry(
@RequestBody @Valid dto: WriteCustomerInquiryReqDto
): ResponseEntity<Void> {
customerInquiryService.writeInquiry(dto)
return ResponseEntity.status(HttpStatus.CREATED).build()
}

@GetMapping("/customer")
fun getInquiryList(
@RequestParam(required = false, defaultValue = "0") page: Int,
@RequestParam(required = false, defaultValue = "20") size: Int,
): ResponseEntity<GetCustomerInquiryListResDto> {
val response = customerInquiryService.getInquiryList(page, size)
return ResponseEntity.ok(response)
}


}
16 changes: 16 additions & 0 deletions src/main/kotlin/gogo/gogosp/global/config/QueryDslConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package gogo.gogosp.global.config

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

@Configuration
class QueryDslConfig(
private val entityManager: EntityManager
) {

@Bean
fun jpaQueryFactory() = JPAQueryFactory(entityManager)

}
5 changes: 5 additions & 0 deletions src/main/kotlin/gogo/gogosp/global/config/SecurityConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package gogo.gogosp.global.config
import gogo.gogosp.global.filter.AuthenticationFilter
import gogo.gogosp.global.handler.CustomAccessDeniedHandler
import gogo.gogosp.global.handler.CustomAuthenticationEntryPointHandler
import gogo.gogosp.global.internal.user.stub.Authority
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpMethod
Expand Down Expand Up @@ -43,6 +44,10 @@ class SecurityConfig(

http.authorizeHttpRequests { httpRequests ->
httpRequests.requestMatchers(HttpMethod.GET, "/sp/health").permitAll()

httpRequests.requestMatchers(HttpMethod.POST, "/sp/customer/inquiry").hasAnyRole(Authority.USER.name, Authority.STAFF.name)
httpRequests.requestMatchers(HttpMethod.GET, "/sp/customer").hasAnyRole(Authority.USER.name, Authority.STAFF.name)

httpRequests.anyRequest().denyAll()
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/gogo/gogosp/global/error/ErrorResponse.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ data class ErrorResponse(
val status: Int
) {
companion object {
fun of(e: StageException) =
fun of(e: SpException) =
ErrorResponse(
message = e.message,
status = e.status
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import org.springframework.web.servlet.NoHandlerFoundException
@RestControllerAdvice
class GlobalExceptionHandler {

@ExceptionHandler(StageException::class)
fun userExceptionHandler(e: StageException): ResponseEntity<ErrorResponse> =
@ExceptionHandler(SpException::class)
fun userExceptionHandler(e: SpException): ResponseEntity<ErrorResponse> =
ResponseEntity(ErrorResponse.of(e), HttpStatus.valueOf(e.status))

@ExceptionHandler(BindException::class)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package gogo.gogosp.global.error

open class StageException(
open class SpException(
override val message: String,
val status: Int
) : RuntimeException(message)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package gogo.gogosp.global.feign
import feign.FeignException
import feign.Response
import feign.codec.ErrorDecoder
import gogo.gogosp.global.error.StageException
import gogo.gogosp.global.error.SpException
import org.springframework.http.HttpStatus

class FeignClientErrorDecoder : ErrorDecoder {
Expand All @@ -12,7 +12,7 @@ class FeignClientErrorDecoder : ErrorDecoder {
response: Response,
): Exception? {
if (response.status() >= 400) {
throw StageException("HTTP 통신 오류", HttpStatus.INTERNAL_SERVER_ERROR.value())
throw SpException("HTTP 통신 오류", HttpStatus.INTERNAL_SERVER_ERROR.value())
}
return FeignException.errorStatus(methodKey, response)
}
Expand Down
Loading