Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/33 : 스크랩 기능 #36

Merged
merged 4 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
426 changes: 0 additions & 426 deletions logs/info.2023-09-16.log

This file was deleted.

2,145 changes: 255 additions & 1,890 deletions logs/info.log

Large diffs are not rendered by default.

17 changes: 12 additions & 5 deletions src/main/java/briefing/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,29 @@ public enum ErrorCode {
HIJACK_JWT_TOKEN_EXCEPTION(UNAUTHORIZED,"AUTH007","탈취된(로그아웃 된) 토큰입니다 다시 로그인 해주세요."),
INVALID_REFRESH_TOKEN(BAD_REQUEST,"AUTH009","리프레쉬 토큰이 유효하지 않습니다. 다시 로그인 해주세요"),



// oauth 에러
APPLE_BAD_REQUEST(BAD_REQUEST, "OAUTH001", "애플 토큰이 잘못되었습니다."),
APPLE_SERVER_ERROR(FORBIDDEN, "OAUTH002", "애플 서버와 통신에 실패 하였습니다."),
FAIL_TO_MAKE_APPLE_PUBLIC_KEY(BAD_REQUEST, "OAUTH003", "새로운 애플 공개키 생성에 실패하였습니다."),
// feign client 에러

// feign client 에러
FEIGN_BAD_REQUEST(BAD_REQUEST, "FEIGN_400_1", "Feign server bad request"),
FEIGN_UNAUTHORIZED(BAD_REQUEST, "FEIGN_400_2", "Feign server unauthorized"),
FEIGN_FORBIDDEN(BAD_REQUEST, "FEIGN_400_3", "Feign server forbidden"),
FEIGN_EXPIRED_TOKEN(BAD_REQUEST, "FEIGN_400_4", "Feign server expired token"),
FEIGN_NOT_FOUND(BAD_REQUEST, "FEIGN_400_5", "Feign server not found error"),
FEIGN_INTERNAL_SERVER_ERROR(BAD_REQUEST, "FEIGN_400_6", "Feign server internal server error"),
FEIGN_METHOD_NOT_ALLOWED(BAD_REQUEST,"FEIGN_400_7" , "Feign server method not allowed"),
FEIGN_SERVER_ERROR(BAD_REQUEST,"FEIGN_500" , "Feign server error");
FEIGN_SERVER_ERROR(BAD_REQUEST,"FEIGN_500" , "Feign server error"),

// member 에러
MEMBER_NOT_FOUND(NOT_FOUND, "MEMBER_001", "존재하지 않는 사용자입니다."),


// scrap 에러
SCRAP_ALREADY_EXISTS(CONFLICT, "SCRAP_001", "이미 스크랩했습니다."),
SCRAP_NOT_FOUND(NOT_FOUND, "SCRAP_002", "존재하지 않는 스크랩입니다.");


private final HttpStatus httpStatus;
private final String code;
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/briefing/member/domain/Member.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package briefing.member.domain;

import briefing.base.BaseDateTimeEntity;
import jakarta.persistence.*;
import lombok.*;

@Entity
@Getter @Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Member {
public class Member extends BaseDateTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/briefing/member/exception/MemberException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package briefing.member.exception;

import briefing.exception.ErrorCode;
import briefing.exception.GeneralException;

public class MemberException extends GeneralException {
public MemberException(ErrorCode errorCode){
super(errorCode);
}
}
44 changes: 44 additions & 0 deletions src/main/java/briefing/scrap/api/ScrapApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package briefing.scrap.api;

import briefing.common.response.CommonResponse;
import briefing.scrap.application.ScrapCommandService;
import briefing.scrap.application.ScrapQueryService;
import briefing.scrap.application.dto.ScrapRequest;
import briefing.scrap.application.dto.ScrapResponse;
import briefing.scrap.domain.Scrap;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Tag(name = "05-Scrap 📁", description = "스크랩 관련 API")
@RestController
@RequestMapping("/scraps")
@RequiredArgsConstructor
public class ScrapApi {
private final ScrapQueryService scrapQueryService;
private final ScrapCommandService scrapCommandService;

@Operation(summary = "05-01 Scrap📁 스크랩하기 #FRAME", description = "브리핑을 스크랩하는 API입니다.")
@PostMapping("/briefings")
public CommonResponse<ScrapResponse.CreateDTO> create(@RequestBody ScrapRequest.CreateDTO request) {
Scrap createdScrap = scrapCommandService.create(request);
return CommonResponse.onSuccess(ScrapConverter.toCreateDTO(createdScrap));
}

@Operation(summary = "05-02 Scrap📁 스크랩 취소 #FRAME", description = "스크랩을 취소하는 API입니다.")
@DeleteMapping("/{scrapId}")
public CommonResponse<ScrapResponse.DeleteDTO> delete(@PathVariable Long scrapId) {
Scrap deletedScrap = scrapCommandService.delete(scrapId);
return CommonResponse.onSuccess(ScrapConverter.toDeleteDTO(deletedScrap));
}

@Operation(summary = "05-03 Scrap📁 내 스크랩 조회 #FRAME", description = "내 스크랩을 조회하는 API입니다.")
@GetMapping("/briefings/members/{memberId}")
public CommonResponse<List<ScrapResponse.ReadDTO>> getScrapsByMember(@PathVariable Long memberId) {
List<Scrap> scraps = scrapQueryService.getScrapsByMemberId(memberId);
return CommonResponse.onSuccess(scraps.stream().map(ScrapConverter::toReadDTO).toList());
}
}
43 changes: 43 additions & 0 deletions src/main/java/briefing/scrap/api/ScrapConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package briefing.scrap.api;

import briefing.briefing.domain.Briefing;
import briefing.member.domain.Member;
import briefing.scrap.application.dto.ScrapResponse;
import briefing.scrap.domain.Scrap;

import java.time.LocalDateTime;

public class ScrapConverter {
public static ScrapResponse.CreateDTO toCreateDTO(Scrap createdScrap) {
return ScrapResponse.CreateDTO.builder()
.scrapId(createdScrap.getId())
.memberId(createdScrap.getMember().getId())
.briefingId(createdScrap.getBriefing().getId())
.createdAt(createdScrap.getCreatedAt())
.build();
}

public static Scrap toScrap(Member member, Briefing briefing) {
return Scrap.builder()
.member(member)
.briefing(briefing)
.build();
}

public static ScrapResponse.DeleteDTO toDeleteDTO(Scrap deletedScrap) {
return ScrapResponse.DeleteDTO.builder()
.scrapId(deletedScrap.getId())
.deletedAt(LocalDateTime.now())
.build();
}

public static ScrapResponse.ReadDTO toReadDTO(Scrap scrap) {
return ScrapResponse.ReadDTO.builder()
.briefingId(scrap.getBriefing().getId())
.ranks(scrap.getBriefing().getRanks())
.title(scrap.getBriefing().getTitle())
.subtitle(scrap.getBriefing().getSubtitle())
.date(scrap.getBriefing().getCreatedAt().toLocalDate())
.build();
}
}
53 changes: 53 additions & 0 deletions src/main/java/briefing/scrap/application/ScrapCommandService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package briefing.scrap.application;

import briefing.briefing.domain.Briefing;
import briefing.briefing.domain.repository.BriefingRepository;
import briefing.briefing.exception.BriefingException;
import briefing.briefing.exception.BriefingExceptionType;
import briefing.exception.ErrorCode;
import briefing.member.domain.Member;
import briefing.member.domain.repository.MemberRepository;
import briefing.member.exception.MemberException;
import briefing.scrap.api.ScrapConverter;
import briefing.scrap.application.dto.ScrapRequest;
import briefing.scrap.domain.Scrap;
import briefing.scrap.domain.repository.ScrapRepository;
import briefing.scrap.exception.ScrapException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
public class ScrapCommandService {

private final ScrapRepository scrapRepository;
private final MemberRepository memberRepository;
private final BriefingRepository briefingRepository;


public Scrap create(ScrapRequest.CreateDTO request) {
// 이미 스크랩한경우
if(scrapRepository.existsByMember_IdAndBriefing_Id(request.getMemberId(), request.getBriefingId()))
throw new ScrapException(ErrorCode.SCRAP_ALREADY_EXISTS);

Member member = memberRepository.findById(request.getMemberId())
.orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND));

Briefing briefing = briefingRepository.findById(request.getBriefingId())
.orElseThrow(() -> new BriefingException(BriefingExceptionType.NOT_FOUND_BRIEFING));

Scrap scrap = ScrapConverter.toScrap(member, briefing);

// Scrap 엔티티 저장 및 반환
return scrapRepository.save(scrap);
}

public Scrap delete(Long scrapId) {
Scrap scrap = scrapRepository.findById(scrapId)
.orElseThrow(() -> new ScrapException(ErrorCode.SCRAP_NOT_FOUND));
scrapRepository.delete(scrap);
return scrap;
}
}
21 changes: 21 additions & 0 deletions src/main/java/briefing/scrap/application/ScrapQueryService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package briefing.scrap.application;

import briefing.scrap.domain.Scrap;
import briefing.scrap.domain.repository.ScrapRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class ScrapQueryService {

private final ScrapRepository scrapRepository;

public List<Scrap> getScrapsByMemberId(Long memberId) {
return scrapRepository.findByMember_Id(memberId);
}
}
12 changes: 12 additions & 0 deletions src/main/java/briefing/scrap/application/dto/ScrapRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package briefing.scrap.application.dto;

import lombok.Getter;

public class ScrapRequest {

@Getter
public static class CreateDTO {
private Long memberId;
private Long briefingId;
}
}
46 changes: 46 additions & 0 deletions src/main/java/briefing/scrap/application/dto/ScrapResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package briefing.scrap.application.dto;

import jakarta.persistence.Column;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDate;
import java.time.LocalDateTime;

public class ScrapResponse {

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class CreateDTO {
private Long scrapId;
private Long memberId;
private Long briefingId;
private LocalDateTime createdAt;
}

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class DeleteDTO {
private Long scrapId;
private LocalDateTime deletedAt;
}


@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class ReadDTO {
private Long briefingId;
private Integer ranks;
private String title;
private String subtitle;
private LocalDate date;
}
}
26 changes: 26 additions & 0 deletions src/main/java/briefing/scrap/domain/Scrap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package briefing.scrap.domain;

import briefing.base.BaseDateTimeEntity;
import briefing.briefing.domain.Briefing;
import briefing.member.domain.Member;
import jakarta.persistence.*;
import lombok.*;

@Entity
@Getter @Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Scrap extends BaseDateTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(nullable = false)
private Member member;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(nullable = false)
private Briefing briefing;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package briefing.scrap.domain.repository;

import briefing.scrap.domain.Scrap;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface ScrapRepository extends JpaRepository<Scrap, Long> {

boolean existsByMember_IdAndBriefing_Id(Long memberId, Long briefingId);

List<Scrap> findByMember_Id(Long memberId);
}
10 changes: 10 additions & 0 deletions src/main/java/briefing/scrap/exception/ScrapException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package briefing.scrap.exception;

import briefing.exception.ErrorCode;
import briefing.exception.GeneralException;

public class ScrapException extends GeneralException {
public ScrapException(ErrorCode errorCode){
super(errorCode);
}
}
3 changes: 2 additions & 1 deletion src/main/java/briefing/security/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ public WebSecurityCustomizer webSecurityCustomizer() {
"/swagger-ui/index.html",
"/swagger-ui/**",
"/docs/**",
"/members/auth/**");
"/members/auth/**",
"/scraps/**"); // NOTE - 토큰 발급 MERGE 전 테스트를 위해 허용
}

@Bean
Expand Down
Loading