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: JD 조회 API 구현 #77

Merged
merged 10 commits into from
May 19, 2024
Merged
4 changes: 3 additions & 1 deletion Api-Module/src/docs/asciidoc/JobDescription.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ operation::JobDescriptionControllerTest/createApplyWithEmptyTitle[snippets='http

operation::JobDescriptionControllerTest/createApplyWithEmptyContent[snippets='http-request,request-headers,http-response']

[[GET-JD-LIST]]
=== JD 목록 조회


operation::JobDescriptionControllerTest/getJobDescription[snippets='http-request,request-headers,query-parameters,http-response,response-fields']


Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.bamyanggang.apimodule.common.dto

import com.bamyanggang.domainmodule.common.pagination.SliceDomain

data class SliceResponse<T>(
val content: List<T>,
val page: Int,
val size: Int,
val hasNext: Boolean
) {
companion object {
fun <T> from(slice: SliceDomain<T>): SliceResponse<T> {
return SliceResponse(
content = slice.content,
page = slice.pageNumber,
size = slice.pageSize,
hasNext = slice.hasNext
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.bamyanggang.apimodule.domain.jobDescription.application.dto

import com.bamyanggang.domainmodule.domain.jobDescription.enums.WriteStatus
import java.time.LocalDateTime
import java.util.UUID

class GetJobDescriptionInfo {

data class Response(
val jobDescriptionId: UUID,
val remainingDate: Int,
val enterpriseName: String,
val title: String,
val writeStatus: WriteStatus,
val createdAt: LocalDateTime,
val startedAt: LocalDateTime,
val endedAt: LocalDateTime,
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.bamyanggang.apimodule.domain.jobDescription.application.service

import com.bamyanggang.apimodule.common.dto.SliceResponse
import com.bamyanggang.apimodule.common.getAuthenticationPrincipal
import com.bamyanggang.apimodule.domain.jobDescription.application.dto.GetJobDescriptionInfo
import com.bamyanggang.domainmodule.common.pagination.SliceDomain
import com.bamyanggang.domainmodule.domain.jobDescription.enums.SortType
import com.bamyanggang.domainmodule.domain.jobDescription.enums.WriteStatus
import com.bamyanggang.domainmodule.domain.jobDescription.service.ApplyReader
import com.bamyanggang.domainmodule.domain.jobDescription.service.JobDescriptionReader
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.SliceImpl
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional


@Service
class JobDescriptionInfoGetService(
private val jobDescriptionReader: JobDescriptionReader,
private val applyReader: ApplyReader
) {

@Transactional(readOnly = true)
fun getJobDescriptionInfo(pageable: Pageable, writeStatus: WriteStatus?, sortType: SortType?): SliceResponse<GetJobDescriptionInfo.Response> {
return getAuthenticationPrincipal().let{ userId ->
val jobDescriptions = jobDescriptionReader.readJobDescriptionByUserIdAndSortType(userId, pageable.pageNumber, pageable.pageSize, sortType)

val jobDescriptionInfoResponses = jobDescriptions.content.map{ jobDescription ->
val apply = applyReader.readApplyByJobDescriptionId(jobDescription.id)
GetJobDescriptionInfo.Response(
jobDescription.id,
jobDescription.getRemainingDate(),
jobDescription.enterpriseName,
jobDescription.title,
apply?.writeStatus?: WriteStatus.NOT_APPLIED,
jobDescription.createdAt,
jobDescription.startedAt,
jobDescription.endedAt
)
}

val jobDescriptionsSlice = SliceDomain(jobDescriptionInfoResponses, jobDescriptions.pageNumber, jobDescriptions.pageSize, jobDescriptions.hasNext)
SliceResponse.from(jobDescriptionsSlice)
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ package com.bamyanggang.apimodule.domain.jobDescription.presentation
object JobDescriptionApi {
const val BASE_URL = "/api/job-description"
const val APPLY = "${BASE_URL}/apply/{jobDescriptionId}"

}
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
package com.bamyanggang.apimodule.domain.jobDescription.presentation

import com.bamyanggang.apimodule.common.dto.SliceResponse
import com.bamyanggang.apimodule.domain.jobDescription.application.dto.CreateApply
import com.bamyanggang.apimodule.domain.jobDescription.application.dto.CreateJobDescription
import com.bamyanggang.apimodule.domain.jobDescription.application.dto.GetJobDescriptionInfo
import com.bamyanggang.apimodule.domain.jobDescription.application.service.ApplyCreateService
import com.bamyanggang.apimodule.domain.jobDescription.application.service.JobDescriptionCreateService
import com.bamyanggang.apimodule.domain.jobDescription.application.service.JobDescriptionInfoGetService
import com.bamyanggang.domainmodule.domain.jobDescription.enums.SortType
import com.bamyanggang.domainmodule.domain.jobDescription.enums.WriteStatus
import org.springframework.data.domain.Pageable
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.RequestParam
import org.springframework.web.bind.annotation.RestController
import java.util.UUID

@RestController
class JobDescriptionController(
private val jobDescriptionCreateService: JobDescriptionCreateService,
private val applyCreateService: ApplyCreateService
private val applyCreateService: ApplyCreateService,
private val jobDescriptionInfoGetService: JobDescriptionInfoGetService
) {

@PostMapping(JobDescriptionApi.BASE_URL)
Expand All @@ -27,4 +36,13 @@ class JobDescriptionController(
@RequestBody request: CreateApply.Request
) = applyCreateService.createApply(request, jobDescriptionId)

@GetMapping(JobDescriptionApi.BASE_URL)
fun getJobDescription(
pageable: Pageable,
@RequestParam writeStatus: WriteStatus?,
@RequestParam sortType: SortType?
): SliceResponse<GetJobDescriptionInfo.Response> {
return jobDescriptionInfoGetService.getJobDescriptionInfo(pageable, writeStatus, sortType)
}

}
Original file line number Diff line number Diff line change
@@ -1,37 +1,52 @@
package com.bamyanggang.apimodule.domain.jobDescription.presentation

import com.bamyanggang.apimodule.BaseRestDocsTest
import com.bamyanggang.apimodule.common.dto.SliceResponse
import com.bamyanggang.apimodule.domain.jobDescription.application.dto.CreateApply
import com.bamyanggang.apimodule.domain.jobDescription.application.dto.CreateApplyContent
import com.bamyanggang.apimodule.domain.jobDescription.application.dto.CreateJobDescription
import com.bamyanggang.apimodule.domain.jobDescription.application.dto.GetJobDescriptionInfo
import com.bamyanggang.apimodule.domain.jobDescription.application.service.ApplyCreateService
import com.bamyanggang.apimodule.domain.jobDescription.application.service.JobDescriptionCreateService
import com.bamyanggang.apimodule.domain.jobDescription.application.service.JobDescriptionInfoGetService
import com.bamyanggang.commonmodule.exception.ExceptionHandler
import com.bamyanggang.commonmodule.fixture.generateFixture
import com.bamyanggang.domainmodule.common.pagination.SliceDomain
import com.bamyanggang.domainmodule.domain.jobDescription.enums.SortType
import com.bamyanggang.domainmodule.domain.jobDescription.enums.WriteStatus
import io.kotest.assertions.print.printWithType
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.springframework.http.MediaType
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.context.annotation.Import
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Slice
import org.springframework.data.domain.SliceImpl
import org.springframework.http.MediaType
import org.springframework.restdocs.headers.HeaderDocumentation.headerWithName
import org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders
import org.springframework.restdocs.payload.PayloadDocumentation.*
import org.springframework.restdocs.request.RequestDocumentation
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import java.time.LocalDateTime
import java.util.UUID
import java.util.*

@WebMvcTest(JobDescriptionController::class)
@Import(ExceptionHandler::class)
class JobDescriptionControllerTest : BaseRestDocsTest() {

@MockBean
private lateinit var jobDescriptionCreateService: JobDescriptionCreateService

@MockBean
private lateinit var applyCreateService: ApplyCreateService

@MockBean
private lateinit var jobDescriptionInfoGetService: JobDescriptionInfoGetService

@Test
@DisplayName("직무 공고를 등록한다.")
fun createJobDescription() {
Expand All @@ -48,7 +63,9 @@ class JobDescriptionControllerTest : BaseRestDocsTest() {
it.set("jobDescriptionId", UUID.randomUUID())
}

given(jobDescriptionCreateService.createJobDescription(createJobDescriptionRequest)).willReturn(createJobDescriptionResponse)
given(jobDescriptionCreateService.createJobDescription(createJobDescriptionRequest)).willReturn(
createJobDescriptionResponse
)

val request = RestDocumentationRequestBuilders.post(JobDescriptionApi.BASE_URL)
.header("Authorization", "Bearer Access Token")
Expand Down Expand Up @@ -91,7 +108,9 @@ class JobDescriptionControllerTest : BaseRestDocsTest() {
it.set("endedAt", LocalDateTime.now())
}

given(jobDescriptionCreateService.createJobDescription(createJobDescriptionRequest)).willThrow(IllegalArgumentException("내용은 필수입니다."))
given(jobDescriptionCreateService.createJobDescription(createJobDescriptionRequest)).willThrow(
IllegalArgumentException("내용은 필수입니다.")
)

val request = RestDocumentationRequestBuilders.post(JobDescriptionApi.BASE_URL)
.header("Authorization", "Bearer Access Token")
Expand Down Expand Up @@ -129,7 +148,9 @@ class JobDescriptionControllerTest : BaseRestDocsTest() {
it.set("endedAt", LocalDateTime.now().minusDays(1))
}

given(jobDescriptionCreateService.createJobDescription(createJobDescriptionRequest)).willThrow(IllegalArgumentException("시작일은 종료일보다 빨라야 합니다."))
given(jobDescriptionCreateService.createJobDescription(createJobDescriptionRequest)).willThrow(
IllegalArgumentException("시작일은 종료일보다 빨라야 합니다.")
)

val request = RestDocumentationRequestBuilders.post(JobDescriptionApi.BASE_URL)
.header("Authorization", "Bearer Access Token")
Expand Down Expand Up @@ -224,4 +245,67 @@ class JobDescriptionControllerTest : BaseRestDocsTest() {
)
}

@Test
@DisplayName("JD 공고를 조회한다")
fun getJobDescription() {
// given
val pageRequest = PageRequest.of(0, 6)
val getJobDescriptionInfoResponse: GetJobDescriptionInfo.Response = generateFixture {
it.set("jobDescriptionId", UUID.randomUUID())
it.set("remainingDate", 1)
it.set("enterpriseName", "기업 이름")
it.set("title", "직무 공고 제목")
it.set("writeStatus", WriteStatus.WRITING)
it.set("createdAt", LocalDateTime.now())
it.set("startedAt", LocalDateTime.now())
it.set("endedAt", LocalDateTime.now()) }

val slice = SliceDomain(listOf(getJobDescriptionInfoResponse), 0, 1, true)
val sliceResponse = SliceResponse.from(slice)
given(jobDescriptionInfoGetService.getJobDescriptionInfo(pageRequest, WriteStatus.WRITING, SortType.ENDED)).willReturn(sliceResponse)


val request = RestDocumentationRequestBuilders.get(JobDescriptionApi.BASE_URL)
.header("Authorization", "Bearer Access Token")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.queryParam("page", pageRequest.pageNumber.toString())
.queryParam("size", pageRequest.pageSize.toString())
.queryParam("writeStatus", WriteStatus.WRITING.toString())
.queryParam("sortType", SortType.ENDED.toString())

//when
val result = mockMvc.perform(request)

//then
result.andExpect(status().isOk)
.andDo(
resultHandler.document(
requestHeaders(
headerWithName("Authorization").description("엑세스 토큰")
),
RequestDocumentation.queryParameters(
RequestDocumentation.parameterWithName("page").description("요청 페이지"),
RequestDocumentation.parameterWithName("size").description("요청 사이즈"),
RequestDocumentation.parameterWithName("writeStatus")
.description("작성 상태. NOT_APPLIED(칩 없음, 작성 전), WRITING(작성 중), WRITTEN(작성 완료), CLOSED(마감)"),
RequestDocumentation.parameterWithName("sortType")
.description("정렬 타입 CREATED(등록순), ENDED(마감순)"),
),
responseFields(
fieldWithPath("content[].jobDescriptionId").description("직무 공고 ID"),
fieldWithPath("content[].remainingDate").description("디데이"),
fieldWithPath("content[].enterpriseName").description("기업 이름"),
fieldWithPath("content[].title").description("직무 공고 제목"),
fieldWithPath("content[].writeStatus").description("작성 상태. NOT_APPLIED(칩 없음, 작성 전), WRITING(작성 중), WRITTEN(작성 완료), CLOSED(마감)"),
fieldWithPath("content[].createdAt").description("생성일"),
fieldWithPath("content[].startedAt").description("시작일"),
fieldWithPath("content[].endedAt").description("종료일"),
fieldWithPath("page").description("요청 페이지"),
fieldWithPath("size").description("요청 사이즈"),
fieldWithPath("hasNext").description("다음 데이터 존재 여부")
)
)
)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.bamyanggang.domainmodule.common.pagination

data class SliceDomain<T>(
val content: List<T>,
val pageNumber: Int,
val pageSize: Int,
val hasNext: Boolean
)
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ data class JobDescription(
require(startedAt.isBefore(endedAt)) { "시작일은 종료일보다 빨라야 합니다." }
}

fun getRemainingDate(): Int {
return LocalDateTime.now().until(endedAt, java.time.temporal.ChronoUnit.DAYS).toInt()
}

companion object {
fun create(
enterpriseName: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.bamyanggang.domainmodule.domain.jobDescription.enums

enum class SortType {
CREATED, // 등록순
ENDED // 마감순
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ package com.bamyanggang.domainmodule.domain.jobDescription.enums
enum class WriteStatus {
WRITING,
WRITTEN,
CLOSED
CLOSED,
NOT_APPLIED
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.bamyanggang.domainmodule.domain.jobDescription.repository

import com.bamyanggang.domainmodule.domain.jobDescription.aggregate.Apply
import java.util.*

interface ApplyRepository {
fun save(apply: Apply)

fun findByJobDescriptionId(jobDescriptionId: UUID): Apply?

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package com.bamyanggang.domainmodule.domain.jobDescription.repository

import com.bamyanggang.domainmodule.common.pagination.SliceDomain
import com.bamyanggang.domainmodule.domain.jobDescription.aggregate.JobDescription
import java.util.*

interface JobDescriptionRepository {

fun save(jobDescription: JobDescription)

fun findAllByUserIdAndSortByCreatedAt(userId: UUID, page: Int, size: Int): SliceDomain<JobDescription>

fun findAllByUserId(userId: UUID, page: Int, size: Int): SliceDomain<JobDescription>

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.bamyanggang.domainmodule.domain.jobDescription.service

import com.bamyanggang.domainmodule.domain.jobDescription.aggregate.Apply
import com.bamyanggang.domainmodule.domain.jobDescription.repository.ApplyRepository
import org.springframework.stereotype.Service
import java.util.*

@Service
class ApplyReader(
private val applyRepository: ApplyRepository
) {

fun readApplyByJobDescriptionId(JobDescriptionId: UUID): Apply?{
return applyRepository.findByJobDescriptionId(JobDescriptionId)
}

}
Loading
Loading