diff --git a/.idea/jpa-buddy.xml b/.idea/jpa-buddy.xml new file mode 100644 index 0000000..966d5f5 --- /dev/null +++ b/.idea/jpa-buddy.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..59780fe --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/jumpit/.gitignore b/jumpit/.gitignore index ed2e7b0..1779025 100644 --- a/jumpit/.gitignore +++ b/jumpit/.gitignore @@ -6,6 +6,8 @@ build/ !**/src/test/**/build/ *.yaml +*.yaml + ### STS ### .apt_generated .classpath diff --git a/jumpit/src/main/java/org/sopt/jumpit/global/common/dto/message/ErrorMessage.java b/jumpit/src/main/java/org/sopt/jumpit/global/common/dto/message/ErrorMessage.java index e8344dd..9d8c719 100644 --- a/jumpit/src/main/java/org/sopt/jumpit/global/common/dto/message/ErrorMessage.java +++ b/jumpit/src/main/java/org/sopt/jumpit/global/common/dto/message/ErrorMessage.java @@ -11,10 +11,12 @@ public enum ErrorMessage { INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(), "[ERROR] 서버 내부 오류가 발생하였습니다."), COMPANY_NOT_FOUND_BY_ID_EXCEPTION(HttpStatus.NOT_FOUND.value(), "[ERROR] ID에 해당하는 기업이 없습니다."), SKILL_NOT_FOUND_BY_ID_EXCEPTION(HttpStatus.NOT_FOUND.value(), "[ERROR] ID에 해당하는 기술스택이 없습니다."), + USER_NOT_FOUND_BY_ID_EXCEPTION(HttpStatus.NOT_FOUND.value(), "[ERROR] ID에 해당하는 유저를 찾을 수 없습니다."), + RESUME_NOT_FOUND_BY_ID_EXCEPTION(HttpStatus.NOT_FOUND.value(), "[ERROR] ID에 해당하는 이력서를 찾을 수 없습니다."), CATEGORIES_NOT_FOUND_BY_POSITION_EXCEPTION(HttpStatus.NOT_FOUND.value(), "[ERROR] 포지션에 해당하는 카테고리가 없습니다."), POSITION_NOT_FOUND_BY_CATEGORIES_EXCEPTION(HttpStatus.NOT_FOUND.value(), "[ERROR] 카테고리에 해당하는 포지션이 없습니다."), POSITION_NOT_FOUND_EXCEPTION(HttpStatus.NOT_FOUND.value(), "[ERROR] ID에 해당하는 포지션이 없습니다."), - PARSE_EXCEPTION(HttpStatus.BAD_REQUEST.value(), "[ERROR] JSON 파싱에 실패하였습니다."), + PARSE_EXCEPTION(HttpStatus.BAD_REQUEST.value(), "[ERROR] JSON 파싱에 실패하였습니다.") ; private final int status; diff --git a/jumpit/src/main/java/org/sopt/jumpit/global/common/dto/message/SuccessMessage.java b/jumpit/src/main/java/org/sopt/jumpit/global/common/dto/message/SuccessMessage.java index 89ddc3d..b4e7d94 100644 --- a/jumpit/src/main/java/org/sopt/jumpit/global/common/dto/message/SuccessMessage.java +++ b/jumpit/src/main/java/org/sopt/jumpit/global/common/dto/message/SuccessMessage.java @@ -8,7 +8,10 @@ @Getter public enum SuccessMessage { SEARCH_COMPLETED_SUCCESS(HttpStatus.OK.value(), "[SUCCESS] 채용 공고 검색이 완료되었습니다."), - FILTER_SEARCH_COMPLETED_SUCCESS(HttpStatus.OK.value(), "[SUCCESS] 채용 공고 필터 검색이 완료되었습니다."); + FILTER_SEARCH_COMPLETED_SUCCESS(HttpStatus.OK.value(), "[SUCCESS] 채용 공고 필터 검색이 완료되었습니다."), + RESUME_CREATED_COMPLETED_SUCCESS(HttpStatus.OK.value(), "[SUCCESS] 이력서 등록을 완료하였습니다."), + RESUME_SEARCH_COMPLETED_SUCCESS(HttpStatus.OK.value(), "[SUCCESS] 이력서 조회를 성공했습니다."), + RESUME_PRIVATE_CHANGE_COMPLETED_SUCCESS(HttpStatus.OK.value(), "[SUCCESS] 이력서 공개 범위 변경이 완료되었습니다."); private final int status; private final String message; diff --git a/jumpit/src/main/java/org/sopt/jumpit/resume/controller/ResumeController.java b/jumpit/src/main/java/org/sopt/jumpit/resume/controller/ResumeController.java new file mode 100644 index 0000000..06fbc3c --- /dev/null +++ b/jumpit/src/main/java/org/sopt/jumpit/resume/controller/ResumeController.java @@ -0,0 +1,49 @@ +package org.sopt.jumpit.resume.controller; + + +import lombok.RequiredArgsConstructor; +import org.sopt.jumpit.global.common.dto.SuccessResponse; +import org.sopt.jumpit.global.common.dto.message.SuccessMessage; +import org.sopt.jumpit.resume.domain.Resume; +import org.sopt.jumpit.resume.dto.ResumeCreateRequest; +import org.sopt.jumpit.resume.dto.ResumePrivateRequest; +import org.sopt.jumpit.resume.dto.ResumeSearchResponse; +import org.sopt.jumpit.resume.service.ResumeService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; + +@RestController +@RequestMapping("/api/v1") +@RequiredArgsConstructor +public class ResumeController { + private final ResumeService resumeService; + + @PostMapping("/resumes") + public ResponseEntity> createResume ( + @RequestBody ResumeCreateRequest resumeCreateRequest + ) { + return ResponseEntity.status(HttpStatus.CREATED) + .header("Location", resumeService.createResume(resumeCreateRequest)) + .body(SuccessResponse.of(SuccessMessage.RESUME_CREATED_COMPLETED_SUCCESS)); + } + + @GetMapping("/resumes/{userId}") + public ResponseEntity> findResumeById(@PathVariable Long userId) { + return ResponseEntity.status(HttpStatus.OK) + .body(SuccessResponse.of(SuccessMessage.RESUME_SEARCH_COMPLETED_SUCCESS, + resumeService.findResumeById(userId))); + } + + @PatchMapping("/resumes/{resumeId}") + public ResponseEntity updateIsPrivate( + @PathVariable Long resumeId, + @RequestBody ResumePrivateRequest request + ) { + resumeService.updateResumePrivate(resumeId, request); + return ResponseEntity.status(HttpStatus.NO_CONTENT) + .body(SuccessResponse.of(SuccessMessage.RESUME_PRIVATE_CHANGE_COMPLETED_SUCCESS)); + } +} diff --git a/jumpit/src/main/java/org/sopt/jumpit/resume/domain/Resume.java b/jumpit/src/main/java/org/sopt/jumpit/resume/domain/Resume.java index 6fd34db..cec79e3 100644 --- a/jumpit/src/main/java/org/sopt/jumpit/resume/domain/Resume.java +++ b/jumpit/src/main/java/org/sopt/jumpit/resume/domain/Resume.java @@ -1,12 +1,20 @@ package org.sopt.jumpit.resume.domain; import jakarta.persistence.*; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.sopt.jumpit.resume.dto.ResumeCreateRequest; import org.sopt.jumpit.user.domain.User; import java.time.LocalDateTime; @Entity @Table(name = "Resume") +@Getter +@Setter +@NoArgsConstructor public class Resume { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -28,4 +36,17 @@ public class Resume { @Column(name = "createdAt") private LocalDateTime createdAt; + @Builder + private Resume(User owner, String title) { + this.owner = owner; + this.title = title; + this.isPrivate = false; + } + + public static Resume create(User owner, String title) { + return Resume.builder() + .title(title) + .owner(owner) + .build(); + } } diff --git a/jumpit/src/main/java/org/sopt/jumpit/resume/dto/ResumeCreateRequest.java b/jumpit/src/main/java/org/sopt/jumpit/resume/dto/ResumeCreateRequest.java new file mode 100644 index 0000000..a6d3cc7 --- /dev/null +++ b/jumpit/src/main/java/org/sopt/jumpit/resume/dto/ResumeCreateRequest.java @@ -0,0 +1,7 @@ +package org.sopt.jumpit.resume.dto; + +public record ResumeCreateRequest( + String title, + Long userId +) { +} diff --git a/jumpit/src/main/java/org/sopt/jumpit/resume/dto/ResumePrivateRequest.java b/jumpit/src/main/java/org/sopt/jumpit/resume/dto/ResumePrivateRequest.java new file mode 100644 index 0000000..adcdc0e --- /dev/null +++ b/jumpit/src/main/java/org/sopt/jumpit/resume/dto/ResumePrivateRequest.java @@ -0,0 +1,6 @@ +package org.sopt.jumpit.resume.dto; + +public record ResumePrivateRequest( + Boolean isPrivate +) { +} diff --git a/jumpit/src/main/java/org/sopt/jumpit/resume/dto/ResumeResponse.java b/jumpit/src/main/java/org/sopt/jumpit/resume/dto/ResumeResponse.java new file mode 100644 index 0000000..9b2a5a7 --- /dev/null +++ b/jumpit/src/main/java/org/sopt/jumpit/resume/dto/ResumeResponse.java @@ -0,0 +1,29 @@ +package org.sopt.jumpit.resume.dto; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import org.sopt.jumpit.resume.domain.Resume; + +import java.time.LocalDateTime; + +@Builder(access = AccessLevel.PRIVATE) +public record ResumeResponse( + Long id, + String title, + Boolean isPrivate, + LocalDateTime createdAt, + LocalDateTime modifiedAt +) { + + public static ResumeResponse of(Resume resume) { + return ResumeResponse.builder() + .id(resume.getId()) + .title(resume.getTitle()) + .isPrivate(resume.isPrivate()) + .createdAt(resume.getCreatedAt()) + .modifiedAt(resume.getModifiedAt()) + .build(); + + } +} diff --git a/jumpit/src/main/java/org/sopt/jumpit/resume/dto/ResumeSearchResponse.java b/jumpit/src/main/java/org/sopt/jumpit/resume/dto/ResumeSearchResponse.java new file mode 100644 index 0000000..a093acb --- /dev/null +++ b/jumpit/src/main/java/org/sopt/jumpit/resume/dto/ResumeSearchResponse.java @@ -0,0 +1,23 @@ +package org.sopt.jumpit.resume.dto; + +import lombok.AccessLevel; +import lombok.Builder; + +import java.util.List; + +@Builder(access = AccessLevel.PRIVATE) + +public record ResumeSearchResponse( + long userId, + List resumes +) { + + public static ResumeSearchResponse of(long userId, List resumes) { + return ResumeSearchResponse.builder() + .userId(userId) + .resumes(resumes) + .build(); + } + + +} diff --git a/jumpit/src/main/java/org/sopt/jumpit/resume/repository/ResumeRepository.java b/jumpit/src/main/java/org/sopt/jumpit/resume/repository/ResumeRepository.java index c8e9504..ff131ee 100644 --- a/jumpit/src/main/java/org/sopt/jumpit/resume/repository/ResumeRepository.java +++ b/jumpit/src/main/java/org/sopt/jumpit/resume/repository/ResumeRepository.java @@ -1,9 +1,14 @@ package org.sopt.jumpit.resume.repository; import org.sopt.jumpit.resume.domain.Resume; +import org.sopt.jumpit.resume.dto.ResumeResponse; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.Optional; + @Repository public interface ResumeRepository extends JpaRepository { + List findByOwnerId(Long userId); } diff --git a/jumpit/src/main/java/org/sopt/jumpit/resume/service/ResumeService.java b/jumpit/src/main/java/org/sopt/jumpit/resume/service/ResumeService.java new file mode 100644 index 0000000..85eefad --- /dev/null +++ b/jumpit/src/main/java/org/sopt/jumpit/resume/service/ResumeService.java @@ -0,0 +1,57 @@ +package org.sopt.jumpit.resume.service; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.sopt.jumpit.global.common.dto.message.ErrorMessage; +import org.sopt.jumpit.global.exception.NotFoundException; +import org.sopt.jumpit.resume.domain.Resume; +import org.sopt.jumpit.resume.dto.ResumeCreateRequest; +import org.sopt.jumpit.resume.dto.ResumePrivateRequest; +import org.sopt.jumpit.resume.dto.ResumeResponse; +import org.sopt.jumpit.resume.dto.ResumeSearchResponse; +import org.sopt.jumpit.resume.repository.ResumeRepository; +import org.sopt.jumpit.user.domain.User; +import org.sopt.jumpit.user.repository.UserRepository; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class ResumeService { + private final ResumeRepository resumeRepository; + private final UserRepository userRepository; + + @Transactional + public String createResume( + ResumeCreateRequest resumeCreateRequest + ) { + User findUser = userRepository.findById(resumeCreateRequest.userId()).orElseThrow( + () -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_ID_EXCEPTION) + ); + return resumeRepository.save(Resume.create(findUser, "내 이력서")).getId().toString(); + } + + public ResumeSearchResponse findResumeById(Long userId) { + List resumes = resumeRepository.findByOwnerId(userId) + .stream() + .map(ResumeResponse::of) + .collect(Collectors.toList()); + userRepository.findById(userId).orElseThrow( + () -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_ID_EXCEPTION) + ); + return ResumeSearchResponse.of(userId, resumes); + } + + @Transactional + public void updateResumePrivate( + Long resumeId, + ResumePrivateRequest resumePrivateRequest + ) { + Resume resume = resumeRepository.findById(resumeId).orElseThrow( + () -> new NotFoundException(ErrorMessage.RESUME_NOT_FOUND_BY_ID_EXCEPTION) + ); + resume.setPrivate(resumePrivateRequest.isPrivate()); + } +}