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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
jacoco/

### STS ###
.apt_generated
Expand Down
41 changes: 39 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
id 'java'
id 'org.springframework.boot' version '3.1.5'
id 'io.spring.dependency-management' version '1.1.3'
id 'jacoco'
}

group = 'site.yapp.study'
Expand All @@ -20,17 +21,53 @@ dependencies {
implementation 'org.projectlombok:lombok:1.18.26'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// Spring Boot ์„ค์ •
//implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
// Database ์„ค์ •
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
// Lombok ์„ค์ •
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// test ์„ค์ •
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'com.mysql:mysql-connector-j'
// Swagger ์„ค์ •
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๋ฒ„์ „์„ ์ƒ์ˆ˜๋กœ ๊ด€๋ฆฌํ•˜๋ฉด, ๋‚˜์ค‘์— ํŽธํ•ด์š”,

}

tasks.named('test') {
jacoco {
toolVersion = '0.8.7'
}

jacocoTestReport {
reports {
html.required.set(true)
xml.required.set(false)
csv.required.set(false)

html.destination file("jacoco/jacocoHtml")
xml.destination file("jacoco/jacoco.xml")
}
}

jacocoTestCoverageVerification {

violationRules {
rule {
// ๋ฃฐ์„ ์ฒดํฌํ•  ๋‹จ์œ„
element = 'CLASS'

// ๋ธŒ๋žœ์น˜ ์ปค๋ฒ„๋ฆฌ์ง€ ์ตœ์†Œ 90% ๋ฏผ์กฑ
limit {
counter = 'BRANCH'
value = 'COVEREDRATIO'
minimum = 0.90
}
}
}
}

test {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package site.yapp.study.todolist.api.todo.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import site.yapp.study.todolist.api.todo.dto.request.TodoBulkCreateRequestDto;
import site.yapp.study.todolist.api.todo.dto.request.TodoCreateRequestDto;
import site.yapp.study.todolist.api.todo.dto.request.TodoUpdateRequestDto;
import site.yapp.study.todolist.api.todo.dto.response.TodoGetResponseDto;
Expand All @@ -16,45 +21,111 @@
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/todos")
@Tag(name = "[Todo] ํˆฌ๋‘๋ฆฌ์ŠคํŠธ ๊ด€๋ จ API (V1)")
public class TodoController {
private final TodoService todoService;

@Operation(summary = "ํ• ์ผ ์ƒ์„ฑ API")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "ํ• ์ผ ์ƒ์„ฑ ์„ฑ๊ณต"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "ํ• ์ผ ์ƒ์„ฑ ์‹คํŒจ", content = @Content),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "500", description = "์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜", content = @Content)
})
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public ApiResponse<Object> createTodo(@RequestBody TodoCreateRequestDto todoCreateRequestDto) {
todoService.createTodo(todoCreateRequestDto);
return ApiResponse.success(SuccessCode.CREATE_TODO_SUCCESS);
}

@Operation(summary = "๋ฒŒํฌ ํ• ์ผ ์ƒ์„ฑ API")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "๋ฒŒํฌ ํ• ์ผ ์ƒ์„ฑ ์„ฑ๊ณต"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "๋ฒŒํฌ ํ• ์ผ ์ƒ์„ฑ ์‹คํŒจ", content = @Content),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "500", description = "์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜", content = @Content)
})
@PostMapping("/bulk")
@ResponseStatus(HttpStatus.CREATED)
public ApiResponse<Object> createBulkTodo(@RequestBody TodoBulkCreateRequestDto todoBulkCreateRequestDto) {
todoService.createBulkTodo(todoBulkCreateRequestDto);
return ApiResponse.success(SuccessCode.CREATE_BULK_TODO_SUCCESS);
}

