Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ jobs:
mybatis:
mapper-locations: classpath:/bst/bobsoolting/mapper/**/*.xml
configuration:
default-enum-type-handler: org.apache.ibatis.type.EnumTypeHandler
map-underscore-to-camel-case: true
type-handlers-package: bst.bobsoolting.util
cors:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@

import org.springframework.http.HttpHeaders;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequiredArgsConstructor
public class PostCommandController implements PostCommandControllerDocs {
Expand All @@ -27,44 +31,51 @@ public class PostCommandController implements PostCommandControllerDocs {
private final SecurityUtil securityUtil;

@PostMapping
public ResponseEntity<ResponseCreatePostVO> createPost(@RequestHeader(HttpHeaders.AUTHORIZATION) String token, @RequestBody RequestCreatePostVO request) {
public ResponseEntity<?> createPost(@RequestHeader(HttpHeaders.AUTHORIZATION) String token, @RequestBody RequestCreatePostVO request) {
String kakaoId = securityUtil.getKakaoIdFromToken(token.replace("Bearer ", ""));
String memberId = memberQueryService.getMemberIdByKakaoId(kakaoId);
if (memberId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(null);
if (memberId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Collections.singletonMap("message", "Unauthorized access"));

PostDTO created = postCommandService.createPost(request, memberId);
ResponseCreatePostVO response = postConverter.fromDTOToCreateVO(created);
return ResponseEntity.ok(response);
}

@PutMapping("/{postId}")
public ResponseEntity<ResponseUpdatePostVO> updatePost(@RequestHeader(HttpHeaders.AUTHORIZATION) String token, @PathVariable Long postId, @RequestBody RequestUpdatePostVO request) {
public ResponseEntity<?> updatePost(@RequestHeader(HttpHeaders.AUTHORIZATION) String token, @PathVariable Long postId, @RequestBody RequestUpdatePostVO request) {
String kakaoId = securityUtil.getKakaoIdFromToken(token.replace("Bearer ", ""));
String memberId = memberQueryService.getMemberIdByKakaoId(kakaoId);
if (memberId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(null);
if (memberId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Collections.singletonMap("message", "Unauthorized access"));

PostDTO updated = postCommandService.updatePost(memberId, postId, request);
ResponseUpdatePostVO response = postConverter.fromDTOToUpdateVO(updated);
return ResponseEntity.ok(response);
}

@PatchMapping("/{postId}/status")
public ResponseEntity<Void> updateRecruitmentStatus(@RequestHeader(HttpHeaders.AUTHORIZATION) String token, @PathVariable("postId") Long postId) {
@PatchMapping("/{postId}/recruit-status")
public ResponseEntity<Map<String, String>> updateRecruitmentStatus(@RequestHeader(HttpHeaders.AUTHORIZATION) String token, @PathVariable("postId") Long postId) {
String kakaoId = securityUtil.getKakaoIdFromToken(token.replace("Bearer ", ""));
String memberId = memberQueryService.getMemberIdByKakaoId(kakaoId);
if (memberId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(null);
if (memberId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Collections.singletonMap("message", "Unauthorized access"));

postCommandService.updateRecruitmentStatus(memberId, postId);
return ResponseEntity.noContent().build();

Map<String, String> response = new HashMap<>();
response.put("message", "모집 상태가 변경되었습니다.");

return ResponseEntity.ok(response);
}

@PatchMapping("/{postId}")
public ResponseEntity<Void> softDeletePost(@RequestHeader(HttpHeaders.AUTHORIZATION) String token, @PathVariable("postId") Long postId) {
public ResponseEntity<Map<String, String>> softDeletePost(@RequestHeader(HttpHeaders.AUTHORIZATION) String token, @PathVariable("postId") Long postId) {
String kakaoId = securityUtil.getKakaoIdFromToken(token.replace("Bearer ", ""));
String memberId = memberQueryService.getMemberIdByKakaoId(kakaoId);
if (memberId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(null);
if (memberId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Collections.singletonMap("message", "Unauthorized access"));
postCommandService.deletePost(memberId, postId);

return ResponseEntity.noContent().build();
Map<String, String> response = new HashMap<>();
response.put("message", "해당 게시글이 삭제되었습니다.");

return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package bst.bobsoolting.post.command.application.controller.docs;

import bst.bobsoolting.post.command.application.dto.PostDTO;
import bst.bobsoolting.post.command.domain.vo.request.RequestCreatePostVO;
import bst.bobsoolting.post.command.domain.vo.request.RequestUpdatePostVO;
import bst.bobsoolting.post.command.domain.vo.response.ResponseCreatePostVO;
Expand All @@ -16,6 +15,8 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@Tag(name = "게시글 API", description = "게시글 생성, 수정, 삭제 관련 API")
@RequestMapping("/api/post")
public interface PostCommandControllerDocs {
Expand All @@ -27,7 +28,7 @@ public interface PostCommandControllerDocs {
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@PostMapping
ResponseEntity<ResponseCreatePostVO> createPost(
ResponseEntity<?> createPost(
@RequestHeader(HttpHeaders.AUTHORIZATION) String token,
@RequestBody @io.swagger.v3.oas.annotations.parameters.RequestBody(
content = @Content(schema = @Schema(implementation = RequestCreatePostVO.class))
Expand All @@ -42,7 +43,7 @@ ResponseEntity<ResponseCreatePostVO> createPost(
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@PutMapping("/{postId}")
ResponseEntity<ResponseUpdatePostVO> updatePost(
ResponseEntity<?> updatePost(
@RequestHeader(HttpHeaders.AUTHORIZATION) String token,
@Parameter(description = "게시글 ID", required = true) @PathVariable Long postId,
@RequestBody @io.swagger.v3.oas.annotations.parameters.RequestBody(
Expand All @@ -57,8 +58,8 @@ ResponseEntity<ResponseUpdatePostVO> updatePost(
@ApiResponse(responseCode = "403", description = "변경 권한 없음"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@PatchMapping("/{postId}/status")
ResponseEntity<Void> updateRecruitmentStatus(
@PatchMapping("/{postId}/recruit- status")
ResponseEntity<Map<String, String>> updateRecruitmentStatus(
@RequestHeader(HttpHeaders.AUTHORIZATION) String token,
@Parameter(description = "게시글 ID", required = true) @PathVariable("postId") Long postId
);
Expand All @@ -71,7 +72,7 @@ ResponseEntity<Void> updateRecruitmentStatus(
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@PatchMapping("/{postId}")
ResponseEntity<Void> softDeletePost(
ResponseEntity<Map<String, String>> softDeletePost(
@RequestHeader(HttpHeaders.AUTHORIZATION) String token, // ✅ Authorization 헤더 추가
@Parameter(description = "게시글 ID", required = true) @PathVariable("postId") Long postId
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import bst.bobsoolting.post.command.domain.vo.request.RequestCreatePostVO;
import bst.bobsoolting.post.command.domain.vo.request.RequestUpdatePostVO;
import bst.bobsoolting.post.command.domain.vo.response.ResponseCreatePostVO;
import bst.bobsoolting.post.command.domain.vo.response.ResponsePostVO;
import bst.bobsoolting.post.command.domain.vo.response.ResponseUpdatePostVO;
import org.springframework.stereotype.Component;

Expand All @@ -15,7 +16,6 @@
@Component
public class PostConverter {

// DTO -> Entity 변환
public Post fromCreateVOToEntity(RequestCreatePostVO request, String memberId) {
if (request == null) return null;

Expand Down Expand Up @@ -55,7 +55,6 @@ public Post fromUpdateVOToEntity(Post existingPost, RequestUpdatePostVO updateVO
.build();
}

// Entity -> DTO 변환
public PostDTO toDTO(Post post) {
if (post == null) return null;
return PostDTO.builder()
Expand Down Expand Up @@ -100,4 +99,20 @@ public ResponseUpdatePostVO fromDTOToUpdateVO(PostDTO updated) {
.memberId(updated.getMemberId())
.build();
}

public ResponsePostVO toResponseVO(PostDTO dto) {
if (dto == null) return null;
return ResponsePostVO.builder()
.postId(dto.getPostId())
.category(dto.getCategory())
.title(dto.getTitle())
.content(dto.getContent())
.maxParticipants(dto.getMaxParticipants())
.participants(dto.getParticipants())
.recruitmentStatus(dto.getRecruitmentStatus())
.date(dto.getDate())
.location(dto.getLocation())
.memberId(dto.getMemberId())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package bst.bobsoolting.post.command.domain.vo.response;

import bst.bobsoolting.post.command.domain.aggregate.Category;
import bst.bobsoolting.post.command.domain.aggregate.RecruitmentStatus;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;

import java.time.LocalDate;
import java.util.List;

@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ToString
@Schema(name = "ResponsePostVO", description = "게시글 조회 응답 객체")
public class ResponsePostVO {

@Schema(description = "게시글 ID", example = "1")
@JsonProperty("post_id")
private Long postId;

@Schema(description = "카테고리", example = "FOOD")
private Category category;

@Schema(description = "제목", example = "저녁 드실 분!")
private String title;

@Schema(description = "내용", example = "3월 15일에 저녁 드실 분 구합니다.")
private String content;

@Schema(description = "최대 참가자 수", example = "5")
@JsonProperty("max_participants")
private Integer maxParticipants;

@Schema(description = "참여자 목록", example = "[\"user1\", \"user2\"]")
private List<String> participants;

@Schema(description = "모집 상태", example = "OPEN")
private RecruitmentStatus recruitmentStatus;

@Schema(description = "활동 날짜", example = "2025-03-15")
private LocalDate date;

@Schema(description = "장소", example = "서울 강남구")
private String location;

@Schema(description = "작성자 ID", example = "20250315-UUID")
@JsonProperty("member_id")
private String memberId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import bst.bobsoolting.member.query.service.MemberQueryService;
import bst.bobsoolting.post.command.application.dto.PostDTO;
import bst.bobsoolting.post.command.application.mapper.PostConverter;
import bst.bobsoolting.post.command.domain.vo.response.ResponsePostVO;
import bst.bobsoolting.post.query.controller.docs.PostQueryControllerDocs;
import bst.bobsoolting.post.query.service.PostQueryService;
import bst.bobsoolting.util.SecurityUtil;
Expand All @@ -12,6 +14,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.http.HttpHeaders;

Expand All @@ -20,42 +23,52 @@
public class PostQueryController implements PostQueryControllerDocs {

private final PostQueryService postQueryService;
private final PostConverter postConverter;
private final MemberQueryService memberQueryService;
private final SecurityUtil securityUtil;

@GetMapping("/{postId}")
public ResponseEntity<PostDTO> getPostById(@PathVariable("postId") Long postId) {
public ResponseEntity<ResponsePostVO> getPostById(@PathVariable("postId") Long postId) {
PostDTO dto = postQueryService.getPostById(postId);
return ResponseEntity.ok(dto);
ResponsePostVO response = postConverter.toResponseVO(dto);
return ResponseEntity.ok(response);
}

@GetMapping
public ResponseEntity<PageInfo<PostDTO>> getAllPosts(@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size) {
public ResponseEntity<PageInfo<ResponsePostVO>> getAllPosts(@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size) {
PageHelper.startPage(page, size);
List<PostDTO> posts = postQueryService.getAllPosts();
List<ResponsePostVO> posts = postQueryService.getAllPosts().stream()
.map(postConverter::toResponseVO)
.collect(Collectors.toList());
return ResponseEntity.ok(new PageInfo<>(posts));
}

@GetMapping("/category")
public ResponseEntity<PageInfo<PostDTO>> getPostsByCategory(@RequestParam("category") String category, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size) {
public ResponseEntity<PageInfo<ResponsePostVO>> getPostsByCategory(@RequestParam("category") String category, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size) {
PageHelper.startPage(page, size);
List<PostDTO> posts = postQueryService.getPostsByCategory(category);
List<ResponsePostVO> posts = postQueryService.getPostsByCategory(category).stream()
.map(postConverter::toResponseVO)
.collect(Collectors.toList());
return ResponseEntity.ok(new PageInfo<>(posts));
}

@GetMapping("/search")
public ResponseEntity<List<PostDTO>> searchPosts(@RequestParam("keyword") String keyword) {
List<PostDTO> posts = postQueryService.searchPostsByKeyword(keyword);
public ResponseEntity<List<ResponsePostVO>> searchPosts(@RequestParam("keyword") String keyword) {
List<ResponsePostVO> posts = postQueryService.searchPostsByKeyword(keyword).stream()
.map(postConverter::toResponseVO)
.collect(Collectors.toList());
return ResponseEntity.ok(posts);
}

@GetMapping("/member")
public ResponseEntity<List<PostDTO>> getMyPosts(@RequestHeader(HttpHeaders.AUTHORIZATION) String token) {
public ResponseEntity<List<ResponsePostVO>> getMyPosts(@RequestHeader(HttpHeaders.AUTHORIZATION) String token) {
String kakaoId = securityUtil.getKakaoIdFromToken(token.replace("Bearer ", ""));
String memberId = memberQueryService.getMemberIdByKakaoId(kakaoId);
if (memberId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(null);
List<PostDTO> posts = postQueryService.getPostsByMemberId(memberId);

List<ResponsePostVO> posts = postQueryService.getPostsByMemberId(memberId).stream()
.map(postConverter::toResponseVO)
.collect(Collectors.toList());
return ResponseEntity.ok(posts);
}
}

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package bst.bobsoolting.post.query.controller.docs;

import bst.bobsoolting.post.command.application.dto.PostDTO;
import bst.bobsoolting.post.command.domain.vo.response.ResponsePostVO;
import com.github.pagehelper.PageInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
Expand All @@ -21,40 +21,28 @@ public interface PostQueryControllerDocs {

@Operation(summary = "게시글 단건 조회", description = "특정 ID를 가진 게시글을 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = PostDTO.class))),
@ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = ResponsePostVO.class))),
@ApiResponse(responseCode = "404", description = "해당 게시글이 존재하지 않음"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@GetMapping("/{postId}")
ResponseEntity<PostDTO> getPostById(
ResponseEntity<ResponsePostVO> getPostById(
@Parameter(description = "게시글 ID", required = true) @PathVariable("postId") Long postId
);

@Operation(summary = "전체 게시글 조회 (페이지네이션)", description = "페이지네이션 적용된 모든 게시글을 조회합니다.")
@GetMapping
ResponseEntity<PageInfo<PostDTO>> getAllPosts(@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size);
ResponseEntity<PageInfo<ResponsePostVO>> getAllPosts(@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size);

@Operation(summary = "카테고리별 게시글 조회 (페이지네이션)", description = "특정 카테고리에 속한 게시글을 페이지네이션하여 조회합니다.")
@GetMapping("/category")
ResponseEntity<PageInfo<PostDTO>> getPostsByCategory(@Parameter(description = "카테고리명", required = true, example = "FOOD") @RequestParam("category") String category, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size);
ResponseEntity<PageInfo<ResponsePostVO>> getPostsByCategory(@RequestParam("category") String category, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size);

@Operation(summary = "게시글 검색", description = "키워드를 이용해 게시글을 검색합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "검색 성공", content = @Content(schema = @Schema(implementation = PostDTO.class))),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@GetMapping("/search")
ResponseEntity<List<PostDTO>> searchPosts(
@Parameter(description = "검색 키워드", required = true, example = "스터디") @RequestParam("keyword") String keyword
);
ResponseEntity<List<ResponsePostVO>> searchPosts(@RequestParam("keyword") String keyword);

@Operation(summary = "내가 작성한 게시글 조회", description = "JWT에서 로그인한 사용자의 memberId를 가져와 해당 사용자의 게시글을 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = PostDTO.class))),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@GetMapping("/member")
ResponseEntity<List<PostDTO>> getMyPosts(
@RequestHeader(HttpHeaders.AUTHORIZATION) String token
);
}
ResponseEntity<List<ResponsePostVO>> getMyPosts(@RequestHeader(HttpHeaders.AUTHORIZATION) String token);
}
Loading
Loading