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
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
package com.back.domain.post.controller;

import com.back.domain.post.dto.PostRequest;
import com.back.domain.post.dto.PostResponse;
import com.back.domain.post.service.PostService;
import com.back.global.common.ApiResponse;
import com.back.global.common.PageResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
* 게시글 관련 API 요청을 처리하는 컨트롤러.
Expand All @@ -15,4 +25,40 @@ public class PostController {

private final PostService postService;

// 게시글 생성
@PostMapping
public ApiResponse<PostResponse> createPost(
@RequestBody @Valid PostRequest request) {
Long userId = 1L; // fixme 임시 사용자 ID
PostResponse response = postService.createPost(userId, request);
return ApiResponse.success(response, "성공적으로 생성되었습니다.", HttpStatus.OK);
}

// 게시글 목록 조회
@GetMapping
public ApiResponse<PageResponse<PostResponse>> getPosts(Pageable pageable) {
Page<PostResponse> responses = postService.getPosts(pageable);
return ApiResponse.success(PageResponse.of(responses), "성공적으로 조회되었습니다.", HttpStatus.OK);
}

// 게시글 단건 조회
@GetMapping("/{postId}")
public ApiResponse<PostResponse> getPost(@PathVariable Long postId) {
return ApiResponse.success(postService.getPost(postId), "성공적으로 조회되었습니다.", HttpStatus.OK);
}

@PutMapping("/{postId}")
public ApiResponse<PostResponse> updatePost(
@PathVariable Long postId,
@RequestBody @Valid PostRequest request) {
Long userId = 1L; // fixme 임시 사용자 ID
return ApiResponse.success(postService.updatePost(userId, postId, request), "성공적으로 수정되었습니다.", HttpStatus.OK);
}

@DeleteMapping("/{postId}")
public ApiResponse<Void> deletePost(@PathVariable Long postId) {
Long userId = 1L; // fixme 임시 사용자 ID
postService.deletePost(userId, postId);
return ApiResponse.success(null, "성공적으로 삭제되었습니다.", HttpStatus.OK);
}
}
18 changes: 18 additions & 0 deletions back/src/main/java/com/back/domain/post/dto/PostRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.back.domain.post.dto;

import com.back.domain.post.enums.PostCategory;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public record PostRequest(
@NotBlank(message = "제목은 필수입니다")
@Size(max = 200, message = "제목은 200자를 초과할 수 없습니다")
String title,

@NotBlank(message = "내용은 필수입니다")
String content,

@NotNull(message = "카테고리는 필수입니다")
PostCategory category
) { }
25 changes: 25 additions & 0 deletions back/src/main/java/com/back/domain/post/dto/PostResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.back.domain.post.dto;

import com.back.domain.post.enums.PostCategory;

import java.time.LocalDateTime;

/**
* @param id
* @param title
* @param content
* @param category
* @param hide
* @param likeCount
* @param createdDate
* fixme @param createdBy 추가 예정
*/
public record PostResponse(
Long id,
String title,
String content,
PostCategory category,
boolean hide,
int likeCount,
LocalDateTime createdDate
) { }
39 changes: 28 additions & 11 deletions back/src/main/java/com/back/domain/post/entity/Post.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.back.domain.post.entity;

import com.back.domain.post.enums.PostCategory;
import com.back.domain.user.entity.User;
import com.back.global.baseentity.BaseEntity;
import com.back.global.exception.ApiException;
import com.back.global.exception.ErrorCode;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.*;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.service.spi.ServiceException;
import org.springframework.data.annotation.LastModifiedDate;