@Operation(summary = "ํ• ์ผ ์ „์ฒด ์กฐํšŒ API")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "ํ• ์ผ ์ „์ฒด ์กฐํšŒ ์„ฑ๊ณต"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "ํ• ์ผ ์ „์ฒด ์กฐํšŒ ์‹คํŒจ", content = @Content),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "500", description = "์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜", content = @Content)
})
@GetMapping
@ResponseStatus(HttpStatus.OK)
public ApiResponse<List<TodoGetResponseDto>> getAllTodo() {
List<TodoGetResponseDto> response = todoService.getAllTodo();
return ApiResponse.success(SuccessCode.GET_TODO_SUCCESS, response);
}

@Operation(summary = "ํ• ์ผ ๊ฐœ๋ณ„ ์กฐํšŒ API")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "ํ• ์ผ ๊ฐœ๋ณ„ ์กฐํšŒ ์„ฑ๊ณต"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "ํ• ์ผ ๊ฐœ๋ณ„ ์กฐํšŒ ์‹คํŒจ", content = @Content),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "์กด์žฌํ•˜์ง€ ์•Š๋Š” ํ• ์ผ", content = @Content),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "500", description = "์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜", content = @Content)
})
@GetMapping("/{todoId}")
@ResponseStatus(HttpStatus.OK)
public ApiResponse<TodoGetResponseDto> getEachTodo(@PathVariable Long todoId) {
TodoGetResponseDto response = todoService.getEachTodo(todoId);
return ApiResponse.success(SuccessCode.GET_EACH_TODO_SUCCESS, response);
}

@Operation(summary = "ํ• ์ผ ์ˆ˜์ • API")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "ํ• ์ผ ์ˆ˜์ • ์„ฑ๊ณต"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "ํ• ์ผ ์ˆ˜์ • ์‹คํŒจ", content = @Content),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "์กด์žฌํ•˜์ง€ ์•Š๋Š” ํ• ์ผ", content = @Content),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "500", description = "์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜", content = @Content)
})
@PatchMapping("/{todoId}")
public ApiResponse<Object> updateTodo(@PathVariable Long todoId, @RequestBody TodoUpdateRequestDto todoUpdateRequestDto) {
todoService.updateTodo(todoId, todoUpdateRequestDto);
return ApiResponse.success(SuccessCode.UPDATE_TODO_SUCCESS);
}

@Operation(summary = "ํ• ์ผ ์‚ญ์ œ API")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "ํ• ์ผ ์‚ญ์ œ ์„ฑ๊ณต"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "ํ• ์ผ ์‚ญ์ œ ์‹คํŒจ", content = @Content),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "์กด์žฌํ•˜์ง€ ์•Š๋Š” ํ• ์ผ", content = @Content),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "500", description = "์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜", content = @Content)
})
@DeleteMapping("/{todoId}")
public ApiResponse<Object> deleteTodo(@PathVariable Long todoId) {
todoService.deleteTodo(todoId);
return ApiResponse.success(SuccessCode.DELETE_TODO_SUCCESS);
}

@Operation(summary = "ํ• ์ผ ์™„๋ฃŒ ํ† ๊ธ€ ๋ณ€๊ฒฝ API")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "ํ• ์ผ ์™„๋ฃŒ ํ† ๊ธ€ ๋ณ€๊ฒฝ ์„ฑ๊ณต"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "ํ• ์ผ ์™„๋ฃŒ ํ† ๊ธ€ ๋ณ€๊ฒฝ ์‹คํŒจ", content = @Content),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "์กด์žฌํ•˜์ง€ ์•Š๋Š” ํ• ์ผ", content = @Content),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "500", description = "์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜", content = @Content)
})
@PatchMapping("/toggle/{todoId}")
public ApiResponse<TodoToggleGetResponseDto> toggleTodoStatus(@PathVariable Long todoId, @RequestParam Boolean isCompleted) {
TodoToggleGetResponseDto response = todoService.toggleTodoStatus(todoId, isCompleted);
return ApiResponse.success(SuccessCode.TOGGLE_TODO_SUCCESS, response);
}

