diff --git a/backend/src/main/java/aiin/backend/common/entity/Like.java b/backend/src/main/java/aiin/backend/common/entity/Like.java new file mode 100644 index 0000000..e4221b0 --- /dev/null +++ b/backend/src/main/java/aiin/backend/common/entity/Like.java @@ -0,0 +1,30 @@ +package aiin.backend.common.entity; + +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import aiin.backend.member.entity.Member; +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@MappedSuperclass +@NoArgsConstructor +@EntityListeners(AuditingEntityListener.class) +public abstract class Like extends BaseEntity { + + @Id @GeneratedValue + @Column(nullable = false) + private Long like_id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + private Member member; +} diff --git a/backend/src/main/java/aiin/backend/common/exception/ErrorCode.java b/backend/src/main/java/aiin/backend/common/exception/ErrorCode.java index 85becb5..a006187 100644 --- a/backend/src/main/java/aiin/backend/common/exception/ErrorCode.java +++ b/backend/src/main/java/aiin/backend/common/exception/ErrorCode.java @@ -10,7 +10,8 @@ public enum ErrorCode { //400 BAD_REQUEST(HttpStatus.BAD_REQUEST, "잘못된 요청입니다.", "COMMON-001"), - INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "요청 파라미터가 잘 못 되었습니다.", "COMMON-002"), + INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "요청 파라미터가 잘못 되었습니다.", "COMMON-002"), + INVALID_PASSWORD(HttpStatus.BAD_REQUEST, "비밀 번호가 잘못 되었습니다.", "COMMON-002"), //401 UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "인증이 필요합니다.", "COMMON-003"), diff --git a/backend/src/main/java/aiin/backend/member/controller/MemberController.java b/backend/src/main/java/aiin/backend/member/controller/MemberController.java index 85394f5..f9c8b6b 100644 --- a/backend/src/main/java/aiin/backend/member/controller/MemberController.java +++ b/backend/src/main/java/aiin/backend/member/controller/MemberController.java @@ -4,6 +4,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -30,11 +31,10 @@ public class MemberController { @PostMapping("/logout") public ResponseEntity> logoutMember(HttpServletRequest request) { - Member member = memberLoader.getMember(); String accessToken = jwtTokenProvider.extractAccessToken(request).orElse(null); String refreshToken = jwtTokenProvider.extractRefreshToken(request).orElse(null); log.info("logout member access token: {} refresh token: {}", accessToken, refreshToken); - memberService.logoutMember(member, accessToken, refreshToken); + memberService.logoutMember(accessToken, refreshToken); return ResponseEntity .status(HttpStatus.CREATED) @@ -51,8 +51,10 @@ public ResponseEntity> signupMember(@RequestBody SignUpReques } @PostMapping("/delete") - public ResponseEntity> deleteMember(@RequestBody DeleteRequest deleteRequest) { - memberService.deleteMember(deleteRequest); + public ResponseEntity> deleteMember( + @RequestHeader("Authorization") String accessToken, + @RequestBody DeleteRequest deleteRequest) { + memberService.deleteMember(accessToken, deleteRequest); return ResponseEntity .status(HttpStatus.NO_CONTENT) diff --git a/backend/src/main/java/aiin/backend/member/dto/DeleteRequest.java b/backend/src/main/java/aiin/backend/member/dto/DeleteRequest.java index 0c023a1..9e6aa63 100644 --- a/backend/src/main/java/aiin/backend/member/dto/DeleteRequest.java +++ b/backend/src/main/java/aiin/backend/member/dto/DeleteRequest.java @@ -1,7 +1,7 @@ package aiin.backend.member.dto; public record DeleteRequest( - String email + String password ) { } diff --git a/backend/src/main/java/aiin/backend/member/entity/Member.java b/backend/src/main/java/aiin/backend/member/entity/Member.java index 6592dd7..a10b4ab 100644 --- a/backend/src/main/java/aiin/backend/member/entity/Member.java +++ b/backend/src/main/java/aiin/backend/member/entity/Member.java @@ -3,7 +3,7 @@ import static lombok.AccessLevel.*; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; import aiin.backend.dispute.entity.Dispute; @@ -11,6 +11,8 @@ import aiin.backend.member.model.MemberRole; import aiin.backend.member.model.Provider; import aiin.backend.common.entity.BaseEntity; +import aiin.backend.openJury.entity.comment.Comment; +import aiin.backend.openJury.entity.post.Post; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -64,7 +66,6 @@ public class Member extends BaseEntity { @JoinColumn(name = "profile_image_id", referencedColumnName = "profile_id") private ProfileImage profileImage; - @Column(nullable = false, name = "phone_number") private String phoneNumber; @@ -74,6 +75,12 @@ public class Member extends BaseEntity { @OneToMany(mappedBy = "member") private List disputes; + @OneToMany(mappedBy = "author") + private List posts = new ArrayList<>(); + + @OneToMany(mappedBy = "author") + private List comments = new ArrayList<>(); + @Builder private Member(String username, String email, String password, MemberRole memberRole, Gender gender, Provider provider, String phoneNumber, LocalDate birthDate) { this.username = username; diff --git a/backend/src/main/java/aiin/backend/member/service/MemberService.java b/backend/src/main/java/aiin/backend/member/service/MemberService.java index 732da20..87a4860 100644 --- a/backend/src/main/java/aiin/backend/member/service/MemberService.java +++ b/backend/src/main/java/aiin/backend/member/service/MemberService.java @@ -1,5 +1,6 @@ package aiin.backend.member.service; +import aiin.backend.auth.security.jwtFilter.JwtTokenProvider; import aiin.backend.common.exception.ApiException; import aiin.backend.common.exception.ErrorCode; import aiin.backend.member.dto.DeleteRequest; @@ -11,6 +12,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.ErrorResponseException; @@ -26,6 +28,8 @@ public class MemberService { private final MemberRepository memberRepository; private final RefreshTokenService refreshTokenService; private final MemberMapper memberMapper; + private final JwtTokenProvider jwtTokenProvider; + private final PasswordEncoder passwordEncoder; // 이메일로 멤버 조회 public Optional findByEmail(String email) { @@ -46,7 +50,7 @@ public void signUp(SignUpRequest signUpRequest) { //로그아웃 @Transactional - public void logoutMember(Member member, String accessToken, String refreshToken) { + public void logoutMember(String accessToken, String refreshToken) { // 회원의 refreshToken 만료 처리 RefreshToken expiredToken = new RefreshToken(accessToken, refreshToken); refreshTokenService.save(expiredToken); @@ -54,10 +58,18 @@ public void logoutMember(Member member, String accessToken, String refreshToken) } @Transactional - public void deleteMember(DeleteRequest deleteRequest) { + public void deleteMember(String accessToken, DeleteRequest deleteRequest) { + Long memberId = jwtTokenProvider + .extractMemberId(accessToken) + .orElseThrow(() -> ApiException.from(ErrorCode.INVALID_ACCESS_TOKEN)); + Member member = memberRepository - .findByEmail(deleteRequest.email()) - .orElseThrow(()-> ApiException.from(ErrorCode.MEMBER_NOT_FOUND)); + .findById(memberId) + .orElseThrow(() -> ApiException.from(ErrorCode.MEMBER_NOT_FOUND)); + + if(!isValid(deleteRequest.password(), member.getPassword())){ + throw ApiException.from(ErrorCode.INVALID_PASSWORD); + } memberRepository.delete(member); } @@ -65,4 +77,8 @@ public void deleteMember(DeleteRequest deleteRequest) { protected boolean isDuplicateEmail(String email) { return memberRepository.findByEmail(email).isPresent(); } + + protected boolean isValid(String password, String encodedPassword) { + return passwordEncoder.matches(password, encodedPassword); + } } diff --git a/backend/src/main/java/aiin/backend/openJury/entity/comment/ChildComment.java b/backend/src/main/java/aiin/backend/openJury/entity/comment/ChildComment.java new file mode 100644 index 0000000..d19a8ee --- /dev/null +++ b/backend/src/main/java/aiin/backend/openJury/entity/comment/ChildComment.java @@ -0,0 +1,19 @@ +package aiin.backend.openJury.entity.comment; + +import static lombok.AccessLevel.*; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table(name = "child_comments") +@NoArgsConstructor(access = PROTECTED) +public class ChildComment extends Comment { + + @Column(nullable = false) + private Long parentId; +} diff --git a/backend/src/main/java/aiin/backend/openJury/entity/comment/Comment.java b/backend/src/main/java/aiin/backend/openJury/entity/comment/Comment.java new file mode 100644 index 0000000..0dfbf8e --- /dev/null +++ b/backend/src/main/java/aiin/backend/openJury/entity/comment/Comment.java @@ -0,0 +1,55 @@ +package aiin.backend.openJury.entity.comment; + +import static lombok.AccessLevel.*; + +import java.util.List; + +import aiin.backend.common.entity.BaseEntity; +import aiin.backend.member.entity.Member; +import aiin.backend.openJury.entity.post.Post; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table(name = "comments") +@NoArgsConstructor(access = PROTECTED) +public class Comment extends BaseEntity { + + @Id @GeneratedValue + @Column(name = "comment_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "author_id") + private Member author; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id") + private Post post; + + @OneToMany(mappedBy = "parent") + private List children; + + @Column(nullable = false) + private String content; + + void setAuthor(Member author) { + this.author = author; + author.getComments().add(this); + } + + void setPost(Post post) { + this.post = post; + post.getComments().add(this); + } +} diff --git a/backend/src/main/java/aiin/backend/openJury/entity/post/Post.java b/backend/src/main/java/aiin/backend/openJury/entity/post/Post.java new file mode 100644 index 0000000..02eea4b --- /dev/null +++ b/backend/src/main/java/aiin/backend/openJury/entity/post/Post.java @@ -0,0 +1,59 @@ +package aiin.backend.openJury.entity.post; + +import static lombok.AccessLevel.*; + +import java.util.List; + +import aiin.backend.common.entity.BaseEntity; +import aiin.backend.dispute.entity.Dispute; +import aiin.backend.member.entity.Member; +import aiin.backend.openJury.entity.comment.Comment; +import aiin.backend.openJury.model.Category; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table(name = "posts") +@NoArgsConstructor(access = PROTECTED) +public class Post extends BaseEntity { + + @Id @GeneratedValue + @Column(name = "post_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member author; + + @OneToMany(mappedBy = "post") + private List comments; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "dispute_id") + private Dispute dispute; + + @Column(nullable = false) + private String title; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Category category; + + public void setAuthor(Member author) { + this.author = author; + author.getPosts().add(this); + } +} diff --git a/backend/src/main/java/aiin/backend/openJury/entity/post/PostLike.java b/backend/src/main/java/aiin/backend/openJury/entity/post/PostLike.java new file mode 100644 index 0000000..c49865c --- /dev/null +++ b/backend/src/main/java/aiin/backend/openJury/entity/post/PostLike.java @@ -0,0 +1,29 @@ +package aiin.backend.openJury.entity.post; + +import static lombok.AccessLevel.*; + +import aiin.backend.common.entity.Like; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table( + name = "post_likes", + uniqueConstraints = { + @UniqueConstraint(columnNames = { "post_id", "member_id" }) + }) +@NoArgsConstructor(access = PROTECTED) +public class PostLike extends Like { + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id") + private Post post; +} diff --git a/backend/src/main/java/aiin/backend/openJury/model/Category.java b/backend/src/main/java/aiin/backend/openJury/model/Category.java new file mode 100644 index 0000000..7690abc --- /dev/null +++ b/backend/src/main/java/aiin/backend/openJury/model/Category.java @@ -0,0 +1,23 @@ +package aiin.backend.openJury.model; + +public enum Category { + + COMMON("일반"), + MBTI("MBTI"), + MARRIAGE("결혼"), + LOVE("연애"), + FOOD("음식"), + DAILY("일상"), + PARENTING("육아"), + SPORTS("스포츠"), + HOBBY("취미"), + INVEST("투자"), + MONEY("돈"), + ART("예술"); + + private final String value; + + Category(String value) { + this.value = value; + } +}