import java.time.LocalDateTime;
Expand All @@ -19,9 +19,8 @@
*/
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
public class Post extends BaseEntity {

Expand All @@ -32,13 +31,16 @@ public class Post extends BaseEntity {
@Column(length = 200)
private String title;

@Column(length = 200)
private String category;
@Column(length = 20)
@Enumerated(EnumType.STRING)
private PostCategory category;

@Column(columnDefinition = "TEXT")
private String content;

@Column(columnDefinition = "jsonb")
/**
* JSON 데이터를 단순 문자열로 저장 (예: {"option1": 10, "option2": 5})
*/
private String voteContent;

@Column(nullable = false)
Expand All @@ -49,4 +51,19 @@ public class Post extends BaseEntity {

@LastModifiedDate
private LocalDateTime updatedAt;

public void assignUser(User user) {
this.user = user;
}

public void updatePost(String title, String content, PostCategory category) {
this.title = title;
this.content = content;
this.category = category;
}

public void checkUser(User targetUser) {
if (!user.equals(targetUser))
throw new ApiException(ErrorCode.UNAUTHORIZED_USER);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.back.domain.post.enums;

public enum PostCategory {
CHAT, // 잡담
SCENARIO, // 시나리오 첨부
POLL // 투표
}
41 changes: 41 additions & 0 deletions back/src/main/java/com/back/domain/post/mapper/PostMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.back.domain.post.mapper;

import com.back.domain.post.dto.PostRequest;
import com.back.domain.post.dto.PostResponse;
import com.back.domain.post.entity.Post;

import java.util.List;

/**
* PostMapper
* 엔티티와 PostRequest, PostResponse 간의 변환을 담당하는 매퍼 클래스
*/
public abstract class PostMapper {
public static Post toEntity(PostRequest request) {
return Post.builder()
.title(request.title())
.content(request.content())
.category(request.category())
.hide(false)
.likeCount(0)
.build();
}

public static PostResponse toResponse(Post post) {
return new PostResponse(
post.getId(),
post.getTitle(),
post.getContent(),
post.getCategory(),
post.isHide(),
post.getLikeCount(),
post.getCreatedDate()
);
}

public static List<PostResponse> toResponseList(List<Post> posts) {
return posts.stream()
.map(PostMapper::toResponse)
.toList();
}
}
66 changes: 66 additions & 0 deletions back/src/main/java/com/back/domain/post/service/PostService.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,82 @@
package com.back.domain.post.service;

import com.back.domain.post.dto.PostRequest;
import com.back.domain.post.dto.PostResponse;
import com.back.domain.post.entity.Post;
import com.back.domain.post.mapper.PostMapper;
import com.back.domain.post.repository.PostRepository;
import com.back.domain.user.entity.User;
import com.back.domain.user.repository.UserRepository;
import com.back.global.exception.ApiException;
import com.back.global.exception.ErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

/**
* 게시글 관련 비즈니스 로직을 처리하는 서비스.
*/
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class PostService {

private final UserRepository userRepository;
private final PostRepository postRepository;

@Transactional
public PostResponse createPost(Long userId, PostRequest request) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new ApiException(ErrorCode.USER_NOT_FOUND));

Post post = PostMapper.toEntity(request);
post.assignUser(user);
Post savedPost = postRepository.save(post);

return PostMapper.toResponse(savedPost);
}

public PostResponse getPost(Long postId) {
return postRepository.findById(postId)
.filter(post -> !post.isHide())
.map(PostMapper::toResponse)
.orElseThrow(() -> new ApiException(ErrorCode.POST_NOT_FOUND));
}

public Page<PostResponse> getPosts(Pageable pageable) {
return postRepository.findAll(pageable)
.map(PostMapper::toResponse);
}

@Transactional
public PostResponse updatePost(Long userId, Long postId, PostRequest request) {
Post post = validatePostOwnership(userId, postId);

post.updatePost(request.title(), request.content(), request.category());

return PostMapper.toResponse(post);
}

@Transactional
public void deletePost(Long userId, Long postId) {
Post post = validatePostOwnership(userId, postId);
postRepository.delete(post);
}

private Post validatePostOwnership(Long userId, Long postId) {
Post post = postRepository.findById(postId)
.orElseThrow(() -> new ApiException(ErrorCode.POST_NOT_FOUND));

User requestUser = userRepository.findById(userId)
.orElseThrow(() -> new ApiException(ErrorCode.USER_NOT_FOUND));

post.checkUser(requestUser);

return post;
}
}
37 changes: 37 additions & 0 deletions back/src/main/java/com/back/global/common/ApiResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.back.global.common;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

/**
* 공통 응답 형태
* {
* "data": { ... },
* "message": "성공적으로 조회되었습니다.",
* "status": 200
* }
*/
@Getter
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiResponse<T> {

private final T data;
private final String message;
private final int status;

private ApiResponse(T data, String message, int status) {
this.data = data;
this.message = message;
this.status = status;
}

public static <T> ApiResponse<T> success(T data, String message) {
return new ApiResponse<>(data, message, HttpStatus.OK.value());
}

public static <T> ApiResponse<T> success(T data, String message, HttpStatus status) {
return new ApiResponse<>(data, message, status.value());
}
}
29 changes: 29 additions & 0 deletions back/src/main/java/com/back/global/common/PageResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.back.global.common;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.data.domain.Page;

import java.util.List;

@Getter
@AllArgsConstructor
public class PageResponse<T> {
private List<T> items;
private int page;
private int size;
private long totalElements;
private int totalPages;
private boolean last;

public static <T> PageResponse<T> of(Page<T> page) {
return new PageResponse<>(
page.getContent(),
page.getNumber() + 1, // 응답도 1페이지 시작으로 반환, Page 객체는 0페이지부터 시작
page.getSize(),
page.getTotalElements(),
page.getTotalPages(),
page.isLast()
);
}
}
Loading