@Operation(summary = "์กฐํšŒ์ˆ˜ API")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "ํ• ์ผ ์กฐํšŒ์ˆ˜ ์ฆ๊ฐ€ ์„ฑ๊ณต"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "ํ• ์ผ ์กฐํšŒ์ˆ˜ ์ฆ๊ฐ€ ์‹คํŒจ", content = @Content),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "500", description = "์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜", content = @Content)
})
@PatchMapping("/view/{todoId}")
public ApiResponse<Object> updateTodoViewCount(@PathVariable Long todoId) {
todoService.updateTodoViewCount(todoId);
return ApiResponse.success(SuccessCode.UPDATE_TODO_VIEWCOUNT_SUCCESS);
}
}
44 changes: 28 additions & 16 deletions src/main/java/site/yapp/study/todolist/api/todo/domain/Todo.java
Original file line number Diff line number Diff line change
@@ -1,48 +1,60 @@
package site.yapp.study.todolist.api.todo.domain;

import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnDefault;
import org.springframework.data.jpa.repository.Lock;
import site.yapp.study.todolist.common.domain.BaseEntity;
import org.springframework.data.jpa.repository.Query;

import java.util.Date;
import java.time.LocalDateTime;

import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PROTECTED;

@Entity
@Getter
@NoArgsConstructor(access = PROTECTED)
public class Todo {
public class Todo extends BaseEntity {

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

private String category;

private String content;

private Date created_at;
@Column(name = "is_deleted")
private LocalDateTime deletedAt;

private Date updated_at;
private boolean isCompleted;

private Date deleted_at;

private boolean is_completed;
@ColumnDefault("0")
private Integer viewCount = 0;

@Builder
public Todo(Long id, String category, String content, Date created_at, Date updated_at) {
this.id = id;
public Todo(String category, String content) {
this.category = category;
this.content = content;
this.created_at = created_at;
this.updated_at = updated_at;
this.deleted_at = null;
this.is_completed = false;
this.deletedAt = null;
this.isCompleted = false;
}

public void updateTodo(String category, String content) {
this.category= category;
this.category = category;
this.content = content;
}

public void toggleTodo(Boolean is_completed) {
this.is_completed = is_completed;
public void toggleTodo(Boolean isCompleted) {
this.isCompleted = isCompleted;
}

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("UPDATE Todo t SET t.viewCount = t.viewCount + 1 WHERE t.id = :todoId")
public void updateViewCount() {
viewCount++;
}
Comment on lines +55 to 59
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Db์— ๋ฝ์„ ๊ฑธ๋ช… ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋Š”?
๊ทธ๋ ‡๋‹ค๋ฉด, ๊ทธ๊ฒƒ์„ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ๋Š”!

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package site.yapp.study.todolist.api.todo.dto.request;

import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@NoArgsConstructor
@Getter
public class TodoBulkCreateRequestDto {

private List<TodoCreateRequestDto> todoList;
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
package site.yapp.study.todolist.api.todo.dto.request;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@AllArgsConstructor(staticName = "of")
import static lombok.AccessLevel.PROTECTED;

@NoArgsConstructor(access = PROTECTED)
@Getter
public class TodoCreateRequestDto {

private String category;

private String content;

@Builder
public TodoCreateRequestDto(String category, String content) {
this.category = category;
this.content = content;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ public class TodoGetResponseDto {

private boolean isCompleted;

private Integer viewCount;

public static TodoGetResponseDto of(Todo todo) {
return TodoGetResponseDto.builder()
.todoId(todo.getId())
.category(todo.getCategory())
.content(todo.getContent())
.isCompleted(todo.is_completed())
.isCompleted(todo.isCompleted())
.viewCount(todo.getViewCount())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class TodoToggleGetResponseDto {
public static TodoToggleGetResponseDto of(Todo todo) {
return TodoToggleGetResponseDto.builder()
.todoId(todo.getId())
.isCompleted(todo.is_completed())
.isCompleted(todo.isCompleted())
.build();
}
}
Loading