diff --git a/.gitignore b/.gitignore index fc72651..4ad9f7f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,15 @@ HELP.md docker-compose.yml .env + + build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ !**/src/test/**/build/ +/src/main/generated/ + +/src/main/resources/static/ ### STS ### .apt_generated diff --git a/build.gradle b/build.gradle index 9088c6f..a54c276 100644 --- a/build.gradle +++ b/build.gradle @@ -91,6 +91,14 @@ tasks.withType(GenerateSwaggerUI) { } } +tasks.withType(Test).configureEach { + jvmArgs = jvmArgs + ['--add-opens=java.base/java.util=ALL-UNNAMED'] +} + +test { + jvmArgs '--add-opens=java.base/java.lang=ALL-UNNAMED' +} + build { dependsOn(':openapi3') } diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/controller/RecruitController.java b/src/main/java/com/server/bbo_gak/domain/recruit/controller/RecruitController.java new file mode 100644 index 0000000..6829e07 --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/controller/RecruitController.java @@ -0,0 +1,107 @@ +package com.server.bbo_gak.domain.recruit.controller; + +import com.server.bbo_gak.domain.recruit.dto.request.RecruitCreateRequest; +import com.server.bbo_gak.domain.recruit.dto.request.RecruitUpdateSeasonRequest; +import com.server.bbo_gak.domain.recruit.dto.request.RecruitUpdateSiteUrlRequest; +import com.server.bbo_gak.domain.recruit.dto.request.RecruitUpdateStatusRequest; +import com.server.bbo_gak.domain.recruit.dto.request.RecruitUpdateTitleRequest; +import com.server.bbo_gak.domain.recruit.dto.response.RecruitGetResponse; +import com.server.bbo_gak.domain.recruit.service.RecruitService; +import com.server.bbo_gak.domain.user.entity.User; +import com.server.bbo_gak.global.annotation.AuthUser; +import java.util.List; +import lombok.RequiredArgsConstructor; +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.PatchMapping; +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.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/recruits") +@RequiredArgsConstructor +public class RecruitController { + + private final RecruitService recruitService; + + @GetMapping("") + public ResponseEntity> getTotalRecruitList( + @AuthUser User user + ) { + return ResponseEntity.ok(recruitService.getTotalRecruitList(user)); + } + + + @GetMapping("/bySeason") + public ResponseEntity> getRecruitListBySeason( + @AuthUser User user, + @RequestParam("season") String season + ) { + return ResponseEntity.ok(recruitService.getRecruitListBySeason(user, season)); + } + + @GetMapping("/progressing") + public ResponseEntity> getProgressingRecruitList( + @AuthUser User user + ) { + return ResponseEntity.ok(recruitService.getProgressingRecruitList(user)); + } + + @PostMapping("") + public ResponseEntity createRecruit( + @AuthUser User user, + @RequestBody RecruitCreateRequest request + ) { + return ResponseEntity.ok(recruitService.createRecruit(user, request)); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteRecruit( + @AuthUser User user, + @PathVariable("id") Long id + ) { + recruitService.deleteRecruit(user, id); + return ResponseEntity.ok().body(null); + } + + @PatchMapping("/{id}/title") + public ResponseEntity updateRecruitTitle( + @AuthUser User user, + @PathVariable("id") Long id, + @RequestBody RecruitUpdateTitleRequest request) { + RecruitGetResponse response = recruitService.updateRecruitTitle(user, id, request.title()); + return ResponseEntity.ok(response); + } + + @PatchMapping("/{id}/season") + public ResponseEntity updateRecruitSeason( + @AuthUser User user, + @PathVariable("id") Long id, + @RequestBody RecruitUpdateSeasonRequest request) { + RecruitGetResponse response = recruitService.updateRecruitSeason(user, id, request.season()); + return ResponseEntity.ok(response); + } + + @PatchMapping("/{id}/status") + public ResponseEntity updateRecruitStatus( + @AuthUser User user, + @PathVariable("id") Long id, + @RequestBody RecruitUpdateStatusRequest request) { + RecruitGetResponse response = recruitService.updateRecruitStatus(user, id, request.recruitStatus()); + return ResponseEntity.ok(response); + } + + @PatchMapping("/{id}/siteUrl") + public ResponseEntity updateRecruitSiteUrl( + @AuthUser User user, + @PathVariable("id") Long id, + @RequestBody RecruitUpdateSiteUrlRequest request) { + RecruitGetResponse response = recruitService.updateRecruitSiteUrl(user, id, request.siteUrl()); + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/controller/SeasonController.java b/src/main/java/com/server/bbo_gak/domain/recruit/controller/SeasonController.java new file mode 100644 index 0000000..e8d6cc7 --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/controller/SeasonController.java @@ -0,0 +1,28 @@ +package com.server.bbo_gak.domain.recruit.controller; + +import com.server.bbo_gak.domain.recruit.dto.response.SeasonGetResponse; +import com.server.bbo_gak.domain.recruit.service.SeasonService; +import com.server.bbo_gak.domain.user.entity.User; +import com.server.bbo_gak.global.annotation.AuthUser; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/seasons") +@RequiredArgsConstructor +public class SeasonController { + + private final SeasonService seasonService; + + + @GetMapping("") + public ResponseEntity> getRecruitSeasonList( + @AuthUser User user + ) { + return ResponseEntity.ok(seasonService.getSeasonListByUser(user)); + } +} diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/dao/RecruitRepository.java b/src/main/java/com/server/bbo_gak/domain/recruit/dao/RecruitRepository.java new file mode 100644 index 0000000..3292fcb --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/dao/RecruitRepository.java @@ -0,0 +1,16 @@ +package com.server.bbo_gak.domain.recruit.dao; + +import com.server.bbo_gak.domain.recruit.entity.Recruit; +import com.server.bbo_gak.domain.recruit.entity.Season; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RecruitRepository extends JpaRepository { + + Optional findByUserIdAndId(Long userId, Long id); + + List findAllByUserId(Long userId); + + List findAllByUserIdAndSeason(Long userId, Season season); +} diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/dao/SeasonRepository.java b/src/main/java/com/server/bbo_gak/domain/recruit/dao/SeasonRepository.java new file mode 100644 index 0000000..90a6f6d --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/dao/SeasonRepository.java @@ -0,0 +1,15 @@ +package com.server.bbo_gak.domain.recruit.dao; + +import com.server.bbo_gak.domain.recruit.entity.Season; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SeasonRepository extends JpaRepository { + + List findAllByUserId(Long userId); + + boolean existsByUserIdAndName(Long userId, String name); + + Optional findByUserIdAndName(Long userId, String name); +} diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/dto/request/RecruitCreateRequest.java b/src/main/java/com/server/bbo_gak/domain/recruit/dto/request/RecruitCreateRequest.java new file mode 100644 index 0000000..81f385f --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/dto/request/RecruitCreateRequest.java @@ -0,0 +1,29 @@ +package com.server.bbo_gak.domain.recruit.dto.request; + +import com.server.bbo_gak.domain.recruit.entity.Recruit; +import com.server.bbo_gak.domain.recruit.entity.RecruitSchedule; +import com.server.bbo_gak.domain.recruit.entity.RecruitScheduleStage; +import com.server.bbo_gak.domain.recruit.entity.RecruitStatus; +import com.server.bbo_gak.domain.recruit.entity.Season; +import com.server.bbo_gak.domain.user.entity.User; + +public record RecruitCreateRequest( + String season, + String title, + String siteUrl, + RecruitScheduleStage recruitScheduleStage, + String deadline +) { + + public Recruit toEntity(User user, Season season, RecruitSchedule schedule) { + return Recruit.builder() + .season(season) + .title(title) + .siteUrl(siteUrl) + .recruitStatus(RecruitStatus.APPLICATION_COMPLETED) + .user(user) + .build() + .addSchedule(schedule); + } + +} diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/dto/request/RecruitScheduleCreateRequest.java b/src/main/java/com/server/bbo_gak/domain/recruit/dto/request/RecruitScheduleCreateRequest.java new file mode 100644 index 0000000..4ec85de --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/dto/request/RecruitScheduleCreateRequest.java @@ -0,0 +1,18 @@ +package com.server.bbo_gak.domain.recruit.dto.request; + +import com.server.bbo_gak.domain.recruit.entity.RecruitSchedule; +import com.server.bbo_gak.domain.recruit.entity.RecruitScheduleStage; +import java.time.LocalDate; + +public record RecruitScheduleCreateRequest( + RecruitScheduleStage recruitScheduleStage, + String deadline +) { + + public static RecruitSchedule of(RecruitScheduleStage recruitScheduleStage, String deadline) { + return RecruitSchedule.builder() + .recruitScheduleStage(recruitScheduleStage) + .deadLine(LocalDate.parse(deadline)) + .build(); + } +} diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/dto/request/RecruitUpdateSeasonRequest.java b/src/main/java/com/server/bbo_gak/domain/recruit/dto/request/RecruitUpdateSeasonRequest.java new file mode 100644 index 0000000..60231fc --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/dto/request/RecruitUpdateSeasonRequest.java @@ -0,0 +1,5 @@ +package com.server.bbo_gak.domain.recruit.dto.request; + +public record RecruitUpdateSeasonRequest(String season) { + +} diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/dto/request/RecruitUpdateSiteUrlRequest.java b/src/main/java/com/server/bbo_gak/domain/recruit/dto/request/RecruitUpdateSiteUrlRequest.java new file mode 100644 index 0000000..dce1930 --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/dto/request/RecruitUpdateSiteUrlRequest.java @@ -0,0 +1,5 @@ +package com.server.bbo_gak.domain.recruit.dto.request; + +public record RecruitUpdateSiteUrlRequest(String siteUrl) { + +} diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/dto/request/RecruitUpdateStatusRequest.java b/src/main/java/com/server/bbo_gak/domain/recruit/dto/request/RecruitUpdateStatusRequest.java new file mode 100644 index 0000000..b442c38 --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/dto/request/RecruitUpdateStatusRequest.java @@ -0,0 +1,7 @@ +package com.server.bbo_gak.domain.recruit.dto.request; + +import com.server.bbo_gak.domain.recruit.entity.RecruitStatus; + +public record RecruitUpdateStatusRequest(RecruitStatus recruitStatus) { + +} diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/dto/request/RecruitUpdateTitleRequest.java b/src/main/java/com/server/bbo_gak/domain/recruit/dto/request/RecruitUpdateTitleRequest.java new file mode 100644 index 0000000..d1f19c2 --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/dto/request/RecruitUpdateTitleRequest.java @@ -0,0 +1,5 @@ +package com.server.bbo_gak.domain.recruit.dto.request; + +public record RecruitUpdateTitleRequest(String title) { + +} diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/dto/response/RecruitGetResponse.java b/src/main/java/com/server/bbo_gak/domain/recruit/dto/response/RecruitGetResponse.java new file mode 100644 index 0000000..fe056a5 --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/dto/response/RecruitGetResponse.java @@ -0,0 +1,28 @@ +package com.server.bbo_gak.domain.recruit.dto.response; + +import com.server.bbo_gak.domain.recruit.entity.Recruit; +import com.server.bbo_gak.global.utils.BaseDateTimeFormatter; +import lombok.Builder; + +@Builder +public record RecruitGetResponse( + Long id, + String title, + String season, + String siteUrl, + String recruitStatus, + String createdDate +) { + + public static RecruitGetResponse from(Recruit recruit) { + + return RecruitGetResponse.builder() + .id(recruit.getId()) + .title(recruit.getTitle()) + .season(recruit.getSeason().getName()) + .siteUrl(recruit.getSiteUrl()) + .recruitStatus(recruit.getRecruitStatus().getValue()) + .createdDate(recruit.getCreatedDate().format(BaseDateTimeFormatter.getLocalDateTimeFormatter())) + .build(); + } +} diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/dto/response/RecruitScheduleGetResponse.java b/src/main/java/com/server/bbo_gak/domain/recruit/dto/response/RecruitScheduleGetResponse.java new file mode 100644 index 0000000..03130b6 --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/dto/response/RecruitScheduleGetResponse.java @@ -0,0 +1,22 @@ +package com.server.bbo_gak.domain.recruit.dto.response; + +import com.server.bbo_gak.domain.recruit.entity.RecruitSchedule; +import com.server.bbo_gak.domain.recruit.entity.RecruitScheduleStage; +import com.server.bbo_gak.global.utils.BaseDateTimeFormatter; +import lombok.Builder; + +@Builder +public record RecruitScheduleGetResponse( + Long id, + RecruitScheduleStage recruitScheduleStage, + String deadLine +) { + + public static RecruitScheduleGetResponse from(RecruitSchedule schedule) { + return RecruitScheduleGetResponse.builder() + .id(schedule.getId()) + .recruitScheduleStage(schedule.getRecruitScheduleStage()) + .deadLine(schedule.getDeadLine().format(BaseDateTimeFormatter.getLocalDateFormatter())) + .build(); + } +} diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/dto/response/SeasonGetResponse.java b/src/main/java/com/server/bbo_gak/domain/recruit/dto/response/SeasonGetResponse.java new file mode 100644 index 0000000..7d88dc1 --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/dto/response/SeasonGetResponse.java @@ -0,0 +1,12 @@ +package com.server.bbo_gak.domain.recruit.dto.response; + +import com.server.bbo_gak.domain.recruit.entity.Season; + +public record SeasonGetResponse( + String name +) { + + public static SeasonGetResponse from(Season season) { + return new SeasonGetResponse(season.getName()); + } +} diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/entity/Recruit.java b/src/main/java/com/server/bbo_gak/domain/recruit/entity/Recruit.java index 472b5f2..831492d 100644 --- a/src/main/java/com/server/bbo_gak/domain/recruit/entity/Recruit.java +++ b/src/main/java/com/server/bbo_gak/domain/recruit/entity/Recruit.java @@ -1,26 +1,36 @@ package com.server.bbo_gak.domain.recruit.entity; import com.server.bbo_gak.domain.card.entity.Card; +import com.server.bbo_gak.domain.user.entity.User; import com.server.bbo_gak.global.common.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +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.OneToMany; import java.util.ArrayList; import java.util.List; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; @Getter @Entity +@SQLRestriction("deleted = false") +@SQLDelete(sql = "UPDATE recruit SET deleted = true WHERE id = ?") @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Recruit extends BaseEntity { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "recruit_id") @@ -28,14 +38,54 @@ public class Recruit extends BaseEntity { private String title; - private String season; - private String siteUrl; @Enumerated(EnumType.STRING) private RecruitStatus recruitStatus; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "recruit_season_id") + private Season season; + @OneToMany(mappedBy = "recruit") private List cardList = new ArrayList<>(); + @OneToMany(mappedBy = "recruit") + private List scheduleList = new ArrayList<>(); + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @Builder + public Recruit(Season season, String title, String siteUrl, RecruitStatus recruitStatus, + User user) { + this.season = season; + this.title = title; + this.siteUrl = siteUrl; + this.recruitStatus = recruitStatus; + this.user = user; + } + + public Recruit addSchedule(RecruitSchedule recruitSchedule) { + recruitSchedule.setRecruit(this); + this.scheduleList.add(recruitSchedule); + return this; + } + + public void updateTitle(String title) { + this.title = title; + } + + public void updateSiteUrl(String siteUrl) { + this.siteUrl = siteUrl; + } + + public void updateRecruitStatus(RecruitStatus recruitStatus) { + this.recruitStatus = recruitStatus; + } + + public void updateSeason(Season season) { + this.season = season; + } } diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/entity/RecruitSchedule.java b/src/main/java/com/server/bbo_gak/domain/recruit/entity/RecruitSchedule.java index 8aea575..9dfa04c 100644 --- a/src/main/java/com/server/bbo_gak/domain/recruit/entity/RecruitSchedule.java +++ b/src/main/java/com/server/bbo_gak/domain/recruit/entity/RecruitSchedule.java @@ -12,6 +12,7 @@ import jakarta.persistence.ManyToOne; import java.time.LocalDate; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -30,7 +31,18 @@ public class RecruitSchedule { private Recruit recruit; @Enumerated(EnumType.STRING) - private RecruitStage recruitStage; + private RecruitScheduleStage recruitScheduleStage; private LocalDate deadLine; + + @Builder + public RecruitSchedule(Recruit recruit, RecruitScheduleStage recruitScheduleStage, LocalDate deadLine) { + this.recruit = recruit; + this.recruitScheduleStage = recruitScheduleStage; + this.deadLine = deadLine; + } + + public void setRecruit(Recruit recruit) { + this.recruit = recruit; + } } diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/entity/RecruitStage.java b/src/main/java/com/server/bbo_gak/domain/recruit/entity/RecruitScheduleStage.java similarity index 81% rename from src/main/java/com/server/bbo_gak/domain/recruit/entity/RecruitStage.java rename to src/main/java/com/server/bbo_gak/domain/recruit/entity/RecruitScheduleStage.java index a4ff7c4..f2603a7 100644 --- a/src/main/java/com/server/bbo_gak/domain/recruit/entity/RecruitStage.java +++ b/src/main/java/com/server/bbo_gak/domain/recruit/entity/RecruitScheduleStage.java @@ -5,8 +5,9 @@ @Getter @AllArgsConstructor -public enum RecruitStage { +public enum RecruitScheduleStage { + CLOSING_DOCUMENT("서류 마감"), FIRST_INTERVIEW("1차 면접"), SECOND_INTERVIEW("2차 면접"), THIRD_INTERVIEW("3차 면접"), diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/entity/RecruitStatus.java b/src/main/java/com/server/bbo_gak/domain/recruit/entity/RecruitStatus.java index 6fce5b6..cfdd79e 100644 --- a/src/main/java/com/server/bbo_gak/domain/recruit/entity/RecruitStatus.java +++ b/src/main/java/com/server/bbo_gak/domain/recruit/entity/RecruitStatus.java @@ -7,12 +7,14 @@ @AllArgsConstructor public enum RecruitStatus { - PREPARATION_IN_PROGRESS("지원 준비 중"), + PREPARATION_IN_PROGRESS("지원 준비"), APPLICATION_COMPLETED("지원 완료"), DOCUMENT_PASSED("서류 통과"), + DOCUMENT_REJECTION("서류 탈락"), INTERVIEW_PASSED("면접 통과"), + INTERVIEW_REJECTION("서류 탈락"), FINAL_ACCEPTANCE("최종 합격"), - REJECTED("불합격"); + FINAL_REJECTED("최종 탈락"); private final String value; diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/entity/RecruitStatusCategory.java b/src/main/java/com/server/bbo_gak/domain/recruit/entity/RecruitStatusCategory.java new file mode 100644 index 0000000..3e5447f --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/entity/RecruitStatusCategory.java @@ -0,0 +1,31 @@ +package com.server.bbo_gak.domain.recruit.entity; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum RecruitStatusCategory { + + GENERAL_STATUS(List.of( + RecruitStatus.PREPARATION_IN_PROGRESS, + RecruitStatus.APPLICATION_COMPLETED + )), + PASSED_STATUS(List.of( + RecruitStatus.DOCUMENT_PASSED, + RecruitStatus.INTERVIEW_PASSED, + RecruitStatus.FINAL_ACCEPTANCE + )), + REJECTION_STATUS(List.of( + RecruitStatus.DOCUMENT_REJECTION, + RecruitStatus.INTERVIEW_REJECTION, + RecruitStatus.FINAL_REJECTED + )); + + private final List statuses; + + public static boolean isRejectionStatus(RecruitStatus status) { + return REJECTION_STATUS.getStatuses().contains(status); + } +} diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/entity/Season.java b/src/main/java/com/server/bbo_gak/domain/recruit/entity/Season.java new file mode 100644 index 0000000..67b5f59 --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/entity/Season.java @@ -0,0 +1,37 @@ +package com.server.bbo_gak.domain.recruit.entity; + +import com.server.bbo_gak.domain.user.entity.User; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +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 lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@Table(name = "recruit_season") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Season { + + String name; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "recruit_season_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + public Season(String name, User user) { + this.name = name; + this.user = user; + } +} diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/entity/SeasonPeriod.java b/src/main/java/com/server/bbo_gak/domain/recruit/entity/SeasonPeriod.java new file mode 100644 index 0000000..15ef7ae --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/entity/SeasonPeriod.java @@ -0,0 +1,26 @@ +package com.server.bbo_gak.domain.recruit.entity; + +public enum SeasonPeriod { + FIRST_HALF("상반기", 1, 6), + SECOND_HALF("하반기", 7, 12); + + private final String name; + private final int startMonth; + private final int endMonth; + + SeasonPeriod(String name, int startMonth, int endMonth) { + this.name = name; + this.startMonth = startMonth; + this.endMonth = endMonth; + } + + public static SeasonPeriod fromMonth(int month) { + return month >= FIRST_HALF.startMonth && month <= FIRST_HALF.endMonth + ? FIRST_HALF + : SECOND_HALF; + } + + public String getSeasonName(int year) { + return year + " " + this.name; + } +} diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/service/RecruitScheduleService.java b/src/main/java/com/server/bbo_gak/domain/recruit/service/RecruitScheduleService.java index c0e85bc..196b406 100644 --- a/src/main/java/com/server/bbo_gak/domain/recruit/service/RecruitScheduleService.java +++ b/src/main/java/com/server/bbo_gak/domain/recruit/service/RecruitScheduleService.java @@ -1,8 +1,12 @@ package com.server.bbo_gak.domain.recruit.service; +import com.server.bbo_gak.domain.recruit.entity.RecruitSchedule; +import org.springframework.stereotype.Service; + +@Service public interface RecruitScheduleService { - void createRecruitSchedule(); + RecruitSchedule createRecruitSchedule(RecruitSchedule recruitSchedule); void getRecruitScheduleList(); diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/service/RecruitScheduleServiceImpl.java b/src/main/java/com/server/bbo_gak/domain/recruit/service/RecruitScheduleServiceImpl.java new file mode 100644 index 0000000..6feaf5d --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/service/RecruitScheduleServiceImpl.java @@ -0,0 +1,28 @@ +package com.server.bbo_gak.domain.recruit.service; + +import com.server.bbo_gak.domain.recruit.entity.RecruitSchedule; +import org.springframework.stereotype.Service; + +@Service +public class RecruitScheduleServiceImpl implements RecruitScheduleService { + + @Override + public void deleteRecruitSchedule() { + + } + + @Override + public void updateRecruitSchedule() { + + } + + @Override + public void getRecruitScheduleList() { + + } + + @Override + public RecruitSchedule createRecruitSchedule(RecruitSchedule recruitSchedule) { + return null; + } +} diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/service/RecruitService.java b/src/main/java/com/server/bbo_gak/domain/recruit/service/RecruitService.java index 3fe75fc..a1d243b 100644 --- a/src/main/java/com/server/bbo_gak/domain/recruit/service/RecruitService.java +++ b/src/main/java/com/server/bbo_gak/domain/recruit/service/RecruitService.java @@ -1,25 +1,163 @@ package com.server.bbo_gak.domain.recruit.service; -import com.server.bbo_gak.domain.card.entity.CardTypeValue; +import com.server.bbo_gak.domain.card.entity.CardType; +import com.server.bbo_gak.domain.recruit.dao.RecruitRepository; +import com.server.bbo_gak.domain.recruit.dto.request.RecruitCreateRequest; +import com.server.bbo_gak.domain.recruit.dto.request.RecruitScheduleCreateRequest; +import com.server.bbo_gak.domain.recruit.dto.response.RecruitGetResponse; +import com.server.bbo_gak.domain.recruit.entity.Recruit; +import com.server.bbo_gak.domain.recruit.entity.RecruitSchedule; +import com.server.bbo_gak.domain.recruit.entity.RecruitStatus; +import com.server.bbo_gak.domain.recruit.entity.RecruitStatusCategory; +import com.server.bbo_gak.domain.recruit.entity.Season; import com.server.bbo_gak.domain.user.entity.User; +import com.server.bbo_gak.global.error.exception.ErrorCode; +import com.server.bbo_gak.global.error.exception.NotFoundException; +import java.time.LocalDate; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -public interface RecruitService { +@Service +@RequiredArgsConstructor +public class RecruitService { - void getTotalRecruitList(); //생성순 정렬 + private final RecruitRepository recruitRepository; + private final SeasonService seasonService; + private final RecruitScheduleService recruitScheduleService; - void getProgressRecruitList(); + public List getTotalRecruitList(User user) { + List recruits = recruitRepository.findAllByUserId(user.getId()); - void createRecruit(); + return recruits.stream() + .sorted((r1, r2) -> r2.getCreatedDate().compareTo(r1.getCreatedDate())) + .map(RecruitGetResponse::from) + .toList(); + } - void deleteRecruit(); + public List getRecruitListBySeason(User user, String seasonName) { + Season season = seasonService.getSeasonByName(user, seasonName); + return recruitRepository.findAllByUserIdAndSeason(user.getId(), season).stream() + .map(RecruitGetResponse::from) + .toList(); + } - void updateRecruit(); - void getRecruitDetail(); + // TODO: 진행중인 공고는 일정등록이 안된 것을 우선으로 그 이후에는 RecruitSchedule이 현재와 가까운 순으로 정렬한다. + public List getProgressingRecruitList(User user) { - void getCardListInRecruit(User user, Long recruitId, CardTypeValue type); + List recruits = recruitRepository.findAllByUserId(user.getId()); - void getCardTypeCountsInRecruit(User user, Long recruitId); + // 불합격이 아닌 공고중에서 스케줄이 비어 있거나 지난 일정만 등록되어 있는 공고 중에서 지원 상태가 불합격인 공고를 분리해냄 + Map> partitionedRecruitsByNeedingSchedule = partitionRecruits(recruits); - void copyMyInfoCardToRecruit(User user, Long CardId, Long recruitId); + List recruitsNeedingSchedule = partitionedRecruitsByNeedingSchedule.get(true); + List recruitsWithSchedule = partitionedRecruitsByNeedingSchedule.get(false); + + //recruitsWithSchedule에서 시간이 지나지 않은 스케줄 중에서 가장 현재와 가까운 걸 기준으로 정렬 + List sortedRecruitsWithSchedule = recruitsWithSchedule.stream() + .sorted(Comparator.comparing(this::getNearestUpcomingDate)) + .toList(); + + recruitsNeedingSchedule.addAll(sortedRecruitsWithSchedule); + + return recruitsNeedingSchedule.stream() + .map(RecruitGetResponse::from) + .toList(); + } + + private LocalDate getNearestUpcomingDate(Recruit recruit) { + return recruit.getScheduleList().stream() + .map(RecruitSchedule::getDeadLine) + .filter(deadLine -> deadLine.isAfter(LocalDate.now())) + .min(Comparator.naturalOrder()) + .orElse(LocalDate.MAX); + } + + private Map> partitionRecruits(List recruits) { + return recruits.stream() + .filter(recruit -> !RecruitStatusCategory.isRejectionStatus(recruit.getRecruitStatus())) // 불합격 상태 필터링 + .collect(Collectors.partitioningBy(this::isNeedsScheduleUpdate)); + } + + private boolean isNeedsScheduleUpdate(Recruit recruit) { + List scheduleList = recruit.getScheduleList(); + return scheduleList.isEmpty() || scheduleList.stream() + .allMatch(schedule -> schedule.getDeadLine().isBefore(LocalDate.now())); + } + + @Transactional + public RecruitGetResponse createRecruit(User user, RecruitCreateRequest request) { + + RecruitSchedule recruitSchedule = recruitScheduleService.createRecruitSchedule( + RecruitScheduleCreateRequest.of(request.recruitScheduleStage(), request.deadline()) + ); + Season season = seasonService.getSeasonByName(user, request.season()); + Recruit recruit = request.toEntity(user, season, recruitSchedule); + return RecruitGetResponse.from(recruitRepository.save(recruit)); + } + + @Transactional + public void deleteRecruit(User user, Long recruitId) { + Recruit recruit = findRecruitByUserAndId(user, recruitId); + + recruitRepository.deleteById(recruitId); + } + + @Transactional + public RecruitGetResponse updateRecruitTitle(User user, Long recruitId, String title) { + Recruit recruit = findRecruitByUserAndId(user, recruitId); + + recruit.updateTitle(title); + + return RecruitGetResponse.from(recruitRepository.save(recruit)); + } + + @Transactional + public RecruitGetResponse updateRecruitSeason(User user, Long recruitId, String seasonName) { + Recruit recruit = findRecruitByUserAndId(user, recruitId); + Season season = seasonService.getSeasonByName(user, seasonName); + recruit.updateSeason(season); + + return RecruitGetResponse.from(recruitRepository.save(recruit)); + } + + @Transactional + public RecruitGetResponse updateRecruitStatus(User user, Long recruitId, RecruitStatus recruitStatus) { + Recruit recruit = findRecruitByUserAndId(user, recruitId); + + recruit.updateRecruitStatus(recruitStatus); + + return RecruitGetResponse.from(recruitRepository.save(recruit)); + } + + @Transactional + public RecruitGetResponse updateRecruitSiteUrl(User user, Long recruitId, String siteUrl) { + Recruit recruit = findRecruitByUserAndId(user, recruitId); + + recruit.updateSiteUrl(siteUrl); + + return RecruitGetResponse.from(recruitRepository.save(recruit)); + } + + private Recruit findRecruitByUserAndId(User user, Long recruitId) { + return recruitRepository.findByUserIdAndId(user.getId(), recruitId) + .orElseThrow(() -> new NotFoundException(ErrorCode.RECRUIT_NOT_FOUND)); + } + + public void getCardListInRecruit(User user, Long recruitId, CardType type) { + + } + + public void getCardTypeCountsInRecruit(User user, Long recruitId) { + + } + + public void copyMyInfoCardToRecruit(User user, Long CardId, Long recruitId) { + + } } diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/service/SeasonService.java b/src/main/java/com/server/bbo_gak/domain/recruit/service/SeasonService.java new file mode 100644 index 0000000..6721e91 --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/service/SeasonService.java @@ -0,0 +1,69 @@ +package com.server.bbo_gak.domain.recruit.service; + + +import com.server.bbo_gak.domain.recruit.dao.SeasonRepository; +import com.server.bbo_gak.domain.recruit.dto.response.SeasonGetResponse; +import com.server.bbo_gak.domain.recruit.entity.Season; +import com.server.bbo_gak.domain.recruit.entity.SeasonPeriod; +import com.server.bbo_gak.domain.user.entity.User; +import com.server.bbo_gak.global.error.exception.ErrorCode; +import com.server.bbo_gak.global.error.exception.NotFoundException; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class SeasonService { + + private final SeasonRepository seasonRepository; + + @Transactional + public List getSeasonListByUser(User user) { + boolean existsCurrentSeason = seasonRepository.existsByUserIdAndName(user.getId(), getCurrentSeason()); + + if (!existsCurrentSeason) { + seasonRepository.saveAll(generateDefaultSeasonByCurrentTime(user)); + } + return seasonRepository.findAllByUserId(user.getId()).stream() + .map(SeasonGetResponse::from) + .toList(); + } + + public Season getSeasonByName(User user, String name) { + return seasonRepository.findByUserIdAndName(user.getId(), name) + .orElseThrow(() -> new NotFoundException(ErrorCode.SEASON_NOT_FOUND)); + } + + private String getCurrentSeason() { + LocalDate currentDate = LocalDate.now(); + int month = currentDate.getMonthValue(); + int year = currentDate.getYear(); + + SeasonPeriod currentPeriod = SeasonPeriod.fromMonth(month); + return currentPeriod.getSeasonName(year); + } + + private List generateDefaultSeasonByCurrentTime(User user) { + LocalDate currentDate = LocalDate.now(); + int month = currentDate.getMonthValue(); + int year = currentDate.getYear(); + + List periods = new ArrayList<>(); + + SeasonPeriod currentPeriod = SeasonPeriod.fromMonth(month); + periods.add(new Season(currentPeriod.getSeasonName(year), user)); + + SeasonPeriod nextPeriod = (currentPeriod == SeasonPeriod.FIRST_HALF) + ? SeasonPeriod.SECOND_HALF + : SeasonPeriod.FIRST_HALF; + + int nextPeriodYear = (currentPeriod == SeasonPeriod.FIRST_HALF) ? year : year + 1; + periods.add(new Season(nextPeriod.getSeasonName(nextPeriodYear), user)); + + return periods; + } +} diff --git a/src/main/java/com/server/bbo_gak/global/error/exception/ErrorCode.java b/src/main/java/com/server/bbo_gak/global/error/exception/ErrorCode.java index eca67cc..cc4fe43 100644 --- a/src/main/java/com/server/bbo_gak/global/error/exception/ErrorCode.java +++ b/src/main/java/com/server/bbo_gak/global/error/exception/ErrorCode.java @@ -38,12 +38,21 @@ public enum ErrorCode { //Tag TAG_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 태그를 찾을 수 없습니다"), - CARD_TAG_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 카드와 태그 매핑을 찾을 수 없습니다"), + TAG_DUPLICATED(HttpStatus.BAD_REQUEST, "해당 태그가 이미 추가 돼있습니다."), + //Season + SEASON_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 공고 분기를 찾을 수 없습니다"), + + //Recruit + RECRUIT_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 공고를 찾을 수 없습니다"), + + CARD_TAG_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 카드와 태그 매핑을 찾을 수 없습니다"), + //CardMemo CARD_MEMO_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 카드를 찾을 수 없습니다"); + private final HttpStatus status; private final String message; diff --git a/src/main/java/com/server/bbo_gak/global/utils/BaseDateTimeFormatter.java b/src/main/java/com/server/bbo_gak/global/utils/BaseDateTimeFormatter.java index 709c800..4422a0e 100644 --- a/src/main/java/com/server/bbo_gak/global/utils/BaseDateTimeFormatter.java +++ b/src/main/java/com/server/bbo_gak/global/utils/BaseDateTimeFormatter.java @@ -11,4 +11,10 @@ public static DateTimeFormatter getLocalDateTimeFormatter() { .withLocale(Locale.KOREA) .withZone(ZoneId.systemDefault()); } + + public static DateTimeFormatter getLocalDateFormatter() { + return DateTimeFormatter.ofPattern("yyyy-MM-dd") + .withLocale(Locale.KOREA) + .withZone(ZoneId.systemDefault()); + } } diff --git a/src/main/resources/application-datasource.yml b/src/main/resources/application-datasource.yml index dc14988..6118682 100644 --- a/src/main/resources/application-datasource.yml +++ b/src/main/resources/application-datasource.yml @@ -7,4 +7,7 @@ spring: driver-class-name: com.mysql.cj.jdbc.Driver password: ${MYSQL_PASSWORD} username: ${MYSQL_USERNAME} - + jpa: + properties: + hibernate: + dialect: org.hibernate.dialect.MySQLDialect diff --git a/src/main/resources/static/docs/open-api-3.0.1.json b/src/main/resources/static/docs/open-api-3.0.1.json index e69de29..df46f5f 100644 --- a/src/main/resources/static/docs/open-api-3.0.1.json +++ b/src/main/resources/static/docs/open-api-3.0.1.json @@ -0,0 +1,1574 @@ +{ + "openapi" : "3.0.1", + "info" : { + "title" : "뽀각 API 문서", + "description" : "뽀각 API 문서입니다.", + "version" : "0.0.1" + }, + "servers" : [ { + "url" : "http://114.70.23.79:8080" + } ], + "tags" : [ ], + "paths" : { + "/api/v1/card" : { + "post" : { + "tags" : [ "Card" ], + "summary" : "카드 신규 생성", + "description" : "카드 신규 생성", + "operationId" : "[create] 카드 신규 생성", + "requestBody" : { + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/CardCreateRequest" + }, + "examples" : { + "[create] 카드 신규 생성" : { + "value" : "{\"cardTypeValueList\":[\"경험_정리\",\"자기소개서\"],\"tagIdList\":[1,2]}" + } + } + } + } + }, + "responses" : { + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/CardCreateResponse" + }, + "examples" : { + "[create] 카드 신규 생성" : { + "value" : "{\n \"cardId\" : 4\n}" + } + } + } + } + } + } + } + }, + "/api/v1/card-images" : { + "delete" : { + "tags" : [ "CardImage" ], + "operationId" : "[delete] ", + "requestBody" : { + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/CardImageDeleteRequest" + }, + "examples" : { + "[delete] s3에서_삭제할_이미지_찾기_실패" : { + "value" : "{\n \"cardId\" : 0,\n \"staticUrl\" : \"invalid-staticUrl\"\n}" + }, + "[delete] 카드 찾기 실패" : { + "value" : "{\n \"cardId\" : 0,\n \"staticUrl\" : \"invalid-staticUrl\"\n}" + } + } + } + } + }, + "responses" : { + "404" : { + "description" : "404", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/api-v1-card-images1501512887" + }, + "examples" : { + "[delete] s3에서_삭제할_이미지_찾기_실패" : { + "value" : "{\n \"message\" : \"해당 카드를 찾을 수 없습니다\",\n \"status\" : \"NOT_FOUND\"\n}" + }, + "[delete] 카드 찾기 실패" : { + "value" : "{\n \"message\" : \"해당 카드를 찾을 수 없습니다\",\n \"status\" : \"NOT_FOUND\"\n}" + } + } + } + } + } + } + } + }, + "/api/v1/cards" : { + "get" : { + "tags" : [ "Card" ], + "summary" : "카드 리스트 조회", + "description" : "카드 리스트 조회", + "operationId" : "[select] 카드 리스트 조회", + "parameters" : [ { + "name" : "type", + "in" : "query", + "description" : "타입", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/CardListGetResponse" + }, + "examples" : { + "[select] 카드 리스트 조회" : { + "value" : "[ {\n \"id\" : 1,\n \"title\" : \"test_title\",\n \"updatedDate\" : \"2024-07-24 21:22:08\",\n \"tagList\" : [ {\n \"id\" : 1,\n \"name\" : \"스프링\",\n \"type\" : \"역량\"\n }, {\n \"id\" : 2,\n \"name\" : \"리액트\",\n \"type\" : \"역량\"\n } ]\n} ]" + } + } + } + } + } + } + } + }, + "/api/v1/recruits" : { + "get" : { + "tags" : [ "Recruit" ], + "summary" : "전체 공고 목록 조회", + "description" : "전체 공고 목록 조회", + "operationId" : "[GET] 전체 공고 목록 조회 성공", + "responses" : { + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/RecruitGetResponse" + }, + "examples" : { + "[GET] 전체 공고 목록 조회 성공" : { + "value" : "[ {\n \"id\" : 1,\n \"title\" : \"New Title\",\n \"season\" : \"2024 상반기\",\n \"siteUrl\" : \"https://example.com\",\n \"recruitStatus\" : \"지원 완료\",\n \"createdDate\" : \"2023-01-01T00:00:00\"\n} ]" + } + } + } + } + } + } + }, + "post" : { + "tags" : [ "Recruit" ], + "summary" : "공고 생성", + "description" : "공고 생성", + "operationId" : "[POST] 공고 생성 성공", + "requestBody" : { + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/RecruitCreateRequest" + }, + "examples" : { + "[POST] 공고 생성 성공" : { + "value" : "{\n \"season\" : \"2024 상반기\",\n \"title\" : \"New Recruit Title\",\n \"siteUrl\" : \"https://example.com\",\n \"recruitScheduleStage\" : \"CLOSING_DOCUMENT\",\n \"deadline\" : \"2024-12-31\"\n}" + } + } + } + } + }, + "responses" : { + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/RecruitGetResponse" + }, + "examples" : { + "[POST] 공고 생성 성공" : { + "value" : "{\n \"id\" : 1,\n \"title\" : \"New Title\",\n \"season\" : \"2024 상반기\",\n \"siteUrl\" : \"https://example.com\",\n \"recruitStatus\" : \"\",\n \"createdDate\" : \"2023-01-01T00:00:00\"\n}" + } + } + } + } + } + } + } + }, + "/api/v1/tags" : { + "get" : { + "tags" : [ "Tag" ], + "summary" : "전체 카드 태그 목록", + "description" : "전체 카드 태그 목록", + "operationId" : "[태그_전체_목록_조회] 성공", + "responses" : { + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/TagGetResponse" + }, + "examples" : { + "[태그_전체_목록_조회] 성공" : { + "value" : "[{\"id\":1,\"name\":\"스프링\",\"type\":\"역량\"},{\"id\":2,\"name\":\"리액트\",\"type\":\"역량\"},{\"id\":3,\"name\":\"봉사활동\",\"type\":\"인성\"}]" + } + } + } + } + } + } + } + }, + "/api/v1/cards/type-count" : { + "get" : { + "tags" : [ "Card" ], + "summary" : "카드 타입 카운트 조회", + "description" : "카드 타입 카운트 조회", + "operationId" : "[select] 카드 타입 카운트 조회", + "responses" : { + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/CardTypeCountGetResponse" + }, + "examples" : { + "[select] 카드 타입 카운트 조회" : { + "value" : "{\n \"경험_정리\" : 2,\n \"자기소개서\" : 1,\n \"면접_질문\" : 0\n}" + } + } + } + } + } + } + } + }, + "/api/v1/cards/{card-id}" : { + "get" : { + "tags" : [ "Card" ], + "summary" : "카드 단건 조회", + "description" : "카드 단건 조회", + "operationId" : "[select] 카드 ", + "parameters" : [ { + "name" : "card-id", + "in" : "path", + "description" : "card-id", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "404" : { + "description" : "404", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + }, + "examples" : { + "[select] 카드 찾기 실패" : { + "value" : "{\n \"message\" : \"해당 카드를 찾을 수 없습니다\",\n \"status\" : \"NOT_FOUND\"\n}" + } + } + } + } + }, + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/CardGetResponse" + }, + "examples" : { + "[select] 카드 단건 조회" : { + "value" : "{\n \"title\" : \"test_title\",\n \"content\" : \"test_contents\",\n \"updatedDate\" : \"2024-07-24 21:22:08\",\n \"cardTypeValueList\" : [ \"경험_정리\", \"자기소개서\", \"면접_질문\" ],\n \"tagList\" : [ {\n \"id\" : 1,\n \"name\" : \"스프링\",\n \"type\" : \"역량\"\n }, {\n \"id\" : 2,\n \"name\" : \"리액트\",\n \"type\" : \"역량\"\n } ]\n}" + } + } + } + } + } + } + }, + "delete" : { + "tags" : [ "Card" ], + "summary" : "카드 삭제", + "description" : "카드 삭제", + "operationId" : "[카드_삭제] ", + "parameters" : [ { + "name" : "card-id", + "in" : "path", + "description" : "Card id", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "404" : { + "description" : "404", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + }, + "examples" : { + "[카드_삭제] 카드 찾기 실패" : { + "value" : "{\"message\":\"해당 카드를 찾을 수 없습니다\",\"status\":\"NOT_FOUND\"}" + } + } + } + } + }, + "200" : { + "description" : "200" + } + } + } + }, + "/api/v1/images/presigned-urls" : { + "post" : { + "tags" : [ "image" ], + "summary" : "presigend url 생성", + "description" : "presigend url 생성", + "operationId" : "[presigned-urls] ", + "requestBody" : { + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/ImageUploadRequest" + }, + "examples" : { + "[presigned-urls] 성공" : { + "value" : "[ {\n \"fileExtension\" : \"PNG\"\n}, {\n \"fileExtension\" : \"PNG\"\n} ]" + }, + "[presigned-urls] 실패-유효하지않은파일이름" : { + "value" : "[ {\n \"fileExtension\" : \"PNG\"\n} ]" + } + } + } + } + }, + "responses" : { + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/ImageUploadResponse" + }, + "examples" : { + "[presigned-urls] 성공" : { + "value" : "[ {\n \"filename\" : \"image1.jpg\",\n \"presignedUrl\" : \"https://example.com/image1.jpg\"\n}, {\n \"filename\" : \"image2.jpg\",\n \"presignedUrl\" : \"https://example.com/image2.jpg\"\n} ]" + } + } + } + } + }, + "400" : { + "description" : "400", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/api-v1-card-images1501512887" + }, + "examples" : { + "[presigned-urls] 실패-유효하지않은파일이름" : { + "value" : "{\n \"message\" : \"지원하지 않는 파일 확장자입니다.\",\n \"status\" : \"BAD_REQUEST\"\n}" + } + } + } + } + } + } + } + }, + "/api/v1/recruits/bySeason" : { + "get" : { + "tags" : [ "Recruit" ], + "summary" : "분기별 공고 목록 조회", + "description" : "분기별 공고 목록 조회", + "operationId" : "[GET] 분기별 공고 목록 조회 성공", + "responses" : { + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/RecruitGetResponse" + }, + "examples" : { + "[GET] 분기별 공고 목록 조회 성공" : { + "value" : "[ {\n \"id\" : 1,\n \"title\" : \"New Title\",\n \"season\" : \"2024 상반기\",\n \"siteUrl\" : \"https://example.com\",\n \"recruitStatus\" : \"지원 완료\",\n \"createdDate\" : \"2023-01-01T00:00:00\"\n} ]" + } + } + } + } + } + } + } + }, + "/api/v1/recruits/progressing" : { + "get" : { + "tags" : [ "Recruit" ], + "summary" : "진행중 공고 목록 조회", + "description" : "진행중 공고 목록 조회", + "operationId" : "[GET] 진행 중 공고 목록 조회 성공", + "responses" : { + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/RecruitGetResponse" + }, + "examples" : { + "[GET] 진행 중 공고 목록 조회 성공" : { + "value" : "[ {\n \"id\" : 1,\n \"title\" : \"New Title\",\n \"season\" : \"2024 상반기\",\n \"siteUrl\" : \"https://example.com\",\n \"recruitStatus\" : \"지원 완료\",\n \"createdDate\" : \"2023-01-01T00:00:00\"\n} ]" + } + } + } + } + } + } + } + }, + "/api/v1/recruits/{id}" : { + "delete" : { + "tags" : [ "Recruit" ], + "summary" : "공고 삭제", + "description" : "공고 삭제", + "operationId" : "[DELETE] 공고 삭제 ", + "parameters" : [ { + "name" : "id", + "in" : "path", + "description" : "", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "200" + }, + "404" : { + "description" : "404", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/api-v1-card-images1501512887" + }, + "examples" : { + "[DELETE] 공고 삭제 실패" : { + "value" : "{\n \"message\" : \"해당 공고를 찾을 수 없습니다\",\n \"status\" : \"NOT_FOUND\"\n}" + } + } + } + } + } + } + } + }, + "/api/v1/card-images/static-urls/{cardId}" : { + "post" : { + "tags" : [ "CardImage" ], + "summary" : "카드 이미지 압로드 완료", + "description" : "카드 이미지 압로드 완료", + "operationId" : "[upload] ", + "parameters" : [ { + "name" : "cardId", + "in" : "path", + "description" : "", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/CardImageUploadCompleteRequest" + }, + "examples" : { + "[upload] S3에서_업로드된_이미지_찾기_실패" : { + "value" : "[ {\n \"fileName\" : \"invalid-filename\"\n}, {\n \"fileName\" : \"invalid-filename\"\n} ]" + }, + "[upload] 성공" : { + "value" : "[ {\n \"fileName\" : \"file1.png\"\n}, {\n \"fileName\" : \"file2.png\"\n} ]" + }, + "[upload] 카드 찾기 실패" : { + "value" : "[ {\n \"fileName\" : \"invalid-filename\"\n}, {\n \"fileName\" : \"invalid-filename\"\n} ]" + } + } + } + } + }, + "responses" : { + "404" : { + "description" : "404", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/api-v1-card-images1501512887" + }, + "examples" : { + "[upload] S3에서_업로드된_이미지_찾기_실패" : { + "value" : "{\n \"message\" : \"해당 파일은 S3내에 존재하지 않습니다\",\n \"status\" : \"NOT_FOUND\"\n}" + }, + "[upload] 카드 찾기 실패" : { + "value" : "{\n \"message\" : \"해당 카드를 찾을 수 없습니다\",\n \"status\" : \"NOT_FOUND\"\n}" + } + } + } + } + }, + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/CardImageUploadCompleteResponse" + }, + "examples" : { + "[upload] 성공" : { + "value" : "[ {\n \"staticUrl\" : \"https://example.com/file1.png\"\n}, {\n \"staticUrl\" : \"https://example.com/file2.png\"\n} ]" + } + } + } + } + } + } + } + }, + "/api/v1/cards/{card-id}/content" : { + "put" : { + "tags" : [ "Card" ], + "summary" : "카드 본문 수정", + "description" : "카드 본문 수정", + "operationId" : "[카드 본문 수정] ", + "parameters" : [ { + "name" : "card-id", + "in" : "path", + "description" : "Card id", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/CardContentUpdateRequest" + }, + "examples" : { + "[카드 본문 수정] 성공" : { + "value" : "{\n \"content\" : \"test content\"\n}" + }, + "[카드 본문 수정] 카드 찾기 실패" : { + "value" : "{\n \"content\" : \"test content\"\n}" + } + } + } + } + }, + "responses" : { + "200" : { + "description" : "200" + }, + "404" : { + "description" : "404", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + }, + "examples" : { + "[카드 본문 수정] 카드 찾기 실패" : { + "value" : "{\"message\":\"해당 카드를 찾을 수 없습니다\",\"status\":\"NOT_FOUND\"}" + } + } + } + } + } + } + } + }, + "/api/v1/cards/{card-id}/tags" : { + "get" : { + "tags" : [ "Tag" ], + "summary" : "카드 태그 목록", + "description" : "카드 태그 목록", + "operationId" : "[카드_태그_목록_조회] ", + "parameters" : [ { + "name" : "card-id", + "in" : "path", + "description" : "Card id", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "404" : { + "description" : "404", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + }, + "examples" : { + "[카드_태그_목록_조회] 카드 찾기 실패" : { + "value" : "{\"message\":\"해당 카드를 찾을 수 없습니다\",\"status\":\"NOT_FOUND\"}" + } + } + } + } + }, + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/TagGetResponse" + }, + "examples" : { + "[카드_태그_목록_조회] 성공" : { + "value" : "[{\"id\":3,\"name\":\"봉사활동\",\"type\":\"인성\"}]" + } + } + } + } + } + } + } + }, + "/api/v1/cards/{card-id}/title" : { + "put" : { + "tags" : [ "Card" ], + "summary" : "카드 제목 수정", + "description" : "카드 제목 수정", + "operationId" : "[update] 카드 ", + "parameters" : [ { + "name" : "card-id", + "in" : "path", + "description" : "Card id", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/CardTitleUpdateRequest" + }, + "examples" : { + "[update] 카드 제목 수정" : { + "value" : "{\n \"title\" : \"test title\"\n}" + }, + "[update] 카드 찾기 실패" : { + "value" : "{\n \"title\" : \"test title\"\n}" + } + } + } + } + }, + "responses" : { + "200" : { + "description" : "200" + }, + "404" : { + "description" : "404", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + }, + "examples" : { + "[update] 카드 찾기 실패" : { + "value" : "{\"message\":\"해당 카드를 찾을 수 없습니다\",\"status\":\"NOT_FOUND\"}" + } + } + } + } + } + } + } + }, + "/api/v1/recruits/{id}/season" : { + "patch" : { + "tags" : [ "Recruit" ], + "summary" : "공고 분기 수정", + "description" : "공고 분기 수정", + "operationId" : "[PATCH] 분기 수정 ", + "parameters" : [ { + "name" : "id", + "in" : "path", + "description" : "", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/RecruitUpdateSeasonRequest" + }, + "examples" : { + "[PATCH] 분기 수정 성공" : { + "value" : "{\n \"season\" : \"2024 상반기\"\n}" + }, + "[PATCH] 분기 수정 실패" : { + "value" : "{\n \"season\" : \"2024 상반기\"\n}" + } + } + } + } + }, + "responses" : { + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/RecruitGetResponse" + }, + "examples" : { + "[PATCH] 분기 수정 성공" : { + "value" : "{\n \"id\" : 1,\n \"title\" : \"New Title\",\n \"season\" : \"2024 상반기\",\n \"siteUrl\" : \"https://example.com\",\n \"recruitStatus\" : \"지원 완료\",\n \"createdDate\" : \"2023-01-01T00:00:00\"\n}" + } + } + } + } + }, + "404" : { + "description" : "404", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/api-v1-card-images1501512887" + }, + "examples" : { + "[PATCH] 분기 수정 실패" : { + "value" : "{\n \"message\" : \"해당 공고를 찾을 수 없습니다\",\n \"status\" : \"NOT_FOUND\"\n}" + } + } + } + } + } + } + } + }, + "/api/v1/recruits/{id}/siteUrl" : { + "patch" : { + "tags" : [ "Recruit" ], + "summary" : "공고 사이트 URL 수정", + "description" : "공고 사이트 URL 수정", + "operationId" : "[PATCH] 사이트 URL 수정 ", + "parameters" : [ { + "name" : "id", + "in" : "path", + "description" : "", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/RecruitUpdateSiteUrlRequest" + }, + "examples" : { + "[PATCH] 사이트 URL 수정 실패" : { + "value" : "{\n \"siteUrl\" : \"https://example.com\"\n}" + }, + "[PATCH] 사이트 URL 수정 성공" : { + "value" : "{\n \"siteUrl\" : \"https://example.com\"\n}" + } + } + } + } + }, + "responses" : { + "404" : { + "description" : "404", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/api-v1-card-images1501512887" + }, + "examples" : { + "[PATCH] 사이트 URL 수정 실패" : { + "value" : "{\n \"message\" : \"해당 공고를 찾을 수 없습니다\",\n \"status\" : \"NOT_FOUND\"\n}" + } + } + } + } + }, + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/RecruitGetResponse" + }, + "examples" : { + "[PATCH] 사이트 URL 수정 성공" : { + "value" : "{\n \"id\" : 1,\n \"title\" : \"New Title\",\n \"season\" : \"2024 상반기\",\n \"siteUrl\" : \"https://example.com\",\n \"recruitStatus\" : \"지원 완료\",\n \"createdDate\" : \"2023-01-01T00:00:00\"\n}" + } + } + } + } + } + } + } + }, + "/api/v1/recruits/{id}/status" : { + "patch" : { + "tags" : [ "Recruit" ], + "summary" : "공고 상태 수정", + "description" : "공고 상태 수정", + "operationId" : "[PATCH] 상태 수정 ", + "parameters" : [ { + "name" : "id", + "in" : "path", + "description" : "", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/RecruitUpdateStatusRequest" + }, + "examples" : { + "[PATCH] 상태 수정 실패" : { + "value" : "{\n \"recruitStatus\" : \"APPLICATION_COMPLETED\"\n}" + }, + "[PATCH] 상태 수정 성공" : { + "value" : "{\n \"recruitStatus\" : \"APPLICATION_COMPLETED\"\n}" + } + } + } + } + }, + "responses" : { + "404" : { + "description" : "404", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/api-v1-card-images1501512887" + }, + "examples" : { + "[PATCH] 상태 수정 실패" : { + "value" : "{\n \"message\" : \"해당 공고를 찾을 수 없습니다\",\n \"status\" : \"NOT_FOUND\"\n}" + } + } + } + } + }, + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/RecruitGetResponse" + }, + "examples" : { + "[PATCH] 상태 수정 성공" : { + "value" : "{\n \"id\" : 1,\n \"title\" : \"New Title\",\n \"season\" : \"2024 상반기\",\n \"siteUrl\" : \"https://example.com\",\n \"recruitStatus\" : \"지원 완료\",\n \"createdDate\" : \"2023-01-01T00:00:00\"\n}" + } + } + } + } + } + } + } + }, + "/api/v1/recruits/{id}/title" : { + "patch" : { + "tags" : [ "Recruit" ], + "summary" : "공고 타이틀 수정", + "description" : "공고 타이틀 수정", + "operationId" : "[PATCH] 제목 수정 ", + "parameters" : [ { + "name" : "id", + "in" : "path", + "description" : "", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/RecruitUpdateTitleRequest" + }, + "examples" : { + "[PATCH] 제목 수정 성공" : { + "value" : "{\n \"title\" : \"New Title\"\n}" + }, + "[PATCH] 제목 수정 실패" : { + "value" : "{\n \"title\" : \"New Title\"\n}" + } + } + } + } + }, + "responses" : { + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/RecruitGetResponse" + }, + "examples" : { + "[PATCH] 제목 수정 성공" : { + "value" : "{\n \"id\" : 1,\n \"title\" : \"New Title\",\n \"season\" : \"2024 상반기\",\n \"siteUrl\" : \"https://example.com\",\n \"recruitStatus\" : \"지원 완료\",\n \"createdDate\" : \"2023-01-01T00:00:00\"\n}" + } + } + } + } + }, + "404" : { + "description" : "404", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/api-v1-card-images1501512887" + }, + "examples" : { + "[PATCH] 제목 수정 실패" : { + "value" : "{\n \"message\" : \"해당 공고를 찾을 수 없습니다\",\n \"status\" : \"NOT_FOUND\"\n}" + } + } + } + } + } + } + } + }, + "/api/v1/users/test/login" : { + "post" : { + "tags" : [ "auth" ], + "summary" : "jwt 토큰 생성", + "description" : "jwt 토큰 생성", + "operationId" : "[로그인] ", + "requestBody" : { + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/LoginRequest" + }, + "examples" : { + "[로그인] 아이디_없음" : { + "value" : "{\n \"loginId\" : \"wrong\",\n \"password\" : \"test123\"\n}" + }, + "[로그인] 성공" : { + "value" : "{\n \"loginId\" : \"test\",\n \"password\" : \"test123\"\n}" + }, + "[로그인] 비밀번호_실패" : { + "value" : "{\n \"loginId\" : \"test\",\n \"password\" : \"wrong\"\n}" + } + } + } + } + }, + "responses" : { + "404" : { + "description" : "404", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/api-v1-card-images1501512887" + }, + "examples" : { + "[로그인] 아이디_없음" : { + "value" : "{\n \"message\" : \"해당 유저를 찾을 수 없습니다.\",\n \"status\" : \"NOT_FOUND\"\n}" + } + } + } + } + }, + "200" : { + "description" : "200", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/TokenDto" + }, + "examples" : { + "[로그인] 성공" : { + "value" : "{\n \"accessToken\" : \"accessToken\",\n \"refreshToken\" : \"refreshToken\"\n}" + } + } + } + } + }, + "400" : { + "description" : "400", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/api-v1-card-images1501512887" + }, + "examples" : { + "[로그인] 비밀번호_실패" : { + "value" : "{\n \"message\" : \"비밀번호를 잘못 입력하셨습니다.\",\n \"status\" : \"BAD_REQUEST\"\n}" + } + } + } + } + } + } + } + }, + "/api/v1/cards/{card-id}/tag/{tag-id}" : { + "post" : { + "tags" : [ "Tag" ], + "summary" : "카드 태그 추가", + "description" : "카드 태그 추가", + "operationId" : "[카드_", + "parameters" : [ { + "name" : "card-id", + "in" : "path", + "description" : "Card id", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "tag-id", + "in" : "path", + "description" : "Tag id", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "200" + }, + "404" : { + "description" : "404", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + }, + "examples" : { + "[카드_태그_추가] 태그 찾기 실패" : { + "value" : "{\"message\":\"해당 태그를 찾을 수 없습니다\",\"status\":\"NOT_FOUND\"}" + }, + "[카드_찾기_실패] 카드 찾기 실패" : { + "value" : "{\"message\":\"해당 카드를 찾을 수 없습니다\",\"status\":\"NOT_FOUND\"}" + } + } + } + } + }, + "400" : { + "description" : "400", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + }, + "examples" : { + "[카드_태그_추가] 태그 중복 추가" : { + "value" : "{\"message\":\"해당 태그가 이미 추가 돼있습니다.\",\"status\":\"BAD_REQUEST\"}" + } + } + } + } + } + } + }, + "delete" : { + "tags" : [ "Tag" ], + "summary" : "카드 태그 삭제", + "description" : "카드 태그 삭제", + "operationId" : "[카드_태그_삭제] ", + "parameters" : [ { + "name" : "card-id", + "in" : "path", + "description" : "Card id", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "tag-id", + "in" : "path", + "description" : "Tag id", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "200" + }, + "404" : { + "description" : "404", + "content" : { + "application/json;charset=UTF-8" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + }, + "examples" : { + "[카드_태그_삭제] 카드 찾기 실패" : { + "value" : "{\"message\":\"해당 카드를 찾을 수 없습니다\",\"status\":\"NOT_FOUND\"}" + }, + "[카드_태그_삭제] 카드 태그 매핑 찾기 실패" : { + "value" : "{\"message\":\"해당 카드와 태그 매핑을 찾을 수 없습니다\",\"status\":\"NOT_FOUND\"}" + }, + "[카드_태그_삭제] 태그 찾기 실패" : { + "value" : "{\"message\":\"해당 태그를 찾을 수 없습니다\",\"status\":\"NOT_FOUND\"}" + } + } + } + } + } + } + } + } + }, + "components" : { + "schemas" : { + "RecruitGetResponse" : { + "title" : "RecruitGetResponse", + "type" : "object", + "properties" : { + "createdDate" : { + "type" : "string", + "description" : "createdDate" + }, + "siteUrl" : { + "type" : "string", + "description" : "siteUrl" + }, + "season" : { + "type" : "string", + "description" : "season" + }, + "id" : { + "type" : "number", + "description" : "id" + }, + "recruitStatus" : { + "type" : "string", + "description" : "recruitStatus" + }, + "title" : { + "type" : "string", + "description" : "title" + } + } + }, + "CardImageDeleteRequest" : { + "title" : "CardImageDeleteRequest", + "type" : "object", + "properties" : { + "cardId" : { + "type" : "number", + "description" : "cardId" + }, + "staticUrl" : { + "type" : "string", + "description" : "staticUrl" + } + } + }, + "CardCreateRequest" : { + "title" : "CardCreateRequest", + "type" : "object", + "properties" : { + "cardTypeValueList" : { + "type" : "array", + "description" : "카드 타입값", + "items" : { + "oneOf" : [ { + "type" : "object" + }, { + "type" : "boolean" + }, { + "type" : "string" + }, { + "type" : "number" + } ] + } + }, + "tagIdList" : { + "type" : "array", + "description" : "태그 ID", + "items" : { + "oneOf" : [ { + "type" : "object" + }, { + "type" : "boolean" + }, { + "type" : "string" + }, { + "type" : "number" + } ] + } + } + } + }, + "ImageUploadResponse" : { + "title" : "ImageUploadResponse", + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "filename" : { + "type" : "string", + "description" : "filename" + }, + "presignedUrl" : { + "type" : "string", + "description" : "presignedUrl" + } + } + } + }, + "TagGetResponse" : { + "title" : "TagGetResponse", + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "name" : { + "type" : "string", + "description" : "태그 이름" + }, + "id" : { + "type" : "number", + "description" : "태그 ID" + }, + "type" : { + "type" : "string", + "description" : "태그 타입" + } + } + } + }, + "CardTitleUpdateRequest" : { + "title" : "CardTitleUpdateRequest", + "type" : "object", + "properties" : { + "title" : { + "type" : "string", + "description" : "카드 제목" + } + } + }, + "api-v1-card-images1501512887" : { + "type" : "object", + "properties" : { + "message" : { + "type" : "string", + "description" : "에러 메시지" + }, + "status" : { + "type" : "string", + "description" : "HTTP 상태 코드" + } + } + }, + "RecruitUpdateStatusRequest" : { + "title" : "RecruitUpdateStatusRequest", + "type" : "object", + "properties" : { + "recruitStatus" : { + "type" : "string", + "description" : "recruitStatus" + } + } + }, + "CardTypeCountGetResponse" : { + "title" : "CardTypeCountGetResponse", + "type" : "object", + "properties" : { + "면접_질문" : { + "type" : "number", + "description" : "면접_질문_개수" + }, + "자기소개서" : { + "type" : "number", + "description" : "자기소개서_개수" + }, + "경험_정리" : { + "type" : "number", + "description" : "면접_질문_개수" + } + } + }, + "LoginRequest" : { + "title" : "LoginRequest", + "type" : "object", + "properties" : { + "password" : { + "type" : "string", + "description" : "password" + }, + "loginId" : { + "type" : "string", + "description" : "loginId" + } + } + }, + "CardContentUpdateRequest" : { + "title" : "CardContentUpdateRequest", + "type" : "object", + "properties" : { + "content" : { + "type" : "string", + "description" : "카드 본문" + } + } + }, + "ImageUploadRequest" : { + "title" : "ImageUploadRequest", + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "fileExtension" : { + "type" : "string", + "description" : "fileExtension" + } + } + } + }, + "CardGetResponse" : { + "title" : "CardGetResponse", + "type" : "object", + "properties" : { + "tagList" : { + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "name" : { + "type" : "string", + "description" : "태그 이름" + }, + "id" : { + "type" : "number", + "description" : "태그 ID" + }, + "type" : { + "type" : "string", + "description" : "태그 타입" + } + } + } + }, + "cardTypeValueList" : { + "type" : "array", + "description" : "Card 타입값 리스트", + "items" : { + "oneOf" : [ { + "type" : "object" + }, { + "type" : "boolean" + }, { + "type" : "string" + }, { + "type" : "number" + } ] + } + }, + "updatedDate" : { + "type" : "string", + "description" : "Card 수정일시" + }, + "title" : { + "type" : "string", + "description" : "Card 제목" + }, + "content" : { + "type" : "string", + "description" : "Card 내용" + } + } + }, + "RecruitCreateRequest" : { + "title" : "RecruitCreateRequest", + "type" : "object", + "properties" : { + "recruitScheduleStage" : { + "type" : "string", + "description" : "recruitScheduleStage" + }, + "siteUrl" : { + "type" : "string", + "description" : "siteUrl" + }, + "season" : { + "type" : "string", + "description" : "season" + }, + "deadline" : { + "type" : "string", + "description" : "deadline" + }, + "title" : { + "type" : "string", + "description" : "title" + } + } + }, + "CardImageUploadCompleteRequest" : { + "title" : "CardImageUploadCompleteRequest", + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "fileName" : { + "type" : "string", + "description" : "fileName" + } + } + } + }, + "CardCreateResponse" : { + "title" : "CardCreateResponse", + "type" : "object", + "properties" : { + "cardId" : { + "type" : "number", + "description" : "Card ID" + } + } + }, + "CardImageUploadCompleteResponse" : { + "title" : "CardImageUploadCompleteResponse", + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "staticUrl" : { + "type" : "string", + "description" : "staticUrl" + } + } + } + }, + "TokenDto" : { + "title" : "TokenDto", + "type" : "object", + "properties" : { + "accessToken" : { + "type" : "string", + "description" : "accessToken" + }, + "refreshToken" : { + "type" : "string", + "description" : "refreshToken" + } + } + }, + "ErrorResponse" : { + "title" : "ErrorResponse", + "type" : "object", + "properties" : { + "message" : { + "type" : "string", + "description" : "Error message" + }, + "status" : { + "type" : "string", + "description" : "HTTP status code" + } + } + }, + "RecruitUpdateSeasonRequest" : { + "title" : "RecruitUpdateSeasonRequest", + "type" : "object", + "properties" : { + "season" : { + "type" : "string", + "description" : "season" + } + } + }, + "RecruitUpdateSiteUrlRequest" : { + "title" : "RecruitUpdateSiteUrlRequest", + "type" : "object", + "properties" : { + "siteUrl" : { + "type" : "string", + "description" : "siteUrl" + } + } + }, + "RecruitUpdateTitleRequest" : { + "title" : "RecruitUpdateTitleRequest", + "type" : "object", + "properties" : { + "title" : { + "type" : "string", + "description" : "title" + } + } + }, + "CardListGetResponse" : { + "title" : "CardListGetResponse", + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "tagList" : { + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "name" : { + "type" : "string", + "description" : "태그 이름" + }, + "id" : { + "type" : "number", + "description" : "태그 ID" + }, + "type" : { + "type" : "string", + "description" : "태그 타입" + } + } + } + }, + "updatedDate" : { + "type" : "string", + "description" : "Card 수정일시" + }, + "id" : { + "type" : "number", + "description" : "Card ID" + }, + "title" : { + "type" : "string", + "description" : "Card 제목" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/test/java/com/server/bbo_gak/domain/recruit/controller/RecruitControllerTest.java b/src/test/java/com/server/bbo_gak/domain/recruit/controller/RecruitControllerTest.java new file mode 100644 index 0000000..6a2c2c9 --- /dev/null +++ b/src/test/java/com/server/bbo_gak/domain/recruit/controller/RecruitControllerTest.java @@ -0,0 +1,275 @@ +package com.server.bbo_gak.domain.recruit.controller; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.server.bbo_gak.domain.recruit.dto.request.RecruitCreateRequest; +import com.server.bbo_gak.domain.recruit.dto.request.RecruitUpdateSeasonRequest; +import com.server.bbo_gak.domain.recruit.dto.request.RecruitUpdateSiteUrlRequest; +import com.server.bbo_gak.domain.recruit.dto.request.RecruitUpdateStatusRequest; +import com.server.bbo_gak.domain.recruit.dto.request.RecruitUpdateTitleRequest; +import com.server.bbo_gak.domain.recruit.dto.response.RecruitGetResponse; +import com.server.bbo_gak.domain.recruit.entity.RecruitScheduleStage; +import com.server.bbo_gak.domain.recruit.entity.RecruitStatus; +import com.server.bbo_gak.domain.recruit.service.RecruitService; +import com.server.bbo_gak.domain.user.entity.User; +import com.server.bbo_gak.global.AbstractRestDocsTests; +import com.server.bbo_gak.global.RestDocsFactory; +import com.server.bbo_gak.global.error.exception.BusinessException; +import com.server.bbo_gak.global.error.exception.ErrorCode; +import com.server.bbo_gak.global.error.exception.NotFoundException; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpMethod; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@ActiveProfiles("test") +public class RecruitControllerTest extends AbstractRestDocsTests { + + private static final String DEFAULT_URL = "/api/v1/recruits"; + + @MockBean + private RecruitService recruitService; + + @Autowired + private RestDocsFactory restDocsFactory; + + private RecruitGetResponse response; + private User user; + + @BeforeEach + void setUp() { + response = RecruitGetResponse.builder() + .id(1L) + .title("New Title") + .season("2024 상반기") + .siteUrl("https://example.com") + .recruitStatus(RecruitStatus.APPLICATION_COMPLETED.getValue()) + .createdDate("2023-01-01T00:00:00") + .build(); + user = new User(/* initialize fields */); + } + + @Nested + class 공고제목_업데이트 { + + @Test + public void 성공() throws Exception { + RecruitUpdateTitleRequest request = new RecruitUpdateTitleRequest("New Title"); + when(recruitService.updateRecruitTitle(any(), any(), any())).thenReturn(response); + + mockMvc.perform( + restDocsFactory.createRequest(DEFAULT_URL + "/{id}/title", request, HttpMethod.PATCH, objectMapper, 1L)) + .andExpect(status().isOk()) + .andDo( + restDocsFactory.getSuccessResource("[PATCH] 제목 수정 성공", "공고 타이틀 수정", "Recruit", request, + response)); + } + + @Test + public void 실패() throws Exception { + RecruitUpdateTitleRequest request = new RecruitUpdateTitleRequest("New Title"); + when(recruitService.updateRecruitTitle(any(), any(), any())).thenThrow( + new BusinessException(ErrorCode.RECRUIT_NOT_FOUND)); + + mockMvc.perform( + restDocsFactory.createRequest(DEFAULT_URL + "/{id}/title", request, HttpMethod.PATCH, objectMapper, 1L)) + .andExpect(status().isNotFound()) + .andDo( + restDocsFactory.getFailureResource("[PATCH] 제목 수정 실패", "Recruit", request)); + } + } + + @Nested + class 공고시즌_업데이트 { + + @Test + public void 성공() throws Exception { + RecruitUpdateSeasonRequest request = new RecruitUpdateSeasonRequest("2024 상반기"); + when(recruitService.updateRecruitSeason(any(), any(), any())).thenReturn(response); + + mockMvc.perform( + restDocsFactory.createRequest(DEFAULT_URL + "/{id}/season", request, HttpMethod.PATCH, objectMapper, + 1L)) + .andExpect(status().isOk()) + .andDo( + restDocsFactory.getSuccessResource("[PATCH] 분기 수정 성공", "공고 분기 수정", "Recruit", request, response)); + } + + @Test + public void 실패() throws Exception { + RecruitUpdateSeasonRequest request = new RecruitUpdateSeasonRequest("2024 상반기"); + when(recruitService.updateRecruitSeason(any(), any(), any())).thenThrow( + new BusinessException(ErrorCode.RECRUIT_NOT_FOUND)); + + mockMvc.perform( + restDocsFactory.createRequest(DEFAULT_URL + "/{id}/season", request, HttpMethod.PATCH, objectMapper, + 1L)) + .andExpect(status().isNotFound()) + .andDo(restDocsFactory.getFailureResource("[PATCH] 분기 수정 실패", "Recruit", request)); + } + } + + @Nested + class 공고상태_업데이트 { + + @Test + public void 성공() throws Exception { + RecruitUpdateStatusRequest request = new RecruitUpdateStatusRequest(RecruitStatus.APPLICATION_COMPLETED); + when(recruitService.updateRecruitStatus(any(), any(), any())).thenReturn(response); + + mockMvc.perform( + restDocsFactory.createRequest(DEFAULT_URL + "/{id}/status", request, HttpMethod.PATCH, objectMapper, + 1L)) + .andExpect(status().isOk()) + .andDo( + restDocsFactory.getSuccessResource("[PATCH] 상태 수정 성공", "공고 상태 수정", "Recruit", request, response)); + } + + @Test + public void 실패() throws Exception { + RecruitUpdateStatusRequest request = new RecruitUpdateStatusRequest(RecruitStatus.APPLICATION_COMPLETED); + when(recruitService.updateRecruitStatus(any(), any(), any())).thenThrow( + new BusinessException(ErrorCode.RECRUIT_NOT_FOUND)); + + mockMvc.perform( + restDocsFactory.createRequest(DEFAULT_URL + "/{id}/status", request, HttpMethod.PATCH, objectMapper, + 1L)) + .andExpect(status().isNotFound()) + .andDo(restDocsFactory.getFailureResource("[PATCH] 상태 수정 실패", "Recruit", request)); + } + } + + @Nested + class 공고사이트_URL_업데이트 { + + @Test + public void 성공() throws Exception { + RecruitUpdateSiteUrlRequest request = new RecruitUpdateSiteUrlRequest("https://example.com"); + when(recruitService.updateRecruitSiteUrl(any(), any(), any())).thenReturn(response); + + mockMvc.perform( + restDocsFactory.createRequest(DEFAULT_URL + "/{id}/siteUrl", request, HttpMethod.PATCH, objectMapper, + 1L)) + .andExpect(status().isOk()) + .andDo( + restDocsFactory.getSuccessResource("[PATCH] 사이트 URL 수정 성공", "공고 사이트 URL 수정", "Recruit", request, + response)); + } + + @Test + public void 실패() throws Exception { + RecruitUpdateSiteUrlRequest request = new RecruitUpdateSiteUrlRequest("https://example.com"); + when(recruitService.updateRecruitSiteUrl(any(), any(), any())).thenThrow( + new BusinessException(ErrorCode.RECRUIT_NOT_FOUND)); + + mockMvc.perform( + restDocsFactory.createRequest(DEFAULT_URL + "/{id}/siteUrl", request, HttpMethod.PATCH, objectMapper, + 1L)) + .andExpect(status().isNotFound()) + .andDo(restDocsFactory.getFailureResource("[PATCH] 사이트 URL 수정 실패", "Recruit", request)); + } + } + + @Nested + class 공고_생성 { + + @Test + public void 성공() throws Exception { + RecruitCreateRequest request = new RecruitCreateRequest( + "2024 상반기", + "New Recruit Title", + "https://example.com", + RecruitScheduleStage.CLOSING_DOCUMENT, + "2024-12-31" + ); + RecruitGetResponse response = RecruitGetResponse.builder() + .id(1L) + .title("New Title") + .season("2024 상반기") + .siteUrl("https://example.com") + .recruitStatus("") + .createdDate("2023-01-01T00:00:00") + .build(); + + when(recruitService.createRecruit(any(), any())).thenReturn(response); + + mockMvc.perform(restDocsFactory.createRequest(DEFAULT_URL, request, HttpMethod.POST, objectMapper)) + .andExpect(status().isOk()) + .andDo(restDocsFactory.getSuccessResource("[POST] 공고 생성 성공", "공고 생성", "Recruit", request, + response)); + } + } + + @Nested + class 공고_삭제 { + + @Test + public void 성공() throws Exception { + doNothing().when(recruitService).deleteRecruit(any(), any()); + + mockMvc.perform( + restDocsFactory.createRequest(DEFAULT_URL + "/{id}", null, HttpMethod.DELETE, objectMapper, 1L)) + .andExpect(status().isOk()) + .andDo(restDocsFactory.getSuccessResource("[DELETE] 공고 삭제 성공", "공고 삭제", "Recruit", null, null)); + } + + @Test + public void 채용_삭제_실패() throws Exception { + doThrow(new NotFoundException(ErrorCode.RECRUIT_NOT_FOUND)).when(recruitService) + .deleteRecruit(any(), any()); + + mockMvc.perform( + restDocsFactory.createRequest(DEFAULT_URL + "/{id}", null, HttpMethod.DELETE, objectMapper, 1L)) + .andExpect(status().isNotFound()) + .andDo(restDocsFactory.getFailureResource("[DELETE] 공고 삭제 실패", "Recruit", null)); + } + } + + @Nested + class 공고_목록_조회 { + + @Test + public void 전체_목록_조회_성공() throws Exception { + when(recruitService.getTotalRecruitList(any())).thenReturn(List.of(response)); + + mockMvc.perform(restDocsFactory.createRequest(DEFAULT_URL, null, HttpMethod.GET, objectMapper)) + .andExpect(status().isOk()) + .andDo( + restDocsFactory.getSuccessResourceList("[GET] 전체 공고 목록 조회 성공", "전체 공고 목록 조회", "Recruit", List.of(), + List.of(response))); + } + + @Test + public void 시즌별_목록_조회_성공() throws Exception { + when(recruitService.getRecruitListBySeason(any(), any())).thenReturn(List.of(response)); + + mockMvc.perform( + restDocsFactory.createRequest(DEFAULT_URL + "/bySeason?season={season}", null, HttpMethod.GET, + objectMapper, "2024 상반기")) + .andExpect(status().isOk()) + .andDo(restDocsFactory.getSuccessResourceList("[GET] 분기별 공고 목록 조회 성공", "분기별 공고 목록 조회", "Recruit", + List.of(), List.of(response))); + } + + @Test + public void 진행중인_목록_조회_성공() throws Exception { + when(recruitService.getProgressingRecruitList(any())).thenReturn(List.of(response)); + + mockMvc.perform( + restDocsFactory.createRequest(DEFAULT_URL + "/progressing", null, HttpMethod.GET, objectMapper)) + .andExpect(status().isOk()) + .andDo(restDocsFactory.getSuccessResourceList("[GET] 진행 중 공고 목록 조회 성공", "진행중 공고 목록 조회", "Recruit", + List.of(), List.of(response))); + } + } +} diff --git a/src/test/java/com/server/bbo_gak/global/RestDocsFactory.java b/src/test/java/com/server/bbo_gak/global/RestDocsFactory.java index 3ca0d8f..99c337f 100644 --- a/src/test/java/com/server/bbo_gak/global/RestDocsFactory.java +++ b/src/test/java/com/server/bbo_gak/global/RestDocsFactory.java @@ -73,9 +73,31 @@ public RequestBuilder createRequestList( public RestDocumentationResultHandler getSuccessResource( String identifier, String description, String tag, T requestDto, R responseDto ) { - String requestSchemaName = requestDto.getClass().getSimpleName(); + String requestSchemaName = requestDto != null ? requestDto.getClass().getSimpleName() : ""; String responseSchemaName = responseDto != null ? responseDto.getClass().getSimpleName() : ""; + return getRestDocumentationResultHandler(identifier, description, tag, requestDto, responseDto, + requestSchemaName, responseSchemaName); + } + + private RestDocumentationResultHandler getRestDocumentationResultHandler(String identifier, + String description, String tag, T requestDto, R responseDto, String requestSchemaName, + String responseSchemaName) { + + if (requestDto == null) { + return MockMvcRestDocumentationWrapper.document(identifier, + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tags(tag) + .description(description) + .responseSchema(Schema.schema(responseSchemaName)) + .responseFields(responseDto != null ? getFields(responseDto) : new FieldDescriptor[]{}) + .build() + ) + ); + } + return MockMvcRestDocumentationWrapper.document(identifier, preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), @@ -95,8 +117,33 @@ public RestDocumentationResultHandler getSuccessResource( public RestDocumentationResultHandler getSuccessResourceList( String identifier, String description, String tag, List requestDtos, List responseDtos ) { - String requestSchemaName = requestDtos.getFirst().getClass().getSimpleName(); - String responseSchemaName = responseDtos.getFirst().getClass().getSimpleName(); + String requestSchemaName = !requestDtos.isEmpty() ? requestDtos.getFirst().getClass().getSimpleName() : ""; + String responseSchemaName = !responseDtos.isEmpty() ? responseDtos.getFirst().getClass().getSimpleName() : ""; + + return getRestDocumentationResultHandler(identifier, description, tag, requestDtos, responseDtos, + requestSchemaName, + responseSchemaName); + } + + private RestDocumentationResultHandler getRestDocumentationResultHandler(String identifier, + String description, String tag, + List requestDtos, List responseDtos, String requestSchemaName, String responseSchemaName) { + + if (requestDtos.isEmpty()) { + return MockMvcRestDocumentationWrapper.document(identifier, + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tags(tag) + .description(description) + .responseSchema(Schema.schema(responseSchemaName)) + .responseFields( + getFieldsList( + !responseDtos.isEmpty() ? responseDtos.getFirst() : new FieldDescriptor[]{})) + .build() + ) + ); + } return MockMvcRestDocumentationWrapper.document(identifier, preprocessRequest(prettyPrint()), @@ -108,7 +155,8 @@ public RestDocumentationResultHandler getSuccessResourceList( .requestSchema(Schema.schema(requestSchemaName)) .responseSchema(Schema.schema(responseSchemaName)) .requestFields(getFieldsList(requestDtos.getFirst())) - .responseFields(getFieldsList(responseDtos.getFirst())) + .responseFields(getFieldsList( + !responseDtos.isEmpty() ? responseDtos.getFirst() : new FieldDescriptor[]{})) .build() ) ); @@ -117,12 +165,26 @@ public RestDocumentationResultHandler getSuccessResourceList( public RestDocumentationResultHandler getFailureResourceList(String identifier, String tag, List requestDtos) { -// List requestFields = new ArrayList<>(); -// for (T requestDto : requestDtos) { -// requestFields.addAll(getFieldsList(requestDto)); -// } - String requestSchemaName = requestDtos.getFirst().getClass().getSimpleName(); + String requestSchemaName = !requestDtos.isEmpty() ? requestDtos.getFirst().getClass().getSimpleName() : ""; + + return getRestDocumentationResultHandler(identifier, tag, requestDtos, requestSchemaName); + } + + private RestDocumentationResultHandler getRestDocumentationResultHandler(String identifier, String tag, + List requestDtos, String requestSchemaName) { + + if (requestDtos.isEmpty()) { + return MockMvcRestDocumentationWrapper.document(identifier, + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tags(tag) + .responseFields(getFailureFields()) + .build() + ) + ); + } return MockMvcRestDocumentationWrapper.document(identifier, preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), @@ -138,8 +200,24 @@ public RestDocumentationResultHandler getFailureResourceList(String identifi } public RestDocumentationResultHandler getFailureResource(String identifier, String tag, T requestDto) { - String requestSchemaName = requestDto.getClass().getSimpleName(); + String requestSchemaName = requestDto != null ? requestDto.getClass().getSimpleName() : ""; + + return getRestDocumentationResultHandler(identifier, tag, requestDto, requestSchemaName); + } + private RestDocumentationResultHandler getRestDocumentationResultHandler(String identifier, String tag, + T requestDto, String requestSchemaName) { + if (requestDto == null) { + return MockMvcRestDocumentationWrapper.document(identifier, + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tags(tag) + .responseFields(getFailureFields()) + .build() + ) + ); + } return MockMvcRestDocumentationWrapper.document(identifier, preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), @@ -161,17 +239,19 @@ private RequestBuilder buildRequest(String url, String content, HttpMethod metho .content(content) .accept(MediaType.APPLICATION_JSON); case "GET" -> RestDocumentationRequestBuilders.get(url) - .contentType(MediaType.APPLICATION_JSON) - .content(content) - .accept(MediaType.APPLICATION_JSON); + .contentType(MediaType.APPLICATION_JSON); case "PUT" -> RestDocumentationRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(content) .accept(MediaType.APPLICATION_JSON); - case "DELETE" -> RestDocumentationRequestBuilders.delete(url) - .contentType(MediaType.APPLICATION_JSON) - .content(content) - .accept(MediaType.APPLICATION_JSON); + case "DELETE" -> content.equals("null") ? + RestDocumentationRequestBuilders.delete(url) + .contentType(MediaType.APPLICATION_JSON) + : + RestDocumentationRequestBuilders.delete(url) + .contentType(MediaType.APPLICATION_JSON) + .content(content) + .accept(MediaType.APPLICATION_JSON); default -> throw new IllegalArgumentException("Invalid HTTP method: " + method); }; } @@ -181,6 +261,7 @@ public RequestBuilder buildRequest(String url, String content, HttpMethod method case "POST" -> createPostRequest(url, content, pathParams); case "GET" -> createGetRequest(url, content, pathParams); case "PUT" -> createPutRequest(url, content, pathParams); + case "PATCH" -> createPatchRequest(url, content, pathParams); case "DELETE" -> createDeleteRequest(url, content, pathParams); default -> throw new IllegalArgumentException("Invalid HTTP method: " + method); }; @@ -193,13 +274,18 @@ private RequestBuilder createPostRequest(String url, String content, Object... p .accept(MediaType.APPLICATION_JSON); } - private RequestBuilder createGetRequest(String url, String content, Object... pathParams) { - return RestDocumentationRequestBuilders.get(url, pathParams) + private RequestBuilder createPatchRequest(String url, String content, Object... pathParams) { + return RestDocumentationRequestBuilders.patch(url, pathParams) .contentType(MediaType.APPLICATION_JSON) .content(content) .accept(MediaType.APPLICATION_JSON); } + private RequestBuilder createGetRequest(String url, String content, Object... pathParams) { + return RestDocumentationRequestBuilders.get(url, pathParams) + .contentType(MediaType.APPLICATION_JSON); + } + private RequestBuilder createPutRequest(String url, String content, Object... pathParams) { return RestDocumentationRequestBuilders.put(url, pathParams) .contentType(MediaType.APPLICATION_JSON) @@ -208,6 +294,10 @@ private RequestBuilder createPutRequest(String url, String content, Object... pa } private RequestBuilder createDeleteRequest(String url, String content, Object... pathParams) { + if (content.equals("null")) { + return RestDocumentationRequestBuilders.delete(url, pathParams) + .contentType(MediaType.APPLICATION_JSON); + } return RestDocumentationRequestBuilders.delete(url, pathParams) .contentType(MediaType.APPLICATION_JSON) .content(content) diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index 955ec36..ae2b28d 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -11,9 +11,10 @@ spring: hibernate: ddl-auto: update show-sql: true - properties: - hibernate: - format_sql: true + properties: + hibernate: + format_sql: true + dialect: org.hibernate.dialect.H2Dialect cloud: aws: