From 2debc4c2ddf1d84740a3d0b0a43644f91232f8b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=B8=EC=A4=80?= Date: Thu, 25 Jul 2024 16:44:45 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20=EC=A4=91=EA=B0=84=20=EC=BB=A4?= =?UTF-8?q?=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/controller/BoardController.java | 49 ++++++++++ .../board/repository/BoardRepository.java | 9 ++ .../domain/board/service/BoardService.java | 80 ++++++++++++++++ .../domain/comment/entity/CommentEntity.java | 63 +++++++++++++ .../comment/entity/SubCommentEntity.java | 46 +++++++++ .../contest/controller/ContestController.java | 53 +++++++++++ .../domain/contest/entity/ContestEntity.java | 7 ++ .../contest/repository/ContestRepository.java | 9 ++ .../contest/service/ContestService.java | 94 +++++++++++++++++++ .../like/controller/LikeController.java | 25 +++++ .../domain/like/entity/LikeEntity.java | 50 ++++++++++ .../domain/like/enums/LikeCategory.java | 7 ++ .../like/repository/LikeRepository.java | 14 +++ .../domain/like/service/LikeService.java | 94 +++++++++++++++++++ .../error/custom/board/BoardNotFound.java | 12 +++ .../error/custom/contest/ContestNotFound.java | 12 +++ .../global/error/exception/StatusEnum.java | 8 +- 17 files changed, 631 insertions(+), 1 deletion(-) create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/controller/BoardController.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/repository/BoardRepository.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/service/BoardService.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/comment/entity/CommentEntity.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/comment/entity/SubCommentEntity.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/controller/ContestController.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/repository/ContestRepository.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/controller/LikeController.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/entity/LikeEntity.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/enums/LikeCategory.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/repository/LikeRepository.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/board/BoardNotFound.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/contest/ContestNotFound.java diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/controller/BoardController.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/controller/BoardController.java new file mode 100644 index 0000000..373cc77 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/controller/BoardController.java @@ -0,0 +1,49 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.board.controller; + +import kr.hs.dgsw.SOPO_server_v2.domain.board.dto.BoardLoadRes; +import kr.hs.dgsw.SOPO_server_v2.domain.board.dto.BoardUpdateReq; +import kr.hs.dgsw.SOPO_server_v2.domain.board.service.BoardService; +import kr.hs.dgsw.SOPO_server_v2.global.response.Response; +import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; +import lombok.RequiredArgsConstructor; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/board") +@RequiredArgsConstructor +public class BoardController { + private final BoardService boardService; + + @GetMapping("/all") + public ResponseData> getBoards() { + return boardService.getBoards(); + } + + @PostMapping + public ResponseData createBoard() { + return boardService.createBoard(); + } + + @GetMapping + public ResponseData getBoard(Long boardId) { + return boardService.findOneBoard(boardId); + } + + @PatchMapping + public Response loadBoard(Long boardId, BoardUpdateReq updateReq) { + return boardService.loadBoard(boardId, updateReq); + } + + @DeleteMapping + public Response deleteBoard(Long boardId) { + return boardService.deleteBoard(boardId); + } + +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/repository/BoardRepository.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/repository/BoardRepository.java new file mode 100644 index 0000000..596f585 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/repository/BoardRepository.java @@ -0,0 +1,9 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.board.repository; + +import kr.hs.dgsw.SOPO_server_v2.domain.board.entity.BoardEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface BoardRepository extends JpaRepository { +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/service/BoardService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/service/BoardService.java new file mode 100644 index 0000000..26bcdb5 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/service/BoardService.java @@ -0,0 +1,80 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.board.service; + +import jakarta.transaction.Transactional; +import kr.hs.dgsw.SOPO_server_v2.domain.board.dto.BoardLoadRes; +import kr.hs.dgsw.SOPO_server_v2.domain.board.dto.BoardUpdateReq; +import kr.hs.dgsw.SOPO_server_v2.domain.board.entity.BoardEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.board.repository.BoardRepository; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.board.BoardNotFound; +import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; +import kr.hs.dgsw.SOPO_server_v2.global.response.Response; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +public class BoardService { + private final BoardRepository boardRepository; + + // 게시글 전체 조회 + public ResponseData> getBoards() { + List boardList = boardRepository.findAll(); + List boardLoadResList = boardList.stream().map( + BoardLoadRes::of + ).toList(); + + return ResponseData.of(HttpStatus.OK, "게시물 전체 조회 완료", boardLoadResList); + } + + // 빈 게시글 생성 + public ResponseData createBoard() { + // 토큰 정보 가져오기 + + BoardEntity board = BoardEntity.builder() + .boardTitle(null) + .boardContent(null) + .file(null) + // .member() + .build(); + return ResponseData.of(HttpStatus.OK, "빈 게시물 생성 완료", board.getBoardId()); + } + + // 게시글 업데이트 + public Response loadBoard(Long boardId, BoardUpdateReq updateReq) { + // 토큰 정보 가져오기 + + BoardEntity board = boardRepository.findById(boardId) + .orElseThrow(() -> BoardNotFound.EXCEPTION); + // 만약 현재 로그인 유저와 Load 하려는 유저가 다르다면 + + board.update(updateReq); + BoardLoadRes boardLoadRes = BoardLoadRes.of(board); + return Response.of(HttpStatus.OK, "게시물 업데이트 완료"); + } + + // 게시글 단일 조회 + public ResponseData findOneBoard(Long boardId) { + BoardEntity board = boardRepository.findById(boardId) + .orElseThrow(() -> BoardNotFound.EXCEPTION); + BoardLoadRes boardLoadRes = BoardLoadRes.of(board); + return ResponseData.of(HttpStatus.OK, "게시물 단일 조회 완료", boardLoadRes); + } + + // 게시글 삭제 + @Transactional + public Response deleteBoard(Long boardId) { + // 토큰 정보 받아오기 + BoardEntity board = boardRepository.findById(boardId) + .orElseThrow(() -> BoardNotFound.EXCEPTION); + // 만약 만든 사람과 삭제하려는 사람이 일치하지 않고 admin이 아니라면.. error + + boardRepository.deleteById(boardId); + return Response.of(HttpStatus.OK, "게시물 삭제 완료"); + } + + +} \ No newline at end of file diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/comment/entity/CommentEntity.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/comment/entity/CommentEntity.java new file mode 100644 index 0000000..9a5adc7 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/comment/entity/CommentEntity.java @@ -0,0 +1,63 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.comment.entity; + +import jakarta.persistence.CascadeType; +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.OneToMany; +import jakarta.persistence.Table; +import kr.hs.dgsw.SOPO_server_v2.domain.board.entity.BoardEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; +import kr.hs.dgsw.SOPO_server_v2.global.common.entity.BaseTimeEntity; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.experimental.SuperBuilder; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Entity +@Table(name = "tbl_comment") +@NoArgsConstructor +@DynamicUpdate +@ToString +@SuperBuilder +public class CommentEntity extends BaseTimeEntity { // 부모 + + // 댓글 아이디 + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "comment_id") + private Long commentId; + + // 댓글 내용 + @Column(name = "comment_content") + private String commentContent; + + // 자식 댓글 + @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true) + private List children = new ArrayList<>(); + + // 게시물 아이디 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "board_id", nullable = false) + private BoardEntity board; + + // 유저 아이디 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + @OnDelete(action = OnDeleteAction.CASCADE) + private MemberEntity member; + + +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/comment/entity/SubCommentEntity.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/comment/entity/SubCommentEntity.java new file mode 100644 index 0000000..11c709d --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/comment/entity/SubCommentEntity.java @@ -0,0 +1,46 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.comment.entity; + +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 kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; +import kr.hs.dgsw.SOPO_server_v2.global.common.entity.BaseTimeEntity; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; + +@Getter +@Entity +@Table(name = "tbl_sub_comment") +@NoArgsConstructor +@DynamicUpdate +@SuperBuilder +public class SubCommentEntity extends BaseTimeEntity { + + // 대댓글 아이디 + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long subCommentId; + + // 대댓글 내용 + private String content; + + // 부모 댓글 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_id") + private CommentEntity parent; + + // 유저 아이디 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + @OnDelete(action = OnDeleteAction.CASCADE) + private MemberEntity member; +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/controller/ContestController.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/controller/ContestController.java new file mode 100644 index 0000000..d44b80c --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/controller/ContestController.java @@ -0,0 +1,53 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.contest.controller; + +import kr.hs.dgsw.SOPO_server_v2.domain.contest.dto.ContestLoadRes; +import kr.hs.dgsw.SOPO_server_v2.domain.contest.dto.ContestUpdateReq; +import kr.hs.dgsw.SOPO_server_v2.domain.contest.service.ContestService; +import kr.hs.dgsw.SOPO_server_v2.global.response.Response; +import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; +import lombok.RequiredArgsConstructor; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/contest") +@RequiredArgsConstructor +public class ContestController { + private final ContestService contestService; + + @GetMapping("/all") + public ResponseData> getContests() { + return contestService.getContests(); + } + + @PostMapping + public ResponseData createContest() { + return contestService.createContest(); + } + + @GetMapping + public ResponseData getContest(Long contestId) { + return contestService.findOneContest(contestId); + } + + @PatchMapping + public Response loadContest(Long contestId, ContestUpdateReq updateReq) { + return contestService.loadContest(contestId, updateReq); + } + + @DeleteMapping + public Response deleteContest(Long contestId) { + return contestService.deleteContest(contestId); + } + + @PatchMapping("/state") + public Response changeContestState(Long contestId) { + return contestService.changeContestState(contestId); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java index 8b7e2f4..6aee5b3 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java @@ -86,4 +86,11 @@ public void likeUpdate(int contestLikeCount) { this.contestLikeCount += contestLikeCount; } + public void stateUpdateActive() { + this.contestState = ContestState.ACTIVE; + } + + public void stateUpdateDisabled() { + this.contestState = ContestState.DISABLED; + } } diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/repository/ContestRepository.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/repository/ContestRepository.java new file mode 100644 index 0000000..67621a9 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/repository/ContestRepository.java @@ -0,0 +1,9 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.contest.repository; + +import kr.hs.dgsw.SOPO_server_v2.domain.contest.entity.ContestEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ContestRepository extends JpaRepository { +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java new file mode 100644 index 0000000..07c4c34 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java @@ -0,0 +1,94 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.contest.service; + +import jakarta.transaction.Transactional; +import kr.hs.dgsw.SOPO_server_v2.domain.contest.dto.ContestLoadRes; +import kr.hs.dgsw.SOPO_server_v2.domain.contest.dto.ContestUpdateReq; +import kr.hs.dgsw.SOPO_server_v2.domain.contest.entity.ContestEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.contest.enums.ContestState; +import kr.hs.dgsw.SOPO_server_v2.domain.contest.repository.ContestRepository; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.contest.ContestNotFound; +import kr.hs.dgsw.SOPO_server_v2.global.response.Response; +import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +public class ContestService { // 대회 전환 필요 -> ACTIVE + private final ContestRepository contestRepository; + + // 대회 전체 조회 + public ResponseData> getContests() { + List contestList = contestRepository.findAll(); + List contestLoadRes = contestList.stream().map( + ContestLoadRes :: of + ).toList(); + + return ResponseData.of(HttpStatus.OK, "대회 전체 조회 완료", contestLoadRes); + } + + // 빈 대회 생성 + public ResponseData createContest() { + // 토큰 정보 가져오기 + ContestEntity contest = ContestEntity.builder() + // .member() + .build(); + return ResponseData.of(HttpStatus.OK, "대회 생성 완료", contest.getContestId()); + } + + // 대회 업데이트 + public Response loadContest(Long contestId, ContestUpdateReq updateReq) { + ContestEntity contest = contestRepository.findById(contestId) + .orElseThrow(() -> ContestNotFound.EXCEPTION); + + // 만약 현재 로그인 유저와 Load 하려는 유저가 다르다면 + + contest.update(updateReq); + ContestLoadRes contestLoadRes = ContestLoadRes.of(contest); + + return Response.of(HttpStatus.OK, "대회 업데이트 완료"); + } + + // 대회 단일 조회 + public ResponseData findOneContest(Long contestId) { + + ContestEntity contest = contestRepository.findById(contestId) + .orElseThrow(() -> ContestNotFound.EXCEPTION); + + ContestLoadRes contestLoadRes = ContestLoadRes.of(contest); + + return ResponseData.of(HttpStatus.OK, "대회 조회 완료", contestLoadRes); + } + + // 대회 삭제 + public Response deleteContest(Long contestId) { + // 토큰 정보 받아오기 + ContestEntity contest = contestRepository.findById(contestId) + .orElseThrow(() -> ContestNotFound.EXCEPTION); + // 만약 만든 사람과 삭제하려는 사람이 일치하지 않고 admin이 아니라면.. error + + contestRepository.deleteById(contestId); + return Response.of(HttpStatus.OK, "대회 삭제 완료"); + } + + // 대회 상태 변경 + public Response changeContestState(Long contestId) { + // 토큰 정보 받아오기 + ContestEntity contest = contestRepository.findById(contestId) + .orElseThrow(() -> ContestNotFound.EXCEPTION); + // 만약 만든 사람과 변경하려는 사람이 일치하지 않고 admin이 아니라면.. error + + if (contest.getContestState() == ContestState.ACTIVE) { + contest.stateUpdateDisabled(); + } else { + contest.stateUpdateActive(); + } + + return Response.of(HttpStatus.OK, "대회 상태 변경 성공!"); + } + +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/controller/LikeController.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/controller/LikeController.java new file mode 100644 index 0000000..53d7289 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/controller/LikeController.java @@ -0,0 +1,25 @@ +//package kr.hs.dgsw.SOPO_server_v2.domain.like.controller; +// +//import kr.hs.dgsw.SOPO_server_v2.domain.like.enums.LikeCategory; +//import kr.hs.dgsw.SOPO_server_v2.domain.like.service.LikeService; +//import kr.hs.dgsw.SOPO_server_v2.global.response.Response; +//import lombok.RequiredArgsConstructor; +//import org.springframework.http.HttpStatus; +//import org.springframework.web.bind.annotation.PatchMapping; +//import org.springframework.web.bind.annotation.RequestMapping; +//import org.springframework.web.bind.annotation.RequestParam; +//import org.springframework.web.bind.annotation.RestController; +// +//@RestController +//@RequestMapping("/like") +//@RequiredArgsConstructor +//public class LikeController { +// private final LikeService likeService; +// +// @PatchMapping +// public Response patch(@RequestParam Long id, @RequestParam LikeCategory category) { +// likeService.toggle(id, category); +// return Response.of(HttpStatus.OK, "좋아요 생성/취소 완료"); +// } +// +//} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/entity/LikeEntity.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/entity/LikeEntity.java new file mode 100644 index 0000000..de7caa7 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/entity/LikeEntity.java @@ -0,0 +1,50 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.like.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import kr.hs.dgsw.SOPO_server_v2.domain.board.entity.BoardEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.contest.entity.ContestEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.like.enums.LikeCategory; +import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Entity(name = "tbl_like") +@Getter +@NoArgsConstructor +@SuperBuilder +public class LikeEntity { + + // 좋아요 idx + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long likeIdx; + + // 유저 idx + @ManyToOne() + @JoinColumn(name = "fk_member_id") + private MemberEntity member; // param으로 좋아요 타입 받기 + + // 좋아요 타입 + @Enumerated(EnumType.STRING) + private LikeCategory likeCategory; + + // 게시물 idx + @ManyToOne() + @JoinColumn(name = "fk_board_id") + private BoardEntity board; + + // 대회 idx + @ManyToOne() + @JoinColumn(name = "fk_contest_id") + private ContestEntity contest; + +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/enums/LikeCategory.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/enums/LikeCategory.java new file mode 100644 index 0000000..541a04f --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/enums/LikeCategory.java @@ -0,0 +1,7 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.like.enums; + +public enum LikeCategory { + BOARD, + CONTEST, + PROFILE +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/repository/LikeRepository.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/repository/LikeRepository.java new file mode 100644 index 0000000..133f190 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/repository/LikeRepository.java @@ -0,0 +1,14 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.like.repository; + +import kr.hs.dgsw.SOPO_server_v2.domain.board.entity.BoardEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.contest.entity.ContestEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.like.entity.LikeEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface LikeRepository extends JpaRepository { + Optional findByMemberAndBoard(MemberEntity member, BoardEntity board); + Optional findByMemberAndContest(MemberEntity member, ContestEntity contest); +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java new file mode 100644 index 0000000..c302892 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java @@ -0,0 +1,94 @@ +//package kr.hs.dgsw.SOPO_server_v2.domain.like.service; +// +//import jakarta.transaction.Transactional; +//import kr.hs.dgsw.SOPO_server_v2.domain.board.entity.BoardEntity; +//import kr.hs.dgsw.SOPO_server_v2.domain.board.repository.BoardRepository; +//import kr.hs.dgsw.SOPO_server_v2.domain.contest.entity.ContestEntity; +//import kr.hs.dgsw.SOPO_server_v2.domain.contest.repository.ContestRepository; +//import kr.hs.dgsw.SOPO_server_v2.domain.like.entity.LikeEntity; +//import kr.hs.dgsw.SOPO_server_v2.domain.like.enums.LikeCategory; +//import kr.hs.dgsw.SOPO_server_v2.domain.like.repository.LikeRepository; +//import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; +//import kr.hs.dgsw.SOPO_server_v2.global.error.custom.board.BoardNotFound; +//import kr.hs.dgsw.SOPO_server_v2.global.error.custom.contest.ContestNotFound; +//import kr.hs.dgsw.SOPO_server_v2.global.response.Response; +//import lombok.RequiredArgsConstructor; +//import org.springframework.http.HttpStatus; +//import org.springframework.stereotype.Service; +// +//import java.util.Optional; +// +//@Service +//@RequiredArgsConstructor +//@Transactional +//public class LikeService { +// // 멤버 필요 +// private final BoardRepository boardRepository; +// private final ContestRepository contestRepository; +// private final LikeRepository likeRepository; +// +// public Response toggle(Long id, LikeCategory category) { +// // 토큰 가져오기 +// // 게시물 좋아요일 때 +// if (category == LikeCategory.BOARD) { +// BoardEntity board = boardRepository.findById(id) +// .orElseThrow(() -> BoardNotFound.EXCEPTION); +// +// Optional like = likeRepository.findByMemberAndBoard(member, board); +// +// if (like.isEmpty()) { +// addBoardLike(member, board); +// board.likeUpdate(1); +// } else { +// boardRepository.delete(like.get().getBoard()); +// board.likeUpdate(-1); +// } +// } +// +// // 대회 좋아요일 때 +// else if (category == LikeCategory.CONTEST) { +// // 토큰 가져오기 +// ContestEntity contest = contestRepository.findById(id) +// .orElseThrow( () -> ContestNotFound.EXCEPTION); +// +// Optional like = likeRepository.findByMemberAndContest(member, contest); +// +// if (like.isEmpty()) { +// addContestLike(member, contest); +// contest.likeUpdate(1); +// } else { +// contestRepository.delete(like.get().getContest()); +// contest.likeUpdate(1); +// } +// } +// +// // else if (프로필) {} +// +// return Response.of(HttpStatus.OK, "좋아요 생성/취소 완료"); +// +// +// +// } +// +// private void addBoardLike(MemberEntity member, BoardEntity board) { +// likeRepository.save( +// LikeEntity.builder() +// .board(board) +// .member(member) +// .likeCategory(LikeCategory.BOARD) +// .build() +// ); +// } +// +// public void addContestLike(MemberEntity member, ContestEntity contest) { +// likeRepository.save( +// LikeEntity.builder() +// .contest(contest) +// .member(member) +// .likeCategory(LikeCategory.CONTEST) +// .build() +// ); +// } +// +// // 프로필 +//} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/board/BoardNotFound.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/board/BoardNotFound.java new file mode 100644 index 0000000..377835c --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/board/BoardNotFound.java @@ -0,0 +1,12 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.board; + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class BoardNotFound extends BusinessException { + public static final BusinessException EXCEPTION = new BoardNotFound(); + + public BoardNotFound(){ + super(StatusEnum.BOARD_NOT_FOUND); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/contest/ContestNotFound.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/contest/ContestNotFound.java new file mode 100644 index 0000000..ae7f1c8 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/contest/ContestNotFound.java @@ -0,0 +1,12 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.contest; + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class ContestNotFound extends BusinessException { + public static final BusinessException EXCEPTION = new ContestNotFound(); + + public ContestNotFound(){ + super(StatusEnum.CONTEST_NOT_FOUND); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/exception/StatusEnum.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/exception/StatusEnum.java index fd91d16..c90a07c 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/exception/StatusEnum.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/exception/StatusEnum.java @@ -47,7 +47,13 @@ public enum StatusEnum { //fcm MESSAGE_SEND_FAILED(403,"Message send failed"), TOKEN_NOT_PROVIDED(400, "잘못된 토큰"), - CLOUD_EXCEPTION(500,"클라우드 에러") + CLOUD_EXCEPTION(500,"클라우드 에러"), + + // board + BOARD_NOT_FOUND(404, "Board not found"), + + // contest + CONTEST_NOT_FOUND(404, "Contest not found") ; private final int statusCode; From 24f6eaa73b020196f39a719f1f6f61ca17102671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=B8=EC=A4=80?= Date: Thu, 25 Jul 2024 16:53:45 +0900 Subject: [PATCH 2/9] fix: git error --- .../auth/controller/AuthController.java | 39 +++ .../auth/controller/EmailController.java | 26 ++ .../auth/controller/TokenController.java | 30 ++ .../auth/dto/req/ReProvideTokenReq.java | 4 + .../domain/auth/dto/req/SignInReq.java | 7 + .../domain/auth/dto/req/SignUpReq.java | 12 + .../auth/dto/res/ReProvideTokenRes.java | 4 + .../domain/auth/dto/res/TokenRes.java | 7 + .../domain/auth/service/AuthEmailService.java | 61 ++++ .../domain/auth/service/AuthService.java | 91 +++++ .../domain/auth/service/AuthTokenService.java | 29 ++ .../domain/member/enums/MemberCategory.java | 5 + .../domain/member/enums/MemberState.java | 12 + .../common/dto/res/JsonWebTokenResponse.java | 15 + .../global/config/security/BCryptConfig.java | 13 + .../config/security/SecurityConfig.java | 96 ++++++ .../global/config/swagger/swaggerConfig.java | 34 ++ .../error/custom/auth/EncryptException.java | 12 + .../custom/auth/InvalidTokenException.java | 15 + .../custom/auth/RefreshTokenException.java | 15 + .../auth/UnableToSendEmailException.java | 13 + .../auth/WithdrawalMemberException.java | 15 + .../custom/auth/WrongPasswordException.java | 15 + .../custom/auth/WrongTokenTypeException.java | 12 + .../custom/email/CodeIsWrongException.java | 12 + .../email/EmailAlreadyExistsException.java | 12 + .../email/UnableToSendEmailException.java | 12 + .../custom/member/MemberExistException.java | 12 + .../member/MemberNotExistException.java | 12 + .../member/MemberNotFoundException.java | 13 + .../member/PermissionDeniedException.java | 13 + .../member/UserNotCoincideException.java | 12 + .../global/error/exception/StatusEnum.java | 38 ++- .../global/infra/jwt/JwtExceptionFilter.java | 57 ++++ .../global/infra/jwt/JwtFilter.java | 44 +++ .../global/infra/jwt/JwtHelper.java | 47 +++ .../global/infra/jwt/JwtProvider.java | 53 +++ .../global/infra/jwt/JwtType.java | 5 + .../global/infra/jwt/ParseToken.java | 43 +++ .../global/infra/mail/MailService.java | 53 +++ .../global/infra/mail/RandomCode.java | 28 ++ .../infra/security/CustomMemberDetails.java | 52 +++ .../infra/security/GetCurrentMember.java | 10 + .../infra/security/MemberDetailsService.java | 20 ++ .../global/properties/JwtProperties.java | 16 + src/main/resources/authEmail.html | 311 ++++++++++++++++++ 46 files changed, 1439 insertions(+), 18 deletions(-) create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/controller/AuthController.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/controller/EmailController.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/controller/TokenController.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/req/ReProvideTokenReq.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/req/SignInReq.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/req/SignUpReq.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/res/ReProvideTokenRes.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/res/TokenRes.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/service/AuthEmailService.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/service/AuthService.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/service/AuthTokenService.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/member/enums/MemberCategory.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/member/enums/MemberState.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/common/dto/res/JsonWebTokenResponse.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/security/BCryptConfig.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/security/SecurityConfig.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/swagger/swaggerConfig.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/EncryptException.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/InvalidTokenException.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/RefreshTokenException.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/UnableToSendEmailException.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/WithdrawalMemberException.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/WrongPasswordException.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/WrongTokenTypeException.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/email/CodeIsWrongException.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/email/EmailAlreadyExistsException.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/email/UnableToSendEmailException.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/MemberExistException.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/MemberNotExistException.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/MemberNotFoundException.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/PermissionDeniedException.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/UserNotCoincideException.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtExceptionFilter.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtFilter.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtHelper.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtProvider.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtType.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/ParseToken.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/mail/MailService.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/mail/RandomCode.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/security/CustomMemberDetails.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/security/GetCurrentMember.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/security/MemberDetailsService.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/properties/JwtProperties.java create mode 100644 src/main/resources/authEmail.html diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/controller/AuthController.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/controller/AuthController.java new file mode 100644 index 0000000..6921b40 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/controller/AuthController.java @@ -0,0 +1,39 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.auth.controller; + +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import kr.hs.dgsw.SOPO_server_v2.domain.auth.dto.req.ReProvideTokenReq; +import kr.hs.dgsw.SOPO_server_v2.domain.auth.dto.req.SignInReq; +import kr.hs.dgsw.SOPO_server_v2.domain.auth.dto.req.SignUpReq; +import kr.hs.dgsw.SOPO_server_v2.domain.auth.dto.res.ReProvideTokenRes; +import kr.hs.dgsw.SOPO_server_v2.domain.auth.service.AuthEmailService; +import kr.hs.dgsw.SOPO_server_v2.domain.auth.service.AuthService; +import kr.hs.dgsw.SOPO_server_v2.domain.auth.service.AuthTokenService; +import kr.hs.dgsw.SOPO_server_v2.global.common.dto.res.JsonWebTokenResponse; +import kr.hs.dgsw.SOPO_server_v2.global.response.Response; +import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "Auth", description = "Auth Api") +@RestController +@RequestMapping(value = "/auth") +@RequiredArgsConstructor +public class AuthController { + private final AuthService authService; + + @PostMapping("/sign_up") + public Response signUp( + @RequestBody @Valid SignUpReq signUpReq + ){ + return authService.signUp(signUpReq); + } + + @PostMapping("/sign_in") + public ResponseData signIn( + @RequestBody @Valid SignInReq signInReq + ){ + return authService.signIn(signInReq); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/controller/EmailController.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/controller/EmailController.java new file mode 100644 index 0000000..02c7198 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/controller/EmailController.java @@ -0,0 +1,26 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.auth.controller; + +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.hs.dgsw.SOPO_server_v2.domain.auth.service.AuthEmailService; +import kr.hs.dgsw.SOPO_server_v2.global.response.Response; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "Email", description = "Email Api") +@RestController +@RequestMapping(value = "/email") +@RequiredArgsConstructor +public class EmailController { + private final AuthEmailService authEmailService; + + @PostMapping("") + public Response requestVerificationCode( + @RequestParam(value = "email", required = true) @Validated String email + ) { + return authEmailService.sendMail(email); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/controller/TokenController.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/controller/TokenController.java new file mode 100644 index 0000000..aea439f --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/controller/TokenController.java @@ -0,0 +1,30 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.auth.controller; + +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import kr.hs.dgsw.SOPO_server_v2.domain.auth.dto.req.ReProvideTokenReq; +import kr.hs.dgsw.SOPO_server_v2.domain.auth.dto.res.ReProvideTokenRes; +import kr.hs.dgsw.SOPO_server_v2.domain.auth.service.AuthTokenService; +import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; +import lombok.RequiredArgsConstructor; +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.RestController; + +@Tag(name = "Refresh", description = "Refresh Api") +@RestController +@RequestMapping(value = "/re_provide") +@RequiredArgsConstructor +public class TokenController { + + private final AuthTokenService authTokenService; + + @PostMapping("") + public ResponseData reProvideToken( + @RequestBody @Valid ReProvideTokenReq reProvideTokenReq + ){ + return authTokenService.reProvideToken(reProvideTokenReq); + } + +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/req/ReProvideTokenReq.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/req/ReProvideTokenReq.java new file mode 100644 index 0000000..cc6e1b2 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/req/ReProvideTokenReq.java @@ -0,0 +1,4 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.auth.dto.req; + +public record ReProvideTokenReq(String refreshToken) { +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/req/SignInReq.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/req/SignInReq.java new file mode 100644 index 0000000..ec91ea4 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/req/SignInReq.java @@ -0,0 +1,7 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.auth.dto.req; + +public record SignInReq( + String memberId, + String memberPassword +) { +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/req/SignUpReq.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/req/SignUpReq.java new file mode 100644 index 0000000..600221f --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/req/SignUpReq.java @@ -0,0 +1,12 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.auth.dto.req; + +public record SignUpReq( + String memberId, + String memberName, + String memberEmail, + String authCode, + String memberPassword, + String memberSchool, + String memberFcmToken +) { +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/res/ReProvideTokenRes.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/res/ReProvideTokenRes.java new file mode 100644 index 0000000..ba22b97 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/res/ReProvideTokenRes.java @@ -0,0 +1,4 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.auth.dto.res; + +public record ReProvideTokenRes(String accessToken) { +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/res/TokenRes.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/res/TokenRes.java new file mode 100644 index 0000000..ec2f159 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/dto/res/TokenRes.java @@ -0,0 +1,7 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.auth.dto.res; + +public record TokenRes( + String accessToken, + String refreshToken +) { +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/service/AuthEmailService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/service/AuthEmailService.java new file mode 100644 index 0000000..052cc32 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/service/AuthEmailService.java @@ -0,0 +1,61 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.auth.service; + +import kr.hs.dgsw.SOPO_server_v2.domain.member.repository.MemberRepository; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.email.EmailAlreadyExistsException; +import kr.hs.dgsw.SOPO_server_v2.global.infra.mail.MailService; +import kr.hs.dgsw.SOPO_server_v2.global.infra.mail.RandomCode; +import kr.hs.dgsw.SOPO_server_v2.global.response.Response; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.thymeleaf.TemplateEngine; + +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.time.Duration; + +@Slf4j +@Component +@RequiredArgsConstructor +public class AuthEmailService { + private static final String AUTH_CODE_PREFIX = "AuthCode_"; + private final MemberRepository memberRepository; + private final MailService mailService; + private final StringRedisTemplate redisTemplate; + private final TemplateEngine templateEngine; + + @Value("${spring.mail.auth-code-expiration-millis}") + private long authCodeExpirationMillis; + + public boolean checkEmailDuplicate(String email) { + if (memberRepository.existsByMemberEmail(email)) + throw EmailAlreadyExistsException.EXCEPTION; + return true; + } + + public Response sendMail(String email) { + URLDecoder.decode(email, StandardCharsets.UTF_8); + checkEmailDuplicate(email); + + String authCode = RandomCode.generate(); + mailService.verificationCode( + email, + "SOPO 회원가입을 위한 이메일 인증코드", + authCode + ); + redisTemplate.opsForValue() + .set(AUTH_CODE_PREFIX + email, authCode, Duration.ofMillis(authCodeExpirationMillis)); + + return Response.of(HttpStatus.OK, "이메일 전송 성공"); + } + + public boolean verifiedCode(String email, String code){ + email = URLDecoder.decode(email, StandardCharsets.UTF_8); + String authCode = redisTemplate.opsForValue().get(AUTH_CODE_PREFIX + email); + return StringUtils.hasText(authCode) && authCode.equals(code); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/service/AuthService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/service/AuthService.java new file mode 100644 index 0000000..1c50ea4 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/service/AuthService.java @@ -0,0 +1,91 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.auth.service; + +import kr.hs.dgsw.SOPO_server_v2.domain.auth.dto.req.SignInReq; +import kr.hs.dgsw.SOPO_server_v2.domain.auth.dto.req.SignUpReq; +import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.member.enums.MemberCategory; +import kr.hs.dgsw.SOPO_server_v2.domain.member.enums.MemberState; +import kr.hs.dgsw.SOPO_server_v2.domain.member.repository.MemberRepository; +import kr.hs.dgsw.SOPO_server_v2.global.common.dto.res.JsonWebTokenResponse; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.auth.WrongPasswordException; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.email.CodeIsWrongException; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.email.EmailAlreadyExistsException; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.member.MemberNotFoundException; +import kr.hs.dgsw.SOPO_server_v2.global.infra.jwt.JwtProvider; +import kr.hs.dgsw.SOPO_server_v2.global.infra.security.CustomMemberDetails; +import kr.hs.dgsw.SOPO_server_v2.global.response.Response; +import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collection; +import java.util.Collections; + +import static kr.hs.dgsw.SOPO_server_v2.global.response.Response.of; + +@Component +@RequiredArgsConstructor +public class AuthService { + private final AuthenticationManager authenticationManager; + private final JwtProvider jwtProvider; + private final AuthEmailService authEmailService; + private final MemberRepository memberRepository; + + @Transactional(rollbackFor = Exception.class) + public Response signUp(SignUpReq signUpReq) { + + if (memberRepository.existsByMemberEmail(signUpReq.memberEmail())) + throw EmailAlreadyExistsException.EXCEPTION; + + if (!authEmailService.verifiedCode(signUpReq.memberEmail(), signUpReq.authCode())) + throw CodeIsWrongException.EXCEPTION; + + memberRepository.save(MemberEntity + .builder() + .memberId(signUpReq.memberId()) + .memberName(signUpReq.memberName()) + .memberEmail(signUpReq.memberEmail()) + .memberPassword(new BCryptPasswordEncoder().encode(signUpReq.memberPassword())) + .memberSchool(signUpReq.memberSchool()) + .memberCategory(MemberCategory.USER) + .memberState(MemberState.ACTIVE) + .memberFcmToken(signUpReq.memberFcmToken()) + .isOffAlarm(Boolean.TRUE) + .memberProfile(null) + .build()); + return Response.of(HttpStatus.OK, "회원가입 성공"); + } + + @Transactional(rollbackFor = Exception.class) + public ResponseData signIn(SignInReq signInReq){ + MemberEntity memberEntity = memberRepository.findByMemberId(signInReq.memberId()); + + if(memberEntity == null){ + throw MemberNotFoundException.EXCEPTION; + } + + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + if (!encoder.matches(signInReq.memberPassword(), memberEntity.getMemberPassword())) { + throw WrongPasswordException.EXCEPTION; + } + + Authentication authenticate = authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken(signInReq.memberId(), signInReq.memberPassword())); + + MemberEntity member = ((CustomMemberDetails) authenticate.getPrincipal()).member(); + return ResponseData.of(HttpStatus.OK, "로그인 성공", JsonWebTokenResponse.builder() + .accessToken(jwtProvider.generateAccessToken(member.getMemberId(), member.getMemberState())) + .refreshToken(jwtProvider.generateRefreshToken(member.getMemberId(), member.getMemberState())) + .build()); + } + +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/service/AuthTokenService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/service/AuthTokenService.java new file mode 100644 index 0000000..29754d5 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/auth/service/AuthTokenService.java @@ -0,0 +1,29 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.auth.service; + +import kr.hs.dgsw.SOPO_server_v2.domain.auth.dto.req.ReProvideTokenReq; +import kr.hs.dgsw.SOPO_server_v2.domain.auth.dto.res.ReProvideTokenRes; +import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.member.repository.MemberRepository; +import kr.hs.dgsw.SOPO_server_v2.global.infra.jwt.JwtProvider; +import kr.hs.dgsw.SOPO_server_v2.global.infra.jwt.ParseToken; +import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +public class AuthTokenService { + private final ParseToken parseToken; + private final MemberRepository memberRepository; + private final JwtProvider jwtProvider; + + @Transactional(readOnly = true) + public ResponseData reProvideToken(ReProvideTokenReq reProvideTokenReq){ + String id = parseToken.getSubjectFromRefreshToken(reProvideTokenReq.refreshToken()); + MemberEntity memberEntity = memberRepository.findByMemberId(id); + String reissuedToken = jwtProvider.generateAccessToken(memberEntity.getMemberId(), memberEntity.getMemberState()); + return ResponseData.of(HttpStatus.OK, "토큰 재발급 성공", new ReProvideTokenRes(reissuedToken)); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/member/enums/MemberCategory.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/member/enums/MemberCategory.java new file mode 100644 index 0000000..4964d23 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/member/enums/MemberCategory.java @@ -0,0 +1,5 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.member.enums; + +public enum MemberCategory { + ADMIN, USER +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/member/enums/MemberState.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/member/enums/MemberState.java new file mode 100644 index 0000000..d2acd90 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/member/enums/MemberState.java @@ -0,0 +1,12 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.member.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum MemberState { + ACTIVE("STATE_ACTIVE"), + DELETED("STAVE_DELETED"); + private final String key; +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/common/dto/res/JsonWebTokenResponse.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/common/dto/res/JsonWebTokenResponse.java new file mode 100644 index 0000000..15f4b53 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/common/dto/res/JsonWebTokenResponse.java @@ -0,0 +1,15 @@ +package kr.hs.dgsw.SOPO_server_v2.global.common.dto.res; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +@Builder +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class JsonWebTokenResponse { + private String accessToken; + private String refreshToken; +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/security/BCryptConfig.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/security/BCryptConfig.java new file mode 100644 index 0000000..40c28a1 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/security/BCryptConfig.java @@ -0,0 +1,13 @@ +package kr.hs.dgsw.SOPO_server_v2.global.config.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +@Configuration +public class BCryptConfig { + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/security/SecurityConfig.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/security/SecurityConfig.java new file mode 100644 index 0000000..8159595 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/security/SecurityConfig.java @@ -0,0 +1,96 @@ +package kr.hs.dgsw.SOPO_server_v2.global.config.security; + +import kr.hs.dgsw.SOPO_server_v2.global.error.ErrorResponse; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; +import kr.hs.dgsw.SOPO_server_v2.global.infra.jwt.JwtExceptionFilter; +import kr.hs.dgsw.SOPO_server_v2.global.infra.jwt.JwtFilter; +import kr.hs.dgsw.SOPO_server_v2.global.infra.security.CustomMemberDetails; +import kr.hs.dgsw.SOPO_server_v2.global.infra.security.MemberDetailsService; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.security.web.authentication.HttpStatusEntryPoint; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + private final MemberDetailsService memberDetailsService; + private final JwtFilter jwtFilter; + private final JwtExceptionFilter jwtExceptionFilter; + private final PasswordEncoder passwordEncoder; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .httpBasic().disable() + .cors() + .and() + .csrf().disable() + .cors(cors -> cors.configurationSource(corsConfigurationSource())) + .csrf(AbstractHttpConfigurer::disable) + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .addFilterAfter(jwtFilter, UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(jwtExceptionFilter, JwtFilter.class) + .authorizeHttpRequests() + .requestMatchers("/v3/api-docs/**", "/swagger-ui/**").permitAll() + .requestMatchers("/auth/**").permitAll() + .requestMatchers("/email/**").permitAll() + .requestMatchers("/re_provide/**").permitAll() + .requestMatchers("/file").hasRole("ACTIVE") + .anyRequest().authenticated() + .and() + .formLogin().disable() + .exceptionHandling() + .accessDeniedHandler((req, res, e) -> jwtExceptionFilter.responseToClient(res, ErrorResponse.of(StatusEnum.INVALID_ROLE, "권한이 없습니다"))) + .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.NOT_FOUND)); + + return http.build(); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.addAllowedOriginPattern("*"); + configuration.addAllowedHeader("*"); + configuration.addAllowedMethod("*"); + configuration.setAllowCredentials(true); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + + return source; + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { + return configuration.getAuthenticationManager(); + } + + @Bean + public AuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); + authenticationProvider.setUserDetailsService(memberDetailsService); + authenticationProvider.setPasswordEncoder(passwordEncoder); + return authenticationProvider; + } +} \ No newline at end of file diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/swagger/swaggerConfig.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/swagger/swaggerConfig.java new file mode 100644 index 0000000..8508841 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/swagger/swaggerConfig.java @@ -0,0 +1,34 @@ +package kr.hs.dgsw.SOPO_server_v2.global.config.swagger; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class swaggerConfig { + @Bean + public OpenAPI openAPI() { + SecurityScheme securityScheme = new SecurityScheme() + .name("Authorization") + .type(SecurityScheme.Type.HTTP) + .in(SecurityScheme.In.HEADER) + .bearerFormat("JWT") + .scheme("bearer"); + OpenAPI openAPI = new OpenAPI().addSecurityItem(new SecurityRequirement().addList("JWT Token")) + .components(new Components()) + .info(apiInfo()); + openAPI.getComponents().addSecuritySchemes("JWT Token", securityScheme); + return openAPI; + } + + private Info apiInfo() { + return new Info() + .title("SOPO_v2") + .description("Java API") + .version("0.0.2"); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/EncryptException.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/EncryptException.java new file mode 100644 index 0000000..644675a --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/EncryptException.java @@ -0,0 +1,12 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.auth; + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class EncryptException extends BusinessException { + public static final BusinessException EXCEPTION = new EncryptException(); + + public EncryptException() { + super(StatusEnum.INTERNAL_SERVER_ERROR); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/InvalidTokenException.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/InvalidTokenException.java new file mode 100644 index 0000000..0e4e218 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/InvalidTokenException.java @@ -0,0 +1,15 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.auth; + + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class InvalidTokenException extends BusinessException { + + public static final BusinessException EXCEPTION = new InvalidTokenException(); + + public InvalidTokenException(){ + super(StatusEnum.INVALID_TOKEN); + } + +} \ No newline at end of file diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/RefreshTokenException.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/RefreshTokenException.java new file mode 100644 index 0000000..bf9460b --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/RefreshTokenException.java @@ -0,0 +1,15 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.auth; + + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class RefreshTokenException extends BusinessException { + + public static final BusinessException EXCEPTION = new RefreshTokenException(); + + public RefreshTokenException(){ + super(StatusEnum.REFRESH_TOKEN_NOT_FOUND); + } + +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/UnableToSendEmailException.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/UnableToSendEmailException.java new file mode 100644 index 0000000..676a483 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/UnableToSendEmailException.java @@ -0,0 +1,13 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.auth; + + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class UnableToSendEmailException extends BusinessException { + public static final BusinessException EXCEPTION = new UnableToSendEmailException(); + + public UnableToSendEmailException(){ + super(StatusEnum.UNABLE_TO_SEND_EMAIL); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/WithdrawalMemberException.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/WithdrawalMemberException.java new file mode 100644 index 0000000..39baa4c --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/WithdrawalMemberException.java @@ -0,0 +1,15 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.auth; + + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class WithdrawalMemberException extends BusinessException { + + public static final BusinessException EXCEPTION = new WithdrawalMemberException(); + + public WithdrawalMemberException(){ + super(StatusEnum.WITHDRAWAL_MEMBER); + } + +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/WrongPasswordException.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/WrongPasswordException.java new file mode 100644 index 0000000..f59b494 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/WrongPasswordException.java @@ -0,0 +1,15 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.auth; + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class WrongPasswordException extends BusinessException { + + public static final BusinessException EXCEPTION = new WrongPasswordException(); + + public WrongPasswordException(){ + super(StatusEnum.WRONG_PASSWORD); + } + +} + diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/WrongTokenTypeException.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/WrongTokenTypeException.java new file mode 100644 index 0000000..8197036 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/auth/WrongTokenTypeException.java @@ -0,0 +1,12 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.auth; + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class WrongTokenTypeException extends BusinessException { + static final BusinessException EXCEPTION = new WrongTokenTypeException(); + + private WrongTokenTypeException() { + super(StatusEnum.WRONG_TOKEN_TYPE); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/email/CodeIsWrongException.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/email/CodeIsWrongException.java new file mode 100644 index 0000000..aa3b5bb --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/email/CodeIsWrongException.java @@ -0,0 +1,12 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.email; + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class CodeIsWrongException extends BusinessException { + public static final BusinessException EXCEPTION = new CodeIsWrongException(); + + public CodeIsWrongException() { + super(StatusEnum.CODE_IS_WRONG); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/email/EmailAlreadyExistsException.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/email/EmailAlreadyExistsException.java new file mode 100644 index 0000000..3aaec80 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/email/EmailAlreadyExistsException.java @@ -0,0 +1,12 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.email; + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class EmailAlreadyExistsException extends BusinessException { + public static final BusinessException EXCEPTION = new EmailAlreadyExistsException(); + + public EmailAlreadyExistsException() { + super(StatusEnum.EMAIL_ALREADY_EXIST); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/email/UnableToSendEmailException.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/email/UnableToSendEmailException.java new file mode 100644 index 0000000..605956e --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/email/UnableToSendEmailException.java @@ -0,0 +1,12 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.email; + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class UnableToSendEmailException extends BusinessException { + public static final BusinessException EXCEPTION = new UnableToSendEmailException(); + + public UnableToSendEmailException() { + super(StatusEnum.UNABLE_TO_SEND_EMAIL); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/MemberExistException.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/MemberExistException.java new file mode 100644 index 0000000..3f3293a --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/MemberExistException.java @@ -0,0 +1,12 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.member; + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class MemberExistException extends BusinessException { + public static final BusinessException EXCEPTION = new MemberExistException(); + + public MemberExistException() { + super(StatusEnum.MEMBER_EXISTS); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/MemberNotExistException.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/MemberNotExistException.java new file mode 100644 index 0000000..ca29675 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/MemberNotExistException.java @@ -0,0 +1,12 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.member; + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class MemberNotExistException extends BusinessException { + public static final BusinessException EXCEPTION = new MemberNotExistException(); + + public MemberNotExistException() { + super(StatusEnum.MEMBER_NOT_EXISTS); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/MemberNotFoundException.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/MemberNotFoundException.java new file mode 100644 index 0000000..e7d36eb --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/MemberNotFoundException.java @@ -0,0 +1,13 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.member; + + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class MemberNotFoundException extends BusinessException { + public static final BusinessException EXCEPTION = new MemberNotFoundException(); + + public MemberNotFoundException() { + super(StatusEnum.USER_NOT_FOUND); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/PermissionDeniedException.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/PermissionDeniedException.java new file mode 100644 index 0000000..4223518 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/PermissionDeniedException.java @@ -0,0 +1,13 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.member; + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class PermissionDeniedException extends BusinessException { + public static final BusinessException EXCEPTION = new PermissionDeniedException(); + + public PermissionDeniedException() { + super(StatusEnum.PERMISSION_DENIED); + } +} + diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/UserNotCoincideException.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/UserNotCoincideException.java new file mode 100644 index 0000000..aa72954 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/UserNotCoincideException.java @@ -0,0 +1,12 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.member; + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class UserNotCoincideException extends BusinessException { + public static final BusinessException EXCEPTION = new UserNotCoincideException(); + + public UserNotCoincideException() { + super(StatusEnum.USER_NOT_COINCIDE); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/exception/StatusEnum.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/exception/StatusEnum.java index c90a07c..962be4b 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/exception/StatusEnum.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/exception/StatusEnum.java @@ -4,22 +4,25 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import static org.springframework.http.HttpStatus.FORBIDDEN; + @Getter @AllArgsConstructor @JsonFormat(shape = JsonFormat.Shape.OBJECT) public enum StatusEnum { //smtp NOT_DUPLICATED_AUTH_CODE(400, "Not Duplicated AuthCode"), - EXPIRED_TOKEN(401 , "Expired token"), + EXPIRED_TOKEN(401, "Expired token"), INVALID_TOKEN(401, "Invalid token"), - REFRESH_TOKEN_NOT_FOUND(401,"RefreshToken not found"), + REFRESH_TOKEN_NOT_FOUND(401, "RefreshToken not found"), MALFORMED_JWT(400, "Jwt is malformed"), UNSUPPORTED_JWT(400, "Jwt is unsupported"), ILLEGAL_ARGUMENT(400, "IllegalArgumentException occurred"), + WRONG_TOKEN_TYPE(400, "Wrong token type"), // general - OK(200,"OK"), - CREATED(201,"Created"), + OK(200, "OK"), + CREATED(201, "Created"), BAD_REQUEST(400, "Bad request"), INTERNAL_SERVER_ERROR(500, "Internal server error"), @@ -31,31 +34,30 @@ public enum StatusEnum { NOT_AUTHENTICATED(401, "NotAuthenticated"), PERMISSION_DENIED(403, "Permission denied"), WITHDRAWAL_MEMBER(400, "Withdrawal member"), + INVALID_ROLE(403, "유효하지 않은 권한"), + WRONG_PASSWORD(403, "비밀번호가 옳지 않습니다"), //file - FILE_NOT_FOUND(404,"File not found"), - FILE_EXISTS(403,"File exists"), + FILE_NOT_FOUND(404, "File not found"), + FILE_EXISTS(403, "File exists"), //email - UNABLE_TO_SEND_EMAIL(403,"Unable to send email"), - - CHILD_CODE_OVER_USE_TWO(400, "CHILD_CODE_OVER_USE_TWO"), - CHILD_CODE_NOT_FOUND(404, "Child code not found"), - CATEGORY_NOT_FOUND(404,"Category not found"), - IS_NOT_PARENT(400, "is not parent account"), + UNABLE_TO_SEND_EMAIL(403, "Unable to send email"), + EMAIL_ALREADY_EXIST(404, "이메일이 이미 존재합니다"), + CODE_IS_WRONG(404, "인증코드가 옳지 않습니다."), //fcm - MESSAGE_SEND_FAILED(403,"Message send failed"), - TOKEN_NOT_PROVIDED(400, "잘못된 토큰"), - CLOUD_EXCEPTION(500,"클라우드 에러"), + MESSAGE_SEND_FAILED(403, "Message send failed"), + TOKEN_NOT_PROVIDED(400, "Token not provided"), + + CLOUD_EXCEPTION(500, "Cloud exception"), // board BOARD_NOT_FOUND(404, "Board not found"), // contest - CONTEST_NOT_FOUND(404, "Contest not found") - ; + CONTEST_NOT_FOUND(404, "Contest not found"); private final int statusCode; private final String message; -} +} \ No newline at end of file diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtExceptionFilter.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtExceptionFilter.java new file mode 100644 index 0000000..e8128b3 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtExceptionFilter.java @@ -0,0 +1,57 @@ +package kr.hs.dgsw.SOPO_server_v2.global.infra.jwt; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.UnsupportedJwtException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import kr.hs.dgsw.SOPO_server_v2.global.error.ErrorResponse; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class JwtExceptionFilter extends OncePerRequestFilter { + + private final ObjectMapper objectMapper; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + try { + filterChain.doFilter(request, response); + } catch (ExpiredJwtException e) { + setErrorResponse(response, StatusEnum.EXPIRED_TOKEN); + } catch (MalformedJwtException e) { + setErrorResponse(response, StatusEnum.MALFORMED_JWT); + } catch (UnsupportedJwtException e) { + setErrorResponse(response, StatusEnum.UNSUPPORTED_JWT); + } catch (IllegalArgumentException e) { + setErrorResponse(response, StatusEnum.ILLEGAL_ARGUMENT); + } + } + + private void setErrorResponse(HttpServletResponse response, StatusEnum error) { + try { + responseToClient(response, ErrorResponse.of(error, error.getMessage())); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void responseToClient(HttpServletResponse response, ErrorResponse errorResponse) throws IOException { + response.setStatus(errorResponse.getStatus()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write(objectMapper.writeValueAsString(errorResponse)); + } + +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtFilter.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtFilter.java new file mode 100644 index 0000000..0059a6c --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtFilter.java @@ -0,0 +1,44 @@ +package kr.hs.dgsw.SOPO_server_v2.global.infra.jwt; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +@RequiredArgsConstructor +@Slf4j +public class JwtFilter extends OncePerRequestFilter { + + private final JwtHelper jwtHelper; + + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws IOException, ServletException { + final String token = extractTokenFromRequest(request); + + if(token != null) { + Authentication authentication = jwtHelper.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + filterChain.doFilter(request, response); + } + + public String extractTokenFromRequest(HttpServletRequest request) { + return jwtHelper.extractToken(request.getHeader(HttpHeaders.AUTHORIZATION)); + } + +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtHelper.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtHelper.java new file mode 100644 index 0000000..5926f29 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtHelper.java @@ -0,0 +1,47 @@ +package kr.hs.dgsw.SOPO_server_v2.global.infra.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.member.repository.MemberRepository; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.member.MemberNotFoundException; +import kr.hs.dgsw.SOPO_server_v2.global.infra.security.CustomMemberDetails; +import kr.hs.dgsw.SOPO_server_v2.global.properties.JwtProperties; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +@Component +@RequiredArgsConstructor +public class JwtHelper { + + private final MemberRepository memberRepository; + private final JwtProperties jwtProperties; + + @Transactional + public Authentication getAuthentication(String accessToken) { + Claims claims = getClaims(accessToken); + MemberEntity member = memberRepository.findById(Long.valueOf(claims.getSubject())) + .orElseThrow(()-> MemberNotFoundException.EXCEPTION); + + CustomMemberDetails details = new CustomMemberDetails(member); + + return new UsernamePasswordAuthenticationToken(details, null, details.getAuthorities()); + } + + public Claims getClaims(String token) { + return Jwts.parserBuilder() + .setSigningKey(jwtProperties.getSecretKey()).build().parseClaimsJws(token).getBody(); + } + + public String extractToken(final String token) { + if (StringUtils.hasText(token) && token.startsWith("Bearer ")) { + return token.substring(7); + } + return token; + } + +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtProvider.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtProvider.java new file mode 100644 index 0000000..d68cbc0 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtProvider.java @@ -0,0 +1,53 @@ +package kr.hs.dgsw.SOPO_server_v2.global.infra.jwt; + +import io.jsonwebtoken.Header; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import kr.hs.dgsw.SOPO_server_v2.domain.auth.dto.res.TokenRes; +import kr.hs.dgsw.SOPO_server_v2.domain.member.enums.MemberState; +import kr.hs.dgsw.SOPO_server_v2.domain.member.repository.MemberRepository; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.auth.WithdrawalMemberException; +import kr.hs.dgsw.SOPO_server_v2.global.properties.JwtProperties; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.Date; + +@Component +@RequiredArgsConstructor +public class JwtProvider { + private final JwtProperties jwtProperties; + private final MemberRepository memberRepository; + + public TokenRes generateToken(String id, final MemberState memberState) { + if(memberRepository.findByMemberId(id).getMemberState()== MemberState.DELETED){ + throw WithdrawalMemberException.EXCEPTION; + } + return new TokenRes( + generateAccessToken(id, memberState), + generateRefreshToken(id, memberState)); + } + + + public String generateAccessToken(String id, final MemberState memberState) { + return Jwts.builder() + .setHeaderParam(Header.JWT_TYPE, JwtType.ACCESS) + .setSubject(id) + .claim("Authorization", memberState) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getAccessExpiration())) + .signWith(SignatureAlgorithm.HS512, jwtProperties.getSecretKey()) + .compact(); + } + + public String generateRefreshToken(String id, final MemberState memberState) { + return Jwts.builder() + .setHeaderParam(Header.JWT_TYPE, JwtType.REFRESH) + .claim("Authorization", memberState) + .setSubject(id.toString()) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getRefreshExpiration())) + .signWith(SignatureAlgorithm.HS512, jwtProperties.getSecretKey()) + .compact(); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtType.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtType.java new file mode 100644 index 0000000..3c5e2cc --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/JwtType.java @@ -0,0 +1,5 @@ +package kr.hs.dgsw.SOPO_server_v2.global.infra.jwt; + +public enum JwtType { + REFRESH, ACCESS +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/ParseToken.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/ParseToken.java new file mode 100644 index 0000000..a39fafc --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/jwt/ParseToken.java @@ -0,0 +1,43 @@ +package kr.hs.dgsw.SOPO_server_v2.global.infra.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Header; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import kr.hs.dgsw.SOPO_server_v2.global.properties.JwtProperties; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.lang.invoke.WrongMethodTypeException; + +@Component +@RequiredArgsConstructor +public class ParseToken { + private final JwtProperties jwtProperties; + private final JwtHelper jwtHelper; + + public String getSubjectFromRefreshToken(final String refreshToken) { + return getSubject(refreshToken, JwtType.REFRESH); + } + + public String getSubjectFromAccessToken(final String accessToken) { + return getSubject(accessToken, JwtType.ACCESS); + } + + private String getSubject(final String token, final JwtType jwtType) { + final String key = jwtProperties.getSecretKey(); + + final Jws jwsClaims = Jwts.parser().setSigningKey(key).parseClaimsJws(jwtHelper.extractToken(token)); + + this.isWrongType(jwsClaims, jwtType); + + return jwsClaims.getBody().getSubject(); + } + + private void isWrongType(final Jws claims, final JwtType jwtType) { + if(!(claims.getHeader().get(Header.JWT_TYPE).equals(jwtType.toString()))) { + throw new WrongMethodTypeException(); + } + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/mail/MailService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/mail/MailService.java new file mode 100644 index 0000000..c4006ba --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/mail/MailService.java @@ -0,0 +1,53 @@ +package kr.hs.dgsw.SOPO_server_v2.global.infra.mail; + +import jakarta.mail.internet.MimeMessage; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.email.UnableToSendEmailException; +import lombok.RequiredArgsConstructor; +import org.springframework.core.io.ClassPathResource; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.stream.Collectors; + +@Component +@Transactional +@RequiredArgsConstructor +public class MailService { + private final JavaMailSender javaMailSender; + + public String verificationCode(String toEmail, String title, String authCode){ + try { + ClassPathResource resource = new ClassPathResource("authEmail.html"); + BufferedReader reader = new BufferedReader( + new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8)); + String templateContent = reader.lines().collect(Collectors.joining("\n")); + String emailContent = templateContent.replace("{authCode}", authCode); + generateEmailForm(toEmail, title, emailContent); + return authCode; + }catch (IOException e){ + throw UnableToSendEmailException.EXCEPTION; + } + } + + private MimeMessage generateEmailForm(String toEmail, + String title, + String message) { + MimeMessage mimeMessage = javaMailSender.createMimeMessage(); + try { + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8"); + helper.setTo(toEmail); + helper.setSubject(title); + helper.setText(message, true); + javaMailSender.send(mimeMessage); + } catch (Exception e) { + throw new kr.hs.dgsw.SOPO_server_v2.global.error.custom.auth.UnableToSendEmailException(); + } + return mimeMessage; + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/mail/RandomCode.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/mail/RandomCode.java new file mode 100644 index 0000000..ec2de9d --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/mail/RandomCode.java @@ -0,0 +1,28 @@ +package kr.hs.dgsw.SOPO_server_v2.global.infra.mail; + +import java.util.Random; + +public class RandomCode { + private static final int LEFT_LIMIT_DIGITS = 48; // numeral '0' + private static final int RIGHT_LIMIT_DIGITS = 57; // numeral '9' + private static final int LEFT_LIMIT_UPPERCASE_LETTERS = 65; // letter 'A' + private static final int RIGHT_LIMIT_UPPERCASE_LETTERS = 90; // letter 'Z' + private static final int TARGET_STR_LENGTH = 6; + private static final Random random = new Random(); + + public static String generate() { + StringBuilder stringBuilder = new StringBuilder(TARGET_STR_LENGTH); + + int firstDigit = random.nextInt(RIGHT_LIMIT_DIGITS - LEFT_LIMIT_DIGITS + 1) + LEFT_LIMIT_DIGITS; + stringBuilder.append((char) firstDigit); + + for (int i = 1; i < TARGET_STR_LENGTH; i++) { + int randomNumber = random.nextBoolean() ? + random.nextInt(RIGHT_LIMIT_DIGITS - LEFT_LIMIT_DIGITS + 1) + LEFT_LIMIT_DIGITS : + random.nextInt(RIGHT_LIMIT_UPPERCASE_LETTERS - LEFT_LIMIT_UPPERCASE_LETTERS + 1) + LEFT_LIMIT_UPPERCASE_LETTERS; + stringBuilder.append((char) randomNumber); + } + + return stringBuilder.toString(); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/security/CustomMemberDetails.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/security/CustomMemberDetails.java new file mode 100644 index 0000000..a1a7be3 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/security/CustomMemberDetails.java @@ -0,0 +1,52 @@ +package kr.hs.dgsw.SOPO_server_v2.global.infra.security; + +import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.ArrayList; +import java.util.Collection; + +public record CustomMemberDetails(MemberEntity member) implements UserDetails { + + @Override + public Collection getAuthorities() { + Collection collections = new ArrayList<>(); + collections.add(() -> member.getMemberState().getKey()); + return collections; + } + + public static CustomMemberDetails of(MemberEntity memberEntity) { + return new CustomMemberDetails(memberEntity); + } + + @Override + public String getPassword() { + return member.getMemberPassword(); + } + + @Override + public String getUsername() { + return member.getMemberName(); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/security/GetCurrentMember.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/security/GetCurrentMember.java new file mode 100644 index 0000000..7e4a768 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/security/GetCurrentMember.java @@ -0,0 +1,10 @@ +package kr.hs.dgsw.SOPO_server_v2.global.infra.security; + +import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; +import org.springframework.security.core.context.SecurityContextHolder; + +public class GetCurrentMember { + public MemberEntity current() { + return ((CustomMemberDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).member(); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/security/MemberDetailsService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/security/MemberDetailsService.java new file mode 100644 index 0000000..63af7eb --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/security/MemberDetailsService.java @@ -0,0 +1,20 @@ +package kr.hs.dgsw.SOPO_server_v2.global.infra.security; + +import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.member.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class MemberDetailsService implements UserDetailsService { + + private final MemberRepository memberRepository; + + public UserDetails loadUserByUsername(String id) { + MemberEntity memberEntity = memberRepository.findByMemberId(id); + return CustomMemberDetails.of(memberEntity); + } +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/properties/JwtProperties.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/properties/JwtProperties.java new file mode 100644 index 0000000..1088ed0 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/properties/JwtProperties.java @@ -0,0 +1,16 @@ +package kr.hs.dgsw.SOPO_server_v2.global.properties; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Getter +@Setter +@Configuration +@ConfigurationProperties("application.jwt") +public class JwtProperties { + private String secretKey; + private Long accessExpiration; + private Long refreshExpiration; +} diff --git a/src/main/resources/authEmail.html b/src/main/resources/authEmail.html new file mode 100644 index 0000000..3dd485b --- /dev/null +++ b/src/main/resources/authEmail.html @@ -0,0 +1,311 @@ + + + + + + SOPO + + + + + +
+ + +
+ + + + + + +
+ +
+
+
+
+
+

