diff --git a/src/main/java/com/example/ReMap/domain/auth/entity/RefreshToken.java b/src/main/java/com/example/ReMap/domain/auth/entity/RefreshToken.java index e4ac48f..9517ca0 100644 --- a/src/main/java/com/example/ReMap/domain/auth/entity/RefreshToken.java +++ b/src/main/java/com/example/ReMap/domain/auth/entity/RefreshToken.java @@ -1,77 +1,45 @@ package com.example.ReMap.domain.auth.entity; - import com.example.ReMap.domain.member.Member; import jakarta.persistence.*; +import lombok.*; import java.time.LocalDateTime; - @Entity @Table(name = "refresh_token") +@Getter @Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder public class RefreshToken { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id", nullable = false) + // Member 삭제 시 토큰도 함께 삭제 + @OneToOne(fetch = FetchType.LAZY, optional = false, cascade = CascadeType.REMOVE) + @JoinColumn(name = "member_id", nullable = false, unique = true) private Member member; - @Column(name = "refresh_token", nullable = false) + @Column(name = "refresh_token", nullable = false, length = 512) private String refreshToken; - @Column(name = "created_at") + @Column(name = "created_at", nullable = false) private LocalDateTime createdAt; - @Column(name = "updated_at") + @Column(name = "updated_at", nullable = false) private LocalDateTime updatedAt; - // 기본 생성자 - public RefreshToken() {} - - // 생성자 - public RefreshToken(Member member, String refreshToken, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.member = member; - this.refreshToken = refreshToken; - this.createdAt = createdAt; - this.updatedAt = updatedAt; - } - - // Getter / Setter - public Long getId() { - return id; - } - - public Member getMember() { - return member; - } - - public void setMember(Member member) { - this.member = member; - } - - public String getRefreshToken() { - return refreshToken; - } - - public void setRefreshToken(String refreshToken) { - this.refreshToken = refreshToken; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public LocalDateTime getUpdatedAt() { - return updatedAt; + @PrePersist + public void prePersist() { + LocalDateTime now = LocalDateTime.now(); + this.createdAt = now; + this.updatedAt = now; } - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; + @PreUpdate + public void preUpdate() { + this.updatedAt = LocalDateTime.now(); } } diff --git a/src/main/java/com/example/ReMap/domain/cast/Cast.java b/src/main/java/com/example/ReMap/domain/cast/Cast.java index 9d26661..cbc9608 100644 --- a/src/main/java/com/example/ReMap/domain/cast/Cast.java +++ b/src/main/java/com/example/ReMap/domain/cast/Cast.java @@ -1,6 +1,7 @@ package com.example.ReMap.domain.cast; import com.example.ReMap.domain.member.Member; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import java.time.LocalDateTime; @@ -20,6 +21,7 @@ public class Cast { @ManyToOne @JoinColumn(name = "member_id") + @JsonIgnore private Member member; // 생성자, getter, setter diff --git a/src/main/java/com/example/ReMap/domain/cast/CastRepository.java b/src/main/java/com/example/ReMap/domain/cast/CastRepository.java new file mode 100644 index 0000000..fdd667d --- /dev/null +++ b/src/main/java/com/example/ReMap/domain/cast/CastRepository.java @@ -0,0 +1,12 @@ +package com.example.ReMap.domain.cast; + + +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * Cast(게시글) 전용 JPA 레포지토리 + * → JpaRepository 를 상속하면 findById, save, delete 등 기본 메서드가 자동 생성됩니다. + */ +public interface CastRepository extends JpaRepository { + // 필요하면 여기에 커스텀 쿼리 메서드 추가 +} diff --git a/src/main/java/com/example/ReMap/domain/comment/Comment.java b/src/main/java/com/example/ReMap/domain/comment/Comment.java index fbe0ae1..d5419ba 100644 --- a/src/main/java/com/example/ReMap/domain/comment/Comment.java +++ b/src/main/java/com/example/ReMap/domain/comment/Comment.java @@ -2,27 +2,50 @@ import com.example.ReMap.domain.cast.Cast; import com.example.ReMap.domain.member.Member; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.CreationTimestamp; import java.time.LocalDateTime; @Entity @Table(name = "comment") +@Getter +@Setter // 전체 Setter 허용 +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder public class Comment { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + /** VARCHAR(255) 에 맞춤 */ + @Column(length = 255, nullable = false) private String content; + /** 생성 시각 – DB 기본값 대신 애플리케이션에서 채움 */ + @CreationTimestamp + @Column(name = "created_at") private LocalDateTime createdAt; - @ManyToOne + /* ---------- 연관 관계 ---------- */ + + /** 작성자 – DDL이 NULL 허용 → optional = true */ + @ManyToOne(fetch = FetchType.LAZY, optional = true) @JoinColumn(name = "member_id") + @JsonIgnore private Member member; - @ManyToOne + /** 대상 캐스트 – DDL이 NULL 허용 → optional = true */ + @ManyToOne(fetch = FetchType.LAZY, optional = true) @JoinColumn(name = "cast_id") private Cast cast; -} + /* ---------- 도메인 메서드 ---------- */ + + public void changeContent(String newContent) { + this.content = newContent; + } +} diff --git a/src/main/java/com/example/ReMap/domain/comment/CommentController.java b/src/main/java/com/example/ReMap/domain/comment/CommentController.java new file mode 100644 index 0000000..869babc --- /dev/null +++ b/src/main/java/com/example/ReMap/domain/comment/CommentController.java @@ -0,0 +1,64 @@ +package com.example.ReMap.domain.comment; + +import com.example.ReMap.domain.comment.CommentService; +import com.example.ReMap.domain.comment.dto.CommentResponse; +import com.example.ReMap.domain.comment.dto.CreateCommentRequest; +import com.example.ReMap.domain.comment.dto.UpdateCommentRequest; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.security.Principal; +import java.util.List; + +@RestController +@RequestMapping("/casts/{castId}/comments") +@RequiredArgsConstructor +public class CommentController { + + private final CommentService commentService; + + /* ------------------------------------ 1. 댓글 등록 ------------------------------------ */ + @PostMapping + public ResponseEntity create( + @PathVariable Long castId, + @RequestBody @Valid CreateCommentRequest req, + Principal principal // 로그인 사용자 정보(아이디 문자열) + ) { + Long memberId = Long.valueOf(principal.getName()); // "1" → 1L 등으로 변환 + CommentResponse resp = commentService.create(castId, memberId, req.getContent()); + return ResponseEntity.status(HttpStatus.CREATED).body(resp); + } + + /* ------------------------------------ 2. 댓글 목록 ------------------------------------ */ + @GetMapping + public List list(@PathVariable Long castId) { + return commentService.list(castId); + } + + /* ------------------------------------ 3. 댓글 수정 ------------------------------------ */ + @PutMapping("/{commentId}") + public CommentResponse update( + @PathVariable Long castId, // 경로 일관성 확보용 + @PathVariable Long commentId, + @RequestBody @Valid UpdateCommentRequest req, + Principal principal + ) { + Long memberId = Long.valueOf(principal.getName()); + return commentService.update(commentId, memberId, req.getContent()); + } + + /* ------------------------------------ 4. 댓글 삭제 ------------------------------------ */ + @DeleteMapping("/{commentId}") + public ResponseEntity delete( + @PathVariable Long castId, + @PathVariable Long commentId, + Principal principal + ) { + Long memberId = Long.valueOf(principal.getName()); + commentService.delete(commentId, memberId); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/com/example/ReMap/domain/comment/CommentRepository.java b/src/main/java/com/example/ReMap/domain/comment/CommentRepository.java new file mode 100644 index 0000000..030a246 --- /dev/null +++ b/src/main/java/com/example/ReMap/domain/comment/CommentRepository.java @@ -0,0 +1,21 @@ +package com.example.ReMap.domain.comment; + + +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +/** + * 댓글 레포지토리 + * + * • findByCastIdOrderByCreatedAtAsc() : 특정 Cast(게시글)의 댓글을 + * 작성 시간순으로 가져옵니다. + * • @EntityGraph("member") : 댓글과 작성자를 한 번에 로딩해서 + * N+1 쿼리 문제를 예방합니다. + */ +public interface CommentRepository extends JpaRepository { + + @EntityGraph(attributePaths = "member") // 댓글 조회 + Member 한 번에 가져오기 + List findByCastIdOrderByCreatedAtAsc(Long castId); +} diff --git a/src/main/java/com/example/ReMap/domain/comment/CommentService.java b/src/main/java/com/example/ReMap/domain/comment/CommentService.java new file mode 100644 index 0000000..bfa65c2 --- /dev/null +++ b/src/main/java/com/example/ReMap/domain/comment/CommentService.java @@ -0,0 +1,91 @@ +package com.example.ReMap.domain.comment; + + +import com.example.ReMap.domain.cast.Cast; +import com.example.ReMap.domain.comment.Comment; +import com.example.ReMap.domain.comment.CommentRepository; +import com.example.ReMap.domain.member.Member; +import com.example.ReMap.domain.member.MemberRepository; +import com.example.ReMap.domain.comment.dto.CommentResponse; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor // 의존성 주입용 생성자 자동 +@Transactional // 메서드 기본 read-write +public class CommentService { + + private final CommentRepository commentRepo; + private final com.example.ReMap.domain.cast.CastRepository castRepo; + private final MemberRepository memberRepo; + + /* ---------- CREATE ---------- */ + + public CommentResponse create(Long castId, Long memberId, String content) { + + Cast cast = castRepo.findById(castId) + .orElseThrow(() -> new IllegalArgumentException("Cast not found : " + castId)); + + Member member = memberRepo.findById(memberId) + .orElseThrow(() -> new IllegalArgumentException("Member not found : " + memberId)); + + // 댓글 엔티티 생성 + Comment comment = Comment.builder() + .content(content) + .build(); + + // FK 세팅 (엔티티 @Setter 덕분에 가능) + comment.setCast(cast); + comment.setMember(member); + + // 저장 + commentRepo.save(comment); + + return CommentResponse.from(comment); + } + + /* ---------- READ (목록) ---------- */ + @Transactional(Transactional.TxType.SUPPORTS) // read-only 트랜잭션 + public List list(Long castId) { + return commentRepo.findByCastIdOrderByCreatedAtAsc(castId) + .stream() + .map(CommentResponse::from) + .toList(); + } + + /* ---------- UPDATE ---------- */ + + public CommentResponse update(Long commentId, Long requesterId, String newContent) { + + Comment comment = commentRepo.findById(commentId) + .orElseThrow(() -> new IllegalArgumentException("Comment not found : " + commentId)); + + // 작성자 권한 확인 + if (!requesterId.equals(comment.getMember() != null ? comment.getMember().getId() : null)) { + throw new AccessDeniedException("본인 댓글만 수정할 수 있습니다."); + } + + comment.changeContent(newContent); // 엔티티 내부에서 필드 변경 + + return CommentResponse.from(comment); + } + + /* ---------- DELETE ---------- */ + + public void delete(Long commentId, Long requesterId) { + + Comment comment = commentRepo.findById(commentId) + .orElseThrow(() -> new IllegalArgumentException("Comment not found : " + commentId)); + + if (!requesterId.equals(comment.getMember() != null ? comment.getMember().getId() : null)) { + throw new AccessDeniedException("본인 댓글만 삭제할 수 있습니다."); + } + + commentRepo.delete(comment); // 하드 삭제 + // → 소프트 삭제가 필요하면 Comment에 deleted 필드를 추가해 플래그만 변경 + } +} diff --git a/src/main/java/com/example/ReMap/domain/comment/dto/CommentResponse.java b/src/main/java/com/example/ReMap/domain/comment/dto/CommentResponse.java new file mode 100644 index 0000000..2ecd9e2 --- /dev/null +++ b/src/main/java/com/example/ReMap/domain/comment/dto/CommentResponse.java @@ -0,0 +1,31 @@ +package com.example.ReMap.domain.comment.dto; + + +import com.example.ReMap.domain.comment.Comment; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +/** 댓글 단건 응답 DTO */ +@Getter +@Builder +public class CommentResponse { + + private Long id; // 댓글 ID + private Long memberId; // 작성자 ID + private String nickname; // 작성자 닉네임 + private String content; // 댓글 본문 + private LocalDateTime createdAt; + + /** Comment 엔티티 → DTO 변환 */ + public static CommentResponse from(Comment c) { + return CommentResponse.builder() + .id(c.getId()) + .memberId(c.getMember() != null ? c.getMember().getId() : null) + .nickname(c.getMember() != null ? c.getMember().getNickname() : "탈퇴한 회원") + .content(c.getContent()) + .createdAt(c.getCreatedAt()) + .build(); + } +} diff --git a/src/main/java/com/example/ReMap/domain/comment/dto/CreateCommentRequest.java b/src/main/java/com/example/ReMap/domain/comment/dto/CreateCommentRequest.java new file mode 100644 index 0000000..70c9023 --- /dev/null +++ b/src/main/java/com/example/ReMap/domain/comment/dto/CreateCommentRequest.java @@ -0,0 +1,16 @@ +package com.example.ReMap.domain.comment.dto; + + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +/** 댓글 등록 요청 DTO */ +@Getter @Setter +public class CreateCommentRequest { + + /** 댓글 본문 (빈 문자열 금지) */ + @NotBlank(message = "댓글 내용을 입력해 주세요.") + private String content; +} + diff --git a/src/main/java/com/example/ReMap/domain/comment/dto/UpdateCommentRequest.java b/src/main/java/com/example/ReMap/domain/comment/dto/UpdateCommentRequest.java new file mode 100644 index 0000000..685cb4e --- /dev/null +++ b/src/main/java/com/example/ReMap/domain/comment/dto/UpdateCommentRequest.java @@ -0,0 +1,16 @@ +package com.example.ReMap.domain.comment.dto; + + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +/** 댓글 수정 요청 DTO */ +@Getter @Setter +public class UpdateCommentRequest { + + /** 수정할 댓글 본문 */ + @NotBlank(message = "댓글 내용을 입력해 주세요.") + private String content; +} + diff --git a/src/main/java/com/example/ReMap/domain/member/ChangeMemberTypeRequest.java b/src/main/java/com/example/ReMap/domain/member/ChangeMemberTypeRequest.java new file mode 100644 index 0000000..d694584 --- /dev/null +++ b/src/main/java/com/example/ReMap/domain/member/ChangeMemberTypeRequest.java @@ -0,0 +1,17 @@ +package com.example.ReMap.domain.member; + +import jakarta.validation.constraints.NotNull; + +public class ChangeMemberTypeRequest { + + @NotNull(message = "회원 유형을 선택해주세요.") + private MemberType memberType; + + public MemberType getMemberType() { + return memberType; + } + + public void setMemberType(MemberType memberType) { + this.memberType = memberType; + } +} diff --git a/src/main/java/com/example/ReMap/domain/member/ChangePasswordRequest.java b/src/main/java/com/example/ReMap/domain/member/ChangePasswordRequest.java new file mode 100644 index 0000000..fb54dd5 --- /dev/null +++ b/src/main/java/com/example/ReMap/domain/member/ChangePasswordRequest.java @@ -0,0 +1,15 @@ +package com.example.ReMap.domain.member; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +@Getter @Setter +public class ChangePasswordRequest { + + @NotBlank(message = "현재 비밀번호를 입력해주세요.") + private String currentPassword; + + @NotBlank(message = "새 비밀번호를 입력해주세요.") + private String newPassword; +} diff --git a/src/main/java/com/example/ReMap/domain/member/Member.java b/src/main/java/com/example/ReMap/domain/member/Member.java index 7e4f49e..8a88908 100644 --- a/src/main/java/com/example/ReMap/domain/member/Member.java +++ b/src/main/java/com/example/ReMap/domain/member/Member.java @@ -2,32 +2,25 @@ import com.example.ReMap.domain.cast.Cast; import com.example.ReMap.domain.comment.Comment; -import com.example.ReMap.domain.auth.entity.RefreshToken; -import com.example.ReMap.domain.member.MemberType; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import lombok.*; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @Entity @Getter -@Builder -@AllArgsConstructor +@Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder @Table(name = "member") public class Member { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false) - private String password; - - private Integer phone; - private Long birth; @Column(unique = true) @@ -36,24 +29,20 @@ public class Member { @Column(nullable = false, unique = true) private String nickname; - @Column(name = "inactive_date") - private LocalDateTime inactiveDate; - - @Column(name = "created_at") - private LocalDateTime createdAt; - - @Column(name = "updated_at") - private LocalDateTime updatedAt; - + @Builder.Default @Enumerated(EnumType.STRING) - @Column(name = "memberType") - private MemberType memberType = MemberType.GENERAL; + @Column(name = "member_type") + private MemberType memberType = MemberType.USER; - // 관계 매핑 + // 내가 올린 오디오 @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) + @JsonIgnore + @Builder.Default private List casts = new ArrayList<>(); + // 내가 쓴 댓글 @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) + @JsonIgnore + @Builder.Default private List comments = new ArrayList<>(); - - } +} diff --git a/src/main/java/com/example/ReMap/domain/member/MemberController.java b/src/main/java/com/example/ReMap/domain/member/MemberController.java new file mode 100644 index 0000000..6703c8a --- /dev/null +++ b/src/main/java/com/example/ReMap/domain/member/MemberController.java @@ -0,0 +1,74 @@ +package com.example.ReMap.domain.member; + +import com.example.ReMap.domain.cast.Cast; +import com.example.ReMap.domain.comment.Comment; +import com.example.ReMap.domain.auth.util.JwtUtil; // 실제 JwtUtil 위치에 맞춰 수정 +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/members") +@RequiredArgsConstructor +public class MemberController { + + private final MemberService memberService; + private final JwtUtil jwtUtil; + + /** 내 정보 조회 **/ + @GetMapping("/setting") + public ResponseEntity getMember( + @RequestHeader("Authorization") String authorization) { + Long memberId = jwtUtil.extractMemberId(authorization); + return ResponseEntity.ok(memberService.getMemberById(memberId)); + } + + /** 내 정보 수정 **/ + @PutMapping("/update") + public ResponseEntity updateMember( + @RequestHeader("Authorization") String authorization, + @Valid @RequestBody UpdateMemberRequest req) { + Long memberId = jwtUtil.extractMemberId(authorization); + return ResponseEntity.ok(memberService.updateMember(memberId, req)); + } + + /** 비밀번호 변경 **/ + @PutMapping("/password") + public ResponseEntity changePassword( + @RequestHeader("Authorization") String authorization, + @Valid @RequestBody ChangePasswordRequest req) { + Long memberId = jwtUtil.extractMemberId(authorization); + memberService.changePassword(memberId, req); + return ResponseEntity.ok().build(); + } + + /** 내가 쓴 댓글 목록 **/ + @GetMapping("/comments") + public ResponseEntity> getComments( + @RequestHeader("Authorization") String authorization) { + Long memberId = jwtUtil.extractMemberId(authorization); + return ResponseEntity.ok(memberService.getMemberComments(memberId)); + } + + /** 내가 올린 오디오 목록 **/ + @GetMapping("/casts") + public ResponseEntity> getCasts( + @RequestHeader("Authorization") String authorization) { + Long memberId = jwtUtil.extractMemberId(authorization); + return ResponseEntity.ok(memberService.getMemberCasts(memberId)); + } + + /** USER ↔ RECORDER 역할 전환 **/ + @PutMapping("/member-type") + public ResponseEntity changeRole( + @RequestHeader("Authorization") String authorization, + @Valid @RequestBody ChangeMemberTypeRequest req) { + Long memberId = jwtUtil.extractMemberId(authorization); + return ResponseEntity.ok( + memberService.changeOwnRole(memberId, req.getMemberType()) + ); + } +} diff --git a/src/main/java/com/example/ReMap/domain/member/MemberRepository.java b/src/main/java/com/example/ReMap/domain/member/MemberRepository.java new file mode 100644 index 0000000..39cec94 --- /dev/null +++ b/src/main/java/com/example/ReMap/domain/member/MemberRepository.java @@ -0,0 +1,10 @@ +package com.example.ReMap.domain.member; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface MemberRepository extends JpaRepository { + boolean existsByNickname(String nickname); +} + diff --git a/src/main/java/com/example/ReMap/domain/member/MemberService.java b/src/main/java/com/example/ReMap/domain/member/MemberService.java new file mode 100644 index 0000000..a90f14d --- /dev/null +++ b/src/main/java/com/example/ReMap/domain/member/MemberService.java @@ -0,0 +1,78 @@ +package com.example.ReMap.domain.member; + +import com.example.ReMap.domain.cast.Cast; +import com.example.ReMap.domain.comment.Comment; +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional +public class MemberService { + + private final MemberRepository memberRepository; + + /* -------------------------- Read (조회) ----------------------------- */ + + @Transactional(readOnly = true) + public Member getMemberById(Long id) { + return memberRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("회원을 찾을 수 없습니다. ID: " + id)); + } + + @Transactional(readOnly = true) + public List getMemberComments(Long memberId) { + return getMemberById(memberId).getComments(); + } + + @Transactional(readOnly = true) + public List getMemberCasts(Long memberId) { + Member member = getMemberById(memberId); + // USER도 자신의 캐스트(업로드)를 볼 수 있게 하려면 아래 검사를 제거하세요. + if (member.getMemberType() != MemberType.RECORDER && member.getMemberType() != MemberType.ADMIN) { + throw new IllegalArgumentException("기록자만 오디오 목록을 조회할 수 있습니다."); + } + return member.getCasts(); + } + + /* -------------------------- Update (수정) --------------------------- */ + + public Member updateMember(Long id, UpdateMemberRequest dto) { + Member member = getMemberById(id); + + // 닉네임 변경 시 중복 검사 + if (dto.getNickname() != null && !dto.getNickname().equals(member.getNickname()) + && memberRepository.existsByNickname(dto.getNickname())) { + throw new IllegalArgumentException("이미 사용 중인 닉네임입니다."); + } + member.setNickname(dto.getNickname()); + + // 생년월일 업데이트 + member.setBirth(dto.getBirth()); + + return member; + } + + /* ----------------------- Role Self-Change -------------------------- */ + + /** + * 본인이 USER ↔ RECORDER 로만 스스로 타입 변경 + */ + public Member changeOwnRole(Long id, MemberType newType) { + Member member = getMemberById(id); + + if (newType != MemberType.USER && newType != MemberType.RECORDER) { + throw new IllegalArgumentException("변경 가능한 역할은 USER 또는 RECORDER 뿐입니다."); + } + + member.setMemberType(newType); + return member; + } + + + +} diff --git a/src/main/java/com/example/ReMap/domain/member/MemberType.java b/src/main/java/com/example/ReMap/domain/member/MemberType.java index 825c576..a9c7812 100644 --- a/src/main/java/com/example/ReMap/domain/member/MemberType.java +++ b/src/main/java/com/example/ReMap/domain/member/MemberType.java @@ -1,6 +1,17 @@ package com.example.ReMap.domain.member; public enum MemberType { - GENERAL, // 일반 유저 - RECORDER // 기록자 -} + USER("일반유저"), // 오디오 감상, 감상후기 작성 + RECORDER("기록자"), // 음성파일 업로드 + ADMIN("관리자"); // 전체 관리 + + private final String description; + + MemberType(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/ReMap/domain/member/UpdateMemberRequest.java b/src/main/java/com/example/ReMap/domain/member/UpdateMemberRequest.java new file mode 100644 index 0000000..5d17d2c --- /dev/null +++ b/src/main/java/com/example/ReMap/domain/member/UpdateMemberRequest.java @@ -0,0 +1,18 @@ +package com.example.ReMap.domain.member; + +import jakarta.validation.constraints.*; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class UpdateMemberRequest { + + + @NotNull(message = "생년월일을 입력해 주세요.") + private Long birth; + + @NotBlank(message = "닉네임을 입력해 주세요.") + @Size(max = 50, message = "닉네임은 최대 50자까지 가능합니다.") + private String nickname; +} diff --git a/src/main/java/com/example/ReMap/domain/tema/Tema.java b/src/main/java/com/example/ReMap/domain/tema/Tema.java index e877d51..e8a505f 100644 --- a/src/main/java/com/example/ReMap/domain/tema/Tema.java +++ b/src/main/java/com/example/ReMap/domain/tema/Tema.java @@ -1,14 +1,29 @@ package com.example.ReMap.domain.tema; +import com.example.ReMap.domain.cast.Cast; import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; @Entity @Table(name = "tema") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Tema { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(nullable = false, length = 255) private String name; -} + @ManyToMany(mappedBy = "temas") + private List casts = new ArrayList<>(); + + @Builder // name 하나만 받도록 + public Tema(String name) { + this.name = name; + } +} diff --git a/src/main/java/com/example/ReMap/domain/tema/TemaController.java b/src/main/java/com/example/ReMap/domain/tema/TemaController.java new file mode 100644 index 0000000..1cde3f7 --- /dev/null +++ b/src/main/java/com/example/ReMap/domain/tema/TemaController.java @@ -0,0 +1,4 @@ +package com.example.ReMap.domain.tema; + +public class TemaController { +} diff --git a/src/main/java/com/example/ReMap/domain/tema/TemaRepository.java b/src/main/java/com/example/ReMap/domain/tema/TemaRepository.java new file mode 100644 index 0000000..5f89b6f --- /dev/null +++ b/src/main/java/com/example/ReMap/domain/tema/TemaRepository.java @@ -0,0 +1,17 @@ +package com.example.ReMap.domain.tema; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +/** + * Tema(테마) 전용 레포지토리 + * - findByName : 이름으로 단건 조회 + * - existsByName : 중복 이름 체크 + */ +public interface TemaRepository extends JpaRepository { + + Optional findByName(String name); + + boolean existsByName(String name); +} diff --git a/src/main/java/com/example/ReMap/domain/tema/dto/TemaCreateRequest.java b/src/main/java/com/example/ReMap/domain/tema/dto/TemaCreateRequest.java new file mode 100644 index 0000000..7e33bcb --- /dev/null +++ b/src/main/java/com/example/ReMap/domain/tema/dto/TemaCreateRequest.java @@ -0,0 +1,13 @@ +package com.example.ReMap.domain.tema.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +/** 테마 생성 요청 */ +@Getter @Setter +public class TemaCreateRequest { + + @NotBlank(message = "테마 이름을 입력해 주세요.") + private String name; +} diff --git a/src/main/java/com/example/ReMap/domain/tema/dto/TemaResponse.java b/src/main/java/com/example/ReMap/domain/tema/dto/TemaResponse.java new file mode 100644 index 0000000..35340d5 --- /dev/null +++ b/src/main/java/com/example/ReMap/domain/tema/dto/TemaResponse.java @@ -0,0 +1,21 @@ +package com.example.ReMap.domain.tema.dto; + +import com.example.ReMap.domain.tema.Tema; +import lombok.Builder; +import lombok.Getter; + +/** 테마 단건 응답 DTO */ +@Getter +@Builder +public class TemaResponse { + + private Long id; + private String name; + + public static TemaResponse from(Tema t) { + return TemaResponse.builder() + .id(t.getId()) + .name(t.getName()) + .build(); + } +} diff --git a/src/main/java/com/example/ReMap/domain/tema/dto/TemaUpdateRequest.java b/src/main/java/com/example/ReMap/domain/tema/dto/TemaUpdateRequest.java new file mode 100644 index 0000000..fdc8c4a --- /dev/null +++ b/src/main/java/com/example/ReMap/domain/tema/dto/TemaUpdateRequest.java @@ -0,0 +1,13 @@ +package com.example.ReMap.domain.tema.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +/** 테마 이름 수정 요청 */ +@Getter @Setter +public class TemaUpdateRequest { + + @NotBlank(message = "새 테마 이름을 입력해 주세요.") + private String name; +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 5bcf2fa..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,22 +0,0 @@ -spring.application.name=ReMap -# ?? ?? -server.port=8080 - -# MySQL ?? ?? -spring.datasource.url=jdbc:mysql://localhost:3306/hackathon?serverTimezone=Asia/Seoul -spring.datasource.username=root -spring.datasource.password=1234 -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver - -# JPA ?? -spring.jpa.hibernate.ddl-auto=none -spring.jpa.show-sql=true -spring.jpa.properties.hibernate.format_sql=true -spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect - -# H2 ?? (???) -spring.h2.console.enabled=true - -# ?? ?? (??) -logging.level.org.hibernate.SQL=debug -logging.level.org.hibernate.type=trace diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..6970400 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,27 @@ +spring: + datasource: + url: jdbc:mysql://localhost:3306/hackathon?createDatabaseIfNotExist=TRUE&serverTimezone=Asia/Seoul&characterEncoding=UTF-8 + username: root + password: 1234 + driver-class-name: com.mysql.cj.jdbc.Driver + jpa: + hibernate: + ddl-auto: update + properties: + hibernate: + format_sql: true + show_sql: true + +springdoc: + swagger-ui: + path: /swagger-ui.html + operationsSorter: method + disable-swagger-default-url: true + display-request-duration: true + api-docs: + path: /api-docs + show-actuator: true + default-consumes-media-type: application/json + default-produces-media-type: application/json + paths-to-match: + - /api/** \ No newline at end of file