diff --git a/src/main/java/bst/bobsoolting/BobsooltingApplication.java b/src/main/java/bst/bobsoolting/BobsooltingApplication.java index d157549..bf9e50a 100644 --- a/src/main/java/bst/bobsoolting/BobsooltingApplication.java +++ b/src/main/java/bst/bobsoolting/BobsooltingApplication.java @@ -6,6 +6,7 @@ @SpringBootApplication(exclude = { org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration.class }) + public class BobsooltingApplication { public static void main(String[] args) { diff --git a/src/main/java/bst/bobsoolting/post/command/application/controller/PostCommandController.java b/src/main/java/bst/bobsoolting/post/command/application/controller/PostCommandController.java new file mode 100644 index 0000000..c0f0af0 --- /dev/null +++ b/src/main/java/bst/bobsoolting/post/command/application/controller/PostCommandController.java @@ -0,0 +1,47 @@ +package bst.bobsoolting.post.command.application.controller; + +import bst.bobsoolting.post.command.application.dto.PostDTO; +import bst.bobsoolting.post.command.application.service.PostCommandService; +import bst.bobsoolting.post.command.vo.request.RequestUpdatePostVO; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/post/command") +@RequiredArgsConstructor +public class PostCommandController { + + private final PostCommandService postCommandService; + + /** + * 게시글 생성 + */ + @PostMapping("/") + public ResponseEntity createPost(@RequestBody PostDTO postDTO) { + PostDTO created = postCommandService.createPost(postDTO); + return ResponseEntity.ok(created); + } + + /** + * 게시글 수정 + */ + @PutMapping("/{postId}") + public ResponseEntity updatePost(@PathVariable Long postId, + @RequestBody RequestUpdatePostVO postDTO) { + postDTO.setPostId(postId); + PostDTO updated = postCommandService.updatePost(postDTO); + return ResponseEntity.ok(updated); + } + + /** + * 게시글 삭제 (소프트 딜리트) + */ + @PatchMapping("/{postId}") + public ResponseEntity softDeletePost(@PathVariable("postId") Long postId) { + postCommandService.deletePost(postId); + return ResponseEntity.noContent().build(); + } + +} + diff --git a/src/main/java/bst/bobsoolting/post/command/application/dto/PostDTO.java b/src/main/java/bst/bobsoolting/post/command/application/dto/PostDTO.java new file mode 100644 index 0000000..6d399ac --- /dev/null +++ b/src/main/java/bst/bobsoolting/post/command/application/dto/PostDTO.java @@ -0,0 +1,28 @@ +package bst.bobsoolting.post.command.application.dto; + +import lombok.*; +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +@ToString +public class PostDTO { + private Long postId; + private String category; // Enum을 문자열로 전달 (Converter에서 변환) + private String title; + private String content; + private String images; + private Integer maxParticipants; + private String participants; + private String recruitmentStatus; + private LocalDate date; + private String location; + private Boolean postStatus; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + private String memberId; +} diff --git a/src/main/java/bst/bobsoolting/post/command/application/mapper/PostConverter.java b/src/main/java/bst/bobsoolting/post/command/application/mapper/PostConverter.java new file mode 100644 index 0000000..33aff76 --- /dev/null +++ b/src/main/java/bst/bobsoolting/post/command/application/mapper/PostConverter.java @@ -0,0 +1,52 @@ +package bst.bobsoolting.post.command.application.mapper; + +import bst.bobsoolting.post.command.application.dto.PostDTO; +import bst.bobsoolting.post.command.domain.aggregate.Category; +import bst.bobsoolting.post.command.domain.aggregate.Post; +import org.springframework.stereotype.Component; + +@Component +public class PostConverter { + + // DTO -> Entity 변환 + public Post toEntity(PostDTO dto) { + if (dto == null) return null; + return Post.builder() + .postId(dto.getPostId()) + .category(dto.getCategory() != null ? Category.valueOf(dto.getCategory()) : null) + .title(dto.getTitle()) + .content(dto.getContent()) + .images(dto.getImages()) + .maxParticipants(dto.getMaxParticipants()) + .participants(dto.getParticipants()) + .recruitmentStatus(dto.getRecruitmentStatus()) + .date(dto.getDate()) + .location(dto.getLocation()) + .postStatus(dto.getPostStatus()) + .createdAt(dto.getCreatedAt()) + .updatedAt(dto.getUpdatedAt()) + .memberId(dto.getMemberId()) + .build(); + } + + // Entity -> DTO 변환 + public PostDTO toDTO(Post post) { + if (post == null) return null; + return PostDTO.builder() + .postId(post.getPostId()) + .category(post.getCategory() != null ? post.getCategory().name() : null) + .title(post.getTitle()) + .content(post.getContent()) + .images(post.getImages()) + .maxParticipants(post.getMaxParticipants()) + .participants(post.getParticipants()) + .recruitmentStatus(post.getRecruitmentStatus()) + .date(post.getDate()) + .location(post.getLocation()) + .postStatus(post.getPostStatus()) + .createdAt(post.getCreatedAt()) + .updatedAt(post.getUpdatedAt()) + .memberId(post.getMemberId()) + .build(); + } +} diff --git a/src/main/java/bst/bobsoolting/post/command/application/service/PostCommandService.java b/src/main/java/bst/bobsoolting/post/command/application/service/PostCommandService.java new file mode 100644 index 0000000..c9775b2 --- /dev/null +++ b/src/main/java/bst/bobsoolting/post/command/application/service/PostCommandService.java @@ -0,0 +1,13 @@ +package bst.bobsoolting.post.command.application.service; + +import bst.bobsoolting.post.command.application.dto.PostDTO; +import bst.bobsoolting.post.command.vo.request.RequestUpdatePostVO; + +public interface PostCommandService { + + PostDTO createPost(PostDTO postDTO); + + PostDTO updatePost(RequestUpdatePostVO updateVO); + + void deletePost(Long postId); +} \ No newline at end of file diff --git a/src/main/java/bst/bobsoolting/post/command/application/service/PostCommandServiceImpl.java b/src/main/java/bst/bobsoolting/post/command/application/service/PostCommandServiceImpl.java new file mode 100644 index 0000000..0ace00e --- /dev/null +++ b/src/main/java/bst/bobsoolting/post/command/application/service/PostCommandServiceImpl.java @@ -0,0 +1,99 @@ +package bst.bobsoolting.post.command.application.service; + +import lombok.extern.java.Log; +import lombok.extern.slf4j.Slf4j; +import bst.bobsoolting.member.command.application.service.MemberCommandServiceImpl; +import bst.bobsoolting.member.command.domain.aggregate.entity.Member; +import bst.bobsoolting.member.query.repository.MemberMapper; +import bst.bobsoolting.post.command.application.dto.PostDTO; +import bst.bobsoolting.post.command.application.mapper.PostConverter; +import bst.bobsoolting.post.command.domain.aggregate.Post; +import bst.bobsoolting.post.command.domain.repository.PostRepository; +import bst.bobsoolting.post.command.vo.request.RequestUpdatePostVO; +import bst.bobsoolting.post.query.repository.PostMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; + +@Slf4j +@Service +@RequiredArgsConstructor +public class PostCommandServiceImpl implements PostCommandService { + + private final PostRepository postRepository; + private final PostMapper postMapper; + private final PostConverter postConverter; // + + @Override + @Transactional + public PostDTO createPost(PostDTO postDTO) { + postDTO.setPostStatus(true); + postDTO.setCreatedAt(LocalDateTime.now()); + postDTO.setUpdatedAt(LocalDateTime.now()); + + // static 메서드 대신 인스턴스 메서드 호출 + Post post = postConverter.toEntity(postDTO); + postRepository.save(post); + + return postConverter.toDTO(post); + } + + @Override + @Transactional + public PostDTO updatePost(RequestUpdatePostVO updateVO) { + Post existing = postMapper.findByPostId(updateVO.getPostId()); + if (existing == null) { + throw new RuntimeException("Post not found with id: " + updateVO.getPostId()); + } + + Post updatedPost = Post.builder() + .postId(existing.getPostId()) + .category(existing.getCategory()) + .title(updateVO.getTitle() != null ? updateVO.getTitle() : existing.getTitle()) + .content(updateVO.getContent() != null ? updateVO.getContent() : existing.getContent()) + .images(updateVO.getImages() != null ? updateVO.getImages() : existing.getImages()) + .maxParticipants(updateVO.getMaxParticipants() != null ? updateVO.getMaxParticipants() : existing.getMaxParticipants()) + .participants(existing.getParticipants()) // 참여자 목록은 그대로 유지 + .recruitmentStatus(updateVO.getRecruitmentStatus() != null ? updateVO.getRecruitmentStatus() : existing.getRecruitmentStatus()) + .date(updateVO.getDate() != null ? updateVO.getDate() : existing.getDate()) + .location(updateVO.getLocation() != null ? updateVO.getLocation() : existing.getLocation()) + .postStatus(existing.getPostStatus()) // 이 값은 변경하지 않는다고 가정 + .createdAt(existing.getCreatedAt()) + .updatedAt(LocalDateTime.now()) + .memberId(existing.getMemberId()) + .build(); + + postRepository.save(updatedPost); + + return postConverter.toDTO(updatedPost); + } + + @Override + @Transactional + public void deletePost(Long postId) { + Post existing = postMapper.findByPostId(postId); + if(existing == null) { + throw new RuntimeException("Post not found with id: " + postId); + } + + Post updatedPost = Post.builder() + .postId(existing.getPostId()) + .category(existing.getCategory()) + .title(existing.getTitle()) + .content(existing.getContent()) + .images(existing.getImages()) + .maxParticipants(existing.getMaxParticipants()) + .participants(existing.getParticipants()) + .recruitmentStatus(existing.getRecruitmentStatus()) + .date(existing.getDate()) + .location(existing.getLocation()) + .postStatus(false) // 소프트 딜리트를 위해 false로 설정 + .createdAt(existing.getCreatedAt()) + .updatedAt(LocalDateTime.now()) + .memberId(existing.getMemberId()) + .build(); + + postRepository.save(updatedPost); + } +} diff --git a/src/main/java/bst/bobsoolting/post/command/domain/aggregate/Category.java b/src/main/java/bst/bobsoolting/post/command/domain/aggregate/Category.java new file mode 100644 index 0000000..5495531 --- /dev/null +++ b/src/main/java/bst/bobsoolting/post/command/domain/aggregate/Category.java @@ -0,0 +1,7 @@ +package bst.bobsoolting.post.command.domain.aggregate; + +public enum Category { + BOB, + SOOL, + TING +} \ No newline at end of file diff --git a/src/main/java/bst/bobsoolting/post/command/domain/aggregate/Post.java b/src/main/java/bst/bobsoolting/post/command/domain/aggregate/Post.java new file mode 100644 index 0000000..81c298a --- /dev/null +++ b/src/main/java/bst/bobsoolting/post/command/domain/aggregate/Post.java @@ -0,0 +1,41 @@ +package bst.bobsoolting.post.command.domain.aggregate; + +import lombok.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import jakarta.persistence.*; + + +@Entity +@Table(name = "post") +@Getter +@AllArgsConstructor +@NoArgsConstructor +@ToString +@Builder +public class Post { + + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long postId; // PK (AUTO_INCREMENT) + + @Enumerated(EnumType.STRING) + private Category category; // Enum (예: STUDY, MEETUP 등) + + private String title; + private String content; + private String images; // JSON 컬럼 + private Integer maxParticipants; + private String participants; // JSON 컬럼 + private String recruitmentStatus; + private LocalDate date; + private String location; + private Boolean postStatus; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + private String memberId; // 외래키 + +} diff --git a/src/main/java/bst/bobsoolting/post/command/domain/repository/PostRepository.java b/src/main/java/bst/bobsoolting/post/command/domain/repository/PostRepository.java new file mode 100644 index 0000000..ab68c45 --- /dev/null +++ b/src/main/java/bst/bobsoolting/post/command/domain/repository/PostRepository.java @@ -0,0 +1,13 @@ +package bst.bobsoolting.post.command.domain.repository; + +import bst.bobsoolting.post.command.domain.aggregate.Post; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + + + +@Repository +public interface PostRepository extends JpaRepository { + +} \ No newline at end of file diff --git a/src/main/java/bst/bobsoolting/post/command/vo/request/RequestDeletePostVO.java b/src/main/java/bst/bobsoolting/post/command/vo/request/RequestDeletePostVO.java new file mode 100644 index 0000000..fa85dd0 --- /dev/null +++ b/src/main/java/bst/bobsoolting/post/command/vo/request/RequestDeletePostVO.java @@ -0,0 +1,32 @@ +package bst.bobsoolting.post.command.vo.request; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonProperty; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Setter +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +@ToString +public class RequestDeletePostVO { + // 업데이트할 게시글의 식별자 (수정 대상 확인용) + @JsonProperty("postId") + private Long postId; + + // 사용자가 수정 가능한 필드들 + @JsonProperty("postStatus") + private Boolean postStatus; + + @JsonProperty("UpdatedAt") + private LocalDateTime UpdatedAt; +} diff --git a/src/main/java/bst/bobsoolting/post/command/vo/request/RequestUpdatePostVO.java b/src/main/java/bst/bobsoolting/post/command/vo/request/RequestUpdatePostVO.java new file mode 100644 index 0000000..c6a00f1 --- /dev/null +++ b/src/main/java/bst/bobsoolting/post/command/vo/request/RequestUpdatePostVO.java @@ -0,0 +1,48 @@ +package bst.bobsoolting.post.command.vo.request; + +import java.time.LocalDate; + +import com.fasterxml.jackson.annotation.JsonProperty; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Setter +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +@ToString +public class RequestUpdatePostVO { + // 업데이트할 게시글의 식별자 (수정 대상 확인용) + @JsonProperty("postId") + private Long postId; + + // 사용자가 수정 가능한 필드들 + @JsonProperty("title") + private String title; + + @JsonProperty("content") + private String content; + + @JsonProperty("images") + private String images; // JSON 문자열 형태로 처리할 수도 있습니다. + + @JsonProperty("maxParticipants") + private Integer maxParticipants; // 수정할 최대 참여 인원 + + @JsonProperty("recruitmentStatus") + private String recruitmentStatus; + + @JsonProperty("date") + private LocalDate date; + + @JsonProperty("location") + private String location; + +} diff --git a/src/main/java/bst/bobsoolting/post/query/controller/PostQueryController.java b/src/main/java/bst/bobsoolting/post/query/controller/PostQueryController.java new file mode 100644 index 0000000..b73fdd8 --- /dev/null +++ b/src/main/java/bst/bobsoolting/post/query/controller/PostQueryController.java @@ -0,0 +1,49 @@ +package bst.bobsoolting.post.query.controller; + +import bst.bobsoolting.post.command.application.dto.PostDTO; +import bst.bobsoolting.post.query.service.PostQueryService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import java.util.List; + +@RestController +@RequestMapping("/api/post/query") +@RequiredArgsConstructor +public class PostQueryController { + + private final PostQueryService postQueryService; + + @GetMapping("/{postId}") + public ResponseEntity getPostById(@PathVariable("postId") Long postId) { + PostDTO dto = postQueryService.getPostById(postId); + return ResponseEntity.ok(dto); + } + + @GetMapping + public ResponseEntity> getAllPosts() { + List posts = postQueryService.getAllPosts(); + return ResponseEntity.ok(posts); + } + + // 새로운 검색 엔드포인트: 키워드를 쿼리 파라미터로 전달 (예: ?keyword=버거킹) + @GetMapping("/search") + public ResponseEntity> searchPosts(@RequestParam("keyword") String keyword) { + List posts = postQueryService.searchPostsByKeyword(keyword); + return ResponseEntity.ok(posts); + } + + // Category별 조회 엔드포인트 (예: ?category=BOB) + @GetMapping("/category") + public ResponseEntity> getPostsByCategory(@RequestParam("category") String category) { + List posts = postQueryService.getPostsByCategory(category); + return ResponseEntity.ok(posts); + } + + // 작성자(memberId)별 조회 엔드포인트 (예: ?memberId=test-user) + @GetMapping("/member") + public ResponseEntity> getPostsByMemberId(@RequestParam("memberId") String memberId) { + List posts = postQueryService.getPostsByMemberId(memberId); + return ResponseEntity.ok(posts); + } +} diff --git a/src/main/java/bst/bobsoolting/post/query/repository/PostMapper.java b/src/main/java/bst/bobsoolting/post/query/repository/PostMapper.java new file mode 100644 index 0000000..7d19ff2 --- /dev/null +++ b/src/main/java/bst/bobsoolting/post/query/repository/PostMapper.java @@ -0,0 +1,28 @@ +package bst.bobsoolting.post.query.repository; + +import java.util.List; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import bst.bobsoolting.post.command.domain.aggregate.Post; + +@Mapper +public interface PostMapper { + +//검색메소드 + + // 게시글 ID로 단건 조회 + Post findByPostId(@Param("postId") Long postId); + + // 전체 게시글 조회 (필요 시) + List findAll(); + + //1.키워드 + List searchByKeyword(String keyword); + //2.카테고리 + List findByCategory(@Param("category") String category); + //3. 작성자id 조회 + List findByMemberId(@Param("memberId") String memberId); + +} diff --git a/src/main/java/bst/bobsoolting/post/query/service/PostQueryService.java b/src/main/java/bst/bobsoolting/post/query/service/PostQueryService.java new file mode 100644 index 0000000..4324e83 --- /dev/null +++ b/src/main/java/bst/bobsoolting/post/query/service/PostQueryService.java @@ -0,0 +1,21 @@ +package bst.bobsoolting.post.query.service; + +import bst.bobsoolting.post.command.application.dto.PostDTO; +import java.util.List; + +public interface PostQueryService { + //글번호로 검색 + PostDTO getPostById(Long postId); + + //전체 글 검색 + List getAllPosts(); + + //키워드 (제목/내용) 검색 + List searchPostsByKeyword(String keyword); + + // Category 검색 + List getPostsByCategory(String category); + + // 작성자 id 검색 + List getPostsByMemberId(String memberId); +} diff --git a/src/main/java/bst/bobsoolting/post/query/service/PostQueryServiceImpl.java b/src/main/java/bst/bobsoolting/post/query/service/PostQueryServiceImpl.java new file mode 100644 index 0000000..bfce60b --- /dev/null +++ b/src/main/java/bst/bobsoolting/post/query/service/PostQueryServiceImpl.java @@ -0,0 +1,57 @@ +package bst.bobsoolting.post.query.service; + +import bst.bobsoolting.post.command.application.mapper.PostConverter; +import bst.bobsoolting.post.command.application.dto.PostDTO; +import bst.bobsoolting.post.command.domain.aggregate.Post; +import bst.bobsoolting.post.query.repository.PostMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class PostQueryServiceImpl implements PostQueryService { + + private final PostMapper postmapper; + private final PostConverter postConverter; + + @Override + public PostDTO getPostById(Long postId) { + Post post = postmapper.findByPostId(postId); + return postConverter.toDTO(post); + } + + @Override + public List getAllPosts() { + List posts = postmapper.findAll(); + return posts.stream() + .map(postConverter::toDTO) + .collect(Collectors.toList()); + } + + @Override + public List searchPostsByKeyword(String keyword) { + List posts = postmapper.searchByKeyword(keyword); + return posts.stream() + .map(postConverter::toDTO) + .collect(Collectors.toList()); + } + + @Override + public List getPostsByCategory(String category) { + // postmapper에서 category를 기준으로 조회한 후 DTO로 변환 + List posts = postmapper.findByCategory(category); + return posts.stream() + .map(postConverter::toDTO) + .collect(Collectors.toList()); + } + + @Override + public List getPostsByMemberId(String memberId) { + List posts = postmapper.findByMemberId(memberId); + return posts.stream() + .map(postConverter::toDTO) + .collect(Collectors.toList()); + } +} diff --git a/src/main/resources/bst/bobsoolting/mapper/post/PostMapper.xml b/src/main/resources/bst/bobsoolting/mapper/post/PostMapper.xml new file mode 100644 index 0000000..b2c5dd5 --- /dev/null +++ b/src/main/resources/bst/bobsoolting/mapper/post/PostMapper.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file