이메일 인증번호 확인

+
+
+
+ + 소포 회원가입을 위한 인증번호 입니다. 아래 인증번호를 진행 + 중인 화면에 입력해서 이메일 인증을 완료해 주세요. +
+
+
+ 인증번호 +
+
+
+
+
+ {authCode} +
+
+
+
+
+
+ + + + + + + + + + + +
+
+ + 본 메일은 발신전용이며, 문의에 대한 회신은 처리되지 + 않습니다. + + + 카카오워크와 관련한 궁금점이나 불편사항은 + SOPO이메일로 문의 해주시길 바랍니다 + +
+
+ + + + \ No newline at end of file From 05844679a2a162cdf262a630df55fa5942af4d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=B8=EC=A4=80?= Date: Fri, 26 Jul 2024 09:34:53 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20=EC=A4=91=EA=B0=84=20=EC=BB=A4?= =?UTF-8?q?=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/board/service/BoardService.java | 29 ++- .../contest/service/ContestService.java | 46 +++-- .../like/controller/LikeController.java | 48 +++-- .../domain/like/service/LikeService.java | 191 +++++++++--------- .../error/custom/member/UserNotCoincide.java | 10 + .../infra/security/GetCurrentMember.java | 2 + 6 files changed, 187 insertions(+), 139 deletions(-) create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/UserNotCoincide.java diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/service/BoardService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/service/BoardService.java index 26bcdb5..0af0f9b 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/service/BoardService.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/service/BoardService.java @@ -5,7 +5,11 @@ import kr.hs.dgsw.SOPO_server_v2.domain.board.dto.BoardUpdateReq; import kr.hs.dgsw.SOPO_server_v2.domain.board.entity.BoardEntity; import kr.hs.dgsw.SOPO_server_v2.domain.board.repository.BoardRepository; +import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.member.enums.MemberCategory; import kr.hs.dgsw.SOPO_server_v2.global.error.custom.board.BoardNotFound; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.member.UserNotCoincide; +import kr.hs.dgsw.SOPO_server_v2.global.infra.security.GetCurrentMember; import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; import kr.hs.dgsw.SOPO_server_v2.global.response.Response; import lombok.RequiredArgsConstructor; @@ -18,7 +22,9 @@ @Transactional @RequiredArgsConstructor public class BoardService { + private final BoardRepository boardRepository; + private final GetCurrentMember getCurrentMember; // 게시글 전체 조회 public ResponseData> getBoards() { @@ -32,27 +38,31 @@ public ResponseData> getBoards() { // 빈 게시글 생성 public ResponseData createBoard() { - // 토큰 정보 가져오기 + MemberEntity curMember = getCurrentMember.current(); BoardEntity board = BoardEntity.builder() .boardTitle(null) .boardContent(null) .file(null) - // .member() + .member(curMember) .build(); + return ResponseData.of(HttpStatus.OK, "빈 게시물 생성 완료", board.getBoardId()); } // 게시글 업데이트 public Response loadBoard(Long boardId, BoardUpdateReq updateReq) { - // 토큰 정보 가져오기 + MemberEntity curMember = getCurrentMember.current(); BoardEntity board = boardRepository.findById(boardId) .orElseThrow(() -> BoardNotFound.EXCEPTION); - // 만약 현재 로그인 유저와 Load 하려는 유저가 다르다면 + + // 만약 현재 로그인 유저와 Load 하려는 유저가 다르고, admin 아니라면 + if (!board.getMember().getMemberId().equals(curMember.getMemberId()) && curMember.getMemberCategory() == MemberCategory.USER) { + throw UserNotCoincide.EXCEPTION; + } board.update(updateReq); - BoardLoadRes boardLoadRes = BoardLoadRes.of(board); return Response.of(HttpStatus.OK, "게시물 업데이트 완료"); } @@ -67,10 +77,15 @@ public ResponseData findOneBoard(Long boardId) { // 게시글 삭제 @Transactional public Response deleteBoard(Long boardId) { - // 토큰 정보 받아오기 + MemberEntity curMember = getCurrentMember.current(); + BoardEntity board = boardRepository.findById(boardId) .orElseThrow(() -> BoardNotFound.EXCEPTION); - // 만약 만든 사람과 삭제하려는 사람이 일치하지 않고 admin이 아니라면.. error + + // 만약 만든 사람과 삭제하려는 사람이 일치하지 않고 admin 아니라면.. error + if (!board.getMember().getMemberId().equals(curMember.getMemberId()) && curMember.getMemberCategory() == MemberCategory.USER) { + throw UserNotCoincide.EXCEPTION; + } boardRepository.deleteById(boardId); return Response.of(HttpStatus.OK, "게시물 삭제 완료"); diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java index 07c4c34..b47c428 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java @@ -6,7 +6,11 @@ import kr.hs.dgsw.SOPO_server_v2.domain.contest.entity.ContestEntity; import kr.hs.dgsw.SOPO_server_v2.domain.contest.enums.ContestState; import kr.hs.dgsw.SOPO_server_v2.domain.contest.repository.ContestRepository; +import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.member.enums.MemberCategory; import kr.hs.dgsw.SOPO_server_v2.global.error.custom.contest.ContestNotFound; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.member.UserNotCoincide; +import kr.hs.dgsw.SOPO_server_v2.global.infra.security.GetCurrentMember; import kr.hs.dgsw.SOPO_server_v2.global.response.Response; import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; import lombok.RequiredArgsConstructor; @@ -20,7 +24,7 @@ @RequiredArgsConstructor public class ContestService { // 대회 전환 필요 -> ACTIVE private final ContestRepository contestRepository; - + private final GetCurrentMember getCurrentMember; // 대회 전체 조회 public ResponseData> getContests() { List contestList = contestRepository.findAll(); @@ -33,24 +37,30 @@ public ResponseData> getContests() { // 빈 대회 생성 public ResponseData createContest() { - // 토큰 정보 가져오기 + + MemberEntity curMember = getCurrentMember.current(); ContestEntity contest = ContestEntity.builder() - // .member() + .member(curMember) .build(); return ResponseData.of(HttpStatus.OK, "대회 생성 완료", contest.getContestId()); } // 대회 업데이트 public Response loadContest(Long contestId, ContestUpdateReq updateReq) { - ContestEntity contest = contestRepository.findById(contestId) - .orElseThrow(() -> ContestNotFound.EXCEPTION); - // 만약 현재 로그인 유저와 Load 하려는 유저가 다르다면 + MemberEntity curMember = getCurrentMember.current(); + + ContestEntity contest = contestRepository.findById(contestId) + .orElseThrow(() -> ContestNotFound.EXCEPTION); + + // 만약 현재 로그인 유저와 Load 하려는 유저가 다르다면 + if (!contest.getMember().getMemberId().equals(curMember.getMemberId()) && curMember.getMemberCategory() == MemberCategory.USER) { + throw UserNotCoincide.EXCEPTION; + } - contest.update(updateReq); - ContestLoadRes contestLoadRes = ContestLoadRes.of(contest); + contest.update(updateReq); - return Response.of(HttpStatus.OK, "대회 업데이트 완료"); + return Response.of(HttpStatus.OK, "대회 업데이트 완료"); } // 대회 단일 조회 @@ -66,10 +76,15 @@ public ResponseData findOneContest(Long contestId) { // 대회 삭제 public Response deleteContest(Long contestId) { - // 토큰 정보 받아오기 + + MemberEntity curMember = getCurrentMember.current(); + ContestEntity contest = contestRepository.findById(contestId) .orElseThrow(() -> ContestNotFound.EXCEPTION); - // 만약 만든 사람과 삭제하려는 사람이 일치하지 않고 admin이 아니라면.. error + + if (!contest.getMember().getMemberId().equals(curMember.getMemberId()) && curMember.getMemberCategory() == MemberCategory.USER) { + throw UserNotCoincide.EXCEPTION; + } contestRepository.deleteById(contestId); return Response.of(HttpStatus.OK, "대회 삭제 완료"); @@ -77,10 +92,15 @@ public Response deleteContest(Long contestId) { // 대회 상태 변경 public Response changeContestState(Long contestId) { - // 토큰 정보 받아오기 + + MemberEntity curMember = getCurrentMember.current(); + ContestEntity contest = contestRepository.findById(contestId) .orElseThrow(() -> ContestNotFound.EXCEPTION); - // 만약 만든 사람과 변경하려는 사람이 일치하지 않고 admin이 아니라면.. error + + if (!contest.getMember().getMemberId().equals(curMember.getMemberId()) && curMember.getMemberCategory() == MemberCategory.USER) { + throw UserNotCoincide.EXCEPTION; + } if (contest.getContestState() == ContestState.ACTIVE) { contest.stateUpdateDisabled(); diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/controller/LikeController.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/controller/LikeController.java index 53d7289..2269eec 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/controller/LikeController.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/controller/LikeController.java @@ -1,25 +1,23 @@ -//package kr.hs.dgsw.SOPO_server_v2.domain.like.controller; -// -//import kr.hs.dgsw.SOPO_server_v2.domain.like.enums.LikeCategory; -//import kr.hs.dgsw.SOPO_server_v2.domain.like.service.LikeService; -//import kr.hs.dgsw.SOPO_server_v2.global.response.Response; -//import lombok.RequiredArgsConstructor; -//import org.springframework.http.HttpStatus; -//import org.springframework.web.bind.annotation.PatchMapping; -//import org.springframework.web.bind.annotation.RequestMapping; -//import org.springframework.web.bind.annotation.RequestParam; -//import org.springframework.web.bind.annotation.RestController; -// -//@RestController -//@RequestMapping("/like") -//@RequiredArgsConstructor -//public class LikeController { -// private final LikeService likeService; -// -// @PatchMapping -// public Response patch(@RequestParam Long id, @RequestParam LikeCategory category) { -// likeService.toggle(id, category); -// return Response.of(HttpStatus.OK, "좋아요 생성/취소 완료"); -// } -// -//} +package kr.hs.dgsw.SOPO_server_v2.domain.like.controller; + +import kr.hs.dgsw.SOPO_server_v2.domain.like.enums.LikeCategory; +import kr.hs.dgsw.SOPO_server_v2.domain.like.service.LikeService; +import kr.hs.dgsw.SOPO_server_v2.global.response.Response; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/like") +@RequiredArgsConstructor +public class LikeController { + private final LikeService likeService; + + @PatchMapping + public Response patch(@RequestParam Long id, @RequestParam LikeCategory category) { + return likeService.toggle(id, category); + } + +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java index c302892..236d0bb 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java @@ -1,94 +1,97 @@ -//package kr.hs.dgsw.SOPO_server_v2.domain.like.service; -// -//import jakarta.transaction.Transactional; -//import kr.hs.dgsw.SOPO_server_v2.domain.board.entity.BoardEntity; -//import kr.hs.dgsw.SOPO_server_v2.domain.board.repository.BoardRepository; -//import kr.hs.dgsw.SOPO_server_v2.domain.contest.entity.ContestEntity; -//import kr.hs.dgsw.SOPO_server_v2.domain.contest.repository.ContestRepository; -//import kr.hs.dgsw.SOPO_server_v2.domain.like.entity.LikeEntity; -//import kr.hs.dgsw.SOPO_server_v2.domain.like.enums.LikeCategory; -//import kr.hs.dgsw.SOPO_server_v2.domain.like.repository.LikeRepository; -//import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; -//import kr.hs.dgsw.SOPO_server_v2.global.error.custom.board.BoardNotFound; -//import kr.hs.dgsw.SOPO_server_v2.global.error.custom.contest.ContestNotFound; -//import kr.hs.dgsw.SOPO_server_v2.global.response.Response; -//import lombok.RequiredArgsConstructor; -//import org.springframework.http.HttpStatus; -//import org.springframework.stereotype.Service; -// -//import java.util.Optional; -// -//@Service -//@RequiredArgsConstructor -//@Transactional -//public class LikeService { -// // 멤버 필요 -// private final BoardRepository boardRepository; -// private final ContestRepository contestRepository; -// private final LikeRepository likeRepository; -// -// public Response toggle(Long id, LikeCategory category) { -// // 토큰 가져오기 -// // 게시물 좋아요일 때 -// if (category == LikeCategory.BOARD) { -// BoardEntity board = boardRepository.findById(id) -// .orElseThrow(() -> BoardNotFound.EXCEPTION); -// -// Optional like = likeRepository.findByMemberAndBoard(member, board); -// -// if (like.isEmpty()) { -// addBoardLike(member, board); -// board.likeUpdate(1); -// } else { -// boardRepository.delete(like.get().getBoard()); -// board.likeUpdate(-1); -// } -// } -// -// // 대회 좋아요일 때 -// else if (category == LikeCategory.CONTEST) { -// // 토큰 가져오기 -// ContestEntity contest = contestRepository.findById(id) -// .orElseThrow( () -> ContestNotFound.EXCEPTION); -// -// Optional like = likeRepository.findByMemberAndContest(member, contest); -// -// if (like.isEmpty()) { -// addContestLike(member, contest); -// contest.likeUpdate(1); -// } else { -// contestRepository.delete(like.get().getContest()); -// contest.likeUpdate(1); -// } -// } -// -// // else if (프로필) {} -// -// return Response.of(HttpStatus.OK, "좋아요 생성/취소 완료"); -// -// -// -// } -// -// private void addBoardLike(MemberEntity member, BoardEntity board) { -// likeRepository.save( -// LikeEntity.builder() -// .board(board) -// .member(member) -// .likeCategory(LikeCategory.BOARD) -// .build() -// ); -// } -// -// public void addContestLike(MemberEntity member, ContestEntity contest) { -// likeRepository.save( -// LikeEntity.builder() -// .contest(contest) -// .member(member) -// .likeCategory(LikeCategory.CONTEST) -// .build() -// ); -// } -// -// // 프로필 -//} +package kr.hs.dgsw.SOPO_server_v2.domain.like.service; + +import jakarta.transaction.Transactional; +import kr.hs.dgsw.SOPO_server_v2.domain.board.entity.BoardEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.board.repository.BoardRepository; +import kr.hs.dgsw.SOPO_server_v2.domain.contest.entity.ContestEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.contest.repository.ContestRepository; +import kr.hs.dgsw.SOPO_server_v2.domain.like.entity.LikeEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.like.enums.LikeCategory; +import kr.hs.dgsw.SOPO_server_v2.domain.like.repository.LikeRepository; +import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.board.BoardNotFound; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.contest.ContestNotFound; +import kr.hs.dgsw.SOPO_server_v2.global.infra.security.GetCurrentMember; +import kr.hs.dgsw.SOPO_server_v2.global.response.Response; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Transactional +public class LikeService { + // 멤버 필요 + private final BoardRepository boardRepository; + private final ContestRepository contestRepository; + private final LikeRepository likeRepository; + private final GetCurrentMember getCurrentMember; + + public Response toggle(Long id, LikeCategory category) { + MemberEntity curMember = getCurrentMember.current(); + // 토큰 가져오기 + // 게시물 좋아요일 때 + if (category == LikeCategory.BOARD) { + BoardEntity board = boardRepository.findById(id) + .orElseThrow(() -> BoardNotFound.EXCEPTION); + + Optional like = likeRepository.findByMemberAndBoard(curMember, board); + + if (like.isEmpty()) { + addBoardLike(curMember, board); + board.likeUpdate(1); + } else { + boardRepository.delete(like.get().getBoard()); + board.likeUpdate(-1); + } + } + + // 대회 좋아요일 때 + else if (category == LikeCategory.CONTEST) { + // 토큰 가져오기 + ContestEntity contest = contestRepository.findById(id) + .orElseThrow( () -> ContestNotFound.EXCEPTION); + + Optional like = likeRepository.findByMemberAndContest(curMember, contest); + + if (like.isEmpty()) { + addContestLike(curMember, contest); + contest.likeUpdate(1); + } else { + contestRepository.delete(like.get().getContest()); + contest.likeUpdate(1); + } + } + + // else if (프로필) {} + + return Response.of(HttpStatus.OK, "좋아요 생성/취소 완료"); + + + + } + + private void addBoardLike(MemberEntity member, BoardEntity board) { + likeRepository.save( + LikeEntity.builder() + .board(board) + .member(member) + .likeCategory(LikeCategory.BOARD) + .build() + ); + } + + public void addContestLike(MemberEntity member, ContestEntity contest) { + likeRepository.save( + LikeEntity.builder() + .contest(contest) + .member(member) + .likeCategory(LikeCategory.CONTEST) + .build() + ); + } + + // 프로필 +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/UserNotCoincide.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/UserNotCoincide.java new file mode 100644 index 0000000..ae2f057 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/member/UserNotCoincide.java @@ -0,0 +1,10 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.member; + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class UserNotCoincide extends BusinessException { + public static final BusinessException EXCEPTION = new UserNotCoincide(); + + public UserNotCoincide() {super(StatusEnum.USER_NOT_COINCIDE);} +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/security/GetCurrentMember.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/security/GetCurrentMember.java index 7e4a768..c0baca4 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/security/GetCurrentMember.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/infra/security/GetCurrentMember.java @@ -2,7 +2,9 @@ import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +@Component public class GetCurrentMember { public MemberEntity current() { return ((CustomMemberDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).member(); From 861ade31f9870a0acffc8eae9dc3460cf9a7104b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=B8=EC=A4=80?= Date: Wed, 31 Jul 2024 10:50:50 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=EC=A4=91=EA=B0=84=20=EC=BB=A4?= =?UTF-8?q?=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SopoServerV2Application.java | 2 + .../board/controller/BoardController.java | 12 ++- .../domain/board/dto/BoardLoadRes.java | 12 +-- .../domain/board/dto/BoardUpdateReq.java | 2 +- .../domain/board/entity/BoardEntity.java | 4 +- .../domain/board/service/BoardService.java | 6 +- .../contest/controller/ContestController.java | 12 ++- .../domain/contest/entity/ContestEntity.java | 7 +- .../contest/service/ContestService.java | 16 ++- .../file/controller/FileController.java | 10 +- .../domain/file/entity/FileEntity.java | 5 +- .../file/repository/FileRepository.java | 4 + .../domain/file/service/FileService.java | 102 ++++++++++++++++-- .../domain/like/service/LikeService.java | 3 +- .../global/config/S3Config.java | 1 - 15 files changed, 162 insertions(+), 36 deletions(-) diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/SopoServerV2Application.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/SopoServerV2Application.java index 50bc048..494099a 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/SopoServerV2Application.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/SopoServerV2Application.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication +@EnableJpaAuditing public class SopoServerV2Application { public static void main(String[] args) { diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/controller/BoardController.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/controller/BoardController.java index 373cc77..87bb1fd 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/controller/BoardController.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/controller/BoardController.java @@ -5,12 +5,15 @@ import kr.hs.dgsw.SOPO_server_v2.domain.board.service.BoardService; import kr.hs.dgsw.SOPO_server_v2.global.response.Response; import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; + import lombok.RequiredArgsConstructor; 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.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; import java.util.List; @@ -37,8 +40,11 @@ public ResponseData getBoard(Long boardId) { } @PatchMapping - public Response loadBoard(Long boardId, BoardUpdateReq updateReq) { - return boardService.loadBoard(boardId, updateReq); + public Response updateBoard( + @RequestParam Long boardId, + @RequestBody BoardUpdateReq updateReq + ) { + return boardService.updateBoard(boardId, updateReq); } @DeleteMapping @@ -46,4 +52,4 @@ public Response deleteBoard(Long boardId) { return boardService.deleteBoard(boardId); } -} +} \ No newline at end of file diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/dto/BoardLoadRes.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/dto/BoardLoadRes.java index aa34495..30edc75 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/dto/BoardLoadRes.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/dto/BoardLoadRes.java @@ -2,21 +2,21 @@ import kr.hs.dgsw.SOPO_server_v2.domain.board.entity.BoardEntity; + public record BoardLoadRes ( Long boardId, String boardTitle, String boardContent, - Integer boardLikeCount - // List fileUrls, - // Long memberId + Integer boardLikeCount, + String memberId ){ public static BoardLoadRes of(BoardEntity board) { return new BoardLoadRes( board.getBoardId(), board.getBoardTitle(), board.getBoardTitle(), - board.getBoardLikeCount() - //board.getFile() Url을 String 으로 묶어서 받아야 함. + board.getBoardLikeCount(), + board.getMember().getMemberId() ); } -} +} \ No newline at end of file diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/dto/BoardUpdateReq.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/dto/BoardUpdateReq.java index 4af3686..53c42e0 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/dto/BoardUpdateReq.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/dto/BoardUpdateReq.java @@ -4,4 +4,4 @@ public record BoardUpdateReq( String boardTitle, String boardContent ) { -} +} \ No newline at end of file diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/entity/BoardEntity.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/entity/BoardEntity.java index b413047..89847c8 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/entity/BoardEntity.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/entity/BoardEntity.java @@ -16,6 +16,7 @@ import kr.hs.dgsw.SOPO_server_v2.global.common.entity.BaseTimeEntity; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import lombok.experimental.SuperBuilder; import java.util.List; @@ -24,6 +25,7 @@ @Entity @Table(name = "tbl_board") @NoArgsConstructor +@Setter @SuperBuilder public class BoardEntity extends BaseTimeEntity { @@ -62,4 +64,4 @@ public void update(BoardUpdateReq updateReq) { public void likeUpdate(int boardLikeCount) { this.boardLikeCount += boardLikeCount; } -} +} \ No newline at end of file diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/service/BoardService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/service/BoardService.java index 0af0f9b..2de4fc1 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/service/BoardService.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/service/BoardService.java @@ -43,15 +43,18 @@ public ResponseData createBoard() { BoardEntity board = BoardEntity.builder() .boardTitle(null) .boardContent(null) + .boardLikeCount(0) .file(null) .member(curMember) .build(); + boardRepository.save(board); + return ResponseData.of(HttpStatus.OK, "빈 게시물 생성 완료", board.getBoardId()); } // 게시글 업데이트 - public Response loadBoard(Long boardId, BoardUpdateReq updateReq) { + public Response updateBoard(Long boardId, BoardUpdateReq updateReq) { MemberEntity curMember = getCurrentMember.current(); BoardEntity board = boardRepository.findById(boardId) @@ -63,6 +66,7 @@ public Response loadBoard(Long boardId, BoardUpdateReq updateReq) { } board.update(updateReq); + return Response.of(HttpStatus.OK, "게시물 업데이트 완료"); } diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/controller/ContestController.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/controller/ContestController.java index d44b80c..e9c7e5b 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/controller/ContestController.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/controller/ContestController.java @@ -10,7 +10,9 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; 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; import java.util.List; @@ -32,22 +34,22 @@ public ResponseData createContest() { } @GetMapping - public ResponseData getContest(Long contestId) { + public ResponseData getContest(@RequestParam Long contestId) { return contestService.findOneContest(contestId); } @PatchMapping - public Response loadContest(Long contestId, ContestUpdateReq updateReq) { - return contestService.loadContest(contestId, updateReq); + public Response updateContest(@RequestParam Long contestId, @RequestBody ContestUpdateReq updateReq) { + return contestService.updateContest(contestId, updateReq); } @DeleteMapping - public Response deleteContest(Long contestId) { + public Response deleteContest(@RequestParam Long contestId) { return contestService.deleteContest(contestId); } @PatchMapping("/state") - public Response changeContestState(Long contestId) { + public Response changeContestState(@RequestParam Long contestId) { return contestService.changeContestState(contestId); } } diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java index 6aee5b3..117ffed 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java @@ -5,6 +5,8 @@ import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @@ -17,14 +19,14 @@ import kr.hs.dgsw.SOPO_server_v2.global.common.entity.BaseTimeEntity; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import lombok.experimental.SuperBuilder; -import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.Date; import java.util.List; @Getter +@Setter @Entity @Table(name = "tbl_contest") @NoArgsConstructor @@ -34,6 +36,7 @@ public class ContestEntity extends BaseTimeEntity { // 대회 아이디 @Id @Column(name = "contest_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long contestId; // 대회 제목 diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java index b47c428..0ee2539 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java @@ -23,8 +23,10 @@ @Transactional @RequiredArgsConstructor public class ContestService { // 대회 전환 필요 -> ACTIVE + private final ContestRepository contestRepository; private final GetCurrentMember getCurrentMember; + // 대회 전체 조회 public ResponseData> getContests() { List contestList = contestRepository.findAll(); @@ -37,16 +39,26 @@ public ResponseData> getContests() { // 빈 대회 생성 public ResponseData createContest() { - MemberEntity curMember = getCurrentMember.current(); + ContestEntity contest = ContestEntity.builder() + .contestMax(null) + .contestContent(null) + .contestPerson(null) + .contestState(ContestState.ACTIVE) + .contestLikeCount(0) + .contestDateTime(null) + .file(null) .member(curMember) .build(); + + contestRepository.save(contest); + return ResponseData.of(HttpStatus.OK, "대회 생성 완료", contest.getContestId()); } // 대회 업데이트 - public Response loadContest(Long contestId, ContestUpdateReq updateReq) { + public Response updateContest(Long contestId, ContestUpdateReq updateReq) { MemberEntity curMember = getCurrentMember.current(); diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/controller/FileController.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/controller/FileController.java index 68a07ea..70b7e8d 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/controller/FileController.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/controller/FileController.java @@ -5,6 +5,7 @@ import kr.hs.dgsw.SOPO_server_v2.domain.file.service.FileService; import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -21,7 +22,12 @@ public class FileController { private final FileService fileService; @PostMapping - public ResponseData> fileUpload(@RequestParam FileCategory fileCategory, @RequestPart List fileList) { - return fileService.fileUpload(fileCategory, fileList); + public ResponseData> fileUpload(@RequestParam Long id, @RequestParam FileCategory fileCategory, @RequestPart List fileList) { + return fileService.fileUpload(id, fileCategory, fileList); + } + + @GetMapping + public ResponseData> getFiles(@RequestParam Long id, @RequestParam FileCategory fileCategory) { + return fileService.getFiles(id, fileCategory); } } diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/entity/FileEntity.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/entity/FileEntity.java index eea3614..8e4f7b2 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/entity/FileEntity.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/entity/FileEntity.java @@ -4,6 +4,8 @@ import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @@ -26,6 +28,7 @@ public class FileEntity extends BaseTimeEntity { // 파일 아이디 @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "file_id") private Long fileId; @@ -56,4 +59,4 @@ public class FileEntity extends BaseTimeEntity { @JoinColumn(name = "member_id") private MemberEntity member; -} +} \ No newline at end of file diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/repository/FileRepository.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/repository/FileRepository.java index 60fd7fd..12e6ba9 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/repository/FileRepository.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/repository/FileRepository.java @@ -3,5 +3,9 @@ import kr.hs.dgsw.SOPO_server_v2.domain.file.entity.FileEntity; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface FileRepository extends JpaRepository { + List findByBoard_BoardId(Long boardId); + List findByContest_ContestId(Long contestId); } diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/service/FileService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/service/FileService.java index 4a45c8f..f2dca8a 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/service/FileService.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/service/FileService.java @@ -3,10 +3,16 @@ import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; +import kr.hs.dgsw.SOPO_server_v2.domain.board.entity.BoardEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.board.repository.BoardRepository; +import kr.hs.dgsw.SOPO_server_v2.domain.contest.entity.ContestEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.contest.repository.ContestRepository; import kr.hs.dgsw.SOPO_server_v2.domain.file.dto.FileRes; import kr.hs.dgsw.SOPO_server_v2.domain.file.entity.FileEntity; import kr.hs.dgsw.SOPO_server_v2.domain.file.enums.FileCategory; import kr.hs.dgsw.SOPO_server_v2.domain.file.repository.FileRepository; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.board.BoardNotFound; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.contest.ContestNotFound; import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -18,6 +24,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -30,10 +37,17 @@ public class FileService { private final FileRepository fileRepository; + private final BoardRepository boardRepository; + + private final ContestRepository contestRepository; + @Transactional - public ResponseData> fileUpload(FileCategory fileCategory, List fileList) { + public ResponseData> fileUpload(Long id, FileCategory fileCategory, List fileList) { + List fileLists = new ArrayList<>(); + try { + for (MultipartFile file : fileList) { String fileName = file.getOriginalFilename(); @@ -52,20 +66,90 @@ public ResponseData> fileUpload(FileCategory fileCategory, List BoardNotFound.EXCEPTION); + + // DB에 저장하는 코드 + FileEntity fileEntity = FileEntity.builder() + .fileName(fileName) + .fileCategory(fileCategory) + .fileUrl(amazonS3.getUrl(bucket, fileName).toString()) + .board(board) + .build(); + fileRepository.save(fileEntity); + + // 현재 게시물의 파일 목록에 새로 업로드된 파일 추가 + List files = board.getFile(); + if (files == null) { + files = new ArrayList<>(); + } + files.add(fileEntity); + board.setFile(files); // BoardEntity의 파일 목록 직접 수정 + + // 게시물 엔티티를 DB에 저장하여 파일 목록 반영 + boardRepository.save(board); + + } else if (fileCategory == FileCategory.CONTEST) { + ContestEntity contest = contestRepository.findById(id) + .orElseThrow(() -> ContestNotFound.EXCEPTION); + + // DB에 저장하는 코드 + FileEntity fileEntity = FileEntity.builder() + .fileName(fileName) + .fileCategory(fileCategory) + .fileUrl(amazonS3.getUrl(bucket, fileName).toString()) + .contest(contest) + .build(); + + fileRepository.save(fileEntity); + + // 현재 게시물의 파일 목록에 새로 업로드된 파일 추가 + List files = contest.getFile(); + if (files == null) { + files = new ArrayList<>(); + } + files.add(fileEntity); + contest.setFile(files); // BoardEntity의 파일 목록 직접 수정 + + // 게시물 엔티티를 DB에 저장하여 파일 목록 반영 + contestRepository.save(contest); + } } } catch (IOException e) { throw new RuntimeException("Error uploading file to S3", e); } return ResponseData.of(HttpStatus.OK, "파일 업로드 완료", fileLists); } + + public ResponseData> getFiles(Long id, FileCategory fileCategory) { + + List fileList = new ArrayList<>(); + + if (fileCategory == FileCategory.BOARD) { + + fileList = fileRepository.findByBoard_BoardId(id).stream() + .map(file -> FileRes.builder() + .fileName(file.getFileName()) + .fileUrl(file.getFileUrl()) + .build()) + .collect(Collectors.toList()); + + } else if (fileCategory == FileCategory.CONTEST) { + fileList = fileRepository.findByContest_ContestId(id).stream() + .map(file -> FileRes.builder() + .fileName(file.getFileName()) + .fileUrl(file.getFileUrl()) + .build()) + .collect(Collectors.toList()); + } + + return ResponseData.of(HttpStatus.OK, "파일 조회 완료", fileList); + } + + } diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java index 236d0bb..18efdf7 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java @@ -31,8 +31,7 @@ public class LikeService { public Response toggle(Long id, LikeCategory category) { MemberEntity curMember = getCurrentMember.current(); - // 토큰 가져오기 - // 게시물 좋아요일 때 + if (category == LikeCategory.BOARD) { BoardEntity board = boardRepository.findById(id) .orElseThrow(() -> BoardNotFound.EXCEPTION); diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/S3Config.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/S3Config.java index 93ab5ea..dd45fde 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/S3Config.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/S3Config.java @@ -25,7 +25,6 @@ public AmazonS3 amazonS3Client() { BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); return AmazonS3ClientBuilder.standard() .withRegion(region) - .enablePathStyleAccess() .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) .build(); } From 58adb7884a62d54d7aea6c48c9159944e42f98fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=B8=EC=A4=80?= Date: Wed, 31 Jul 2024 16:37:09 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20=EC=A4=91=EA=B0=84=20=EC=BB=A4?= =?UTF-8?q?=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/contest/entity/ContestEntity.java | 4 + .../contest/service/ContestService.java | 4 +- .../enroll/controller/EnrollController.java | 22 ++++++ .../domain/enroll/entity/EnrollEntity.java | 37 +++++++++ .../enroll/repository/EnrollRepository.java | 12 +++ .../domain/enroll/service/EnrollService.java | 79 +++++++++++++++++++ .../domain/like/service/LikeService.java | 12 +-- 7 files changed, 163 insertions(+), 7 deletions(-) create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/controller/EnrollController.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/entity/EnrollEntity.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/repository/EnrollRepository.java create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/service/EnrollService.java diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java index 117ffed..8e0d67b 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java @@ -89,6 +89,10 @@ public void likeUpdate(int contestLikeCount) { this.contestLikeCount += contestLikeCount; } + public void addContestPerson(int contestPerson) { + this.contestPerson += contestPerson; + } + public void stateUpdateActive() { this.contestState = ContestState.ACTIVE; } diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java index 0ee2539..24568dd 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java @@ -42,9 +42,9 @@ public ResponseData createContest() { MemberEntity curMember = getCurrentMember.current(); ContestEntity contest = ContestEntity.builder() - .contestMax(null) + .contestMax(0) .contestContent(null) - .contestPerson(null) + .contestPerson(0) .contestState(ContestState.ACTIVE) .contestLikeCount(0) .contestDateTime(null) diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/controller/EnrollController.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/controller/EnrollController.java new file mode 100644 index 0000000..a127641 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/controller/EnrollController.java @@ -0,0 +1,22 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.enroll.controller; + +import kr.hs.dgsw.SOPO_server_v2.domain.enroll.service.EnrollService; +import kr.hs.dgsw.SOPO_server_v2.global.response.Response; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/enroll") +@RequiredArgsConstructor +public class EnrollController { + private final EnrollService enrollService; + + @PatchMapping + public Response patch(@RequestParam Long contestId){ + return enrollService.toggle(contestId); + } + +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/entity/EnrollEntity.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/entity/EnrollEntity.java new file mode 100644 index 0000000..4630259 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/entity/EnrollEntity.java @@ -0,0 +1,37 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.enroll.entity; + +import jakarta.persistence.Entity; +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 kr.hs.dgsw.SOPO_server_v2.domain.contest.entity.ContestEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +@Entity +@SuperBuilder +@Getter +@Setter +@NoArgsConstructor +@Table(name = "tbl_enroll") +public class EnrollEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long EnrollIdx; + + @ManyToOne() + @JoinColumn(name = "fk_member_id") + private MemberEntity member; + + @ManyToOne() + @JoinColumn(name = "fk_contest_id") + private ContestEntity contest; + +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/repository/EnrollRepository.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/repository/EnrollRepository.java new file mode 100644 index 0000000..dfa4f29 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/repository/EnrollRepository.java @@ -0,0 +1,12 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.enroll.repository; + +import kr.hs.dgsw.SOPO_server_v2.domain.contest.entity.ContestEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.enroll.entity.EnrollEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface EnrollRepository extends JpaRepository { + Optional findByMemberAndContest(MemberEntity member, ContestEntity contest); +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/service/EnrollService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/service/EnrollService.java new file mode 100644 index 0000000..c5d758c --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/service/EnrollService.java @@ -0,0 +1,79 @@ +package kr.hs.dgsw.SOPO_server_v2.domain.enroll.service; + +import jakarta.transaction.Transactional; +import kr.hs.dgsw.SOPO_server_v2.domain.contest.entity.ContestEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.contest.enums.ContestState; +import kr.hs.dgsw.SOPO_server_v2.domain.contest.repository.ContestRepository; +import kr.hs.dgsw.SOPO_server_v2.domain.enroll.entity.EnrollEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.enroll.repository.EnrollRepository; +import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.contest.ContestNotFound; +import kr.hs.dgsw.SOPO_server_v2.global.infra.security.GetCurrentMember; +import kr.hs.dgsw.SOPO_server_v2.global.response.Response; +import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +@Transactional +@RequiredArgsConstructor +public class EnrollService { + + private final ContestRepository contestRepository; + private final GetCurrentMember getCurrentMember; + private final EnrollRepository enrollRepository; + + // 대회 신청하는 기능 + public Response toggle(Long contestId) { + + MemberEntity curMember = getCurrentMember.current(); + + ContestEntity contest = contestRepository.findById(contestId) + .orElseThrow(() -> ContestNotFound.EXCEPTION); + + Optional enroll = enrollRepository.findByMemberAndContest(curMember, contest); + + if (enroll.isEmpty()) { + enrollContest(curMember, contest); + + if (contest.getContestState() == ContestState.DISABLED) { + + return Response.of(HttpStatus.OK, "신청이 마감되었습니다."); + + } + else { + contest.addContestPerson(1); + if (contest.getContestMax().equals(contest.getContestPerson())) { + contest.stateUpdateDisabled(); + } + return Response.of(HttpStatus.OK, "신청 성공"); + } + + } + else { + enrollRepository.delete(enroll.get()); + contest.addContestPerson(-1); + + return Response.of(HttpStatus.OK, "신청 취소 성공"); + } + + } + + private void enrollContest(MemberEntity member, ContestEntity contest) { + enrollRepository.save( + EnrollEntity.builder() + .contest(contest) + .member(member) + .build() + ); + } + + // 대회 허락하는 기능 신청 허락 성공, 신청 거절 성공 -> 이때 쟤네 삭제하는 것도 만들어야 함. + + // 신청한 사람 보여주는 기능 -> 이거 신청한 시간이랑 같이 보여줘야 될 듯 -> 대회 id 넘겨받기 memberid 리스트로 넘겨주기 + +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java index 18efdf7..62dec5b 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java @@ -41,9 +41,11 @@ public Response toggle(Long id, LikeCategory category) { if (like.isEmpty()) { addBoardLike(curMember, board); board.likeUpdate(1); + return Response.of(HttpStatus.OK, "좋아요 생성 완료"); } else { boardRepository.delete(like.get().getBoard()); board.likeUpdate(-1); + return Response.of(HttpStatus.OK, "좋아요 취소 완료"); } } @@ -58,17 +60,17 @@ else if (category == LikeCategory.CONTEST) { if (like.isEmpty()) { addContestLike(curMember, contest); contest.likeUpdate(1); + return Response.of(HttpStatus.OK, "좋아요 생성 완료"); } else { contestRepository.delete(like.get().getContest()); contest.likeUpdate(1); + return Response.of(HttpStatus.OK, "좋아요 취소 완료"); } } - // else if (프로필) {} - - return Response.of(HttpStatus.OK, "좋아요 생성/취소 완료"); - - + else { // 여기다가 프로필 만드셈 + return Response.of(HttpStatus.OK, "???"); + } } From 49e3a44b8d73109898ffa5fded499e88fa6660ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=B8=EC=A4=80?= Date: Fri, 2 Aug 2024 09:01:45 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20=EB=8C=80=ED=9A=8C=20=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EB=A1=9C=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/controller/BoardController.java | 4 +- .../domain/board/dto/BoardLoadRes.java | 4 +- .../domain/contest/dto/ContestLoadRes.java | 21 +++- .../domain/contest/dto/ContestUpdateReq.java | 2 - .../domain/contest/entity/ContestEntity.java | 13 +- .../contest/service/ContestService.java | 3 +- .../enroll/controller/EnrollController.java | 19 +++ .../enroll/repository/EnrollRepository.java | 4 +- .../domain/enroll/service/EnrollService.java | 112 ++++++++++++++++-- .../domain/file/enums/FileCategory.java | 3 +- .../config/security/SecurityConfig.java | 1 + .../error/custom/enroll/ENROLL_NOT_FOUND.java | 12 ++ .../global/error/exception/StatusEnum.java | 5 +- 13 files changed, 175 insertions(+), 28 deletions(-) create mode 100644 src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/enroll/ENROLL_NOT_FOUND.java diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/controller/BoardController.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/controller/BoardController.java index 87bb1fd..bad0f57 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/controller/BoardController.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/controller/BoardController.java @@ -35,7 +35,7 @@ public ResponseData createBoard() { } @GetMapping - public ResponseData getBoard(Long boardId) { + public ResponseData getBoard(@RequestParam Long boardId) { return boardService.findOneBoard(boardId); } @@ -48,7 +48,7 @@ public Response updateBoard( } @DeleteMapping - public Response deleteBoard(Long boardId) { + public Response deleteBoard(@RequestParam Long boardId) { return boardService.deleteBoard(boardId); } diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/dto/BoardLoadRes.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/dto/BoardLoadRes.java index 30edc75..2e7dc03 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/dto/BoardLoadRes.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/dto/BoardLoadRes.java @@ -8,7 +8,7 @@ public record BoardLoadRes ( String boardTitle, String boardContent, Integer boardLikeCount, - String memberId + String memberName ){ public static BoardLoadRes of(BoardEntity board) { return new BoardLoadRes( @@ -16,7 +16,7 @@ public static BoardLoadRes of(BoardEntity board) { board.getBoardTitle(), board.getBoardTitle(), board.getBoardLikeCount(), - board.getMember().getMemberId() + board.getMember().getMemberName() ); } } \ No newline at end of file diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/dto/ContestLoadRes.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/dto/ContestLoadRes.java index d02e589..88050b4 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/dto/ContestLoadRes.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/dto/ContestLoadRes.java @@ -1,8 +1,11 @@ package kr.hs.dgsw.SOPO_server_v2.domain.contest.dto; import kr.hs.dgsw.SOPO_server_v2.domain.contest.entity.ContestEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; public record ContestLoadRes ( Long contestId, @@ -10,19 +13,27 @@ public record ContestLoadRes ( String contestContent, Integer contestMax, Integer contestPerson, - LocalDateTime contestDateTime - // List fileUrls, - // Long memberId + LocalDateTime contestDateTime, + String memberName, + List memberIdList ) { public static ContestLoadRes of(ContestEntity contest) { + List memberIdList = contest.getMemberIdList().stream() + .map(MemberEntity::getMemberId) + .collect(Collectors.toList()); + + // 대회 생성자의 이름 가져오기 + String memberName = contest.getMember().getMemberName(); + return new ContestLoadRes ( contest.getContestId(), contest.getContestTitle(), contest.getContestContent(), contest.getContestMax(), contest.getContestPerson(), - contest.getContestDateTime() - //contest.getFile() Url을 String 으로 묶어서 받아야 함. + contest.getContestDateTime(), + memberName, + memberIdList ); } } diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/dto/ContestUpdateReq.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/dto/ContestUpdateReq.java index d4dc108..0361784 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/dto/ContestUpdateReq.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/dto/ContestUpdateReq.java @@ -1,7 +1,5 @@ package kr.hs.dgsw.SOPO_server_v2.domain.contest.dto; -import kr.hs.dgsw.SOPO_server_v2.domain.contest.enums.ContestState; - import java.time.LocalDateTime; public record ContestUpdateReq ( diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java index 8e0d67b..2ce35c5 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java @@ -9,6 +9,7 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; @@ -31,7 +32,7 @@ @Table(name = "tbl_contest") @NoArgsConstructor @SuperBuilder -public class ContestEntity extends BaseTimeEntity { +public class ContestEntity extends BaseTimeEntity { // board, contest 에 유저 이름이랑 아이디 같이 보내주기 // 대회 아이디 @Id @@ -77,6 +78,12 @@ public class ContestEntity extends BaseTimeEntity { @OneToMany(mappedBy = "contest", cascade = CascadeType.ALL, orphanRemoval = true) // 읽기만, 게시물 삭제될 때 함께 삭제 private List file; + // 대회 유저 이름 + @ManyToMany + @JoinColumn(name = "member_id_list") + private List memberIdList; + + public void update(ContestUpdateReq updateReq) { this.contestTitle = updateReq.contestTitle(); this.contestContent = updateReq.contestContent(); @@ -93,6 +100,10 @@ public void addContestPerson(int contestPerson) { this.contestPerson += contestPerson; } + public void addAllowMember(MemberEntity member) { + this.memberIdList.add(member); + } + public void stateUpdateActive() { this.contestState = ContestState.ACTIVE; } diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java index 6e1fdb3..1e285da 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java @@ -22,7 +22,7 @@ @Service @Transactional @RequiredArgsConstructor -public class ContestService { // 대회 전환 필요 -> ACTIVE +public class ContestService { private final ContestRepository contestRepository; private final GetCurrentMember getCurrentMember; @@ -48,6 +48,7 @@ public ResponseData createContest() { .contestState(ContestState.ACTIVE) .contestLikeCount(0) .contestDateTime(null) + .memberIdList(null) .file(null) .member(curMember) .build(); diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/controller/EnrollController.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/controller/EnrollController.java index a127641..a7d6800 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/controller/EnrollController.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/controller/EnrollController.java @@ -2,12 +2,17 @@ import kr.hs.dgsw.SOPO_server_v2.domain.enroll.service.EnrollService; import kr.hs.dgsw.SOPO_server_v2.global.response.Response; +import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; import lombok.RequiredArgsConstructor; +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.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @RestController @RequestMapping("/enroll") @RequiredArgsConstructor @@ -19,4 +24,18 @@ public Response patch(@RequestParam Long contestId){ return enrollService.toggle(contestId); } + @GetMapping + public ResponseData> showEnrollPeople(@RequestParam Long contestId) { + return enrollService.showEnrollPeople(contestId); + } + + @DeleteMapping("/allow") + public Response allowContest(@RequestParam Long contestId, @RequestParam String memberId) { + return enrollService.allowContest(contestId, memberId); + } + + @DeleteMapping("/refuse") + public Response refuseContest(@RequestParam Long contestId, @RequestParam String memberId) { + return enrollService.refuseContest(contestId, memberId); + } } diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/repository/EnrollRepository.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/repository/EnrollRepository.java index dfa4f29..7ffc4ca 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/repository/EnrollRepository.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/repository/EnrollRepository.java @@ -5,8 +5,10 @@ import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; import java.util.Optional; public interface EnrollRepository extends JpaRepository { Optional findByMemberAndContest(MemberEntity member, ContestEntity contest); -} + List findByContest(ContestEntity contest); +} \ No newline at end of file diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/service/EnrollService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/service/EnrollService.java index c5d758c..fe14dd2 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/service/EnrollService.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/enroll/service/EnrollService.java @@ -7,7 +7,11 @@ import kr.hs.dgsw.SOPO_server_v2.domain.enroll.entity.EnrollEntity; import kr.hs.dgsw.SOPO_server_v2.domain.enroll.repository.EnrollRepository; import kr.hs.dgsw.SOPO_server_v2.domain.member.entity.MemberEntity; +import kr.hs.dgsw.SOPO_server_v2.domain.member.enums.MemberCategory; +import kr.hs.dgsw.SOPO_server_v2.domain.member.repository.MemberRepository; import kr.hs.dgsw.SOPO_server_v2.global.error.custom.contest.ContestNotFound; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.enroll.ENROLL_NOT_FOUND; +import kr.hs.dgsw.SOPO_server_v2.global.error.custom.member.MemberNotCoincideException; import kr.hs.dgsw.SOPO_server_v2.global.infra.security.GetCurrentMember; import kr.hs.dgsw.SOPO_server_v2.global.response.Response; import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; @@ -15,6 +19,7 @@ import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -26,8 +31,9 @@ public class EnrollService { private final ContestRepository contestRepository; private final GetCurrentMember getCurrentMember; private final EnrollRepository enrollRepository; + private final MemberRepository memberRepository; - // 대회 신청하는 기능 + // 신청 기능 public Response toggle(Long contestId) { MemberEntity curMember = getCurrentMember.current(); @@ -41,29 +47,22 @@ public Response toggle(Long contestId) { enrollContest(curMember, contest); if (contest.getContestState() == ContestState.DISABLED) { - return Response.of(HttpStatus.OK, "신청이 마감되었습니다."); - } else { - contest.addContestPerson(1); - if (contest.getContestMax().equals(contest.getContestPerson())) { - contest.stateUpdateDisabled(); - } + // enroll 찾기 return Response.of(HttpStatus.OK, "신청 성공"); } - } else { enrollRepository.delete(enroll.get()); - contest.addContestPerson(-1); return Response.of(HttpStatus.OK, "신청 취소 성공"); } } - private void enrollContest(MemberEntity member, ContestEntity contest) { + public void enrollContest(MemberEntity member, ContestEntity contest) { enrollRepository.save( EnrollEntity.builder() .contest(contest) @@ -72,8 +71,97 @@ private void enrollContest(MemberEntity member, ContestEntity contest) { ); } - // 대회 허락하는 기능 신청 허락 성공, 신청 거절 성공 -> 이때 쟤네 삭제하는 것도 만들어야 함. + // 허락 기능 + public Response allowContest(Long contestId, String memberId) { // 대회, 허락받은 사람 + + MemberEntity curMember = getCurrentMember.current(); + + + ContestEntity contest = contestRepository.findById(contestId) + .orElseThrow(() -> ContestNotFound.EXCEPTION); + + MemberEntity member = memberRepository.findByMemberId(memberId); + + // 대회가 이미 닫혀있다면? + if (contest.getContestState() == ContestState.DISABLED) { + return Response.of(HttpStatus.OK, "허락할 수 없습니다."); + } + + if (!contest.getMember().getMemberId().equals(curMember.getMemberId()) && curMember.getMemberCategory() == MemberCategory.USER) { + throw MemberNotCoincideException.EXCEPTION; + } + + + // enroll 찾기 + Optional enroll = enrollRepository.findByMemberAndContest(member, contest); + + System.out.println(enroll); + + // enroll 삭제 + enroll.ifPresent(enrollRepository::delete); + + if (enroll.isEmpty()) { + throw ENROLL_NOT_FOUND.EXCEPTION; + } + + contest.addContestPerson(1); + + if (contest.getContestMax().equals(contest.getContestPerson())) { + contest.stateUpdateDisabled(); + } + + // member 정보 넣기 + contest.addAllowMember(member); + + return Response.of(HttpStatus.OK, "대회 신청 허락 완료"); + } - // 신청한 사람 보여주는 기능 -> 이거 신청한 시간이랑 같이 보여줘야 될 듯 -> 대회 id 넘겨받기 memberid 리스트로 넘겨주기 + // 거절 기능 + public Response refuseContest(Long contestId, String memberId) { + + MemberEntity curMember = getCurrentMember.current(); + + ContestEntity contest = contestRepository.findById(contestId) + .orElseThrow(() -> ContestNotFound.EXCEPTION); + + MemberEntity member = memberRepository.findByMemberId(memberId); + + if (!contest.getMember().getMemberId().equals(curMember.getMemberId()) && curMember.getMemberCategory() == MemberCategory.USER) { + throw MemberNotCoincideException.EXCEPTION; + } + + // enroll 찾기 + Optional enroll = enrollRepository.findByMemberAndContest(member, contest); + + if (contest.getContestMax().equals(contest.getContestPerson())) { + contest.stateUpdateDisabled(); + } + + // enroll 삭제 + enroll.ifPresent(enrollRepository::delete); + + return Response.of(HttpStatus.OK, "대회 신청 거절 완료"); + } + + // 신청한 사람 보여주는 기능 + public ResponseData> showEnrollPeople(Long contestId) { + MemberEntity curMember = getCurrentMember.current(); + + ContestEntity contest = contestRepository.findById(contestId) + .orElseThrow(() -> ContestNotFound.EXCEPTION); + + List enrollList = enrollRepository.findByContest(contest); + + List memberNameList = new ArrayList<>(); + for (EnrollEntity enroll : enrollList) { + memberNameList.add(enroll.getMember().getMemberName()); + } + + if (!contest.getMember().getMemberId().equals(curMember.getMemberId()) && curMember.getMemberCategory() == MemberCategory.USER) { + throw MemberNotCoincideException.EXCEPTION; + } + + return ResponseData.of(HttpStatus.OK, "신청한 사람 조회 성공", memberNameList); + } } diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/enums/FileCategory.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/enums/FileCategory.java index 674f253..95db14b 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/enums/FileCategory.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/file/enums/FileCategory.java @@ -3,5 +3,6 @@ public enum FileCategory { BOARD, CONTEST, - PROFILE + PROFILE, + PORTFOLIO } diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/security/SecurityConfig.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/security/SecurityConfig.java index f21f695..fe761ab 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/security/SecurityConfig.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/security/SecurityConfig.java @@ -49,6 +49,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/member/**").hasAuthority("ROLE_ACTIVE") .requestMatchers("/contest/**").authenticated() .requestMatchers("/like/**").hasAuthority("ROLE_ACTIVE") + .requestMatchers("/enroll/**").hasAuthority("ROLE_ACTIVE") .anyRequest().authenticated() .and() .formLogin().disable() diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/enroll/ENROLL_NOT_FOUND.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/enroll/ENROLL_NOT_FOUND.java new file mode 100644 index 0000000..93afc89 --- /dev/null +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/custom/enroll/ENROLL_NOT_FOUND.java @@ -0,0 +1,12 @@ +package kr.hs.dgsw.SOPO_server_v2.global.error.custom.enroll; + +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.BusinessException; +import kr.hs.dgsw.SOPO_server_v2.global.error.exception.StatusEnum; + +public class ENROLL_NOT_FOUND extends BusinessException { + public static final BusinessException EXCEPTION = new ENROLL_NOT_FOUND(); + + public ENROLL_NOT_FOUND() { + super(StatusEnum.ENROLL_NOT_FOUND);} + +} diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/exception/StatusEnum.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/exception/StatusEnum.java index 51c1bad..3262cda 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/exception/StatusEnum.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/error/exception/StatusEnum.java @@ -56,7 +56,10 @@ public enum StatusEnum { BOARD_NOT_FOUND(404, "게시물을 찾을 수 없습니다."), // contest - CONTEST_NOT_FOUND(404, "대회를 찾을 수 없습니다"); + CONTEST_NOT_FOUND(404, "대회를 찾을 수 없습니다"), + + // enroll + ENROLL_NOT_FOUND(404, "신청을 찾을 수 없습니다"); private final int statusCode; private final String message; From 94a956cdcebff759f404129f6e968b01fc493e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=B8=EC=A4=80?= Date: Fri, 2 Aug 2024 09:31:46 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20board=20=ED=8E=98=EC=9D=B4=EC=A7=95?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/controller/BoardController.java | 36 ++++--------------- .../domain/board/service/BoardService.java | 26 +++++--------- .../contest/controller/ContestController.java | 23 +----------- .../domain/contest/entity/ContestEntity.java | 5 +-- .../contest/service/ContestService.java | 23 ++---------- .../domain/like/service/LikeService.java | 27 +------------- .../config/security/SecurityConfig.java | 4 --- 7 files changed, 22 insertions(+), 122 deletions(-) diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/controller/BoardController.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/controller/BoardController.java index 3158b6e..01c05fa 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/controller/BoardController.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/controller/BoardController.java @@ -3,24 +3,19 @@ import kr.hs.dgsw.SOPO_server_v2.domain.board.dto.BoardLoadRes; import kr.hs.dgsw.SOPO_server_v2.domain.board.dto.BoardUpdateReq; import kr.hs.dgsw.SOPO_server_v2.domain.board.service.BoardService; +import kr.hs.dgsw.SOPO_server_v2.global.page.PageRequest; import kr.hs.dgsw.SOPO_server_v2.global.response.Response; import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; -<<<<<<< HEAD -======= ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; -<<<<<<< HEAD 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.RequestMapping; ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 import org.springframework.web.bind.annotation.RestController; import java.util.List; @@ -32,8 +27,8 @@ public class BoardController { private final BoardService boardService; @GetMapping("/all") - public ResponseData> getBoards() { - return boardService.getBoards(); + public ResponseData> getBoards(@ModelAttribute PageRequest pageRequest) { + return boardService.getBoards(pageRequest); } @PostMapping @@ -42,19 +37,14 @@ public ResponseData createBoard() { } @GetMapping -<<<<<<< HEAD public ResponseData getBoard(@RequestParam Long boardId) { -======= - public ResponseData getBoard(Long boardId) { ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 return boardService.findOneBoard(boardId); } @PatchMapping -<<<<<<< HEAD public Response updateBoard( - @RequestParam Long boardId, - @RequestBody BoardUpdateReq updateReq + @RequestParam Long boardId, + @RequestBody BoardUpdateReq updateReq ) { return boardService.updateBoard(boardId, updateReq); } @@ -64,16 +54,4 @@ public Response deleteBoard(@RequestParam Long boardId) { return boardService.deleteBoard(boardId); } -} -======= - public Response loadBoard(Long boardId, BoardUpdateReq updateReq) { - return boardService.loadBoard(boardId, updateReq); - } - - @DeleteMapping - public Response deleteBoard(Long boardId) { - return boardService.deleteBoard(boardId); - } - -} ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 +} \ No newline at end of file diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/service/BoardService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/service/BoardService.java index 7ccefe7..00da9bf 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/service/BoardService.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/board/service/BoardService.java @@ -10,6 +10,7 @@ import kr.hs.dgsw.SOPO_server_v2.global.error.custom.board.BoardNotFound; import kr.hs.dgsw.SOPO_server_v2.global.error.custom.member.MemberNotCoincideException; import kr.hs.dgsw.SOPO_server_v2.global.infra.security.GetCurrentMember; +import kr.hs.dgsw.SOPO_server_v2.global.page.PageRequest; import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; import kr.hs.dgsw.SOPO_server_v2.global.response.Response; import lombok.RequiredArgsConstructor; @@ -17,6 +18,7 @@ import org.springframework.stereotype.Service; import java.util.List; +import java.util.stream.Collectors; @Service @Transactional @@ -27,11 +29,14 @@ public class BoardService { private final GetCurrentMember getCurrentMember; // 게시글 전체 조회 - public ResponseData> getBoards() { + public ResponseData> getBoards(PageRequest pageRequest) { List boardList = boardRepository.findAll(); - List boardLoadResList = boardList.stream().map( - BoardLoadRes::of - ).toList(); + + List boardLoadResList = boardList.stream() + .map(BoardLoadRes::of) + .skip((pageRequest.page() -1) * pageRequest.size()) + .limit(pageRequest.size()) + .collect(Collectors.toList()); return ResponseData.of(HttpStatus.OK, "게시물 전체 조회 완료", boardLoadResList); } @@ -43,28 +48,18 @@ public ResponseData createBoard() { BoardEntity board = BoardEntity.builder() .boardTitle(null) .boardContent(null) -<<<<<<< HEAD .boardLikeCount(0) -======= ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 .file(null) .member(curMember) .build(); -<<<<<<< HEAD boardRepository.save(board); -======= ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 return ResponseData.of(HttpStatus.OK, "빈 게시물 생성 완료", board.getBoardId()); } // 게시글 업데이트 -<<<<<<< HEAD public Response updateBoard(Long boardId, BoardUpdateReq updateReq) { -======= - public Response loadBoard(Long boardId, BoardUpdateReq updateReq) { ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 MemberEntity curMember = getCurrentMember.current(); BoardEntity board = boardRepository.findById(boardId) @@ -76,10 +71,7 @@ public Response loadBoard(Long boardId, BoardUpdateReq updateReq) { } board.update(updateReq); -<<<<<<< HEAD -======= ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 return Response.of(HttpStatus.OK, "게시물 업데이트 완료"); } diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/controller/ContestController.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/controller/ContestController.java index ac5eaca..36b6a38 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/controller/ContestController.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/controller/ContestController.java @@ -10,13 +10,9 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; -<<<<<<< HEAD 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.RequestMapping; ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 import org.springframework.web.bind.annotation.RestController; import java.util.List; @@ -38,39 +34,22 @@ public ResponseData createContest() { } @GetMapping -<<<<<<< HEAD public ResponseData getContest(@RequestParam Long contestId) { -======= - public ResponseData getContest(Long contestId) { ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 return contestService.findOneContest(contestId); } @PatchMapping -<<<<<<< HEAD public Response updateContest(@RequestParam Long contestId, @RequestBody ContestUpdateReq updateReq) { return contestService.updateContest(contestId, updateReq); } @DeleteMapping public Response deleteContest(@RequestParam Long contestId) { -======= - public Response loadContest(Long contestId, ContestUpdateReq updateReq) { - return contestService.loadContest(contestId, updateReq); - } - - @DeleteMapping - public Response deleteContest(Long contestId) { ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 return contestService.deleteContest(contestId); } @PatchMapping("/state") -<<<<<<< HEAD public Response changeContestState(@RequestParam Long contestId) { -======= - public Response changeContestState(Long contestId) { ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 return contestService.changeContestState(contestId); } -} +} \ No newline at end of file diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java index b3e5233..c340200 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/entity/ContestEntity.java @@ -96,7 +96,6 @@ public void likeUpdate(int contestLikeCount) { this.contestLikeCount += contestLikeCount; } -<<<<<<< HEAD public void addContestPerson(int contestPerson) { this.contestPerson += contestPerson; } @@ -105,8 +104,6 @@ public void addAllowMember(MemberEntity member) { this.memberIdList.add(member); } -======= ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 public void stateUpdateActive() { this.contestState = ContestState.ACTIVE; } @@ -114,4 +111,4 @@ public void stateUpdateActive() { public void stateUpdateDisabled() { this.contestState = ContestState.DISABLED; } -} +} \ No newline at end of file diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java index ba22ba0..c030990 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java @@ -11,6 +11,7 @@ import kr.hs.dgsw.SOPO_server_v2.global.error.custom.contest.ContestNotFound; import kr.hs.dgsw.SOPO_server_v2.global.error.custom.member.MemberNotCoincideException; import kr.hs.dgsw.SOPO_server_v2.global.infra.security.GetCurrentMember; +import kr.hs.dgsw.SOPO_server_v2.global.page.PageRequest; import kr.hs.dgsw.SOPO_server_v2.global.response.Response; import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; import lombok.RequiredArgsConstructor; @@ -22,19 +23,13 @@ @Service @Transactional @RequiredArgsConstructor -<<<<<<< HEAD public class ContestService { private final ContestRepository contestRepository; private final GetCurrentMember getCurrentMember; -======= -public class ContestService { // 대회 전환 필요 -> ACTIVE - private final ContestRepository contestRepository; - private final GetCurrentMember getCurrentMember; ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 // 대회 전체 조회 - public ResponseData> getContests() { + public ResponseData> getContests(PageRequest pageRequest) { List contestList = contestRepository.findAll(); List contestLoadRes = contestList.stream().map( ContestLoadRes :: of @@ -45,7 +40,6 @@ public ResponseData> getContests() { // 빈 대회 생성 public ResponseData createContest() { -<<<<<<< HEAD MemberEntity curMember = getCurrentMember.current(); ContestEntity contest = ContestEntity.builder() @@ -62,22 +56,11 @@ public ResponseData createContest() { contestRepository.save(contest); -======= - - MemberEntity curMember = getCurrentMember.current(); - ContestEntity contest = ContestEntity.builder() - .member(curMember) - .build(); ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 return ResponseData.of(HttpStatus.OK, "대회 생성 완료", contest.getContestId()); } // 대회 업데이트 -<<<<<<< HEAD public Response updateContest(Long contestId, ContestUpdateReq updateReq) { -======= - public Response loadContest(Long contestId, ContestUpdateReq updateReq) { ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 MemberEntity curMember = getCurrentMember.current(); @@ -142,4 +125,4 @@ public Response changeContestState(Long contestId) { return Response.of(HttpStatus.OK, "대회 상태 변경 성공!"); } -} +} \ No newline at end of file diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java index 3e9d901..f9dbdb8 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/like/service/LikeService.java @@ -31,12 +31,7 @@ public class LikeService { public Response toggle(Long id, LikeCategory category) { MemberEntity curMember = getCurrentMember.current(); -<<<<<<< HEAD -======= - // 토큰 가져오기 - // 게시물 좋아요일 때 ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 if (category == LikeCategory.BOARD) { BoardEntity board = boardRepository.findById(id) .orElseThrow(() -> BoardNotFound.EXCEPTION); @@ -46,17 +41,11 @@ public Response toggle(Long id, LikeCategory category) { if (like.isEmpty()) { addBoardLike(curMember, board); board.likeUpdate(1); -<<<<<<< HEAD return Response.of(HttpStatus.OK, "좋아요 생성 완료"); } else { boardRepository.delete(like.get().getBoard()); board.likeUpdate(-1); return Response.of(HttpStatus.OK, "좋아요 취소 완료"); -======= - } else { - boardRepository.delete(like.get().getBoard()); - board.likeUpdate(-1); ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 } } @@ -71,7 +60,6 @@ else if (category == LikeCategory.CONTEST) { if (like.isEmpty()) { addContestLike(curMember, contest); contest.likeUpdate(1); -<<<<<<< HEAD return Response.of(HttpStatus.OK, "좋아요 생성 완료"); } else { contestRepository.delete(like.get().getContest()); @@ -83,19 +71,6 @@ else if (category == LikeCategory.CONTEST) { else { // 여기다가 프로필 만드셈 return Response.of(HttpStatus.OK, "???"); } -======= - } else { - contestRepository.delete(like.get().getContest()); - contest.likeUpdate(1); - } - } - - // else if (프로필) {} - - return Response.of(HttpStatus.OK, "좋아요 생성/취소 완료"); - - ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 } @@ -120,4 +95,4 @@ public void addContestLike(MemberEntity member, ContestEntity contest) { } // 프로필 -} +} \ No newline at end of file diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/security/SecurityConfig.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/security/SecurityConfig.java index 414f88e..d3003e0 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/security/SecurityConfig.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/global/config/security/SecurityConfig.java @@ -51,11 +51,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/member/**").hasAuthority("ROLE_ACTIVE") .requestMatchers("/contest/**").hasAuthority("ROLE_ACTIVE") .requestMatchers("/like/**").hasAuthority("ROLE_ACTIVE") -<<<<<<< HEAD - .requestMatchers("/enroll/**").hasAuthority("ROLE_ACTIVE") -======= .requestMatchers("/profile/**").hasAuthority("ROLE_ACTIVE") ->>>>>>> e493e579002345c8a1e3507d0ba6d7a8691fc148 .anyRequest().authenticated() .and() .formLogin().disable() From 2015832f42e7e56a04241b7c05edf8016a43c59d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=B8=EC=A4=80?= Date: Fri, 2 Aug 2024 09:32:20 +0900 Subject: [PATCH 8/9] =?UTF-8?q?feat:=20board=20=ED=8E=98=EC=9D=B4=EC=A7=95?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SOPO_server_v2/domain/contest/service/ContestService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java index c030990..bc08113 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java @@ -11,7 +11,6 @@ import kr.hs.dgsw.SOPO_server_v2.global.error.custom.contest.ContestNotFound; import kr.hs.dgsw.SOPO_server_v2.global.error.custom.member.MemberNotCoincideException; import kr.hs.dgsw.SOPO_server_v2.global.infra.security.GetCurrentMember; -import kr.hs.dgsw.SOPO_server_v2.global.page.PageRequest; import kr.hs.dgsw.SOPO_server_v2.global.response.Response; import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; import lombok.RequiredArgsConstructor; @@ -29,7 +28,7 @@ public class ContestService { private final GetCurrentMember getCurrentMember; // 대회 전체 조회 - public ResponseData> getContests(PageRequest pageRequest) { + public ResponseData> getContests() { List contestList = contestRepository.findAll(); List contestLoadRes = contestList.stream().map( ContestLoadRes :: of From 6e44e4c2b4c65ca284df6b7ef1a36e6638dcec16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=B8=EC=A4=80?= Date: Fri, 2 Aug 2024 09:40:50 +0900 Subject: [PATCH 9/9] =?UTF-8?q?feat:=20contest=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=95=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contest/controller/ContestController.java | 5 +++-- .../domain/contest/service/ContestService.java | 13 +++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/controller/ContestController.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/controller/ContestController.java index 36b6a38..eb4109f 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/controller/ContestController.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/controller/ContestController.java @@ -3,6 +3,7 @@ import kr.hs.dgsw.SOPO_server_v2.domain.contest.dto.ContestLoadRes; import kr.hs.dgsw.SOPO_server_v2.domain.contest.dto.ContestUpdateReq; import kr.hs.dgsw.SOPO_server_v2.domain.contest.service.ContestService; +import kr.hs.dgsw.SOPO_server_v2.global.page.PageRequest; import kr.hs.dgsw.SOPO_server_v2.global.response.Response; import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; import lombok.RequiredArgsConstructor; @@ -24,8 +25,8 @@ public class ContestController { private final ContestService contestService; @GetMapping("/all") - public ResponseData> getContests() { - return contestService.getContests(); + public ResponseData> getContests(PageRequest pageRequest) { + return contestService.getContests(pageRequest); } @PostMapping diff --git a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java index bc08113..5036c72 100644 --- a/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java +++ b/src/main/java/kr/hs/dgsw/SOPO_server_v2/domain/contest/service/ContestService.java @@ -11,6 +11,7 @@ import kr.hs.dgsw.SOPO_server_v2.global.error.custom.contest.ContestNotFound; import kr.hs.dgsw.SOPO_server_v2.global.error.custom.member.MemberNotCoincideException; import kr.hs.dgsw.SOPO_server_v2.global.infra.security.GetCurrentMember; +import kr.hs.dgsw.SOPO_server_v2.global.page.PageRequest; import kr.hs.dgsw.SOPO_server_v2.global.response.Response; import kr.hs.dgsw.SOPO_server_v2.global.response.ResponseData; import lombok.RequiredArgsConstructor; @@ -18,6 +19,7 @@ import org.springframework.stereotype.Service; import java.util.List; +import java.util.stream.Collectors; @Service @Transactional @@ -28,11 +30,14 @@ public class ContestService { private final GetCurrentMember getCurrentMember; // 대회 전체 조회 - public ResponseData> getContests() { + public ResponseData> getContests(PageRequest pageRequest) { List contestList = contestRepository.findAll(); - List contestLoadRes = contestList.stream().map( - ContestLoadRes :: of - ).toList(); + + List contestLoadRes = contestList.stream() + .map(ContestLoadRes::of) + .skip((pageRequest.page() -1) * pageRequest.size()) + .limit(pageRequest.size()) + .collect(Collectors.toList()); return ResponseData.of(HttpStatus.OK, "대회 전체 조회 완료", contestLoadRes); }