diff --git a/build.gradle b/build.gradle index 34ece7c..5d240b6 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ version = '0.0.1-SNAPSHOT' java { toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(23) } } @@ -31,6 +31,30 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + //implementation 'org.springframework.boot:spring-boot-starter-security' + + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + implementation 'org.springframework.boot:spring-boot-starter-web' + compileOnly 'org.projectlombok:lombok' + developmentOnly 'org.springframework.boot:spring-boot-devtools' + + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.security:spring-security-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'mysql:mysql-connector-java:8.0.33' + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + implementation 'com.auth0:java-jwt:4.4.0' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-mail' } tasks.named('test') { diff --git a/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/Application.java b/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/Application.java index 56abd6a..215e7f4 100644 --- a/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/Application.java +++ b/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/Application.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication +@EnableJpaAuditing public class Application { public static void main(String[] args) { diff --git a/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/DTO/ArticleDTO.java b/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/DTO/ArticleDTO.java new file mode 100644 index 0000000..5a03732 --- /dev/null +++ b/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/DTO/ArticleDTO.java @@ -0,0 +1,23 @@ +package com.gsm._8th.class4.backed.task._1._1.domain.DTO; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ArticleDTO { + private Long idx; + private String title; + private String content; + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime createdAt; + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/README.md b/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/README.md deleted file mode 100644 index 589add4..0000000 --- a/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/README.md +++ /dev/null @@ -1,2 +0,0 @@ -여기서부터 작업을 시작해주시면 됩니다! -Entity의 경우에는 API 명세서를 참고하셔서 필요한 요소들을 모두 적용해주세요! \ No newline at end of file diff --git a/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/controller/ArticleController.java b/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/controller/ArticleController.java new file mode 100644 index 0000000..a5c8706 --- /dev/null +++ b/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/controller/ArticleController.java @@ -0,0 +1,51 @@ +package com.gsm._8th.class4.backed.task._1._1.domain.controller; + +import com.gsm._8th.class4.backed.task._1._1.domain.DTO.ArticleDTO; +import com.gsm._8th.class4.backed.task._1._1.domain.service.ArticleService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/articles") +@RequiredArgsConstructor +public class ArticleController { + + private final ArticleService articleService; + + @PostMapping + public ResponseEntity posting(@RequestBody ArticleDTO dto) { + articleService.savePost(dto); + return ResponseEntity.status(201).body("Article 리소스 생성에 성공함"); + } + + + @GetMapping + public ResponseEntity> read() { + List list = articleService.findList(); + return ResponseEntity.ok(list); + } + + @GetMapping("/{idx}") + public ResponseEntity reading(@PathVariable Long idx) { + ArticleDTO post = articleService.readPost(idx); + return ResponseEntity.ok(post); + } + + @PatchMapping("/{idx}") + public ResponseEntity sujeong( + @PathVariable Long idx, + @RequestBody ArticleDTO dto) { + articleService.sujeong(idx, dto); + return ResponseEntity.ok("게시글이 수정되었습니다."); + } + + @DeleteMapping("/{idx}") + public ResponseEntity deleteByTitle(@PathVariable Long idx) { + articleService.sakjeByTitle(idx); + return ResponseEntity.noContent().build(); + } +} + diff --git a/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/entity/ArticleEntity.java b/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/entity/ArticleEntity.java new file mode 100644 index 0000000..1718ebe --- /dev/null +++ b/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/entity/ArticleEntity.java @@ -0,0 +1,25 @@ +package com.gsm._8th.class4.backed.task._1._1.domain.entity; + +import com.gsm._8th.class4.backed.task._1._1.global.entity.BaseIdxEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.*; + +@Entity +@Table(name = "articles") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ArticleEntity extends BaseIdxEntity { + + @Column(nullable = false) + private String title; + + @Column(nullable = false) + private String content; +} + diff --git a/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/exception/GlobalExceptionHandler.java b/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..ebc1b10 --- /dev/null +++ b/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/exception/GlobalExceptionHandler.java @@ -0,0 +1,15 @@ +package com.gsm._8th.class4.backed.task._1._1.domain.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleNotFound(IllegalArgumentException ex) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage()); + } +} diff --git a/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/repository/ArticleRepository.java b/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/repository/ArticleRepository.java new file mode 100644 index 0000000..4e4fefe --- /dev/null +++ b/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/repository/ArticleRepository.java @@ -0,0 +1,8 @@ +package com.gsm._8th.class4.backed.task._1._1.domain.repository; + +import com.gsm._8th.class4.backed.task._1._1.domain.entity.ArticleEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ArticleRepository extends JpaRepository { +} + diff --git a/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/service/ArticleService.java b/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/service/ArticleService.java new file mode 100644 index 0000000..71afc4b --- /dev/null +++ b/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/domain/service/ArticleService.java @@ -0,0 +1,80 @@ +package com.gsm._8th.class4.backed.task._1._1.domain.service; + +import com.gsm._8th.class4.backed.task._1._1.domain.DTO.ArticleDTO; +import com.gsm._8th.class4.backed.task._1._1.domain.entity.ArticleEntity; +import com.gsm._8th.class4.backed.task._1._1.domain.repository.ArticleRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + + +@Service +@Transactional +@RequiredArgsConstructor +public class ArticleService { + private final ArticleRepository articleRepository; + + public void savePost(ArticleDTO ArticleDTO) { + ArticleEntity articleEntity = ArticleEntity.builder() + .title(ArticleDTO.getTitle()) + .content(ArticleDTO.getContent()) + .build(); + articleRepository.save(articleEntity); + } + + public List findList() { + List posts = articleRepository.findAll(); + if (posts.isEmpty()) { + return Collections.emptyList(); // 데이터가 없으면 200 OK + 빈 리스트([]) 반환 + } + return posts.stream() + .map(post -> new ArticleDTO( + post.getIdx(), + post.getTitle(), + post.getContent(), + post.getCreatedAt(), + post.getUpdatedAt() + )) + .collect(Collectors.toList()); + } + + public ArticleDTO readPost(Long idx) { + ArticleEntity post = articleRepository.findById(idx) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 게시글입니다.")); + + return new ArticleDTO( + post.getIdx(), + post.getTitle(), + post.getContent(), + post.getCreatedAt(), + post.getUpdatedAt() + ); + } + + public void sujeong(Long idx, ArticleDTO dto) { + ArticleEntity post = articleRepository.findById(idx) + .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다.")); + + post.setTitle(dto.getTitle()); + post.setContent(dto.getContent()); + + articleRepository.save(post); + } + + public void sakjeByTitle(Long idx) { + ArticleEntity post = articleRepository.findById(idx) + .orElseThrow(() -> new IllegalArgumentException("해당 ID의 게시글이 존재하지 않습니다.")); + + articleRepository.delete(post); + } + + + + +} + diff --git a/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/global/entity/BaseIdxEntity.java b/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/global/entity/BaseIdxEntity.java index 72bf006..0b286af 100644 --- a/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/global/entity/BaseIdxEntity.java +++ b/src/main/java/com/gsm/_8th/class4/backed/task/_1/_1/global/entity/BaseIdxEntity.java @@ -7,7 +7,7 @@ @Getter @MappedSuperclass -abstract class BaseIdxEntity { +public abstract class BaseIdxEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "idx", nullable = false, updatable = false, insertable = false, unique = true) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 743a92a..549aa46 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -3,13 +3,16 @@ spring: name: task.1-1 datasource: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://${RDB_HOST:localhost}:${RDB_PORT:3306}/${RDB_NAME:task_1_1} + url: jdbc:mysql://${RDB_HOST:localhost}:${RDB_PORT:3306}/${RDB_NAME:task} username: ${RDB_USER:root} - password: ${RDB_PASSWORD:123456} + password: ${RDB_PASSWORD} jpa: hibernate: ddl-auto: ${DDL_AUTO:update} show-sql: true properties: hibernate: - dialect: org.hibernate.dialect.MySQL8Dialect \ No newline at end of file + dialect: org.hibernate.dialect.MySQL8Dialect + jackson: + deserialization: + fail-on-unknown-properties: true \ No newline at end of file