From de6b49ce24e1654c13358a1d61727b1b9a8795bc Mon Sep 17 00:00:00 2001 From: tmdals0429 Date: Thu, 9 Oct 2025 17:15:06 +0900 Subject: [PATCH 1/3] db complete --- .gitignore | 1 + build.gradle | 8 +++++--- src/main/resources/application.properties | 12 ++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index c2065bc..9bd38c3 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ out/ ### VS Code ### .vscode/ +.env \ No newline at end of file diff --git a/build.gradle b/build.gradle index 610d6a6..8f83701 100644 --- a/build.gradle +++ b/build.gradle @@ -19,9 +19,11 @@ repositories { } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-web' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + runtimeOnly 'com.mysql:mysql-connector-j' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f3f10af..499d539 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,13 @@ spring.application.name=devSns + +# DB ???? (?????? ??) +spring.datasource.url=${DATASOURCE_URL} +spring.datasource.username=${DATASOURCE_USERNAME} +spring.datasource.password=${DATASOURCE_PASSWORD} +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.hikari.connection-timeout=5000 + +# JPA ?? +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect \ No newline at end of file From aa62b7b116dd7d51a97bf6f57764d0b353900c2f Mon Sep 17 00:00:00 2001 From: tmdals0429 Date: Mon, 3 Nov 2025 15:04:43 +0900 Subject: [PATCH 2/3] =?UTF-8?q?1=EC=A3=BC=EC=B0=A8=20=EA=B3=BC=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/devSns/task/Task.java | 45 ++++++++++++++++++ .../example/devSns/task/TaskController.java | 46 +++++++++++++++++++ .../com/example/devSns/task/TaskNotFound.java | 5 ++ .../example/devSns/task/TaskRepository.java | 8 ++++ .../com/example/devSns/task/TaskService.java | 46 +++++++++++++++++++ .../example/devSns/task/dto/TaskRequest.java | 14 ++++++ .../example/devSns/task/dto/TaskResponse.java | 24 ++++++++++ 7 files changed, 188 insertions(+) create mode 100644 src/main/java/com/example/devSns/task/Task.java create mode 100644 src/main/java/com/example/devSns/task/TaskController.java create mode 100644 src/main/java/com/example/devSns/task/TaskNotFound.java create mode 100644 src/main/java/com/example/devSns/task/TaskRepository.java create mode 100644 src/main/java/com/example/devSns/task/TaskService.java create mode 100644 src/main/java/com/example/devSns/task/dto/TaskRequest.java create mode 100644 src/main/java/com/example/devSns/task/dto/TaskResponse.java diff --git a/src/main/java/com/example/devSns/task/Task.java b/src/main/java/com/example/devSns/task/Task.java new file mode 100644 index 0000000..b08ebf1 --- /dev/null +++ b/src/main/java/com/example/devSns/task/Task.java @@ -0,0 +1,45 @@ +//Task을 DB 테이블과 연결하는 엔티티 + +package com.example.devSns.task; + +import jakarta.persistence.*; +import java.time.LocalDate; + +@Entity //jpa 엔터티로 선언 +@Table(name = "tasks") //이름이 tasks 인 테이블 명시. +public class Task { + + public enum Status { TODO, IN_PROGRESS, DONE } + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String title; + + @Column(length = 2000) + private String description; + + private LocalDate dueDate; + + private Integer priority; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Status status = Status.TODO; + + protected Task() {} // JPA 기본생성자 + + // getter/setter + public Long getId() { return id; } + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + public LocalDate getDueDate() { return dueDate; } + public void setDueDate(LocalDate dueDate) { this.dueDate = dueDate; } + public Integer getPriority() { return priority; } + public void setPriority(Integer priority) { this.priority = priority; } + public Status getStatus() { return status; } + public void setStatus(Status status) { this.status = status; } +} diff --git a/src/main/java/com/example/devSns/task/TaskController.java b/src/main/java/com/example/devSns/task/TaskController.java new file mode 100644 index 0000000..e9919db --- /dev/null +++ b/src/main/java/com/example/devSns/task/TaskController.java @@ -0,0 +1,46 @@ +//HTTP 요청을 받아 서비스 호출과 응답 반환을 담당하는 API + + +package com.example.devSns.task; +import com.example.devSns.task.dto.TaskRequest; +import com.example.devSns.task.dto.TaskResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; +import java.util.List; + +@RestController +@RequestMapping("/api/tasks") +public class TaskController { + private final TaskService service; + public TaskController(TaskService service) { this.service = service; } + + @PostMapping + public ResponseEntity create(@RequestBody TaskRequest req) { + Task saved = service.create(req); + return ResponseEntity.created(URI.create("/api/tasks/" + saved.getId())) + .body(new TaskResponse(saved)); + } + + @GetMapping + public List list() { + return service.findAll().stream().map(TaskResponse::new).toList(); + } + + @GetMapping("/{id}") + public TaskResponse get(@PathVariable Long id) { + return new TaskResponse(service.findById(id)); + } + + @PutMapping("/{id}") + public TaskResponse update(@PathVariable Long id, @RequestBody TaskRequest req) { + return new TaskResponse(service.update(id, req)); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable Long id) { + service.delete(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/com/example/devSns/task/TaskNotFound.java b/src/main/java/com/example/devSns/task/TaskNotFound.java new file mode 100644 index 0000000..7066533 --- /dev/null +++ b/src/main/java/com/example/devSns/task/TaskNotFound.java @@ -0,0 +1,5 @@ +package com.example.devSns.task; + +public class TaskNotFound extends RuntimeException { + public TaskNotFound(Long id) { super("Task not found: " + id); } +} diff --git a/src/main/java/com/example/devSns/task/TaskRepository.java b/src/main/java/com/example/devSns/task/TaskRepository.java new file mode 100644 index 0000000..e8cf71c --- /dev/null +++ b/src/main/java/com/example/devSns/task/TaskRepository.java @@ -0,0 +1,8 @@ +//DB에 저장·조회·삭제 등을 대신 처리 + +package com.example.devSns.task; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TaskRepository extends JpaRepository { } +//crud 메소드 자동 제공 \ No newline at end of file diff --git a/src/main/java/com/example/devSns/task/TaskService.java b/src/main/java/com/example/devSns/task/TaskService.java new file mode 100644 index 0000000..0362a94 --- /dev/null +++ b/src/main/java/com/example/devSns/task/TaskService.java @@ -0,0 +1,46 @@ +//컨트롤러와 DB 사이의 중간 관리자 + +package com.example.devSns.task; + +import com.example.devSns.task.dto.TaskRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional +public class TaskService { + private final TaskRepository repo; + public TaskService(TaskRepository repo) { this.repo = repo; } + + public Task create(TaskRequest r) { + Task t = new Task(); + if (r.title != null) t.setTitle(r.title); + t.setDescription(r.description); + t.setDueDate(r.dueDate); + t.setPriority(r.priority); + t.setStatus(r.status == null ? Task.Status.TODO : r.status); + return repo.save(t); + } + + @Transactional(readOnly = true) + public List findAll() { return repo.findAll(); } + + @Transactional(readOnly = true) + public Task findById(Long id) { // 값이 없으면 TaskNotFound + return repo.findById(id).orElseThrow(() -> new TaskNotFound(id)); + } + + public Task update(Long id, TaskRequest r) { //부분 수정 가능 + Task t = findById(id); + if (r.title != null) t.setTitle(r.title); + if (r.description != null) t.setDescription(r.description); + if (r.dueDate != null) t.setDueDate(r.dueDate); + if (r.priority != null) t.setPriority(r.priority); + if (r.status != null) t.setStatus(r.status); + return t; // JPA dirty checking + } + + public void delete(Long id) { repo.delete(findById(id)); } +} // 존재하지 않으면 TaskNotFound diff --git a/src/main/java/com/example/devSns/task/dto/TaskRequest.java b/src/main/java/com/example/devSns/task/dto/TaskRequest.java new file mode 100644 index 0000000..0449e26 --- /dev/null +++ b/src/main/java/com/example/devSns/task/dto/TaskRequest.java @@ -0,0 +1,14 @@ +//client가 보낸 json 요청 받기 위한 dto + +package com.example.devSns.task.dto; + +import com.example.devSns.task.Task; +import java.time.LocalDate; + +public class TaskRequest { // client 가 보낸 json 담는 곳 + public String title; + public String description; + public LocalDate dueDate; + public Integer priority; + public Task.Status status; +} diff --git a/src/main/java/com/example/devSns/task/dto/TaskResponse.java b/src/main/java/com/example/devSns/task/dto/TaskResponse.java new file mode 100644 index 0000000..a47d204 --- /dev/null +++ b/src/main/java/com/example/devSns/task/dto/TaskResponse.java @@ -0,0 +1,24 @@ +//DB에서 가져온 엔티티를 clinet에게 보낼 형식으로 만드는 DTO + +package com.example.devSns.task.dto; + +import com.example.devSns.task.Task; +import java.time.LocalDate; + +public class TaskResponse { //외부에 노출할 필드만 담는 상자 + public Long id; + public String title; + public String description; + public LocalDate dueDate; + public Integer priority; + public Task.Status status; + + public TaskResponse(Task t) { //복사 + this.id = t.getId(); + this.title = t.getTitle(); + this.description = t.getDescription(); + this.dueDate = t.getDueDate(); + this.priority = t.getPriority(); + this.status = t.getStatus(); + } +} From 2e9e9bf2d2391ee7002d759d503843615b40a70e Mon Sep 17 00:00:00 2001 From: tmdals0429 Date: Thu, 6 Nov 2025 00:09:42 +0900 Subject: [PATCH 3/3] =?UTF-8?q?comment=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/devSns/comment/Comment.java | 37 +++++++++++++++++++ .../devSns/comment/CommentController.java | 37 +++++++++++++++++++ .../devSns/comment/CommentRepository.java | 8 ++++ .../devSns/comment/CommentService.java | 36 ++++++++++++++++++ .../devSns/comment/dto/CommentRequest.java | 24 ++++++++++++ .../devSns/comment/dto/CommentResponse.java | 23 ++++++++++++ .../java/com/example/devSns/task/Task.java | 17 +++++---- .../example/devSns/task/TaskController.java | 27 +++++++++----- .../example/devSns/task/TaskRepository.java | 3 +- .../example/devSns/task/dto/TaskRequest.java | 2 +- .../example/devSns/task/dto/TaskResponse.java | 4 +- 11 files changed, 196 insertions(+), 22 deletions(-) create mode 100644 src/main/java/com/example/devSns/comment/Comment.java create mode 100644 src/main/java/com/example/devSns/comment/CommentController.java create mode 100644 src/main/java/com/example/devSns/comment/CommentRepository.java create mode 100644 src/main/java/com/example/devSns/comment/CommentService.java create mode 100644 src/main/java/com/example/devSns/comment/dto/CommentRequest.java create mode 100644 src/main/java/com/example/devSns/comment/dto/CommentResponse.java diff --git a/src/main/java/com/example/devSns/comment/Comment.java b/src/main/java/com/example/devSns/comment/Comment.java new file mode 100644 index 0000000..39b1273 --- /dev/null +++ b/src/main/java/com/example/devSns/comment/Comment.java @@ -0,0 +1,37 @@ +package com.example.devSns.comment; + +import com.example.devSns.task.Task; +import jakarta.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "comments") +public class Comment { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 1000) + private String content; + + private String username; + private LocalDateTime createdAt = LocalDateTime.now(); + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "task_id") + private Task task; + + protected Comment() {} + + public Comment(String content, String username, Task task) { + this.content = content; + this.username = username; + this.task = task; + } + + public Long getId() { return id; } + public String getContent() { return content; } + public String getUsername() { return username; } + public LocalDateTime getCreatedAt() { return createdAt; } + public Task getTask() { return task; } +} diff --git a/src/main/java/com/example/devSns/comment/CommentController.java b/src/main/java/com/example/devSns/comment/CommentController.java new file mode 100644 index 0000000..27c85a4 --- /dev/null +++ b/src/main/java/com/example/devSns/comment/CommentController.java @@ -0,0 +1,37 @@ +package com.example.devSns.comment; + +import com.example.devSns.comment.dto.CommentRequest; +import com.example.devSns.comment.dto.CommentResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import java.util.List; + +@RestController +@RequestMapping("/api/tasks/{taskId}/comments") +public class CommentController { + private final CommentService service; + + public CommentController(CommentService service) { + this.service = service; + } + + @PostMapping + public ResponseEntity create( + @PathVariable Long taskId, + @RequestBody CommentRequest req) { + return ResponseEntity.ok(service.create(taskId, req)); + } + + @GetMapping + public List list(@PathVariable Long taskId) { + return service.findByTask(taskId); + } + + @DeleteMapping("/{commentId}") + public ResponseEntity delete( + @PathVariable Long taskId, + @PathVariable Long commentId) { + service.delete(commentId); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/com/example/devSns/comment/CommentRepository.java b/src/main/java/com/example/devSns/comment/CommentRepository.java new file mode 100644 index 0000000..e32d79a --- /dev/null +++ b/src/main/java/com/example/devSns/comment/CommentRepository.java @@ -0,0 +1,8 @@ +package com.example.devSns.comment; + +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + +public interface CommentRepository extends JpaRepository { + List findByTaskId(Long taskId); +} diff --git a/src/main/java/com/example/devSns/comment/CommentService.java b/src/main/java/com/example/devSns/comment/CommentService.java new file mode 100644 index 0000000..6179550 --- /dev/null +++ b/src/main/java/com/example/devSns/comment/CommentService.java @@ -0,0 +1,36 @@ +package com.example.devSns.comment; + +import com.example.devSns.comment.dto.CommentRequest; +import com.example.devSns.comment.dto.CommentResponse; +import com.example.devSns.task.Task; +import com.example.devSns.task.TaskRepository; +import org.springframework.stereotype.Service; +import java.util.List; + +@Service +public class CommentService { + private final CommentRepository commentRepo; + private final TaskRepository taskRepo; + + public CommentService(CommentRepository commentRepo, TaskRepository taskRepo) { + this.commentRepo = commentRepo; + this.taskRepo = taskRepo; + } + + public CommentResponse create(Long taskId, CommentRequest req) { + Task task = taskRepo.findById(taskId) + .orElseThrow(() -> new IllegalArgumentException("Task not found")); + Comment comment = new Comment(req.getContent(), req.getUsername(), task); + Comment saved = commentRepo.save(comment); + return new CommentResponse(saved); + } + + public List findByTask(Long taskId) { + return commentRepo.findByTaskId(taskId) + .stream().map(CommentResponse::new).toList(); + } + + public void delete(Long commentId) { + commentRepo.deleteById(commentId); + } +} diff --git a/src/main/java/com/example/devSns/comment/dto/CommentRequest.java b/src/main/java/com/example/devSns/comment/dto/CommentRequest.java new file mode 100644 index 0000000..dcddd5d --- /dev/null +++ b/src/main/java/com/example/devSns/comment/dto/CommentRequest.java @@ -0,0 +1,24 @@ +package com.example.devSns.comment.dto; + +public class CommentRequest { + private String content; + private String username; + + public CommentRequest() {} + + public String getContent() { + return content; + } + + public void setContent(String content) { // ← 이거 추가 + this.content = content; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { // ← 이거 추가 + this.username = username; + } +} diff --git a/src/main/java/com/example/devSns/comment/dto/CommentResponse.java b/src/main/java/com/example/devSns/comment/dto/CommentResponse.java new file mode 100644 index 0000000..6e6eacb --- /dev/null +++ b/src/main/java/com/example/devSns/comment/dto/CommentResponse.java @@ -0,0 +1,23 @@ +package com.example.devSns.comment.dto; + +import com.example.devSns.comment.Comment; +import java.time.LocalDateTime; + +public class CommentResponse { + private Long id; + private String content; + private String username; + private LocalDateTime createdAt; + + public CommentResponse(Comment comment) { + this.id = comment.getId(); + this.content = comment.getContent(); + this.username = comment.getUsername(); + this.createdAt = comment.getCreatedAt(); + } + + public Long getId() { return id; } + public String getContent() { return content; } + public String getUsername() { return username; } + public LocalDateTime getCreatedAt() { return createdAt; } +} diff --git a/src/main/java/com/example/devSns/task/Task.java b/src/main/java/com/example/devSns/task/Task.java index b08ebf1..73d05b9 100644 --- a/src/main/java/com/example/devSns/task/Task.java +++ b/src/main/java/com/example/devSns/task/Task.java @@ -1,12 +1,13 @@ -//Task을 DB 테이블과 연결하는 엔티티 - package com.example.devSns.task; +import com.example.devSns.comment.Comment; import jakarta.persistence.*; import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; -@Entity //jpa 엔터티로 선언 -@Table(name = "tasks") //이름이 tasks 인 테이블 명시. +@Entity +@Table(name = "tasks") public class Task { public enum Status { TODO, IN_PROGRESS, DONE } @@ -21,16 +22,17 @@ public enum Status { TODO, IN_PROGRESS, DONE } private String description; private LocalDate dueDate; - private Integer priority; @Enumerated(EnumType.STRING) @Column(nullable = false) private Status status = Status.TODO; - protected Task() {} // JPA 기본생성자 + @OneToMany(mappedBy = "task", cascade = CascadeType.ALL, orphanRemoval = true) + private List comments = new ArrayList<>(); + + protected Task() {} - // getter/setter public Long getId() { return id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } @@ -42,4 +44,5 @@ protected Task() {} // JPA 기본생성자 public void setPriority(Integer priority) { this.priority = priority; } public Status getStatus() { return status; } public void setStatus(Status status) { this.status = status; } + public List getComments() { return comments; } } diff --git a/src/main/java/com/example/devSns/task/TaskController.java b/src/main/java/com/example/devSns/task/TaskController.java index e9919db..1b834ef 100644 --- a/src/main/java/com/example/devSns/task/TaskController.java +++ b/src/main/java/com/example/devSns/task/TaskController.java @@ -1,7 +1,5 @@ -//HTTP 요청을 받아 서비스 호출과 응답 반환을 담당하는 API - - package com.example.devSns.task; + import com.example.devSns.task.dto.TaskRequest; import com.example.devSns.task.dto.TaskResponse; import org.springframework.http.ResponseEntity; @@ -13,8 +11,12 @@ @RestController @RequestMapping("/api/tasks") public class TaskController { + private final TaskService service; - public TaskController(TaskService service) { this.service = service; } + + public TaskController(TaskService service) { + this.service = service; + } @PostMapping public ResponseEntity create(@RequestBody TaskRequest req) { @@ -24,18 +26,23 @@ public ResponseEntity create(@RequestBody TaskRequest req) { } @GetMapping - public List list() { - return service.findAll().stream().map(TaskResponse::new).toList(); + public ResponseEntity> list() { + List tasks = service.findAll().stream() + .map(TaskResponse::new) + .toList(); + return ResponseEntity.ok(tasks); } @GetMapping("/{id}") - public TaskResponse get(@PathVariable Long id) { - return new TaskResponse(service.findById(id)); + public ResponseEntity get(@PathVariable Long id) { + Task found = service.findById(id); + return ResponseEntity.ok(new TaskResponse(found)); } @PutMapping("/{id}") - public TaskResponse update(@PathVariable Long id, @RequestBody TaskRequest req) { - return new TaskResponse(service.update(id, req)); + public ResponseEntity update(@PathVariable Long id, @RequestBody TaskRequest req) { + Task updated = service.update(id, req); + return ResponseEntity.ok(new TaskResponse(updated)); } @DeleteMapping("/{id}") diff --git a/src/main/java/com/example/devSns/task/TaskRepository.java b/src/main/java/com/example/devSns/task/TaskRepository.java index e8cf71c..3e9aa55 100644 --- a/src/main/java/com/example/devSns/task/TaskRepository.java +++ b/src/main/java/com/example/devSns/task/TaskRepository.java @@ -4,5 +4,4 @@ import org.springframework.data.jpa.repository.JpaRepository; -public interface TaskRepository extends JpaRepository { } -//crud 메소드 자동 제공 \ No newline at end of file +public interface TaskRepository extends JpaRepository { } \ No newline at end of file diff --git a/src/main/java/com/example/devSns/task/dto/TaskRequest.java b/src/main/java/com/example/devSns/task/dto/TaskRequest.java index 0449e26..d43ba4c 100644 --- a/src/main/java/com/example/devSns/task/dto/TaskRequest.java +++ b/src/main/java/com/example/devSns/task/dto/TaskRequest.java @@ -5,7 +5,7 @@ import com.example.devSns.task.Task; import java.time.LocalDate; -public class TaskRequest { // client 가 보낸 json 담는 곳 +public class TaskRequest { public String title; public String description; public LocalDate dueDate; diff --git a/src/main/java/com/example/devSns/task/dto/TaskResponse.java b/src/main/java/com/example/devSns/task/dto/TaskResponse.java index a47d204..23d5549 100644 --- a/src/main/java/com/example/devSns/task/dto/TaskResponse.java +++ b/src/main/java/com/example/devSns/task/dto/TaskResponse.java @@ -5,7 +5,7 @@ import com.example.devSns.task.Task; import java.time.LocalDate; -public class TaskResponse { //외부에 노출할 필드만 담는 상자 +public class TaskResponse { public Long id; public String title; public String description; @@ -13,7 +13,7 @@ public class TaskResponse { //외부에 노출할 필드만 담는 상자 public Integer priority; public Task.Status status; - public TaskResponse(Task t) { //복사 + public TaskResponse(Task t) { this.id = t.getId(); this.title = t.getTitle(); this.description = t.getDescription();