From 9045cc9a75dc2600be037379e99208f4b7b642b6 Mon Sep 17 00:00:00 2001 From: YooJHyun Date: Fri, 1 Nov 2024 15:27:54 +0900 Subject: [PATCH 1/2] =?UTF-8?q?Feat:=20jobpost,=20apply,=20like=20?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EB=B0=8F=20=EC=B6=94=EA=B0=80=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bittakotlin/BittaKotlinApplication.kt | 4 + .../apply/controller/ApplyController.kt | 235 ++++++++++ .../advice/ApplyControllerAdvice.kt | 18 + .../tenten/bittakotlin/apply/dto/ApplyDTO.kt | 40 ++ .../apply/dto/ApplyStatusUpdateDTO.kt | 21 + .../tenten/bittakotlin/apply/entity/Apply.kt | 33 ++ .../bittakotlin/apply/entity/ApplyStatus.kt | 8 + .../apply/exception/ApplyException.kt | 14 + .../apply/exception/ApplyTaskException.kt | 13 + .../apply/repository/ApplyRepository.kt | 30 ++ .../bittakotlin/apply/service/ApplyService.kt | 25 ++ .../apply/service/ApplyServiceImpl.kt | 128 ++++++ .../bittakotlin/apply/util/ApplyProvider.kt | 20 + .../bittakotlin/global/dto/PageRequestDTO.kt | 23 + .../jobpost/controller/JobPostController.kt | 403 ++++++++++++++++++ .../controller/JobPostViewController.kt | 17 + .../advice/JobPostControllerAdvice.kt | 15 + .../bittakotlin/jobpost/dto/JobPostDTO.kt | 70 +++ .../bittakotlin/jobpost/entity/JobPost.kt | 96 +++++ .../bittakotlin/jobpost/entity/PayStatus.kt | 6 + .../jobpost/entity/WorkCategory.kt | 20 + .../jobpost/exception/JobPostException.kt | 15 + .../jobpost/exception/JobPostTaskException.kt | 11 + .../jobpost/repository/JobPostRepository.kt | 34 ++ .../jobpost/service/DayScheduler.kt | 28 ++ .../jobpost/service/JobPostService.kt | 29 ++ .../jobpost/service/JobPostServiceImpl.kt | 179 ++++++++ .../jobpost/util/JobPostProvider.kt | 16 + .../like/controller/LikeController.kt | 115 +++++ .../controller/advice/LikeControllerAdvice.kt | 15 + .../tenten/bittakotlin/like/entity/Like.kt | 34 ++ .../like/exception/LikeException.kt | 13 + .../like/exception/LikeTaskException.kt | 11 + .../like/repository/LikeRepository.kt | 20 + .../bittakotlin/like/service/LikeService.kt | 10 + .../like/service/LikeServiceImpl.kt | 57 +++ .../member/dto/MemberNicknameDTO.kt | 5 + .../bittakotlin/profile/entity/Profile.kt | 13 +- .../profile/service/ProfileProvider.kt | 14 + .../profile/service/ProfileServiceImpl.kt | 2 +- 40 files changed, 1857 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/org/tenten/bittakotlin/apply/controller/ApplyController.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/apply/controller/advice/ApplyControllerAdvice.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/apply/dto/ApplyDTO.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/apply/dto/ApplyStatusUpdateDTO.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/apply/entity/Apply.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/apply/entity/ApplyStatus.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/apply/exception/ApplyException.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/apply/exception/ApplyTaskException.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/apply/repository/ApplyRepository.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/apply/service/ApplyService.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/apply/service/ApplyServiceImpl.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/apply/util/ApplyProvider.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/global/dto/PageRequestDTO.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/jobpost/controller/JobPostController.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/jobpost/controller/JobPostViewController.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/jobpost/controller/advice/JobPostControllerAdvice.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/jobpost/dto/JobPostDTO.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/jobpost/entity/JobPost.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/jobpost/entity/PayStatus.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/jobpost/entity/WorkCategory.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/jobpost/exception/JobPostException.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/jobpost/exception/JobPostTaskException.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/jobpost/repository/JobPostRepository.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/jobpost/service/DayScheduler.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/jobpost/service/JobPostService.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/jobpost/service/JobPostServiceImpl.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/jobpost/util/JobPostProvider.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/like/controller/LikeController.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/like/controller/advice/LikeControllerAdvice.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/like/entity/Like.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/like/exception/LikeException.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/like/exception/LikeTaskException.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/like/repository/LikeRepository.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/like/service/LikeService.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/like/service/LikeServiceImpl.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/member/dto/MemberNicknameDTO.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/profile/service/ProfileProvider.kt diff --git a/src/main/kotlin/org/tenten/bittakotlin/BittaKotlinApplication.kt b/src/main/kotlin/org/tenten/bittakotlin/BittaKotlinApplication.kt index fde40bb..107098c 100644 --- a/src/main/kotlin/org/tenten/bittakotlin/BittaKotlinApplication.kt +++ b/src/main/kotlin/org/tenten/bittakotlin/BittaKotlinApplication.kt @@ -2,8 +2,12 @@ package org.tenten.bittakotlin import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication +import org.springframework.data.web.config.EnableSpringDataWebSupport +import org.springframework.scheduling.annotation.EnableScheduling @SpringBootApplication +@EnableScheduling +@EnableSpringDataWebSupport(pageSerializationMode = EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO) class BittaKotlinApplication fun main(args: Array) { diff --git a/src/main/kotlin/org/tenten/bittakotlin/apply/controller/ApplyController.kt b/src/main/kotlin/org/tenten/bittakotlin/apply/controller/ApplyController.kt new file mode 100644 index 0000000..acaddb9 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/apply/controller/ApplyController.kt @@ -0,0 +1,235 @@ +package org.tenten.bittakotlin.apply.controller + +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.tags.Tag +import jakarta.validation.Valid +import lombok.RequiredArgsConstructor +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import org.tenten.bittakotlin.apply.dto.ApplyDTO +import org.tenten.bittakotlin.apply.dto.ApplyStatusUpdateDTO +import org.tenten.bittakotlin.apply.service.ApplyService +import org.tenten.bittakotlin.profile.entity.Profile + +@Tag(name = "지원서 API 컨트롤러", description = "지원서와 관련된 REST API를 제공하는 컨트롤러입니다.") +@RestController +@RequestMapping("api/v1/apply") +@RequiredArgsConstructor +class ApplyController( + private val applyService: ApplyService +) { + +// @Operation( +// summary = "전체 지원서 조회", +// description = "회원의 전체 지원서를 조회합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "지원서를 성공적으로 조회했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = APPLY_SUCCESS_READ_ALL) +// )] +// ) +// ] +// ) + @GetMapping + fun findAll(profile: Profile): ResponseEntity> { + return ResponseEntity.ok(applyService.readAll(profile)) + } + +// @Operation( +// summary = "지원서 등록", +// description = "지원서를 등록합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "지원서를 성공적으로 등록했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = APPLY_SUCCESS_REGISTER) +// )] +// ), +// ApiResponse( +// responseCode = "400", +// description = "지원서 등록에 실패했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = APPLY_FAILURE_NOT_REGISTERED) +// )] +// ) +// ] +// ) + @PostMapping + fun registerApply(@Valid @RequestBody applyDTO: ApplyDTO): ResponseEntity> { + return ResponseEntity.ok(applyService.register(applyDTO)) + } + +// @Operation( +// summary = "단일 지원서 조회", +// description = "단일 지원서를 조회합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "지원서를 조회했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = APPLY_SUCCESS_READ) +// )] +// ), +// ApiResponse( +// responseCode = "400", +// description = "지원서가 존재하지 않습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = APPLY_FAILURE_NOT_FOUND) +// )] +// ) +// ] +// ) +// @Parameter( +// name = "id", +// description = "조회할 지원서의 ID", +// required = true, +// example = "1", +// schema = Schema(type = "integer") +// ) + @GetMapping("/{id}") + fun readApply(@PathVariable("id") id: Long): ResponseEntity { + val applyDTO = applyService.findById(id) + return ResponseEntity.ok(applyDTO) + } + +// @Operation( +// summary = "지원서 삭제", +// description = "지원서를 삭제합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "지원서가 삭제되었습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = APPLY_SUCCESS_DELETE) +// )] +// ), +// ApiResponse( +// responseCode = "400", +// description = "지원서 삭제에 실패했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = APPLY_FAILURE_NOT_REMOVED) +// )] +// ), +// ApiResponse( +// responseCode = "404", +// description = "삭제할 지원서가 존재하지 않습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = APPLY_FAILURE_NOT_FOUND) +// )] +// ) +// ] +// ) +// @Parameter( +// name = "id", +// description = "삭제할 지원서의 ID", +// required = true, +// example = "1", +// schema = Schema(type = "integer") +// ) + @DeleteMapping("/{id}") + fun deleteApply(@Valid @PathVariable("id") id: Long): ResponseEntity> { + applyService.delete(id) + return ResponseEntity.ok(mapOf("message" to "삭제가 완료되었습니다")) + } + +// @Operation( +// summary = "게시물의 지원서 조회", +// description = "게시물에 해당하는 지원서를 조회합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "지원서를 성공적으로 조회했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = APPLY_SUCCESS_READ_ALL) +// )] +// ), +// ApiResponse( +// responseCode = "404", +// description = "지원서가 존재하지 않습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = APPLY_FAILURE_NOT_FOUND) +// )] +// ) +// ] +// ) +// @Parameter( +// name = "jobPostId, memberId", +// description = "지원서의 해당 일거리 ID, 작성자의 회원 ID", +// required = true, +// example = "1, 1", +// schema = Schema(type = "integer") +// ) + @GetMapping("/job-post/{jobPostId}/member/{profileId}") + fun getApplyIntoJobPost( + @PathVariable jobPostId: Long, + @PathVariable profileId: Long + ): ResponseEntity> { + val applies = applyService.getApplyIntoJobPost(jobPostId, profileId) + return ResponseEntity.ok(applies) + } + +// @Operation( +// summary = "지원 검토 상태 수정", +// description = "게시물에 해당하는 지원의 검토 상태를 수정합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "검토 상태를 성공적으로 수정했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = APPLY_SUCCESS_MODIFY) +// )] +// ), +// ApiResponse( +// responseCode = "400", +// description = "검토 상태 수정에 실패했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = APPLY_FAILURE_NOT_MODIFY) +// )] +// ), +// ApiResponse( +// responseCode = "404", +// description = "일거리가 존재하지 않습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = APPLY_FAILURE_NOT_FOUND) +// )] +// ) +// ] +// ) +// @Parameter( +// name = "applyId, profileId", +// description = "해당 게시물의 지원 ID, 게시자의 프로필 ID", +// required = true, +// example = "1, 1", +// schema = Schema(type = "integer") +// ) + @PutMapping("/{applyId}/status/{profileId}") + fun applyStatusUpdate( + @PathVariable applyId: Long, + @PathVariable profileId: Long, + @RequestBody applyStatusUpdateDTO: ApplyStatusUpdateDTO + ): ResponseEntity { + applyService.applyStatusUpdate(applyId, applyStatusUpdateDTO.applyStatus!!, profileId) + return ResponseEntity.ok("상태가 변경되었습니다") + } +} + diff --git a/src/main/kotlin/org/tenten/bittakotlin/apply/controller/advice/ApplyControllerAdvice.kt b/src/main/kotlin/org/tenten/bittakotlin/apply/controller/advice/ApplyControllerAdvice.kt new file mode 100644 index 0000000..e4d63b6 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/apply/controller/advice/ApplyControllerAdvice.kt @@ -0,0 +1,18 @@ +package org.tenten.bittakotlin.apply.controller.advice + +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.RestControllerAdvice +import org.tenten.bittakotlin.apply.exception.ApplyTaskException + +@RestControllerAdvice +class ApplyControllerAdvice { + + @ExceptionHandler(ApplyTaskException::class) + fun handleApplyTaskException(e: ApplyTaskException): ResponseEntity> { + return ResponseEntity.status(e.code) + .body(mapOf("error" to e.message)) + } +} + + diff --git a/src/main/kotlin/org/tenten/bittakotlin/apply/dto/ApplyDTO.kt b/src/main/kotlin/org/tenten/bittakotlin/apply/dto/ApplyDTO.kt new file mode 100644 index 0000000..38028fa --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/apply/dto/ApplyDTO.kt @@ -0,0 +1,40 @@ +package org.tenten.bittakotlin.apply.dto + +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotNull +import lombok.AllArgsConstructor +import lombok.Builder +import lombok.Data +import lombok.NoArgsConstructor +import org.tenten.bittakotlin.apply.entity.ApplyStatus +import java.time.LocalDateTime + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(title = "지원서 DTO", description = "지원서 요청 및 응답에 사용하는 DTO입니다.") +data class ApplyDTO( + + @Schema(title = "지원서 ID (PK)", description = "지원서의 고유 ID 입니다.", example = "1", minimum = "1") + @field:Min(value = 1, message = "ID는 음수가 될 수 없습니다") + var id: Long? = null, + + @Schema(title = "일거리 ID (FK)", description = "일거리의 고유 ID 입니다.", example = "1", minimum = "1") + @field:Min(value = 1, message = "ID는 음수가 될 수 없습니다") + @field:NotNull(message = "게시글의 ID가 필요합니다") + var jobPostId: Long? = null, + + @Schema(title = "프로필 ID (FK)", description = "회원의 고유 프로필 ID 입니다.", example = "1", minimum = "1") + @field:Min(value = 1, message = "ID는 음수가 될 수 없습니다") + @field:NotNull(message = "회원의 ID가 필요합니다") + var profileId: Long? = null, + + @Schema(title = "지원서 생성일시", description = "지원서가 생성된 날짜 및 시간입니다.", example = "2023-09-24T14:45:00") + var appliedAt: LocalDateTime? = null, + + @Schema(title = "지원 검토 상태", description = "지원서의 현재 검토 상태입니다", example = "PENDING") + var status: ApplyStatus? = null +) + diff --git a/src/main/kotlin/org/tenten/bittakotlin/apply/dto/ApplyStatusUpdateDTO.kt b/src/main/kotlin/org/tenten/bittakotlin/apply/dto/ApplyStatusUpdateDTO.kt new file mode 100644 index 0000000..e4c9c1a --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/apply/dto/ApplyStatusUpdateDTO.kt @@ -0,0 +1,21 @@ +package org.tenten.bittakotlin.apply.dto + +import io.swagger.v3.oas.annotations.media.Schema +import lombok.AllArgsConstructor +import lombok.Builder +import lombok.Data +import lombok.NoArgsConstructor +import org.tenten.bittakotlin.apply.entity.ApplyStatus + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(title = "지원서 상태 DTO", description = "지원서의 현재 검토 상태 변경시 사용하는 DTO입니다.") +data class ApplyStatusUpdateDTO( + @Schema(title = "지원서 ID", description = "지원서의 고유 ID입니다.", example = "1") + var id: Long? = null, + + @Schema(title = "지원서 검토 상태", description = "지원서의 현재 검토 상태입니다.", example = "PENDING") + var applyStatus: ApplyStatus? = null +) diff --git a/src/main/kotlin/org/tenten/bittakotlin/apply/entity/Apply.kt b/src/main/kotlin/org/tenten/bittakotlin/apply/entity/Apply.kt new file mode 100644 index 0000000..6420df1 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/apply/entity/Apply.kt @@ -0,0 +1,33 @@ +package org.tenten.bittakotlin.apply.entity + +import jakarta.persistence.* +import org.springframework.data.annotation.CreatedDate +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import org.tenten.bittakotlin.jobpost.entity.JobPost +import org.tenten.bittakotlin.profile.entity.Profile +import java.time.LocalDateTime + +@Entity +@Table(name = "application") +@EntityListeners(AuditingEntityListener::class) +data class Apply( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "job_post_id", nullable = false) + var jobPost: JobPost? = null, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "profile_id", nullable = false) + var profile: Profile? = null, + + @CreatedDate + var appliedAt: LocalDateTime? = null, + + @Column(length = 200) + var status: ApplyStatus? = null +) + + diff --git a/src/main/kotlin/org/tenten/bittakotlin/apply/entity/ApplyStatus.kt b/src/main/kotlin/org/tenten/bittakotlin/apply/entity/ApplyStatus.kt new file mode 100644 index 0000000..6ff1164 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/apply/entity/ApplyStatus.kt @@ -0,0 +1,8 @@ +package org.tenten.bittakotlin.apply.entity + +enum class ApplyStatus { + UNDETERMINED, // 미정 + PENDING, // 보류 + ACCEPTED, // 합격 + NOT_REVIEWED // 확인하지 않음 +} \ No newline at end of file diff --git a/src/main/kotlin/org/tenten/bittakotlin/apply/exception/ApplyException.kt b/src/main/kotlin/org/tenten/bittakotlin/apply/exception/ApplyException.kt new file mode 100644 index 0000000..e7d692d --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/apply/exception/ApplyException.kt @@ -0,0 +1,14 @@ +package org.tenten.bittakotlin.apply.exception + +enum class ApplyException(private val applyTaskException: ApplyTaskException) { + NOT_FOUND("지원서를 찾을 수 없습니다", 404), + NOT_REGISTERED("지원서가 등록되지 않았습니다", 400), + NOT_REMOVED("지원서가 삭제되지 않았습니다", 400), + NOT_FETCHED("지원서 조회에 실패하였습니다", 400); + + constructor(message: String, code: Int) : this(ApplyTaskException(message, code)) + + fun get(): ApplyTaskException = applyTaskException +} + + diff --git a/src/main/kotlin/org/tenten/bittakotlin/apply/exception/ApplyTaskException.kt b/src/main/kotlin/org/tenten/bittakotlin/apply/exception/ApplyTaskException.kt new file mode 100644 index 0000000..d7ae2ce --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/apply/exception/ApplyTaskException.kt @@ -0,0 +1,13 @@ +package org.tenten.bittakotlin.apply.exception + +import lombok.AllArgsConstructor +import lombok.Getter + +@Getter +@AllArgsConstructor +class ApplyTaskException( + override val message: String, + val code: Int +) : RuntimeException(message) + + diff --git a/src/main/kotlin/org/tenten/bittakotlin/apply/repository/ApplyRepository.kt b/src/main/kotlin/org/tenten/bittakotlin/apply/repository/ApplyRepository.kt new file mode 100644 index 0000000..263ece6 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/apply/repository/ApplyRepository.kt @@ -0,0 +1,30 @@ +package org.tenten.bittakotlin.apply.repository + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.stereotype.Repository +import org.tenten.bittakotlin.apply.dto.ApplyDTO +import org.tenten.bittakotlin.apply.entity.Apply +import org.tenten.bittakotlin.jobpost.entity.JobPost +import org.tenten.bittakotlin.profile.entity.Profile +import java.util.* + +@Repository +interface ApplyRepository : JpaRepository { + + @Query("SELECT a FROM Apply a WHERE a.profile = :profile") + fun findAllByMember(@Param("profile") profile: Profile): List + + @Query("SELECT a FROM Apply a WHERE a.jobPost.id = :jobPostId") + fun findAllByJobPostId(@Param("jobPostId") jobPostId: Long): List + + @Query("SELECT a FROM Apply a WHERE a.id = :id") + fun getApplyDTO(@Param("id") id: Long): Optional + + @Query("SELECT a FROM Apply a WHERE a.jobPost = :jobPost") + fun findAllByJobPost(@Param("jobPost") jobPost: JobPost): List + + @Query("SELECT COUNT(a) FROM Apply a WHERE a.jobPost.id = :jobPostId") + fun countByJobPostId(@Param("jobPostId") jobPostId: Long): Long +} diff --git a/src/main/kotlin/org/tenten/bittakotlin/apply/service/ApplyService.kt b/src/main/kotlin/org/tenten/bittakotlin/apply/service/ApplyService.kt new file mode 100644 index 0000000..6619af1 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/apply/service/ApplyService.kt @@ -0,0 +1,25 @@ +package org.tenten.bittakotlin.apply.service + +import org.tenten.bittakotlin.apply.dto.ApplyDTO +import org.tenten.bittakotlin.apply.entity.ApplyStatus +import org.tenten.bittakotlin.profile.entity.Profile + +interface ApplyService { + fun readAll(profile: Profile): List? + + fun register(applyDTO: ApplyDTO): Map + + fun delete(id: Long) + + fun read(id: Long): ApplyDTO + + fun getApplyIntoJobPost(jobPostId: Long, profileId: Long): List + + fun findById(id: Long): ApplyDTO + + fun getApplyCount(jobPostId: Long): Long + + fun applyStatusUpdate(applyId: Long, applyStatus: ApplyStatus, profileId: Long) +} + + diff --git a/src/main/kotlin/org/tenten/bittakotlin/apply/service/ApplyServiceImpl.kt b/src/main/kotlin/org/tenten/bittakotlin/apply/service/ApplyServiceImpl.kt new file mode 100644 index 0000000..55dd906 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/apply/service/ApplyServiceImpl.kt @@ -0,0 +1,128 @@ +package org.tenten.bittakotlin.apply.service + +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import org.tenten.bittakotlin.apply.dto.ApplyDTO +import org.tenten.bittakotlin.apply.entity.Apply +import org.tenten.bittakotlin.apply.entity.ApplyStatus +import org.tenten.bittakotlin.apply.exception.ApplyException +import org.tenten.bittakotlin.apply.repository.ApplyRepository +import org.tenten.bittakotlin.jobpost.exception.JobPostException +import org.tenten.bittakotlin.jobpost.repository.JobPostRepository +import org.tenten.bittakotlin.jobpost.util.JobPostProvider +import org.tenten.bittakotlin.profile.entity.Profile + +@Service +class ApplyServiceImpl( + private val applyRepository: ApplyRepository, + private val jobPostRepository: JobPostRepository, +// private val memberProvider: MemberProvider, + private val jobPostProvider: JobPostProvider +) : ApplyService { + + private val log = LoggerFactory.getLogger(this::class.java) + + @Transactional + override fun readAll(profile: Profile): List? { + val applies = applyRepository.findAllByMember(profile) + return if (applies.isEmpty()) null else applies.map { entityToDto(it) } + } + + @Transactional + override fun register(applyDTO: ApplyDTO): Map { + return try { + var apply = dtoToEntity(applyDTO) + apply = applyRepository.save(apply) + + val jobPost = apply.jobPost + jobPost!!.plusApplyCount() + jobPostRepository.save(jobPost) + + mapOf( + "message" to "${apply.profile!!.nickname}님 지원 완료", + "data" to entityToDto(apply) + ) + } catch (e: Exception) { + log.error(e.message) + throw ApplyException.NOT_REGISTERED.get() + } + } + + @Transactional + override fun delete(id: Long) { + val apply = applyRepository.findById(id).orElseThrow { ApplyException.NOT_FOUND.get() } + + val jobPost = apply.jobPost + if (apply.profile != null) { + apply.profile = null + } + if (apply.jobPost != null) { + jobPost!!.minusApplyCount() + jobPostRepository.save(jobPost) + apply.jobPost = null + } + applyRepository.delete(apply) + } + + @Transactional + override fun read(id: Long): ApplyDTO { + val applyDTO = applyRepository.getApplyDTO(id).map { entityToDto(it) }.orElseThrow{ApplyException.NOT_FOUND.get()} + return applyDTO + } + + override fun getApplyIntoJobPost(jobPostId: Long, profileId: Long): List { + val jobPost = jobPostRepository.findById(jobPostId).orElseThrow { JobPostException.NOT_FOUND.get() } + + if (jobPost.profile!!.id != profileId) { + throw JobPostException.BAD_REQUEST.get() + } + + val applies = applyRepository.findAllByJobPost(jobPost) + return applies//.map { entityToDto(it) } + } + + @Transactional + override fun findById(id: Long): ApplyDTO { + val applyDTO = applyRepository.findById(id) + return applyDTO.map { entityToDto(it) }.orElseThrow { ApplyException.NOT_FOUND.get() } + } + + override fun getApplyCount(jobPostId: Long): Long { + return applyRepository.countByJobPostId(jobPostId) + } + + override fun applyStatusUpdate(applyId: Long, applyStatus: ApplyStatus, profileId: Long) { + val apply = applyRepository.findById(applyId).orElseThrow { ApplyException.NOT_FOUND.get() } + + val jobPostOwnerId = apply.jobPost!!.profile!!.id + if (jobPostOwnerId != profileId) { + throw ApplyException.NOT_FOUND.get() + } + + apply.status = applyStatus + applyRepository.save(apply) + } + + private fun dtoToEntity(applyDTO: ApplyDTO): Apply { + return Apply( + id = applyDTO.id, +// profile = profileProvider.getById(applyDTO.profileId!!), + jobPost = jobPostProvider.getById(applyDTO.jobPostId!!), + appliedAt = applyDTO.appliedAt + ) + } + + private fun entityToDto(apply: Apply): ApplyDTO { + val profileId = apply.profile?.id ?: throw ApplyException.NOT_FOUND.get() + val jobPostId = apply.jobPost?.id ?: throw ApplyException.NOT_FOUND.get() + + return ApplyDTO( + id = apply.id, + profileId = profileId, + jobPostId = jobPostId, + appliedAt = apply.appliedAt + ) + } +} + diff --git a/src/main/kotlin/org/tenten/bittakotlin/apply/util/ApplyProvider.kt b/src/main/kotlin/org/tenten/bittakotlin/apply/util/ApplyProvider.kt new file mode 100644 index 0000000..429796f --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/apply/util/ApplyProvider.kt @@ -0,0 +1,20 @@ +package org.tenten.bittakotlin.apply.util + +import lombok.RequiredArgsConstructor +import org.tenten.bittakotlin.apply.entity.Apply +import org.tenten.bittakotlin.apply.repository.ApplyRepository + +import org.springframework.stereotype.Component + +@Component +@RequiredArgsConstructor +class ApplyProvider( + private val applyRepository: ApplyRepository +) { + + fun getAllByJobPost(jobPostId: Long): List? { + val applies = applyRepository.findAllByJobPostId(jobPostId) + return applies.ifEmpty { null } + } +} + diff --git a/src/main/kotlin/org/tenten/bittakotlin/global/dto/PageRequestDTO.kt b/src/main/kotlin/org/tenten/bittakotlin/global/dto/PageRequestDTO.kt new file mode 100644 index 0000000..594a381 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/global/dto/PageRequestDTO.kt @@ -0,0 +1,23 @@ +package org.tenten.bittakotlin.global.dto + +import jakarta.validation.constraints.Max +import jakarta.validation.constraints.Min +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Pageable +import org.springframework.data.domain.Sort + +data class PageRequestDTO( + @field:Min(1) + var page: Int = 1, + + @field:Min(10) + @field:Max(100) + var size: Int = 10 +) { + fun getPageable(sort: Sort): Pageable { + val pageNum = (page - 1).coerceAtLeast(0) + val sizeNum = size.coerceAtLeast(10) + + return PageRequest.of(pageNum, sizeNum, sort) + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/tenten/bittakotlin/jobpost/controller/JobPostController.kt b/src/main/kotlin/org/tenten/bittakotlin/jobpost/controller/JobPostController.kt new file mode 100644 index 0000000..2e3db4d --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/jobpost/controller/JobPostController.kt @@ -0,0 +1,403 @@ +package org.tenten.bittakotlin.jobpost.controller + +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.tags.Tag +import jakarta.validation.Valid +import lombok.RequiredArgsConstructor +import lombok.extern.log4j.Log4j2 +import org.springframework.data.domain.Page +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.ModelAttribute +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import org.tenten.bittakotlin.apply.repository.ApplyRepository +import org.tenten.bittakotlin.apply.service.ApplyService +import org.tenten.bittakotlin.global.dto.PageRequestDTO +import org.tenten.bittakotlin.jobpost.dto.JobPostDTO +import org.tenten.bittakotlin.jobpost.repository.JobPostRepository +import org.tenten.bittakotlin.jobpost.service.JobPostService + +@Tag(name = "일거리 API 컨트롤러", description = "일거리와 관련된 REST API를 제공하는 컨트롤러입니다.") +@RestController +@RequestMapping("/api/v1/job-post") +@Log4j2 +@RequiredArgsConstructor +class JobPostController( + private val jobPostService: JobPostService, + private val applyService: ApplyService, + private val jobPostRepository: JobPostRepository, + private val applyRepository: ApplyRepository +) { + +// @Operation( +// summary = "전체 일거리 조회", +// description = "전체 일거리를 조회합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "일거리를 성공적으로 조회했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_SUCCESS_READ_ALL) +// )] +// ), +// ApiResponse( +// responseCode = "404", +// description = "일거리가 존재하지 않습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_FAILURE_NOT_FOUND) +// )] +// ) +// ] +// ) + @GetMapping + fun getList(@Valid pageRequestDTO: PageRequestDTO): ResponseEntity> = + ResponseEntity.ok(jobPostService.getList(pageRequestDTO)) + +// @Operation( +// summary = "일거리 등록", +// description = "일거리를 등록합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "일거리를 성공적으로 등록했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_SUCCESS_REGISTER) +// )] +// ), +// ApiResponse( +// responseCode = "400", +// description = "일거리 등록에 실패했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_FAILURE_NOT_REGISTERED) +// )] +// ) +// ] +// ) + @PostMapping + fun registerJobPost(@RequestBody jobPostDTO: JobPostDTO): ResponseEntity = + ResponseEntity.ok(jobPostService.register(jobPostDTO)) + +// @Operation( +// summary = "단일 일거리 조회", +// description = "ID와 일치하는 일거리를 조회합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "일거리를 성공적으로 조회했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_SUCCESS_READ) +// )] +// ), +// ApiResponse( +// responseCode = "404", +// description = "일거리가 존재하지 않습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_FAILURE_NOT_FOUND) +// )] +// ) +// ] +// ) +// @Parameter( +// name = "id", +// description = "조회할 일거리의 ID", +// required = true, +// example = "1", +// schema = Schema(type = "integer") +// ) + @GetMapping("/{id}") + fun readJobPost(@PathVariable("id") @Valid id: Long): ResponseEntity = + ResponseEntity.ok(jobPostService.read(id)) + +// @Operation( +// summary = "일거리 수정", +// description = "ID와 일치하는 일거리를 수정합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "일거리를 성공적으로 수정했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_SUCCESS_MODIFY) +// )] +// ), +// ApiResponse( +// responseCode = "400", +// description = "일거리 수정에 실패했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_FAILURE_NOT_MODIFIED) +// )] +// ), +// ApiResponse( +// responseCode = "404", +// description = "일거리가 존재하지 않습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_FAILURE_NOT_FOUND) +// )] +// ) +// ] +// ) +// @Parameter( +// name = "id", +// description = "수정할 일거리의 ID", +// required = true, +// example = "1", +// schema = Schema(type = "integer") +// ) + @PutMapping("/{id}") + fun modifyJobPost(@RequestBody jobPostDTO: JobPostDTO, @Valid @PathVariable("id") id: Long): ResponseEntity = + ResponseEntity.ok(jobPostService.modify(jobPostDTO)) + +// @Operation( +// summary = "일거리 삭제", +// description = "ID와 일치하는 일거리를 삭제합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "일거리를 삭제했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_SUCCESS_DELETE) +// )] +// ), +// ApiResponse( +// responseCode = "400", +// description = "일거리 삭제에 실패했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_FAILURE_NOT_REMOVED) +// )] +// ), +// ApiResponse( +// responseCode = "404", +// description = "삭제할 일거리가 존재하지 않습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_FAILURE_NOT_FOUND) +// )] +// ) +// ] +// ) +// @Parameter( +// name = "id", +// description = "삭제할 일거리의 ID", +// required = true, +// example = "1", +// schema = Schema(type = "integer") +// ) + @DeleteMapping("/{id}") + fun deleteJobPost(@Valid @PathVariable("id") id: Long): ResponseEntity> { + jobPostService.removeJobPost(id) + return ResponseEntity.ok(mapOf("message" to "삭제가 완료되었습니다")) + } + +// @Operation( +// summary = "작성자의 다른 게시물 조회", +// description = "작성자의 ID와 동일한 게시물을 조회합니다", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "작성자의 게시물 조회에 성공했습니다", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_SUCCESS_READ) +// )] +// ), +// ApiResponse( +// responseCode = "404", +// description = "작성자의 게시물을 찾을 수 없습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_FAILURE_NOT_FOUND) +// )] +// ) +// ] +// ) +// @Parameter( +// name = "profileId", +// description = "작성자의 ID", +// required = true, +// example = "1", +// schema = Schema(type = "integer") +// ) + @GetMapping("/member/{profileId}") + fun getJobPostByMember( + @PathVariable profileId: Long, + @ModelAttribute pageRequestDTO: PageRequestDTO + ): ResponseEntity> { + val result = jobPostService.getJobPostByMember(profileId, pageRequestDTO) + return ResponseEntity.ok(result) + } + +// @Operation( +// summary = "키워드 검색", +// description = "키워드가 포함된 게시물을 검색합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "검색 결과를 성공적으로 조회했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_SUCCESS_READ) +// )] +// ), +// ApiResponse( +// responseCode = "404", +// description = "검색 결과가 존재하지 않습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_FAILURE_NOT_FOUND) +// )] +// ) +// ] +// ) +// @Parameter( +// name = "keyword", +// description = "검색할 키워드", +// required = true, +// example = "1", +// schema = Schema(type = "string") +// ) + @GetMapping("/search/{keyword}") + fun searchJobPost( + @PathVariable("keyword") keyword: String, + @ModelAttribute pageRequestDTO: PageRequestDTO + ): ResponseEntity> { + val result = jobPostService.searchJobPosts(keyword, pageRequestDTO) + return ResponseEntity.ok(result) + } + +// @Operation( +// summary = "게시물의 지원 수 조회", +// description = "게시물에 해당하는 지원서의 수를 조회합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "지원서 수를 성공적으로 조회했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_SUCCESS_READ) +// )] +// ), +// ApiResponse( +// responseCode = "404", +// description = "지원서가 존재하지 않습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_FAILURE_NOT_FOUND) +// )] +// ) +// ] +// ) +// @Parameter( +// name = "id", +// description = "지원서의 해당 일거리 ID", +// required = true, +// example = "1", +// schema = Schema(type = "integer") +// ) + @GetMapping("/{id}/applyCount") + fun getApplyCount(@PathVariable id: Long): ResponseEntity { + val applyCount = applyService.getApplyCount(id) + return ResponseEntity.ok(applyCount) + } + +// @Operation( +// summary = "남은 일자 기준 오름차순 조회", +// description = "남은 일자가 적은 순으로 전체 일거리를 조회합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "일거리를 성공적으로 조회했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_SUCCESS_READ) +// )] +// ), +// ApiResponse( +// responseCode = "404", +// description = "일거리가 존재하지 않습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_FAILURE_NOT_FOUND) +// )] +// ) +// ] +// ) + @GetMapping("/sortByRestDate") + fun getSortByDay(@ModelAttribute pageRequestDTO: PageRequestDTO): ResponseEntity> = + ResponseEntity.ok(jobPostService.getSortByDay(pageRequestDTO)) + +// @Operation( +// summary = "지원 수 기준 오름차순 조회", +// description = "지원 수가 많은 순으로 전체 일거리를 조회합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "일거리를 성공적으로 조회했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_SUCCESS_READ) +// )] +// ), +// ApiResponse( +// responseCode = "404", +// description = "일거리가 존재하지 않습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_FAILURE_NOT_FOUND) +// )] +// ) +// ] +// ) + @GetMapping("/sortByApplyCount") + fun getSortByApplyCount(@ModelAttribute pageRequestDTO: PageRequestDTO): ResponseEntity> = + ResponseEntity.ok(jobPostService.getSortByApplyCount(pageRequestDTO)) + +// @Operation( +// summary = "생성 일자 기준 오름차순 조회", +// description = "생성 일자가 이른 순으로 전체 일거리를 조회합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "일거리를 성공적으로 조회했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_SUCCESS_READ) +// )] +// ), +// ApiResponse( +// responseCode = "404", +// description = "일거리가 존재하지 않습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = JOB_POST_FAILURE_NOT_FOUND) +// )] +// ) +// ] +// ) + @GetMapping("/sortByCreatedAt") + fun getSortByCreatedAt(@ModelAttribute pageRequestDTO: PageRequestDTO): ResponseEntity> = + ResponseEntity.ok(jobPostService.getSortByCreatedAt(pageRequestDTO)) +} + + + + diff --git a/src/main/kotlin/org/tenten/bittakotlin/jobpost/controller/JobPostViewController.kt b/src/main/kotlin/org/tenten/bittakotlin/jobpost/controller/JobPostViewController.kt new file mode 100644 index 0000000..0557268 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/jobpost/controller/JobPostViewController.kt @@ -0,0 +1,17 @@ +package org.tenten.bittakotlin.jobpost.controller + +import org.springframework.stereotype.Controller +import org.springframework.ui.Model +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping + +@Controller +@RequestMapping("/jobpost") +class JobPostViewController { + @GetMapping + fun showJobpostPage(model: Model?): String { + return "jobpost/jobpost" + } +} + + diff --git a/src/main/kotlin/org/tenten/bittakotlin/jobpost/controller/advice/JobPostControllerAdvice.kt b/src/main/kotlin/org/tenten/bittakotlin/jobpost/controller/advice/JobPostControllerAdvice.kt new file mode 100644 index 0000000..77ba032 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/jobpost/controller/advice/JobPostControllerAdvice.kt @@ -0,0 +1,15 @@ +package org.tenten.bittakotlin.jobpost.controller.advice + +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.RestControllerAdvice +import org.tenten.bittakotlin.jobpost.exception.JobPostTaskException + +@RestControllerAdvice +class JobPostControllerAdvice { + @ExceptionHandler(JobPostTaskException::class) + fun handleArgsException(e: JobPostTaskException): ResponseEntity> { + return ResponseEntity.status(e.code) + .body(mapOf("error" to e.message)) + } +} diff --git a/src/main/kotlin/org/tenten/bittakotlin/jobpost/dto/JobPostDTO.kt b/src/main/kotlin/org/tenten/bittakotlin/jobpost/dto/JobPostDTO.kt new file mode 100644 index 0000000..8d5a651 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/jobpost/dto/JobPostDTO.kt @@ -0,0 +1,70 @@ +package org.tenten.bittakotlin.jobpost.dto + +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull +import lombok.AllArgsConstructor +import lombok.Builder +import lombok.Data +import org.tenten.bittakotlin.jobpost.entity.PayStatus +import org.tenten.bittakotlin.jobpost.entity.WorkCategory +import java.time.LocalDate +import java.time.LocalDateTime + +@Data +@Builder +@AllArgsConstructor +@Schema(title = "일거리 DTO", description = "일거리의 요청 및 응답에 사용하는 DTO입니다.") +data class JobPostDTO( + @Schema(title = "일거리 ID (PK)", description = "일거리의 고유 ID 입니다.", example = "1") + @Min(1) + val id: Long? = null, + + @Schema(title = "회원 ID (FK)", description = "회원의 고유 ID 입니다.", example = "1") + @Min(1) + @field:NotNull(message = "회원 ID가 필요합니다") + val profileId: Long? = null, + + @Schema(title = "일거리 제목", description = "일거리 제목입니다.", example = "Job Title") + @field:NotBlank(message = "제목 입력은 필수적 입니다") + val title: String? = null, + + @Schema(title = "일거리 내용", description = "일거리 내용입니다.", example = "Job Content") + @field:NotBlank(message = "설명 입력은 필수적 입니다") + val description: String? = null, + + @Schema(title = "출근지", description = "출근 지역입니다.", example = "서울 특별시 광진구일대") + @field:NotNull(message = "지역명은 필수적으로 입력해야 합니다") + val location: String? = null, + + @Schema(title = "지불 유형", description = "지불 유형입니다.", example = "FREE") + @field:NotNull(message = "급여 여부는 필수적으로 입력해야 합니다") + val payStatus: PayStatus? = null, + + @Schema(title = "일거리 등록일시", description = "일거리가 등록된 날짜 및 시간입니다.", example = "2023-09-24T14:45:00") + val createdAt: LocalDateTime? = null, + + @Schema(title = "일거리 수정일시", description = "일거리가 수정된 날짜 및 시간입니다.", example = "2023-09-24T14:45:00") + val updateAt: LocalDateTime? = null, + + @Schema(title = "촬영 방법", description = "일거리의 진행 방식입니다.", example = "SNAPSHOT") + @field:NotNull(message = "작품 카테고리는 필수적으로 입력해야 합니다") + val workCategory: WorkCategory? = null, + + @Schema(title = "오디션일", description = "오디션을 진행하는 날짜입니다.", example = "2023-09-24") + val auditionDate: LocalDate? = null, + + @Schema(title = "촬영 시작일", description = "일이 시작하는 날짜입니다.", example = "2023-09-24") + val startDate: LocalDate? = null, + + @Schema(title = "촬영 종료일", description = "일이 종료되는 날짜입니다.", example = "2023-09-24") + val endDate: LocalDate? = null, + + @Schema(title = "모집 종료일", description = "모집 종료 일자입니다.", example = "2024-09-24") + val closeDate: LocalDate? = null, + + @Schema(title = "마감까지 남은 일자", description = "모집 종료까지 남은 일자입니다.", example = "38") + val restDate: Int? = null +) + diff --git a/src/main/kotlin/org/tenten/bittakotlin/jobpost/entity/JobPost.kt b/src/main/kotlin/org/tenten/bittakotlin/jobpost/entity/JobPost.kt new file mode 100644 index 0000000..be0a26d --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/jobpost/entity/JobPost.kt @@ -0,0 +1,96 @@ +package org.tenten.bittakotlin.jobpost.entity + +import jakarta.persistence.Entity +import jakarta.persistence.EntityListeners +import jakarta.persistence.Enumerated +import jakarta.persistence.FetchType +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.JoinColumn +import jakarta.persistence.Table +import org.springframework.data.annotation.CreatedDate +import org.springframework.data.annotation.LastModifiedDate +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import org.tenten.bittakotlin.apply.entity.Apply +import java.time.LocalDate + +import jakarta.persistence.* +import lombok.* +import org.tenten.bittakotlin.like.entity.Like +import org.tenten.bittakotlin.profile.entity.Profile +import java.time.LocalDateTime + +@Data +@Entity +@Getter +@ToString +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "job_post") +@EntityListeners(AuditingEntityListener::class) +class JobPost( + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "profile_id", nullable = false) + val profile: Profile? = null, // 게시글 작성자 + + @Column(length = 100, nullable = false) + var title: String, // 게시글 제목 + + @Column(length = 500, nullable = false) + var description: String, // 설명 + + @Column(length = 200, nullable = false) + var location: String, // 촬영 지역 + + @Enumerated(EnumType.STRING) + var payStatus: PayStatus? = null, // 급여 방식 + + @CreatedDate + val createdAt: LocalDateTime? = null, // 게시글 생성일자 + + @LastModifiedDate + val updatedAt: LocalDateTime? = null, // 게시글 수정일자 + + @Enumerated(EnumType.STRING) + var workCategory: WorkCategory? = null, // 작품 카테고리 + + val auditionDate: LocalDate? = null, // 오디션 일자 + + val startDate: LocalDate? = null, // 촬영 기간 시작일 + val endDate: LocalDate? = null, // 촬영 기간 종료일 + + val closeDate: LocalDate? = null, // 게시글 마감 일자 + + var restDate: Int? = null, // 마감까지 남은 일자 + + var applyCount: Int = 0, // 해당 게시글에 지원한 수 + +// @OneToOne(mappedBy = "jobPost", cascade = [CascadeType.REMOVE], orphanRemoval = true) +// var media: Media? = null, + + @OneToMany(mappedBy = "jobPost", fetch = FetchType.EAGER, cascade = [CascadeType.REMOVE], orphanRemoval = true) + val apply: List = mutableListOf(), + + @OneToMany(mappedBy = "jobPost", fetch = FetchType.EAGER, cascade = [CascadeType.REMOVE], orphanRemoval = true) + val likes: List = mutableListOf() +) { + + fun plusApplyCount() { + this.applyCount++ + } + + fun minusApplyCount() { + if (this.applyCount > 0) { + this.applyCount-- + } + } +} + + + diff --git a/src/main/kotlin/org/tenten/bittakotlin/jobpost/entity/PayStatus.kt b/src/main/kotlin/org/tenten/bittakotlin/jobpost/entity/PayStatus.kt new file mode 100644 index 0000000..dff79b5 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/jobpost/entity/PayStatus.kt @@ -0,0 +1,6 @@ +package org.tenten.bittakotlin.jobpost.entity + +enum class PayStatus { + FREE, PAID // 모집 방식 (무급, 유급) +} + diff --git a/src/main/kotlin/org/tenten/bittakotlin/jobpost/entity/WorkCategory.kt b/src/main/kotlin/org/tenten/bittakotlin/jobpost/entity/WorkCategory.kt new file mode 100644 index 0000000..cb87cae --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/jobpost/entity/WorkCategory.kt @@ -0,0 +1,20 @@ +package org.tenten.bittakotlin.jobpost.entity + +enum class WorkCategory { + FEATURE_FILM, // 장편 영화 + SHORT_FILM, // 단편 영화 + FEATURE_WEBDRAMA, // 장편 웹드라마 + SHORT_WEBDRAMA, // 단편 웹드라마 + MUSIC_VIDEO, // 뮤직비디오 + COMMERCIAL, // 광고 + DOCUMENTARY, // 다큐멘터리 + PHOTOGRAPHY, // 사진 촬영 + COVER_MODEL, // 표지 모델 + SNAPSHOT, // 스냅샷 + PHOTO_SHOOT, // 화보 촬영 + FASHION_FILM, // 패션 필름 + VLOG, // 브이로그 + INTERVIEW, // 인터뷰 영상 + EVENT_VIDEO, // 행사 영상 + YOUTUBE // 유튜브 섭외 +} diff --git a/src/main/kotlin/org/tenten/bittakotlin/jobpost/exception/JobPostException.kt b/src/main/kotlin/org/tenten/bittakotlin/jobpost/exception/JobPostException.kt new file mode 100644 index 0000000..012f395 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/jobpost/exception/JobPostException.kt @@ -0,0 +1,15 @@ +package org.tenten.bittakotlin.jobpost.exception + +enum class JobPostException(private val jobPostTaskException: JobPostTaskException) { + BAD_REQUEST("잘못된 접근입니다", 400), + NOT_FOUND("게시글을 찾을 수 없습니다", 404), + NOT_REGISTERED("게시글을 등록할 수 없습니다", 400), + NOT_MODIFIED("게시글을 수정할 수 없습니다", 400), + NOT_REMOVED("게시글을 삭제할 수 없습니다", 400), + NOT_FETCHED("게시글을 조회할 수 없습니다", 400); + + constructor(message: String, code: Int) : this(JobPostTaskException(message, code)) + + fun get(): JobPostTaskException = jobPostTaskException +} + diff --git a/src/main/kotlin/org/tenten/bittakotlin/jobpost/exception/JobPostTaskException.kt b/src/main/kotlin/org/tenten/bittakotlin/jobpost/exception/JobPostTaskException.kt new file mode 100644 index 0000000..e3a36a8 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/jobpost/exception/JobPostTaskException.kt @@ -0,0 +1,11 @@ +package org.tenten.bittakotlin.jobpost.exception + +import lombok.AllArgsConstructor +import lombok.Getter + +@Getter +@AllArgsConstructor +class JobPostTaskException ( + override val message: String, + val code: Int +) : RuntimeException(message) diff --git a/src/main/kotlin/org/tenten/bittakotlin/jobpost/repository/JobPostRepository.kt b/src/main/kotlin/org/tenten/bittakotlin/jobpost/repository/JobPostRepository.kt new file mode 100644 index 0000000..4d171f7 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/jobpost/repository/JobPostRepository.kt @@ -0,0 +1,34 @@ +package org.tenten.bittakotlin.jobpost.repository + +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import org.springframework.data.repository.query.Param +import org.tenten.bittakotlin.jobpost.entity.JobPost +import java.util.Optional + +interface JobPostRepository : JpaRepository { + + @Query("SELECT j FROM JobPost j WHERE j.id = :id") + fun getJobPost(@Param("id") id: Long): Optional + + @Query("SELECT j FROM JobPost j ORDER BY j.id DESC") + fun getList(pageable: Pageable): Page + + @Query("SELECT j FROM JobPost j WHERE j.profile.id = :profileId") + fun findJobPostByMember(@Param("profileId") profileId: Long, pageable: Pageable): Page + + @Query("SELECT j FROM JobPost j WHERE LOWER(j.title) LIKE LOWER(CONCAT('%', :keyword, '%')) OR LOWER(j.description) LIKE LOWER(CONCAT('%', :keyword, '%'))") + fun searchByKeyword(@Param("keyword") keyword: String, pageable: Pageable): Page + + @Query("SELECT j FROM JobPost j WHERE j.restDate IS NOT NULL ORDER BY j.restDate ASC") + fun findSortedByRestDate(pageable: Pageable): Page + + @Query("SELECT j FROM JobPost j WHERE j.applyCount IS NOT NULL ORDER BY j.applyCount DESC") + fun findSortedByApplyCountDesc(pageable: Pageable): Page + + @Query("SELECT j FROM JobPost j WHERE j.createdAt IS NOT NULL ORDER BY j.createdAt ASC") + fun findSortedByCreatedAt(pageable: Pageable): Page +} + diff --git a/src/main/kotlin/org/tenten/bittakotlin/jobpost/service/DayScheduler.kt b/src/main/kotlin/org/tenten/bittakotlin/jobpost/service/DayScheduler.kt new file mode 100644 index 0000000..1b980eb --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/jobpost/service/DayScheduler.kt @@ -0,0 +1,28 @@ +package org.tenten.bittakotlin.jobpost.service + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Service +import org.tenten.bittakotlin.jobpost.repository.JobPostRepository +import java.time.LocalDate +import java.time.temporal.ChronoUnit + +@Service +class DayScheduler @Autowired constructor( + private val jobPostRepository: JobPostRepository +) { + + @Scheduled(cron = "0 0 0 * * ?") // 자정마다 실행 + fun checkDday() { + val posts = jobPostRepository.findAll() + + for (post in posts) { + val today = LocalDate.now() + val endDate = post.endDate + val restDate = ChronoUnit.DAYS.between(today, endDate).toInt() + + post.restDate = restDate + jobPostRepository.save(post) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/tenten/bittakotlin/jobpost/service/JobPostService.kt b/src/main/kotlin/org/tenten/bittakotlin/jobpost/service/JobPostService.kt new file mode 100644 index 0000000..3134c75 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/jobpost/service/JobPostService.kt @@ -0,0 +1,29 @@ +package org.tenten.bittakotlin.jobpost.service + +import org.springframework.data.domain.Page +import org.tenten.bittakotlin.global.dto.PageRequestDTO +import org.tenten.bittakotlin.jobpost.dto.JobPostDTO + +interface JobPostService { + + fun register(jobPostDTO: JobPostDTO): JobPostDTO + + fun read(id: Long): JobPostDTO + + fun modify(jobPostDTO: JobPostDTO): JobPostDTO + + fun getList(pageRequestDTO: PageRequestDTO): Page + + fun removeJobPost(jobPostId: Long) + + fun getJobPostByMember(memberId: Long, pageRequestDTO: PageRequestDTO): Page + + fun searchJobPosts(keyword: String, pageRequestDTO: PageRequestDTO): Page + + fun getSortByDay(pageRequestDTO: PageRequestDTO): Page + + fun getSortByApplyCount(pageRequestDTO: PageRequestDTO): Page + + fun getSortByCreatedAt(pageRequestDTO: PageRequestDTO): Page +} + diff --git a/src/main/kotlin/org/tenten/bittakotlin/jobpost/service/JobPostServiceImpl.kt b/src/main/kotlin/org/tenten/bittakotlin/jobpost/service/JobPostServiceImpl.kt new file mode 100644 index 0000000..8e432f8 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/jobpost/service/JobPostServiceImpl.kt @@ -0,0 +1,179 @@ +package org.tenten.bittakotlin.jobpost.service + +import org.hibernate.query.sqm.tree.SqmNode.log +import org.springframework.data.domain.PageRequest +import org.tenten.bittakotlin.apply.repository.ApplyRepository +import org.tenten.bittakotlin.apply.util.ApplyProvider +import org.tenten.bittakotlin.jobpost.dto.JobPostDTO +import org.tenten.bittakotlin.jobpost.entity.JobPost +import org.tenten.bittakotlin.jobpost.exception.JobPostException +import org.tenten.bittakotlin.jobpost.repository.JobPostRepository + +import org.springframework.data.domain.Page +import org.springframework.data.domain.Sort +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import org.tenten.bittakotlin.apply.dto.ApplyDTO +import org.tenten.bittakotlin.apply.entity.Apply +import org.tenten.bittakotlin.global.dto.PageRequestDTO +import org.tenten.bittakotlin.profile.repository.ProfileRepository +import org.tenten.bittakotlin.profile.service.ProfileProvider + +@Service +class JobPostServiceImpl( + private val jobPostRepository: JobPostRepository, + private val profileProvider: ProfileProvider, + private val applyProvider: ApplyProvider, +// private val mediaService: MediaService, + private val applyRepository: ApplyRepository, + private val profileRepository: ProfileRepository +) : JobPostService { + + @Transactional + override fun getList(pageRequestDTO: PageRequestDTO): Page { + val sort = Sort.by("id").descending() + val pageable = pageRequestDTO.getPageable(sort) + val jobPosts = jobPostRepository.getList(pageable) + return jobPosts.map { entityToDto(it) } + } + + @Transactional + override fun register(jobPostDTO: JobPostDTO): JobPostDTO { + return try { + var jobPost = dtoToEntity(jobPostDTO) + jobPost = jobPostRepository.save(jobPost) + entityToDto(jobPost) + } catch (e: Exception) { + log.error(e.message) + throw JobPostException.NOT_REGISTERED.get() + } + } + + @Transactional + override fun read(jobPostId: Long): JobPostDTO { + val jobPost = jobPostRepository.getJobPost(jobPostId) + return jobPost.map { entityToDto(it) }.orElseThrow { JobPostException.NOT_REGISTERED.get() } + } + + @Transactional + override fun modify(jobPostDTO: JobPostDTO): JobPostDTO { + val jobPost = jobPostRepository.findById(jobPostDTO.id!!) + .orElseThrow { JobPostException.NOT_FOUND.get() } + + return try { + jobPost.title = jobPostDTO.title!! + jobPost.description = jobPostDTO.description!! + jobPost.location = jobPostDTO.location!! + jobPost.payStatus = jobPostDTO.payStatus + jobPost.workCategory = jobPostDTO.workCategory + + jobPostRepository.save(jobPost) + entityToDto(jobPost) + } catch (e: Exception) { + log.error(e.message) + throw JobPostException.NOT_MODIFIED.get() + } + } + + @Transactional + override fun removeJobPost(jobPostId: Long) { + val jobPost = jobPostRepository.findById(jobPostId).orElseThrow { JobPostException.NOT_FOUND.get() } + val applies = jobPost.apply + applies?.let { + if (it.isNotEmpty()) { + it.forEach { apply -> apply.jobPost = null } + applyRepository.deleteAllInBatch(it) + } + } +// jobPost.media?.let { +// mediaService.delete(it) +// jobPost.media = null +// } + jobPostRepository.delete(jobPost) + } + + override fun getJobPostByMember(memberId: Long, pageRequestDTO: PageRequestDTO): Page { + val pageable = PageRequest.of(pageRequestDTO.page, pageRequestDTO.size) + val jobPostPage = jobPostRepository.findJobPostByMember(memberId, pageable) + return jobPostPage.map { entityToDto(it) } + } + + override fun searchJobPosts(keyword: String, pageRequestDTO: PageRequestDTO): Page { + val sort = Sort.by("id").descending() + val pageable = pageRequestDTO.getPageable(sort) + val jobPosts = jobPostRepository.searchByKeyword(keyword, pageable) + return jobPosts.map { entityToDto(it) } + } + + @Transactional + override fun getSortByDay(pageRequestDTO: PageRequestDTO): Page { + val sort = Sort.by("restDate").ascending() + val pageable = pageRequestDTO.getPageable(sort) + val jobPosts = jobPostRepository.findSortedByRestDate(pageable) + return jobPosts.map { entityToDto(it) } + } + + @Transactional + override fun getSortByApplyCount(pageRequestDTO: PageRequestDTO): Page { + val sort = Sort.by("applyCount").descending() + val pageable = pageRequestDTO.getPageable(sort) + val jobPosts = jobPostRepository.findSortedByApplyCountDesc(pageable) + return jobPosts.map { entityToDto(it) } + } + + override fun getSortByCreatedAt(pageRequestDTO: PageRequestDTO): Page { + val sort = Sort.by("createdAt").ascending() + val pageable = pageRequestDTO.getPageable(sort) + val jobPosts = jobPostRepository.findSortedByCreatedAt(pageable) + return jobPosts.map { entityToDto(it) } + } + + private fun entityToDto(apply: Apply): ApplyDTO { + return ApplyDTO( + id = apply.id, + profileId = apply.profile?.id, + jobPostId = apply.jobPost?.id, + appliedAt = apply.appliedAt + ) + } + + private fun entityToDto(jobPost: JobPost): JobPostDTO { + return JobPostDTO( + id = jobPost.id, + title = jobPost.title, + description = jobPost.description, + location = jobPost.location, + payStatus = jobPost.payStatus, + closeDate = jobPost.closeDate, + workCategory = jobPost.workCategory, + auditionDate = jobPost.auditionDate, + startDate = jobPost.startDate, + endDate = jobPost.endDate, + updateAt = jobPost.updatedAt, + restDate = jobPost.restDate, + profileId = jobPost.profile?.id + ) + } + + private fun dtoToEntity(jobPostDTO: JobPostDTO): JobPost { + return JobPost( + id = jobPostDTO.id, + title = jobPostDTO.title!!, + description = jobPostDTO.description!!, + location = jobPostDTO.location!!, + payStatus = jobPostDTO.payStatus, + closeDate = jobPostDTO.closeDate, + workCategory = jobPostDTO.workCategory, + auditionDate = jobPostDTO.auditionDate, + startDate = jobPostDTO.startDate, + endDate = jobPostDTO.endDate, + updatedAt = jobPostDTO.updateAt, + restDate = jobPostDTO.restDate, + profile = profileProvider.getById(jobPostDTO.profileId!!)!!, + apply = applyProvider.getAllByJobPost(jobPostDTO.id!!)!! + ) + } +} + + + diff --git a/src/main/kotlin/org/tenten/bittakotlin/jobpost/util/JobPostProvider.kt b/src/main/kotlin/org/tenten/bittakotlin/jobpost/util/JobPostProvider.kt new file mode 100644 index 0000000..8aa55bd --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/jobpost/util/JobPostProvider.kt @@ -0,0 +1,16 @@ +package org.tenten.bittakotlin.jobpost.util + +import org.tenten.bittakotlin.jobpost.entity.JobPost +import org.tenten.bittakotlin.jobpost.repository.JobPostRepository + +import org.springframework.stereotype.Component + +@Component +class JobPostProvider( + private val jobPostRepository: JobPostRepository +) { + + fun getById(id: Long): JobPost? { + return jobPostRepository.findById(id).orElse(null) + } +} diff --git a/src/main/kotlin/org/tenten/bittakotlin/like/controller/LikeController.kt b/src/main/kotlin/org/tenten/bittakotlin/like/controller/LikeController.kt new file mode 100644 index 0000000..c311e3b --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/like/controller/LikeController.kt @@ -0,0 +1,115 @@ +package org.tenten.bittakotlin.like.controller + +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import org.apache.logging.log4j.Logger +import org.apache.logging.log4j.LogManager +import org.tenten.bittakotlin.like.service.LikeService +import org.tenten.bittakotlin.member.dto.MemberNicknameDTO +import org.tenten.bittakotlin.profile.entity.Profile + +@Tag(name = "좋아요 API 컨트롤러", description = "좋아요 기능과 관련된 REST API를 제공하는 컨트롤러입니다") +@RestController +@RequestMapping("/api/v1/like") +class LikeController ( + private val likeService: LikeService +) { + private val log: Logger = LogManager.getLogger(LikeController::class.java) + +// @Operation( +// summary = "좋아요 등록", +// description = "좋아요를 등록합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "좋아요를 성공적으로 등록했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = LIKE_SUCCESS_REGISTER) +// )] +// ), +// ApiResponse( +// responseCode = "400", +// description = "좋아요 등록에 실패했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = LIKE_FAILURE_NOT_REGISTERED) +// )] +// ) +// ] +// ) + @PostMapping("/jobPost/{jobPostId}/member/{profileId}") + fun addLike(@PathVariable jobPostId: Long, @PathVariable profileId: Long): ResponseEntity { + likeService.addLike(jobPostId, profileId) + return ResponseEntity.ok("좋아요를 눌렀습니다") + } + +// @Operation( +// summary = "좋아요 삭제", +// description = "ID와 일치하는 좋아요를 취소합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "좋아요를 취소했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = LIKE_SUCCESS_DELETE) +// )] +// ), +// ApiResponse( +// responseCode = "400", +// description = "좋아요 취소에 실패했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = LIKE_FAILURE_NOT_REMOVED) +// )] +// ), +// ApiResponse( +// responseCode = "404", +// description = "취소할 좋아요가 존재하지 않습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = LIKE_FAILURE_NOT_FOUND) +// )] +// ) +// ] +// ) + @DeleteMapping("/jobPost/{jobPostId}/member/{profileId}") + fun removeLike(@PathVariable jobPostId: Long, @PathVariable profileId: Long): ResponseEntity { + likeService.removeLike(jobPostId, profileId) + return ResponseEntity.ok("좋아요를 취소했습니다") + } + +// @Operation( +// summary = "좋아요를 누른 회원 조회", +// description = "해당 게시물에 좋아요를 누른 회원을 조회합니다.", +// responses = [ +// ApiResponse( +// responseCode = "200", +// description = "좋아요를 누른 회원을 성공적으로 조회했습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = LIKE_SUCCESS_READ_ALL) +// )] +// ), +// ApiResponse( +// responseCode = "404", +// description = "좋아요가 존재하지 않습니다.", +// content = [Content( +// mediaType = "application/json", +// schema = Schema(example = LIKE_FAILURE_NOT_FOUND) +// )] +// ) +// ] +// ) + @GetMapping("/jobPost/{jobPostId}") + fun getLikes(@PathVariable jobPostId: Long): ResponseEntity> { + val members = likeService.getLikesForJobPost(jobPostId) + return ResponseEntity.ok(members) + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/tenten/bittakotlin/like/controller/advice/LikeControllerAdvice.kt b/src/main/kotlin/org/tenten/bittakotlin/like/controller/advice/LikeControllerAdvice.kt new file mode 100644 index 0000000..8f9f0ec --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/like/controller/advice/LikeControllerAdvice.kt @@ -0,0 +1,15 @@ +package org.tenten.bittakotlin.like.controller.advice + +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.RestControllerAdvice +import org.tenten.bittakotlin.jobpost.exception.JobPostTaskException + +@RestControllerAdvice +class LikeControllerAdvice { + @ExceptionHandler(JobPostTaskException::class) + fun handleArgsException(e: JobPostTaskException): ResponseEntity> { + return ResponseEntity.status(e.code) + .body(mapOf("error" to e.message)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/tenten/bittakotlin/like/entity/Like.kt b/src/main/kotlin/org/tenten/bittakotlin/like/entity/Like.kt new file mode 100644 index 0000000..c794ba4 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/like/entity/Like.kt @@ -0,0 +1,34 @@ +package org.tenten.bittakotlin.like.entity + +import jakarta.persistence.Entity +import jakarta.persistence.EntityListeners +import jakarta.persistence.FetchType +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import jakarta.persistence.Table +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import org.tenten.bittakotlin.jobpost.entity.JobPost +import org.tenten.bittakotlin.profile.entity.Profile +import java.time.LocalDateTime + +@Entity +@Table(name = "heart") +@EntityListeners(AuditingEntityListener::class) +data class Like( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "profile_id", nullable = false) + var profile: Profile? = null, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "job_post_id", nullable = false) + var jobPost: JobPost? = null, + + var likedAt: LocalDateTime? = null +) diff --git a/src/main/kotlin/org/tenten/bittakotlin/like/exception/LikeException.kt b/src/main/kotlin/org/tenten/bittakotlin/like/exception/LikeException.kt new file mode 100644 index 0000000..d5fc276 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/like/exception/LikeException.kt @@ -0,0 +1,13 @@ +package org.tenten.bittakotlin.like.exception + +enum class LikeException(private val likeTaskException: LikeTaskException) { + BAD_REQUEST("잘못된 접근입니다", 400), + NOT_FOUND("좋아요를 찾을 수 없습니다", 404), + NOT_REGISTERED("좋아요를 등록할 수 없습니다", 400), + NOT_REMOVED("좋아요를 취소할 수 없습니다", 400), + NOT_FETCHED("좋아요를 누른 회원을 조회할 수 없습니다", 400); + + constructor(message: String, code: Int) : this(LikeTaskException(message, code)) + + fun get(): LikeTaskException = likeTaskException +} \ No newline at end of file diff --git a/src/main/kotlin/org/tenten/bittakotlin/like/exception/LikeTaskException.kt b/src/main/kotlin/org/tenten/bittakotlin/like/exception/LikeTaskException.kt new file mode 100644 index 0000000..c4432aa --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/like/exception/LikeTaskException.kt @@ -0,0 +1,11 @@ +package org.tenten.bittakotlin.like.exception + +import lombok.AllArgsConstructor +import lombok.Getter + +@Getter +@AllArgsConstructor +class LikeTaskException( + override val message: String, + val code: Int +) : RuntimeException(message) \ No newline at end of file diff --git a/src/main/kotlin/org/tenten/bittakotlin/like/repository/LikeRepository.kt b/src/main/kotlin/org/tenten/bittakotlin/like/repository/LikeRepository.kt new file mode 100644 index 0000000..3fb98a1 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/like/repository/LikeRepository.kt @@ -0,0 +1,20 @@ +package org.tenten.bittakotlin.like.repository + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import org.tenten.bittakotlin.jobpost.entity.JobPost +import org.tenten.bittakotlin.like.entity.Like +import org.tenten.bittakotlin.profile.entity.Profile +import java.util.Optional + +interface LikeRepository : JpaRepository { + + @Query("SELECT l FROM Like l WHERE l.jobPost = :jobPost") + fun findByJobPost(jobPost: JobPost): List + + @Query("SELECT CASE WHEN COUNT(l) > 0 THEN true ELSE false END FROM Like l WHERE l.jobPost = :jobPost AND l.profile = :profile") + fun existsByJobPostAndProfile(jobPost: JobPost, profile: Profile): Boolean + + @Query("SELECT l FROM Like l WHERE l.jobPost.id = :jobPostId AND l.profile.id = :profileId") + fun findByJobPostIdAndProfileId(jobPostId: Long, profileId: Long): Optional +} diff --git a/src/main/kotlin/org/tenten/bittakotlin/like/service/LikeService.kt b/src/main/kotlin/org/tenten/bittakotlin/like/service/LikeService.kt new file mode 100644 index 0000000..a502b41 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/like/service/LikeService.kt @@ -0,0 +1,10 @@ +package org.tenten.bittakotlin.like.service + +import org.tenten.bittakotlin.member.dto.MemberNicknameDTO + +interface LikeService { + fun addLike(jobPostId: Long, profileId: Long) + fun getLikesForJobPost(jobPostId: Long): List + fun removeLike(jobPostId: Long, profileId: Long) +} + diff --git a/src/main/kotlin/org/tenten/bittakotlin/like/service/LikeServiceImpl.kt b/src/main/kotlin/org/tenten/bittakotlin/like/service/LikeServiceImpl.kt new file mode 100644 index 0000000..07ec88f --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/like/service/LikeServiceImpl.kt @@ -0,0 +1,57 @@ +package org.tenten.bittakotlin.like.service + +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import org.tenten.bittakotlin.jobpost.exception.JobPostException +import org.tenten.bittakotlin.jobpost.repository.JobPostRepository +import org.tenten.bittakotlin.like.entity.Like +import org.tenten.bittakotlin.like.exception.LikeException +import org.tenten.bittakotlin.like.repository.LikeRepository +import org.tenten.bittakotlin.member.dto.MemberNicknameDTO +import org.tenten.bittakotlin.profile.repository.ProfileRepository +import java.time.LocalDateTime + +@Service +class LikeServiceImpl( + private val likeRepository: LikeRepository, + private val profileRepository: ProfileRepository, + private val jobPostRepository: JobPostRepository +) : LikeService { + + @Transactional + override fun addLike(jobPostId: Long, profileId: Long) { + val jobPost = jobPostRepository.findById(jobPostId).orElseThrow { LikeException.NOT_FOUND.get() } + val profile = profileRepository.findById(profileId).orElseThrow { LikeException.NOT_FOUND.get() } + + if (!likeRepository.existsByJobPostAndProfile(jobPost, profile)) { + val like = Like().apply { + this.jobPost = jobPost + this.profile = profile + this.likedAt = LocalDateTime.now() + } + likeRepository.save(like) + } + } + + @Transactional + override fun getLikesForJobPost(jobPostId: Long): List { + val jobPost = jobPostRepository.findById(jobPostId).orElseThrow { RuntimeException() } + return likeRepository.findByJobPost(jobPost).map { like -> + MemberNicknameDTO(like.profile!!.nickname) + } + } + + @Transactional + override fun removeLike(jobPostId: Long, profileId: Long) { + val like = likeRepository.findByJobPostIdAndProfileId(jobPostId, profileId) + .orElseThrow { JobPostException.NOT_FOUND.get() } as Like + + like.jobPost = null + like.profile = null + + likeRepository.delete(like) + } +} + + + diff --git a/src/main/kotlin/org/tenten/bittakotlin/member/dto/MemberNicknameDTO.kt b/src/main/kotlin/org/tenten/bittakotlin/member/dto/MemberNicknameDTO.kt new file mode 100644 index 0000000..323f817 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/member/dto/MemberNicknameDTO.kt @@ -0,0 +1,5 @@ +package org.tenten.bittakotlin.member.dto + +data class MemberNicknameDTO( + val nickname: String +) diff --git a/src/main/kotlin/org/tenten/bittakotlin/profile/entity/Profile.kt b/src/main/kotlin/org/tenten/bittakotlin/profile/entity/Profile.kt index 7d36cb1..a76beed 100644 --- a/src/main/kotlin/org/tenten/bittakotlin/profile/entity/Profile.kt +++ b/src/main/kotlin/org/tenten/bittakotlin/profile/entity/Profile.kt @@ -2,6 +2,9 @@ package org.tenten.bittakotlin.profile.entity import jakarta.persistence.* +import org.tenten.bittakotlin.apply.entity.Apply +import org.tenten.bittakotlin.jobpost.entity.JobPost +import org.tenten.bittakotlin.like.entity.Like import org.tenten.bittakotlin.member.entity.Member import org.tenten.bittakotlin.profile.constant.Job @@ -13,7 +16,7 @@ class Profile( @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id", nullable = false) - val member: Member, + val member: Member? = null, @Column(length = 20, nullable = false) var nickname: String, @@ -29,5 +32,11 @@ class Profile( var job: Job? = null, @Column(columnDefinition = "JSON") - var socialMedia: String? = null + var socialMedia: String? = null, + + @OneToMany(mappedBy = "profile", fetch = FetchType.EAGER, cascade = [CascadeType.REMOVE], orphanRemoval = true) + val apply: List = mutableListOf(), + + @OneToMany(mappedBy = "profile", fetch = FetchType.EAGER, cascade = [CascadeType.REMOVE], orphanRemoval = true) + val like: List = mutableListOf() ) \ No newline at end of file diff --git a/src/main/kotlin/org/tenten/bittakotlin/profile/service/ProfileProvider.kt b/src/main/kotlin/org/tenten/bittakotlin/profile/service/ProfileProvider.kt new file mode 100644 index 0000000..f9cb54d --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/profile/service/ProfileProvider.kt @@ -0,0 +1,14 @@ +package org.tenten.bittakotlin.profile.service + +import org.springframework.stereotype.Service +import org.tenten.bittakotlin.profile.entity.Profile +import org.tenten.bittakotlin.profile.repository.ProfileRepository + +@Service +class ProfileProvider( + private val profileRepository: ProfileRepository +) { + fun getById(id: Long): Profile? { + return profileRepository.findById(id).orElse(null) + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/tenten/bittakotlin/profile/service/ProfileServiceImpl.kt b/src/main/kotlin/org/tenten/bittakotlin/profile/service/ProfileServiceImpl.kt index 044fc4b..164ad24 100644 --- a/src/main/kotlin/org/tenten/bittakotlin/profile/service/ProfileServiceImpl.kt +++ b/src/main/kotlin/org/tenten/bittakotlin/profile/service/ProfileServiceImpl.kt @@ -92,7 +92,7 @@ class ProfileServiceImpl( private fun toDto(profile: Profile): ProfileDTO { return ProfileDTO( - memberId = profile.member.id ?: throw IllegalStateException("Member ID is missing"), + memberId = profile.member!!.id ?: throw IllegalStateException("Member ID is missing"), nickname = profile.nickname, profileUrl = profile.profileUrl, description = profile.description, From 44a0a7322957910eae4ec79e6ed150b9702d767e Mon Sep 17 00:00:00 2001 From: YooJHyun Date: Tue, 5 Nov 2024 09:58:17 +0900 Subject: [PATCH 2/2] =?UTF-8?q?Feat:=20Calendar=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apply/controller/ApplyController.kt | 7 +++ .../bittakotlin/apply/service/ApplyService.kt | 8 ++++ .../apply/service/ApplyServiceImpl.kt | 46 +++++++++++++++++-- .../controller/EventCalendarController.kt | 22 +++++++++ .../calendar/dto/EventCalendarDTO.kt | 24 ++++++++++ .../calendar/entity/EventCalendar.kt | 29 ++++++++++++ .../repository/EventCalendarRepository.kt | 10 ++++ .../calendar/service/EventCalendarService.kt | 7 +++ .../service/EventCalendarServiceImpl.kt | 31 +++++++++++++ .../controller/JobPostViewController.kt | 1 - .../bittakotlin/jobpost/dto/JobPostDTO.kt | 2 +- .../bittakotlin/jobpost/entity/JobPost.kt | 4 +- 12 files changed, 183 insertions(+), 8 deletions(-) create mode 100644 src/main/kotlin/org/tenten/bittakotlin/calendar/controller/EventCalendarController.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/calendar/dto/EventCalendarDTO.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/calendar/entity/EventCalendar.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/calendar/repository/EventCalendarRepository.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/calendar/service/EventCalendarService.kt create mode 100644 src/main/kotlin/org/tenten/bittakotlin/calendar/service/EventCalendarServiceImpl.kt diff --git a/src/main/kotlin/org/tenten/bittakotlin/apply/controller/ApplyController.kt b/src/main/kotlin/org/tenten/bittakotlin/apply/controller/ApplyController.kt index acaddb9..3620635 100644 --- a/src/main/kotlin/org/tenten/bittakotlin/apply/controller/ApplyController.kt +++ b/src/main/kotlin/org/tenten/bittakotlin/apply/controller/ApplyController.kt @@ -231,5 +231,12 @@ class ApplyController( applyService.applyStatusUpdate(applyId, applyStatusUpdateDTO.applyStatus!!, profileId) return ResponseEntity.ok("상태가 변경되었습니다") } + + @PostMapping("/calendar") + fun applyToCalendar(@RequestParam jobPostId: Long, @RequestParam profileId: Long): ResponseEntity { + applyService.applyToCalendar(jobPostId, profileId) + return ResponseEntity.ok("캘린더가 등록되었습니다") + } + } diff --git a/src/main/kotlin/org/tenten/bittakotlin/apply/service/ApplyService.kt b/src/main/kotlin/org/tenten/bittakotlin/apply/service/ApplyService.kt index 6619af1..9987ff6 100644 --- a/src/main/kotlin/org/tenten/bittakotlin/apply/service/ApplyService.kt +++ b/src/main/kotlin/org/tenten/bittakotlin/apply/service/ApplyService.kt @@ -1,7 +1,9 @@ package org.tenten.bittakotlin.apply.service import org.tenten.bittakotlin.apply.dto.ApplyDTO +import org.tenten.bittakotlin.apply.entity.Apply import org.tenten.bittakotlin.apply.entity.ApplyStatus +import org.tenten.bittakotlin.calendar.entity.EventCalendar import org.tenten.bittakotlin.profile.entity.Profile interface ApplyService { @@ -20,6 +22,12 @@ interface ApplyService { fun getApplyCount(jobPostId: Long): Long fun applyStatusUpdate(applyId: Long, applyStatus: ApplyStatus, profileId: Long) + + fun setCalendar(apply: Apply?) + + fun getCalendar(profileId: Long?): List? + + fun applyToCalendar(jobPostId: Long?, profileId: Long?) } diff --git a/src/main/kotlin/org/tenten/bittakotlin/apply/service/ApplyServiceImpl.kt b/src/main/kotlin/org/tenten/bittakotlin/apply/service/ApplyServiceImpl.kt index 55dd906..332a01d 100644 --- a/src/main/kotlin/org/tenten/bittakotlin/apply/service/ApplyServiceImpl.kt +++ b/src/main/kotlin/org/tenten/bittakotlin/apply/service/ApplyServiceImpl.kt @@ -8,17 +8,21 @@ import org.tenten.bittakotlin.apply.entity.Apply import org.tenten.bittakotlin.apply.entity.ApplyStatus import org.tenten.bittakotlin.apply.exception.ApplyException import org.tenten.bittakotlin.apply.repository.ApplyRepository +import org.tenten.bittakotlin.calendar.entity.EventCalendar +import org.tenten.bittakotlin.calendar.repository.EventCalendarRepository import org.tenten.bittakotlin.jobpost.exception.JobPostException import org.tenten.bittakotlin.jobpost.repository.JobPostRepository import org.tenten.bittakotlin.jobpost.util.JobPostProvider import org.tenten.bittakotlin.profile.entity.Profile +import org.tenten.bittakotlin.profile.service.ProfileProvider @Service class ApplyServiceImpl( private val applyRepository: ApplyRepository, private val jobPostRepository: JobPostRepository, -// private val memberProvider: MemberProvider, - private val jobPostProvider: JobPostProvider + private val profileProvider: ProfileProvider, + private val jobPostProvider: JobPostProvider, + private val eventCalendarRepository: EventCalendarRepository ) : ApplyService { private val log = LoggerFactory.getLogger(this::class.java) @@ -39,6 +43,8 @@ class ApplyServiceImpl( jobPost!!.plusApplyCount() jobPostRepository.save(jobPost) + addCalendar(apply) + mapOf( "message" to "${apply.profile!!.nickname}님 지원 완료", "data" to entityToDto(apply) @@ -49,6 +55,18 @@ class ApplyServiceImpl( } } + private fun addCalendar(apply: Apply) { + val jobPost = apply.jobPost + val event = EventCalendar( + profile = apply.profile, + title = jobPost!!.title, + startDate = jobPost.startDate, + endDate = jobPost.endDate, + auditionDate = jobPost.auditionDate + ) + eventCalendarRepository.save(event) + } + @Transactional override fun delete(id: Long) { val apply = applyRepository.findById(id).orElseThrow { ApplyException.NOT_FOUND.get() } @@ -79,7 +97,7 @@ class ApplyServiceImpl( } val applies = applyRepository.findAllByJobPost(jobPost) - return applies//.map { entityToDto(it) } + return applies } @Transactional @@ -104,10 +122,30 @@ class ApplyServiceImpl( applyRepository.save(apply) } + override fun setCalendar(apply: Apply?) { + val event = EventCalendar( + profile = apply!!.profile, + title = apply.jobPost!!.title, + startDate = apply.jobPost!!.startDate, + endDate = apply.jobPost!!.endDate, + auditionDate = apply.jobPost!!.auditionDate + ) + eventCalendarRepository.save(event) + } + + override fun getCalendar(profileId: Long?): List? { + return eventCalendarRepository.findAllByProfileId(profileId!!) + } + + @Transactional + override fun applyToCalendar(jobPostId: Long?, profileId: Long?) { + val apply = Apply() + } + private fun dtoToEntity(applyDTO: ApplyDTO): Apply { return Apply( id = applyDTO.id, -// profile = profileProvider.getById(applyDTO.profileId!!), + profile = profileProvider.getById(applyDTO.profileId!!), jobPost = jobPostProvider.getById(applyDTO.jobPostId!!), appliedAt = applyDTO.appliedAt ) diff --git a/src/main/kotlin/org/tenten/bittakotlin/calendar/controller/EventCalendarController.kt b/src/main/kotlin/org/tenten/bittakotlin/calendar/controller/EventCalendarController.kt new file mode 100644 index 0000000..f501eb9 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/calendar/controller/EventCalendarController.kt @@ -0,0 +1,22 @@ +package org.tenten.bittakotlin.calendar.controller + +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import org.tenten.bittakotlin.calendar.dto.EventCalendarDTO +import org.tenten.bittakotlin.calendar.service.EventCalendarService + +@RestController +@RequestMapping("/api/v1/calendar") +class EventCalendarController( + private val eventCalendarService: EventCalendarService +) { + + @GetMapping("/{profileId}") + fun getCalendar(@PathVariable profileId: Long): ResponseEntity> { + val events = eventCalendarService.getEventCalendar(profileId) + return ResponseEntity.ok(events) + } +} diff --git a/src/main/kotlin/org/tenten/bittakotlin/calendar/dto/EventCalendarDTO.kt b/src/main/kotlin/org/tenten/bittakotlin/calendar/dto/EventCalendarDTO.kt new file mode 100644 index 0000000..b8ecac5 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/calendar/dto/EventCalendarDTO.kt @@ -0,0 +1,24 @@ +package org.tenten.bittakotlin.calendar.dto + +import java.time.LocalDate +import java.time.LocalDateTime +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotNull +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "캘린더 DTO", description = "회원의 일정을 확인할 때 사용하는 DTO입니다.") +data class EventCalendarDTO( + @Schema(title = "캘린더 ID (PK)", description = "캘린더의 고유 ID 입니다.", example = "1", minimum = "1") + @field:Min(value = 1, message = "ID는 음수가 될 수 없습니다") + val id: Long? = null, + + @Schema(title = "회원 ID", description = "회원의 고유 ID 입니다.", example = "1", minimum = "1") + @field:Min(value = 1, message = "ID는 음수가 될 수 없습니다") + @field:NotNull(message = "회원의 ID가 필요합니다") + val profileId: Long? = null, + + val startDate: LocalDate? = null, + val endDate: LocalDate? = null, + val auditionDate: LocalDateTime? = null, + val title: String? = null +) diff --git a/src/main/kotlin/org/tenten/bittakotlin/calendar/entity/EventCalendar.kt b/src/main/kotlin/org/tenten/bittakotlin/calendar/entity/EventCalendar.kt new file mode 100644 index 0000000..faa7ab3 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/calendar/entity/EventCalendar.kt @@ -0,0 +1,29 @@ +package org.tenten.bittakotlin.calendar.entity + +import jakarta.persistence.* +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import org.tenten.bittakotlin.profile.entity.Profile +import java.time.LocalDate +import java.time.LocalDateTime + +@Entity +@Table(name = "event_calendar") +@EntityListeners(AuditingEntityListener::class) +data class EventCalendar( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @ManyToOne + @JoinColumn(name = "profileId", nullable = false) + val profile: Profile? = null, + + @Column(nullable = false) + val title: String, + + val startDate: LocalDate? = null, + + val endDate: LocalDate? = null, + + val auditionDate: LocalDateTime? = null +) diff --git a/src/main/kotlin/org/tenten/bittakotlin/calendar/repository/EventCalendarRepository.kt b/src/main/kotlin/org/tenten/bittakotlin/calendar/repository/EventCalendarRepository.kt new file mode 100644 index 0000000..1ea2ba4 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/calendar/repository/EventCalendarRepository.kt @@ -0,0 +1,10 @@ +package org.tenten.bittakotlin.calendar.repository + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import org.tenten.bittakotlin.calendar.entity.EventCalendar + +interface EventCalendarRepository : JpaRepository { + @Query("SELECT c FROM EventCalendar c WHERE c.profile.id = :profileId") + fun findAllByProfileId(profileId: Long): List +} diff --git a/src/main/kotlin/org/tenten/bittakotlin/calendar/service/EventCalendarService.kt b/src/main/kotlin/org/tenten/bittakotlin/calendar/service/EventCalendarService.kt new file mode 100644 index 0000000..b3d7f5a --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/calendar/service/EventCalendarService.kt @@ -0,0 +1,7 @@ +package org.tenten.bittakotlin.calendar.service + +import org.tenten.bittakotlin.calendar.dto.EventCalendarDTO + +interface EventCalendarService { + fun getEventCalendar(profileId: Long): List +} diff --git a/src/main/kotlin/org/tenten/bittakotlin/calendar/service/EventCalendarServiceImpl.kt b/src/main/kotlin/org/tenten/bittakotlin/calendar/service/EventCalendarServiceImpl.kt new file mode 100644 index 0000000..ea73833 --- /dev/null +++ b/src/main/kotlin/org/tenten/bittakotlin/calendar/service/EventCalendarServiceImpl.kt @@ -0,0 +1,31 @@ +package org.tenten.bittakotlin.calendar.service + +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service +import org.tenten.bittakotlin.calendar.dto.EventCalendarDTO +import org.tenten.bittakotlin.calendar.entity.EventCalendar +import org.tenten.bittakotlin.calendar.repository.EventCalendarRepository + +@Service +class EventCalendarServiceImpl( + private val eventCalendarRepository: EventCalendarRepository +) : EventCalendarService { + + private val logger = LoggerFactory.getLogger(EventCalendarServiceImpl::class.java) + + override fun getEventCalendar(profileId: Long): List { + val events = eventCalendarRepository.findAllByProfileId(profileId) + return events.map { entityToDto(it) } + } + + private fun entityToDto(event: EventCalendar): EventCalendarDTO { + return EventCalendarDTO( + id = event.id, + profileId = event.profile!!.id, + startDate = event.startDate, + endDate = event.endDate, + title = event.title, + auditionDate = event.auditionDate + ) + } +} diff --git a/src/main/kotlin/org/tenten/bittakotlin/jobpost/controller/JobPostViewController.kt b/src/main/kotlin/org/tenten/bittakotlin/jobpost/controller/JobPostViewController.kt index 0557268..dd4b733 100644 --- a/src/main/kotlin/org/tenten/bittakotlin/jobpost/controller/JobPostViewController.kt +++ b/src/main/kotlin/org/tenten/bittakotlin/jobpost/controller/JobPostViewController.kt @@ -14,4 +14,3 @@ class JobPostViewController { } } - diff --git a/src/main/kotlin/org/tenten/bittakotlin/jobpost/dto/JobPostDTO.kt b/src/main/kotlin/org/tenten/bittakotlin/jobpost/dto/JobPostDTO.kt index 8d5a651..633936c 100644 --- a/src/main/kotlin/org/tenten/bittakotlin/jobpost/dto/JobPostDTO.kt +++ b/src/main/kotlin/org/tenten/bittakotlin/jobpost/dto/JobPostDTO.kt @@ -53,7 +53,7 @@ data class JobPostDTO( val workCategory: WorkCategory? = null, @Schema(title = "오디션일", description = "오디션을 진행하는 날짜입니다.", example = "2023-09-24") - val auditionDate: LocalDate? = null, + val auditionDate: LocalDateTime? = null, @Schema(title = "촬영 시작일", description = "일이 시작하는 날짜입니다.", example = "2023-09-24") val startDate: LocalDate? = null, diff --git a/src/main/kotlin/org/tenten/bittakotlin/jobpost/entity/JobPost.kt b/src/main/kotlin/org/tenten/bittakotlin/jobpost/entity/JobPost.kt index be0a26d..2ab9338 100644 --- a/src/main/kotlin/org/tenten/bittakotlin/jobpost/entity/JobPost.kt +++ b/src/main/kotlin/org/tenten/bittakotlin/jobpost/entity/JobPost.kt @@ -60,7 +60,7 @@ class JobPost( @Enumerated(EnumType.STRING) var workCategory: WorkCategory? = null, // 작품 카테고리 - val auditionDate: LocalDate? = null, // 오디션 일자 + val auditionDate: LocalDateTime? = null, // 오디션 일자 val startDate: LocalDate? = null, // 촬영 기간 시작일 val endDate: LocalDate? = null, // 촬영 기간 종료일 @@ -78,7 +78,7 @@ class JobPost( val apply: List = mutableListOf(), @OneToMany(mappedBy = "jobPost", fetch = FetchType.EAGER, cascade = [CascadeType.REMOVE], orphanRemoval = true) - val likes: List = mutableListOf() + val like: List = mutableListOf() ) { fun plusApplyCount() {