diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..00fadce --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,67 @@ +name: Spring Boot & Gradle CI/CD + +on: + push: + branches: + - develop + pull_request: + branches: + - develop + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + # JDK 17 설치 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: make application.yml + run: | + cd ./src/main/resources + touch ./application-dev.yml + echo "${{ secrets.APPLICATION_DEV_YML }}" >> ./application-dev.yml + shell: bash + + # gradlew에 실행 권한을 부여합니다. + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + # test는 CI 과정에서 수행되므로 여기서는 `-x`로 테스트를 생략했습니다. + # `--stacktrace`로 더 자세한 로그가 출력되게 해줍니다. + - name: Build with Gradle (without Test) + run: ./gradlew clean build -x test --stacktrace + + # docker hub에 로그인하고 이미지를 빌드합니다. 이후에 push를 진행합니다. + # docker_username을 적지 않으면 push 시에 요청이 거부될 수 있습니다. + - name: Docker Hub build & push + run: | + docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} + docker build -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }} . + docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }} + + # EC2에 접속하고 배포합니다. + - name: Deploy + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USERNAME }} + key: ${{ secrets.EC2_PRIVATE_KEY }} + + script: | + cd /home/ubuntu/Server + sudo docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} + sudo docker rm -f $(sudo docker ps -qa) + sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }} + sudo docker-compose up -d + sudo docker image prune -f diff --git a/.gitignore b/.gitignore index b3db3e2..4457eb4 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,4 @@ out/ ### VS Code ### .vscode/ -application.yml +application-dev.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4cfe0e5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:17 +ARG JAR_FILE=build/libs/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java","-jar","-Dspring.profiles.active=dev","/app.jar"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6f4c7f3 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +version: '3' + +services: + redis: + image: redis + ports: + - "6379:6379" + + application: + container_name: springboot + image: ${DOCKER_USERNAME}/${DOCKER_REPO} + ports: + - "8080:8080" + expose: + - "8080" diff --git a/src/main/java/UMC/career_mate/domain/answer/controller/AnswerController.java b/src/main/java/UMC/career_mate/domain/answer/controller/AnswerController.java index 4e42240..960ff42 100644 --- a/src/main/java/UMC/career_mate/domain/answer/controller/AnswerController.java +++ b/src/main/java/UMC/career_mate/domain/answer/controller/AnswerController.java @@ -5,7 +5,9 @@ import UMC.career_mate.domain.answer.dto.response.AnswerInfoListDTO; import UMC.career_mate.domain.answer.service.AnswerCommandService; import UMC.career_mate.domain.answer.service.AnswerQueryService; +import UMC.career_mate.domain.member.Member; import UMC.career_mate.domain.template.enums.TemplateType; +import UMC.career_mate.global.annotation.LoginMember; import UMC.career_mate.global.response.ApiResponse; import UMC.career_mate.global.response.result.code.CommonResultCode; import io.swagger.v3.oas.annotations.Operation; @@ -107,9 +109,9 @@ public class AnswerController { ``` """ ) - public ApiResponse saveAnswerList(@RequestParam Long memberId, + public ApiResponse saveAnswerList(@LoginMember Member member, @Valid @RequestBody AnswerCreateOrUpdateDTO answerCreateOrUpdateDTO) { - answerCommandService.saveAnswerList(memberId, answerCreateOrUpdateDTO); + answerCommandService.saveAnswerList(member, answerCreateOrUpdateDTO); return ApiResponse.onSuccess(CREATE_ANSWER_LIST); } @@ -205,9 +207,9 @@ public ApiResponse> getAnswerList(@RequestParam Long mem ``` """ ) - public ApiResponse updateAnswerList(@RequestParam Long memberId, + public ApiResponse updateAnswerList(@LoginMember Member member, @Valid @RequestBody AnswerCreateOrUpdateDTO answerCreateOrUpdateDTO) { - answerCommandService.updateAnswerList(memberId, answerCreateOrUpdateDTO); + answerCommandService.updateAnswerList(member, answerCreateOrUpdateDTO); return ApiResponse.onSuccess(UPDATE_ANSWER_LIST); } @@ -259,7 +261,7 @@ public ApiResponse updateAnswerList(@RequestParam Long memberI ``` """ ) - public ApiResponse getAnswerCompletionStatus(@RequestParam Long memberId) { - return ApiResponse.onSuccess(GET_ANSWER_COMPLETION_STATUS, answerQueryService.getAnswerCompletionStatus(memberId)); + public ApiResponse getAnswerCompletionStatus(@LoginMember Member member) { + return ApiResponse.onSuccess(GET_ANSWER_COMPLETION_STATUS, answerQueryService.getAnswerCompletionStatus(member)); } } diff --git a/src/main/java/UMC/career_mate/domain/answer/converter/AnswerConverter.java b/src/main/java/UMC/career_mate/domain/answer/converter/AnswerConverter.java index 2d473da..83a14cc 100644 --- a/src/main/java/UMC/career_mate/domain/answer/converter/AnswerConverter.java +++ b/src/main/java/UMC/career_mate/domain/answer/converter/AnswerConverter.java @@ -14,7 +14,6 @@ import java.util.stream.Collectors; public class AnswerConverter { - public static Answer toAnswer(AnswerCreateOrUpdateDTO.AnswerInfo answerInfo, Member member, Question question, long sequence) { return Answer.builder() .content(answerInfo.content()) diff --git a/src/main/java/UMC/career_mate/domain/answer/service/AnswerCommandService.java b/src/main/java/UMC/career_mate/domain/answer/service/AnswerCommandService.java index 4b8a029..c80ff23 100644 --- a/src/main/java/UMC/career_mate/domain/answer/service/AnswerCommandService.java +++ b/src/main/java/UMC/career_mate/domain/answer/service/AnswerCommandService.java @@ -24,12 +24,9 @@ public class AnswerCommandService { private final MemberRepository memberRepository; @Transactional - public void saveAnswerList(Long memberId, AnswerCreateOrUpdateDTO answerCreateOrUpdateDTO) { + public void saveAnswerList(Member member, AnswerCreateOrUpdateDTO answerCreateOrUpdateDTO) { long start_sequence = 1L; - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new GeneralException(CommonErrorCode.BAD_REQUEST)); - for (AnswerList answerList : answerCreateOrUpdateDTO.answerList()) { for (AnswerInfo answerInfo : answerList.answerInfoList()) { Question question = questionRepository.findById(answerInfo.questionId()) @@ -43,10 +40,7 @@ public void saveAnswerList(Long memberId, AnswerCreateOrUpdateDTO answerCreateOr } @Transactional - public void updateAnswerList(Long memberId, AnswerCreateOrUpdateDTO request) { - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new GeneralException(CommonErrorCode.BAD_REQUEST)); - + public void updateAnswerList(Member member, AnswerCreateOrUpdateDTO request) { for (AnswerList answerList : request.answerList()) { for (AnswerInfo answerInfo : answerList.answerInfoList()) { Question question = questionRepository.findById(answerInfo.questionId()) diff --git a/src/main/java/UMC/career_mate/domain/answer/service/AnswerQueryService.java b/src/main/java/UMC/career_mate/domain/answer/service/AnswerQueryService.java index 894308f..e7f02c7 100644 --- a/src/main/java/UMC/career_mate/domain/answer/service/AnswerQueryService.java +++ b/src/main/java/UMC/career_mate/domain/answer/service/AnswerQueryService.java @@ -43,10 +43,7 @@ public List getAnswersByTemplateType(Long memberId, TemplateT } @Transactional(readOnly = true) - public AnswerCompletionStatusInfoListDTO getAnswerCompletionStatus(Long memberId) { - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new GeneralException(CommonErrorCode.BAD_REQUEST)); - + public AnswerCompletionStatusInfoListDTO getAnswerCompletionStatus(Member member) { List answerCompletionStatusInfoDTOList = new ArrayList<>(); boolean isAllCompleted = true; diff --git a/src/main/java/UMC/career_mate/domain/content/controller/ContentController.java b/src/main/java/UMC/career_mate/domain/content/controller/ContentController.java index 5dd44b6..e6df6cc 100644 --- a/src/main/java/UMC/career_mate/domain/content/controller/ContentController.java +++ b/src/main/java/UMC/career_mate/domain/content/controller/ContentController.java @@ -1,110 +1,110 @@ -package UMC.career_mate.domain.content.controller; - -import UMC.career_mate.domain.content.dto.request.ContentRequestDTO; -import UMC.career_mate.domain.content.dto.response.ContentResponseDTO; -import UMC.career_mate.domain.content.service.ContentService; -import UMC.career_mate.domain.contentScrap.dto.response.ContentScrapResponseDTO; -import UMC.career_mate.domain.contentScrap.service.ContentScrapService; -import UMC.career_mate.domain.member.Member; -import UMC.career_mate.global.annotation.LoginMember; -import UMC.career_mate.global.common.PageResponseDTO; -import UMC.career_mate.global.response.ApiResponse; -import io.swagger.v3.oas.annotations.Operation; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/content") -public class ContentController { - - private final ContentService contentService; - private final ContentScrapService contentScrapService; - - @PostMapping - @Operation( - summary = "컨텐츠 업로드 API", - description = """ - 새로운 컨텐츠를 생성합니다. (관리자용) - ### 요청 예시 JSON: - ```json - { - "title": "Spring Boot Guide", - "url": "https://example.com/spring-boot", - "photo": "https://example.com/image.jpg", - "jobId": 1 - } - ``` - """ - ) - public ApiResponse uploadContent(@RequestBody ContentRequestDTO contentRequestDTO) { - return ApiResponse.onSuccess(contentService.uploadContent(contentRequestDTO)); - } - - @GetMapping - @Operation( - summary = "직무별 컨텐츠 조회 API", - description = """ - 특정 직무 ID에 해당하는 컨텐츠를 조회합니다. - 이때 사용자가 해당 컨텐츠를 스크랩했는지 여부가 담겨 조회됩니다. - Query Parameters: - - `jobId`: 직무 ID (필수) - - `page`: 페이지 번호 (기본값: 1) - - `size`: 페이지 크기 (기본값: 10) - """ - ) - public ApiResponse>> getContentsByJobId( - @RequestParam Long jobId, - @RequestParam(defaultValue = "1") int page, - @RequestParam(defaultValue = "10") int size, - @LoginMember Member member) { - return ApiResponse.onSuccess(contentService.getContentsByJobId(jobId, page, size, member)); - } - - @PostMapping("/{contentId}/scrap") - @Operation( - summary = "컨텐츠 스크랩 API", - description = """ - 컨텐츠를 스크랩합니다. - Path Parameters: - - 'contentId': 스크랩할 콘텐츠 ID - """ - ) - public ApiResponse createScrapContents(@LoginMember Member member, @PathVariable Long contentId) { - contentScrapService.createScrapContents(member, contentId); - return ApiResponse.onSuccess("컨텐츠 스크랩 완료"); - } - - @DeleteMapping("/{contentId}/scrap") - @Operation( - summary = "컨텐츠 스크랩 삭제 API", - description = """ - 컨텐츠의 스크랩을 삭제합니다. - Path Parameters: - - 'contentId': 삭제할 콘텐츠 ID - """ - ) - public ApiResponse deleteScrapContents(@PathVariable Long contentId, @LoginMember Member member) { - contentScrapService.deleteScrapContents(contentId, member); - return ApiResponse.onSuccess("컨텐츠 스크랩 삭제 완료"); - } - - @GetMapping("/scrap") - @Operation( - summary = "스크랩한 컨텐츠 조회 API", - description = """ - 사용자가 스크랩한 컨텐츠를 조회합니다. - Query Parameters: - - 'page': 페이지 번호 (기본값: 1) - - 'size': 페이지 크기 (기본값: 10) - """ - ) - public ApiResponse>> getScrapContents( - @RequestParam(defaultValue = "1") int page, - @RequestParam(defaultValue = "10") int size, - @LoginMember Member member) { - return ApiResponse.onSuccess(contentScrapService.getScrapContents(member, page, size)); - } +package UMC.career_mate.domain.content.controller; + +import UMC.career_mate.domain.content.dto.request.ContentRequestDTO; +import UMC.career_mate.domain.content.dto.response.ContentResponseDTO; +import UMC.career_mate.domain.content.service.ContentService; +import UMC.career_mate.domain.contentScrap.dto.response.ContentScrapResponseDTO; +import UMC.career_mate.domain.contentScrap.service.ContentScrapService; +import UMC.career_mate.domain.member.Member; +import UMC.career_mate.global.annotation.LoginMember; +import UMC.career_mate.global.common.PageResponseDTO; +import UMC.career_mate.global.response.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/content") +public class ContentController { + + private final ContentService contentService; + private final ContentScrapService contentScrapService; + + @PostMapping + @Operation( + summary = "컨텐츠 업로드 API", + description = """ + 새로운 컨텐츠를 생성합니다. (관리자용) + ### 요청 예시 JSON: + ```json + { + "title": "Spring Boot Guide", + "url": "https://example.com/spring-boot", + "photo": "https://example.com/image.jpg", + "jobId": 1 + } + ``` + """ + ) + public ApiResponse uploadContent(@RequestBody ContentRequestDTO contentRequestDTO) { + return ApiResponse.onSuccess(contentService.uploadContent(contentRequestDTO)); + } + + @GetMapping + @Operation( + summary = "직무별 컨텐츠 조회 API", + description = """ + 특정 직무 ID에 해당하는 컨텐츠를 조회합니다. + 이때 사용자가 해당 컨텐츠를 스크랩했는지 여부가 담겨 조회됩니다. + Query Parameters: + - `jobId`: 직무 ID (필수) + - `page`: 페이지 번호 (기본값: 1) + - `size`: 페이지 크기 (기본값: 10) + """ + ) + public ApiResponse>> getContentsByJobId( + @RequestParam Long jobId, + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size, + @LoginMember Member member) { + return ApiResponse.onSuccess(contentService.getContentsByJobId(jobId, page, size, member)); + } + + @PostMapping("/{contentId}/scrap") + @Operation( + summary = "컨텐츠 스크랩 API", + description = """ + 컨텐츠를 스크랩합니다. + Path Parameters: + - 'contentId': 스크랩할 콘텐츠 ID + """ + ) + public ApiResponse createScrapContents(@LoginMember Member member, @PathVariable Long contentId) { + contentScrapService.createScrapContents(member, contentId); + return ApiResponse.onSuccess("컨텐츠 스크랩 완료"); + } + + @DeleteMapping("/{contentId}/scrap") + @Operation( + summary = "컨텐츠 스크랩 삭제 API", + description = """ + 컨텐츠의 스크랩을 삭제합니다. + Path Parameters: + - 'contentId': 삭제할 콘텐츠 ID + """ + ) + public ApiResponse deleteScrapContents(@PathVariable Long contentId, @LoginMember Member member) { + contentScrapService.deleteScrapContents(contentId, member); + return ApiResponse.onSuccess("컨텐츠 스크랩 삭제 완료"); + } + + @GetMapping("/scrap") + @Operation( + summary = "스크랩한 컨텐츠 조회 API", + description = """ + 사용자가 스크랩한 컨텐츠를 조회합니다. + Query Parameters: + - 'page': 페이지 번호 (기본값: 1) + - 'size': 페이지 크기 (기본값: 10) + """ + ) + public ApiResponse>> getScrapContents( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size, + @LoginMember Member member) { + return ApiResponse.onSuccess(contentScrapService.getScrapContents(member, page, size)); + } } \ No newline at end of file diff --git a/src/main/java/UMC/career_mate/domain/content/converter/ContentConverter.java b/src/main/java/UMC/career_mate/domain/content/converter/ContentConverter.java index dabc263..b8a1cba 100644 --- a/src/main/java/UMC/career_mate/domain/content/converter/ContentConverter.java +++ b/src/main/java/UMC/career_mate/domain/content/converter/ContentConverter.java @@ -1,39 +1,39 @@ -package UMC.career_mate.domain.content.converter; - -import UMC.career_mate.domain.content.Content; -import UMC.career_mate.domain.content.dto.request.ContentRequestDTO; -import UMC.career_mate.domain.content.dto.response.ContentResponseDTO; -import UMC.career_mate.domain.job.Job; - -public class ContentConverter { - - public static Content toContent(ContentRequestDTO contentRequestDTO, Job job) { - return Content.builder() - .title(contentRequestDTO.title()) - .url(contentRequestDTO.url()) - .photo(contentRequestDTO.photo()) - .job(job) - .build(); - } - - public static ContentResponseDTO toContentResponseDTO(Content content) { - return ContentResponseDTO.builder() - .id(content.getId()) - .title(content.getTitle()) - .url(content.getUrl()) - .photo(content.getPhoto()) - .jobId(content.getJob().getId()) - .build(); - } - - public static ContentResponseDTO toContentResponseDTOWithScrapStatus(Content content, boolean isScrapped) { - return ContentResponseDTO.builder() - .id(content.getId()) - .title(content.getTitle()) - .url(content.getUrl()) - .photo(content.getPhoto()) - .jobId(content.getJob().getId()) - .isScrapped(isScrapped) - .build(); - } +package UMC.career_mate.domain.content.converter; + +import UMC.career_mate.domain.content.Content; +import UMC.career_mate.domain.content.dto.request.ContentRequestDTO; +import UMC.career_mate.domain.content.dto.response.ContentResponseDTO; +import UMC.career_mate.domain.job.Job; + +public class ContentConverter { + + public static Content toContent(ContentRequestDTO contentRequestDTO, Job job) { + return Content.builder() + .title(contentRequestDTO.title()) + .url(contentRequestDTO.url()) + .photo(contentRequestDTO.photo()) + .job(job) + .build(); + } + + public static ContentResponseDTO toContentResponseDTO(Content content) { + return ContentResponseDTO.builder() + .id(content.getId()) + .title(content.getTitle()) + .url(content.getUrl()) + .photo(content.getPhoto()) + .jobId(content.getJob().getId()) + .build(); + } + + public static ContentResponseDTO toContentResponseDTOWithScrapStatus(Content content, boolean isScrapped) { + return ContentResponseDTO.builder() + .id(content.getId()) + .title(content.getTitle()) + .url(content.getUrl()) + .photo(content.getPhoto()) + .jobId(content.getJob().getId()) + .isScrapped(isScrapped) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/UMC/career_mate/domain/content/dto/request/ContentRequestDTO.java b/src/main/java/UMC/career_mate/domain/content/dto/request/ContentRequestDTO.java index da668ee..8370277 100644 --- a/src/main/java/UMC/career_mate/domain/content/dto/request/ContentRequestDTO.java +++ b/src/main/java/UMC/career_mate/domain/content/dto/request/ContentRequestDTO.java @@ -1,9 +1,9 @@ -package UMC.career_mate.domain.content.dto.request; - -public record ContentRequestDTO( - String title, - String url, - String photo, - Long jobId -) { +package UMC.career_mate.domain.content.dto.request; + +public record ContentRequestDTO( + String title, + String url, + String photo, + Long jobId +) { } \ No newline at end of file diff --git a/src/main/java/UMC/career_mate/domain/content/dto/response/ContentResponseDTO.java b/src/main/java/UMC/career_mate/domain/content/dto/response/ContentResponseDTO.java index 4a507e7..bcd87ff 100644 --- a/src/main/java/UMC/career_mate/domain/content/dto/response/ContentResponseDTO.java +++ b/src/main/java/UMC/career_mate/domain/content/dto/response/ContentResponseDTO.java @@ -1,14 +1,15 @@ -package UMC.career_mate.domain.content.dto.response; - -import lombok.Builder; - -@Builder -public record ContentResponseDTO( - Long id, - String title, - String url, - String photo, - Long jobId, - boolean isScrapped // 추가 -) { +package UMC.career_mate.domain.content.dto.response; + +import lombok.Builder; + +@Builder +public record ContentResponseDTO( + Long id, + String title, + String url, + String photo, + Long jobId, + boolean isScrapped // 추가 +) { + } \ No newline at end of file diff --git a/src/main/java/UMC/career_mate/domain/content/repository/ContentRepository.java b/src/main/java/UMC/career_mate/domain/content/repository/ContentRepository.java index f1fb35a..1375aff 100644 --- a/src/main/java/UMC/career_mate/domain/content/repository/ContentRepository.java +++ b/src/main/java/UMC/career_mate/domain/content/repository/ContentRepository.java @@ -1,10 +1,10 @@ -package UMC.career_mate.domain.content.repository; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import UMC.career_mate.domain.content.Content; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface ContentRepository extends JpaRepository { - Page findByJobId(Long jobId, Pageable pageable); +package UMC.career_mate.domain.content.repository; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import UMC.career_mate.domain.content.Content; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ContentRepository extends JpaRepository { + Page findByJobId(Long jobId, Pageable pageable); } \ No newline at end of file diff --git a/src/main/java/UMC/career_mate/domain/content/service/ContentService.java b/src/main/java/UMC/career_mate/domain/content/service/ContentService.java index dc0af12..147321d 100644 --- a/src/main/java/UMC/career_mate/domain/content/service/ContentService.java +++ b/src/main/java/UMC/career_mate/domain/content/service/ContentService.java @@ -1,64 +1,64 @@ -package UMC.career_mate.domain.content.service; - - -import UMC.career_mate.domain.content.Content; -import UMC.career_mate.domain.content.converter.ContentConverter; -import UMC.career_mate.domain.content.dto.request.ContentRequestDTO; -import UMC.career_mate.domain.content.dto.response.ContentResponseDTO; -import UMC.career_mate.domain.content.repository.ContentRepository; -import UMC.career_mate.domain.contentScrap.repository.ContentScrapRepository; -import UMC.career_mate.domain.job.Job; -import UMC.career_mate.domain.job.Service.JobService; -import UMC.career_mate.domain.member.Member; -import UMC.career_mate.global.common.PageResponseDTO; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -@Service -@RequiredArgsConstructor -@Transactional -public class ContentService { - - private final ContentRepository contentRepository; - private final JobService jobService; - private final ContentScrapRepository contentScrapRepository; - - public ContentResponseDTO uploadContent(ContentRequestDTO contentRequestDTO) { - // Job ID 유효성 확인 - Job job = jobService.findJobById(contentRequestDTO.jobId()); - - // Content 엔티티 생성 및 저장 - Content content = ContentConverter.toContent(contentRequestDTO, job); - contentRepository.save(content); - - return ContentConverter.toContentResponseDTO(content); - } - - @Transactional(readOnly = true) - public PageResponseDTO> getContentsByJobId(Long jobId, int page, int size, Member member) { - // Job ID 유효성 확인 - jobService.findJobById(jobId); - - PageRequest pageRequest = PageRequest.of(page - 1, size); - Page contentPage = contentRepository.findByJobId(jobId, pageRequest); - - // 사용자가 스크랩한 콘텐츠들 id 가져오기 - List scrappedContentIds = contentScrapRepository.findByMember(member) - .stream() - .map(scrap -> scrap.getContent().getId()) - .toList(); - - // 컨텐츠가 위 목록에 있는지 확인하며 isScrapped 값을 포함(true) 포함x(false) 설정 - List contentList = contentPage.stream() - .map(content -> ContentConverter.toContentResponseDTOWithScrapStatus(content, scrappedContentIds.contains(content.getId()))) - .toList(); - - boolean hasNext = contentPage.hasNext(); - return new PageResponseDTO<>(page, hasNext, contentList); - } +package UMC.career_mate.domain.content.service; + + +import UMC.career_mate.domain.content.Content; +import UMC.career_mate.domain.content.converter.ContentConverter; +import UMC.career_mate.domain.content.dto.request.ContentRequestDTO; +import UMC.career_mate.domain.content.dto.response.ContentResponseDTO; +import UMC.career_mate.domain.content.repository.ContentRepository; +import UMC.career_mate.domain.contentScrap.repository.ContentScrapRepository; +import UMC.career_mate.domain.job.Job; +import UMC.career_mate.domain.job.Service.JobService; +import UMC.career_mate.domain.member.Member; +import UMC.career_mate.global.common.PageResponseDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional +public class ContentService { + + private final ContentRepository contentRepository; + private final JobService jobService; + private final ContentScrapRepository contentScrapRepository; + + public ContentResponseDTO uploadContent(ContentRequestDTO contentRequestDTO) { + // Job ID 유효성 확인 + Job job = jobService.findJobById(contentRequestDTO.jobId()); + + // Content 엔티티 생성 및 저장 + Content content = ContentConverter.toContent(contentRequestDTO, job); + contentRepository.save(content); + + return ContentConverter.toContentResponseDTO(content); + } + + @Transactional(readOnly = true) + public PageResponseDTO> getContentsByJobId(Long jobId, int page, int size, Member member) { + // Job ID 유효성 확인 + jobService.findJobById(jobId); + + PageRequest pageRequest = PageRequest.of(page - 1, size); + Page contentPage = contentRepository.findByJobId(jobId, pageRequest); + + // 사용자가 스크랩한 콘텐츠들 id 가져오기 + List scrappedContentIds = contentScrapRepository.findByMember(member) + .stream() + .map(scrap -> scrap.getContent().getId()) + .toList(); + + // 컨텐츠가 위 목록에 있는지 확인하며 isScrapped 값을 포함(true) 포함x(false) 설정 + List contentList = contentPage.stream() + .map(content -> ContentConverter.toContentResponseDTOWithScrapStatus(content, scrappedContentIds.contains(content.getId()))) + .toList(); + + boolean hasNext = contentPage.hasNext(); + return new PageResponseDTO<>(page, hasNext, contentList); + } } \ No newline at end of file diff --git a/src/main/java/UMC/career_mate/domain/contentScrap/converter/ContentScrapConverter.java b/src/main/java/UMC/career_mate/domain/contentScrap/converter/ContentScrapConverter.java index 73ad5b9..beb7af6 100644 --- a/src/main/java/UMC/career_mate/domain/contentScrap/converter/ContentScrapConverter.java +++ b/src/main/java/UMC/career_mate/domain/contentScrap/converter/ContentScrapConverter.java @@ -1,26 +1,26 @@ -package UMC.career_mate.domain.contentScrap.converter; - -import UMC.career_mate.domain.content.Content; -import UMC.career_mate.domain.contentScrap.ContentScrap; -import UMC.career_mate.domain.contentScrap.dto.response.ContentScrapResponseDTO; -import UMC.career_mate.domain.member.Member; - -public class ContentScrapConverter { - - public static ContentScrap toContentScrap(Content content, Member member) { - return ContentScrap.builder() - .content(content) - .member(member) - .build(); - } - - public static ContentScrapResponseDTO toContentScrapResponseDTO(ContentScrap scrap) { - return ContentScrapResponseDTO.builder() - .contentId(scrap.getContent().getId()) - .title(scrap.getContent().getTitle()) - .url(scrap.getContent().getUrl()) - .photo(scrap.getContent().getPhoto()) - .isScraped(true) //isScraped 값이 참인 것들을 가져옵니다. - .build(); - } +package UMC.career_mate.domain.contentScrap.converter; + +import UMC.career_mate.domain.content.Content; +import UMC.career_mate.domain.contentScrap.ContentScrap; +import UMC.career_mate.domain.contentScrap.dto.response.ContentScrapResponseDTO; +import UMC.career_mate.domain.member.Member; + +public class ContentScrapConverter { + + public static ContentScrap toContentScrap(Content content, Member member) { + return ContentScrap.builder() + .content(content) + .member(member) + .build(); + } + + public static ContentScrapResponseDTO toContentScrapResponseDTO(ContentScrap scrap) { + return ContentScrapResponseDTO.builder() + .contentId(scrap.getContent().getId()) + .title(scrap.getContent().getTitle()) + .url(scrap.getContent().getUrl()) + .photo(scrap.getContent().getPhoto()) + .isScraped(true) //isScraped 값이 참인 것들을 가져옵니다. + .build(); + } } \ No newline at end of file diff --git a/src/main/java/UMC/career_mate/domain/contentScrap/dto/response/ContentScrapResponseDTO.java b/src/main/java/UMC/career_mate/domain/contentScrap/dto/response/ContentScrapResponseDTO.java index 3bf8ba1..f24e207 100644 --- a/src/main/java/UMC/career_mate/domain/contentScrap/dto/response/ContentScrapResponseDTO.java +++ b/src/main/java/UMC/career_mate/domain/contentScrap/dto/response/ContentScrapResponseDTO.java @@ -1,13 +1,13 @@ -package UMC.career_mate.domain.contentScrap.dto.response; - -import lombok.Builder; - -@Builder -public record ContentScrapResponseDTO( - Long contentId, - String title, - String url, - String photo, - boolean isScraped -) { +package UMC.career_mate.domain.contentScrap.dto.response; + +import lombok.Builder; + +@Builder +public record ContentScrapResponseDTO( + Long contentId, + String title, + String url, + String photo, + boolean isScraped +) { } \ No newline at end of file diff --git a/src/main/java/UMC/career_mate/domain/contentScrap/repository/ContentScrapRepository.java b/src/main/java/UMC/career_mate/domain/contentScrap/repository/ContentScrapRepository.java index 86928c8..a05dd88 100644 --- a/src/main/java/UMC/career_mate/domain/contentScrap/repository/ContentScrapRepository.java +++ b/src/main/java/UMC/career_mate/domain/contentScrap/repository/ContentScrapRepository.java @@ -1,22 +1,22 @@ -package UMC.career_mate.domain.contentScrap.repository; - -import UMC.career_mate.domain.contentScrap.ContentScrap; -import UMC.career_mate.domain.member.Member; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -import java.util.List; -import java.util.Optional; - -public interface ContentScrapRepository extends JpaRepository { - Optional findByContentIdAndMember(Long contentId, Member member); - - Page findByMember(Member member, Pageable pageable); - - //회원이 스크랩한 컨텐츠를 모두 조회 - @Query("SELECT cs FROM ContentScrap cs WHERE cs.member = :member") - List findByMember(@Param("member") Member member); +package UMC.career_mate.domain.contentScrap.repository; + +import UMC.career_mate.domain.contentScrap.ContentScrap; +import UMC.career_mate.domain.member.Member; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +public interface ContentScrapRepository extends JpaRepository { + Optional findByContentIdAndMember(Long contentId, Member member); + + Page findByMember(Member member, Pageable pageable); + + //회원이 스크랩한 컨텐츠를 모두 조회 + @Query("SELECT cs FROM ContentScrap cs WHERE cs.member = :member") + List findByMember(@Param("member") Member member); } \ No newline at end of file diff --git a/src/main/java/UMC/career_mate/domain/contentScrap/service/ContentScrapService.java b/src/main/java/UMC/career_mate/domain/contentScrap/service/ContentScrapService.java index bebeecd..b44d121 100644 --- a/src/main/java/UMC/career_mate/domain/contentScrap/service/ContentScrapService.java +++ b/src/main/java/UMC/career_mate/domain/contentScrap/service/ContentScrapService.java @@ -1,64 +1,64 @@ -package UMC.career_mate.domain.contentScrap.service; - -import UMC.career_mate.domain.content.Content; -import UMC.career_mate.domain.content.repository.ContentRepository; -import UMC.career_mate.domain.contentScrap.ContentScrap; -import UMC.career_mate.domain.contentScrap.converter.ContentScrapConverter; -import UMC.career_mate.domain.contentScrap.dto.response.ContentScrapResponseDTO; -import UMC.career_mate.domain.contentScrap.repository.ContentScrapRepository; -import UMC.career_mate.domain.member.Member; -import UMC.career_mate.global.common.PageResponseDTO; -import UMC.career_mate.global.response.exception.GeneralException; -import UMC.career_mate.global.response.exception.code.CommonErrorCode; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -@Service -@RequiredArgsConstructor -@Transactional -public class ContentScrapService { - - private final ContentScrapRepository contentScrapRepository; - private final ContentRepository contentRepository; - - public void createScrapContents(Member member, Long contentId) { - - // 유효한 컨텐츠인지 검사 - Content content = contentRepository.findById(contentId) - .orElseThrow(() -> new GeneralException(CommonErrorCode.NOT_FOUND_CONTENT)); - - // 중복 검사 - if (contentScrapRepository.findByContentIdAndMember(contentId, member).isPresent()) { - throw new GeneralException(CommonErrorCode.DUPLICATE_SCRAP); - } - - ContentScrap scrap = ContentScrapConverter.toContentScrap(content, member); - contentScrapRepository.save(scrap); - } - - public void deleteScrapContents(Long contentId, Member member) { - - //유효한 스크랩인지 검사 - ContentScrap scrap = contentScrapRepository.findByContentIdAndMember(contentId, member) - .orElseThrow(() -> new GeneralException(CommonErrorCode.NOT_FOUND_SCRAP)); - - contentScrapRepository.delete(scrap); - } - - @Transactional(readOnly = true) - public PageResponseDTO> getScrapContents(Member member, int page, int size) { - PageRequest pageRequest = PageRequest.of(page - 1, size); - Page scraps = contentScrapRepository.findByMember(member, pageRequest); - - List contentList = scraps.stream() - .map(ContentScrapConverter::toContentScrapResponseDTO) - .toList(); - - return new PageResponseDTO<>(page, scraps.hasNext(), contentList); - } +package UMC.career_mate.domain.contentScrap.service; + +import UMC.career_mate.domain.content.Content; +import UMC.career_mate.domain.content.repository.ContentRepository; +import UMC.career_mate.domain.contentScrap.ContentScrap; +import UMC.career_mate.domain.contentScrap.converter.ContentScrapConverter; +import UMC.career_mate.domain.contentScrap.dto.response.ContentScrapResponseDTO; +import UMC.career_mate.domain.contentScrap.repository.ContentScrapRepository; +import UMC.career_mate.domain.member.Member; +import UMC.career_mate.global.common.PageResponseDTO; +import UMC.career_mate.global.response.exception.GeneralException; +import UMC.career_mate.global.response.exception.code.CommonErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional +public class ContentScrapService { + + private final ContentScrapRepository contentScrapRepository; + private final ContentRepository contentRepository; + + public void createScrapContents(Member member, Long contentId) { + + // 유효한 컨텐츠인지 검사 + Content content = contentRepository.findById(contentId) + .orElseThrow(() -> new GeneralException(CommonErrorCode.NOT_FOUND_CONTENT)); + + // 중복 검사 + if (contentScrapRepository.findByContentIdAndMember(contentId, member).isPresent()) { + throw new GeneralException(CommonErrorCode.DUPLICATE_SCRAP); + } + + ContentScrap scrap = ContentScrapConverter.toContentScrap(content, member); + contentScrapRepository.save(scrap); + } + + public void deleteScrapContents(Long contentId, Member member) { + + //유효한 스크랩인지 검사 + ContentScrap scrap = contentScrapRepository.findByContentIdAndMember(contentId, member) + .orElseThrow(() -> new GeneralException(CommonErrorCode.NOT_FOUND_SCRAP)); + + contentScrapRepository.delete(scrap); + } + + @Transactional(readOnly = true) + public PageResponseDTO> getScrapContents(Member member, int page, int size) { + PageRequest pageRequest = PageRequest.of(page - 1, size); + Page scraps = contentScrapRepository.findByMember(member, pageRequest); + + List contentList = scraps.stream() + .map(ContentScrapConverter::toContentScrapResponseDTO) + .toList(); + + return new PageResponseDTO<>(page, scraps.hasNext(), contentList); + } } \ No newline at end of file diff --git a/src/main/java/UMC/career_mate/domain/planner/.DS_Store b/src/main/java/UMC/career_mate/domain/planner/.DS_Store deleted file mode 100644 index 4c9be0e..0000000 Binary files a/src/main/java/UMC/career_mate/domain/planner/.DS_Store and /dev/null differ diff --git a/src/main/java/UMC/career_mate/domain/planner/dto/.DS_Store b/src/main/java/UMC/career_mate/domain/planner/dto/.DS_Store deleted file mode 100644 index 346e7a8..0000000 Binary files a/src/main/java/UMC/career_mate/domain/planner/dto/.DS_Store and /dev/null differ diff --git a/src/main/java/UMC/career_mate/domain/template/controller/TemplateController.java b/src/main/java/UMC/career_mate/domain/template/controller/TemplateController.java index 28d0422..64fe71b 100644 --- a/src/main/java/UMC/career_mate/domain/template/controller/TemplateController.java +++ b/src/main/java/UMC/career_mate/domain/template/controller/TemplateController.java @@ -1,9 +1,10 @@ package UMC.career_mate.domain.template.controller; import UMC.career_mate.domain.member.Member; -import UMC.career_mate.domain.template.dto.response.TemplateResponseDTO; +import UMC.career_mate.domain.template.dto.response.TemplateInfoListDTO; import UMC.career_mate.domain.template.enums.TemplateType; import UMC.career_mate.domain.template.service.TemplateQueryService; +import UMC.career_mate.global.annotation.LoginMember; import UMC.career_mate.global.response.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; @@ -34,7 +35,7 @@ public class TemplateController { 5. 최종 정리 (SUMMARY) """ ) - public ApiResponse getTemplate(@RequestParam Long memberId, @RequestParam TemplateType type) { - return ApiResponse.onSuccess(GET_TEMPLATE, templateQueryService.getTemplate(memberId, type)); + public ApiResponse getTemplate(@LoginMember Member member, @RequestParam TemplateType templateType) { + return ApiResponse.onSuccess(GET_TEMPLATE, templateQueryService.getTemplate(member, templateType)); } } diff --git a/src/main/java/UMC/career_mate/domain/template/converter/TemplateConverter.java b/src/main/java/UMC/career_mate/domain/template/converter/TemplateConverter.java index 4205a9a..cec2932 100644 --- a/src/main/java/UMC/career_mate/domain/template/converter/TemplateConverter.java +++ b/src/main/java/UMC/career_mate/domain/template/converter/TemplateConverter.java @@ -4,21 +4,21 @@ import UMC.career_mate.domain.question.converter.QuestionConverter; import UMC.career_mate.domain.question.dto.response.QuestionDTO; import UMC.career_mate.domain.template.Template; -import UMC.career_mate.domain.template.dto.response.TemplateDTO; -import UMC.career_mate.domain.template.dto.response.TemplateResponseDTO; +import UMC.career_mate.domain.template.dto.response.TemplateInfoDTO; +import UMC.career_mate.domain.template.dto.response.TemplateInfoListDTO; import java.util.List; public class TemplateConverter { - public static TemplateResponseDTO toTemplateResponseDTO(List questionList, Template template) { + public static TemplateInfoListDTO toTemplateResponseDTO(List questionList, Template template) { List questionDTOList = QuestionConverter.toQuestionDTOList(questionList); - TemplateDTO templateDTO = TemplateDTO.builder() + TemplateInfoDTO templateInfoDTO = TemplateInfoDTO.builder() .templateId(template.getId()) .templateType(template.getTemplateType().getDescription()) .questionDTOList(questionDTOList) .build(); - return new TemplateResponseDTO(List.of(templateDTO)); + return new TemplateInfoListDTO(List.of(templateInfoDTO)); } } diff --git a/src/main/java/UMC/career_mate/domain/template/dto/response/TemplateDTO.java b/src/main/java/UMC/career_mate/domain/template/dto/response/TemplateInfoDTO.java similarity index 90% rename from src/main/java/UMC/career_mate/domain/template/dto/response/TemplateDTO.java rename to src/main/java/UMC/career_mate/domain/template/dto/response/TemplateInfoDTO.java index a9b74aa..6058e29 100644 --- a/src/main/java/UMC/career_mate/domain/template/dto/response/TemplateDTO.java +++ b/src/main/java/UMC/career_mate/domain/template/dto/response/TemplateInfoDTO.java @@ -6,7 +6,7 @@ import java.util.List; @Builder -public record TemplateDTO ( +public record TemplateInfoDTO( Long templateId, String templateType, List questionDTOList diff --git a/src/main/java/UMC/career_mate/domain/template/dto/response/TemplateResponseDTO.java b/src/main/java/UMC/career_mate/domain/template/dto/response/TemplateInfoListDTO.java similarity index 50% rename from src/main/java/UMC/career_mate/domain/template/dto/response/TemplateResponseDTO.java rename to src/main/java/UMC/career_mate/domain/template/dto/response/TemplateInfoListDTO.java index 80906b8..5f7d731 100644 --- a/src/main/java/UMC/career_mate/domain/template/dto/response/TemplateResponseDTO.java +++ b/src/main/java/UMC/career_mate/domain/template/dto/response/TemplateInfoListDTO.java @@ -2,8 +2,8 @@ import java.util.List; -public record TemplateResponseDTO ( - List templateDTOList +public record TemplateInfoListDTO( + List templateInfoDTOList ) { } diff --git a/src/main/java/UMC/career_mate/domain/template/service/TemplateQueryService.java b/src/main/java/UMC/career_mate/domain/template/service/TemplateQueryService.java index 775b1d2..06a69c9 100644 --- a/src/main/java/UMC/career_mate/domain/template/service/TemplateQueryService.java +++ b/src/main/java/UMC/career_mate/domain/template/service/TemplateQueryService.java @@ -1,15 +1,11 @@ package UMC.career_mate.domain.template.service; -import UMC.career_mate.domain.job.Job; -import UMC.career_mate.domain.job.repository.JobRepository; import UMC.career_mate.domain.member.Member; -import UMC.career_mate.domain.member.repository.MemberRepository; import UMC.career_mate.domain.question.Question; import UMC.career_mate.domain.question.repository.QuestionRepository; import UMC.career_mate.domain.template.Template; import UMC.career_mate.domain.template.converter.TemplateConverter; -import UMC.career_mate.domain.template.dto.response.TemplateDTO; -import UMC.career_mate.domain.template.dto.response.TemplateResponseDTO; +import UMC.career_mate.domain.template.dto.response.TemplateInfoListDTO; import UMC.career_mate.domain.template.enums.TemplateType; import UMC.career_mate.domain.template.repository.TemplateRepository; import UMC.career_mate.global.response.exception.GeneralException; @@ -24,16 +20,11 @@ @RequiredArgsConstructor public class TemplateQueryService { private final TemplateRepository templateRepository; - private final JobRepository jobRepository; private final QuestionRepository questionRepository; - private final MemberRepository memberRepository; @Transactional(readOnly = true) - public TemplateResponseDTO getTemplate(Long memberId, TemplateType type) { - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new GeneralException(CommonErrorCode.BAD_REQUEST)); - - Template template = templateRepository.findByJobAndTemplateType(member.getJob(), type) + public TemplateInfoListDTO getTemplate(Member member, TemplateType templateType) { + Template template = templateRepository.findByJobAndTemplateType(member.getJob(), templateType) .orElseThrow(() -> new GeneralException(CommonErrorCode.NOT_FOUND_TEMPLATE)); List questionList = questionRepository.findByTemplate(template); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..3d7808a --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,3 @@ +spring: + profiles: + active: dev