Skip to content
Open
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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,10 @@ out/

### VS Code ###
.vscode/

# 데이터베이스 데이터 폴더 제외
/mysql_data/

# OS 관련 임시 파일
.DS_Store
Thumbs.db
15 changes: 11 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
id 'java'
id 'org.springframework.boot' version '4.0.3'
id 'org.springframework.boot' version '3.5.11'
id 'io.spring.dependency-management' version '1.1.7'
}

Expand All @@ -26,13 +26,20 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test'
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

// Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.13'
// Validation
implementation 'org.springframework.boot:spring-boot-starter-validation'
}

tasks.named('test') {
Expand Down
12 changes: 12 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: '3.8'
services:
db:
image: mysql:8.0
container_name: spring-study-db
ports:
- "3307:3306"
environment:
MYSQL_ROOT_PASSWORD: tngus3872
MYSQL_DATABASE: spring-1team
volumes:
- ./mysql_data:/var/lib/mysql
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
public class Spring1teamApplication {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.study.spring1team.controller;
package com.study.spring1team.domain.ping.controller;

import com.study.spring1team.global.apiPayload.ApiResponse;
import com.study.spring1team.global.apiPayload.code.GeneralSuccessCode;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -9,7 +11,7 @@
public class PingController {

@GetMapping("/ping")
public String ping() {
return "pong";
public ApiResponse ping() {
return ApiResponse.onSuccess(GeneralSuccessCode.OK,"pong");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.study.spring1team.domain.post.controller;

import com.study.spring1team.domain.post.entity.Post;
import com.study.spring1team.domain.post.service.PostService;
import com.study.spring1team.global.apiPayload.ApiResponse;
import com.study.spring1team.domain.post.dto.PostRequestDTO;
import com.study.spring1team.global.apiPayload.code.GeneralSuccessCode;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/posts")
@RequiredArgsConstructor
public class PostController {
private final PostService postService;

@PostMapping()
public ApiResponse<String> createPost(@Valid @RequestBody PostRequestDTO request) {
postService.createPost(request);
return ApiResponse.onSuccess(GeneralSuccessCode.OK, "게시글이 성공적으로 생성되었습니다.");
}

@GetMapping
public List<Post> getPosts() {
return postService.getPostList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.study.spring1team.domain.post.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Getter;

@Getter
public class PostRequestDTO {

@NotBlank(message = "제목은 필수 입력 항목입니다.")
@Size(min = 1, max = 50, message = "제목은 1자 이상 50자 이하로 입력해주세요.")
private String title;

@NotBlank(message = "내용은 필수 입력 항목입니다.")
private String content;

@NotBlank(message = "작성자는 필수 입력 항목입니다.")
private String author;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.study.spring1team.domain.post.dto;

public class PostResponseDTO {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.study.spring1team.domain.post.entity;

import com.study.spring1team.global.entity.BaseEntity;
import jakarta.persistence.*;
import lombok.*;

import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Category extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, length = 20)
private String name;

@OneToMany(mappedBy = "category", cascade = CascadeType.ALL)
private List<Post> postList = new ArrayList<>();
}
34 changes: 34 additions & 0 deletions src/main/java/com/study/spring1team/domain/post/entity/Post.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.study.spring1team.domain.post.entity;

import com.study.spring1team.domain.user.entity.User;
import com.study.spring1team.global.entity.BaseEntity;
import jakarta.persistence.*;
import lombok.*;

import java.util.ArrayList;

@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Post extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, length = 50)
private String title;

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

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User author; // 작성자 매핑

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
private Category category; // 카테고리 매핑
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.study.spring1team.domain.post.repository;

import com.study.spring1team.domain.post.entity.Post;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PostRepository extends JpaRepository<Post, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.study.spring1team.domain.post.service;

import com.study.spring1team.domain.post.dto.PostRequestDTO;
import com.study.spring1team.domain.post.entity.Post;
import com.study.spring1team.domain.post.repository.PostRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional
@RequiredArgsConstructor
public class PostService {

private final PostRepository postRepository;

public Long createPost(PostRequestDTO request) {
Post post = Post.builder()
.title(request.getTitle())
.content(request.getContent())
.build();

return postRepository.save(post).getId();
}

public List<Post> getPostList() {
return postRepository.findAll();
}
}
32 changes: 32 additions & 0 deletions src/main/java/com/study/spring1team/domain/user/entity/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.study.spring1team.domain.user.entity;

import com.study.spring1team.domain.post.entity.Post;
import com.study.spring1team.global.entity.BaseEntity;
import jakarta.persistence.*;
import lombok.*;

import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class User extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, length = 100)
private String password;

@Column(nullable = false, length = 20)
private String name;

@Column(nullable = false, unique = true, length = 50)
private String email;

@OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
private List<Post> postList = new ArrayList<>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.study.spring1team.global.apiPayload;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.study.spring1team.global.apiPayload.code.BaseErrorCode;
import com.study.spring1team.global.apiPayload.code.BaseSuccessCode;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
@JsonPropertyOrder({"isSuccess", "code", "message", "result"})
public class ApiResponse<T> {

@JsonProperty("isSuccess")
private final Boolean isSuccess;

@JsonProperty("code")
private final String code;

@JsonProperty("message")
private final String message;

@JsonInclude(JsonInclude.Include.NON_NULL) // 결과가 null일 때 JSON에서 아예 안 보이게 함
@JsonProperty("result")
private T result;

// 성공한 경우 (result 포함)
public static <T> ApiResponse<T> onSuccess(BaseSuccessCode code, T result) {
return new ApiResponse<>(true, code.getCode(), code.getMessage(), result);
}

// 실패한 경우 (result 포함)
public static <T> ApiResponse<T> onFailure(BaseErrorCode code, T result) {
return new ApiResponse<>(false, code.getCode(), code.getMessage(), result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.study.spring1team.global.apiPayload.code;

import org.springframework.http.HttpStatus;

public interface BaseErrorCode {
HttpStatus getStatus();
String getCode();
String getMessage();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.study.spring1team.global.apiPayload.code;

import org.springframework.http.HttpStatus;

public interface BaseSuccessCode {
HttpStatus getStatus();
String getCode();
String getMessage();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.study.spring1team.global.apiPayload.code;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum GeneralErrorCode implements BaseErrorCode{

BAD_REQUEST(HttpStatus.BAD_REQUEST,
"COMMON400_1",
"잘못된 요청입니다."),
UNAUTHORIZED(HttpStatus.UNAUTHORIZED,
"AUTH401_1",
"인증이 필요합니다."),
FORBIDDEN(HttpStatus.FORBIDDEN,
"AUTH403_1",
"요청이 거부되었습니다."),
NOT_FOUND(HttpStatus.NOT_FOUND,
"COMMON404_1",
"요청한 리소스를 찾을 수 없습니다."),
;

private final HttpStatus status;
private final String code;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.study.spring1team.global.apiPayload.code;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum GeneralSuccessCode implements BaseSuccessCode {
OK(HttpStatus.OK, "COMMON200", "요청에 성공하였습니다.");

private final HttpStatus status;
private final String code;
private final String message;
}
Loading