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
31 changes: 31 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: CI/CD Build and Deploy

on:
push:
branches: [ main ]

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout source
uses: actions/checkout@v3

- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'

- name: Grant execute permission for gradlew
run: chmod +x ./gradlew

- name: Build with Gradle
run: ./gradlew clean build

- name: Docker build
run: docker build -t todo-upgrade-app .

- name: Docker image 확인
run: docker images
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
runtimeOnly 'com.mysql:mysql-connector-j'
implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0'
implementation 'org.hibernate.orm:hibernate-core:6.2.7.Final'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'

}

tasks.named('test') {
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/com/todolist/config/OpenApiConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.todolist.config;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OpenApiConfig {

@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("Todo List API")
.version("1.0.0")
.description("공유 투두 리스트 프로젝트용 Swagger 명세서"));
}
}
62 changes: 62 additions & 0 deletions src/main/java/com/todolist/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.todolist.controller;

import com.todolist.entity.User;
import com.todolist.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import com.todolist.entity.Todo;
import java.util.Map;

@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {

private final UserService userService;

@PostMapping
public ResponseEntity<User> createUser(@RequestBody Map<String, String> body) {
String username = body.get("username");
return ResponseEntity.status(201).body(userService.createUser(username));
}

@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
return ResponseEntity.ok(userService.findAllUsers());
}

@PostMapping("/{followerId}/follow/{followeeId}")
public ResponseEntity<Void> followUser(@PathVariable Long followerId, @PathVariable Long followeeId) {
userService.followUser(followerId, followeeId);
return ResponseEntity.ok().build();
}

@DeleteMapping("/{followerId}/unfollow/{followeeId}")
public ResponseEntity<Void> unfollowUser(@PathVariable Long followerId, @PathVariable Long followeeId) {
userService.unfollowUser(followerId, followeeId);
return ResponseEntity.ok().build();
}

@GetMapping("/{id}/following")
public ResponseEntity<List<User>> getFollowing(@PathVariable Long id) {
return ResponseEntity.ok(userService.getFollowing(id));
}

@GetMapping("/{id}/followers")
public ResponseEntity<List<User>> getFollowers(@PathVariable Long id) {
return ResponseEntity.ok(userService.getFollowers(id));
}

@GetMapping("/{id}/todos")
public ResponseEntity<List<Todo>> getUserTodos(@PathVariable Long id, @RequestParam(required = false, defaultValue = "false") boolean increaseView) {
return ResponseEntity.ok(userService.getUserTodos(id, increaseView));
}

@GetMapping("/{id}/my-todos")
public ResponseEntity<List<Todo>> getMyTodos(@PathVariable Long id) {
return ResponseEntity.ok(userService.getMyTodos(id));
}
}
28 changes: 28 additions & 0 deletions src/main/java/com/todolist/entity/Todo.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.JoinColumn;
import com.todolist.entity.User;

@Entity
public class Todo {
Expand All @@ -16,6 +19,12 @@ public class Todo {
@Enumerated(EnumType.STRING)
private TodoStatus status = TodoStatus.PENDING;

@ManyToOne
@JoinColumn(name = "user_id")
private User user;

private int viewCount = 0;

@OneToMany(mappedBy = "todo", cascade = CascadeType.ALL, orphanRemoval = true)
private List<SubTask> subTasks = new ArrayList<>();

Expand All @@ -42,4 +51,23 @@ public String getTitle() {
public TodoStatus getStatus() {
return status;
}

public User getUser() {
return user;
}

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

public int getViewCount() {
return viewCount;
}

public void increaseViewCount() {
this.viewCount++;
}
public List<SubTask> getSubTasks() {
return subTasks;
}
}
70 changes: 70 additions & 0 deletions src/main/java/com/todolist/entity/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.todolist.entity;

import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
public class User {

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

private String username;

@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Todo> todos = new ArrayList<>();

@ManyToMany
@JoinTable(
name = "user_following",
joinColumns = @JoinColumn(name = "follower_id"),
inverseJoinColumns = @JoinColumn(name = "followee_id")
)
private List<User> following = new ArrayList<>();

@ManyToMany(mappedBy = "following")
private List<User> followers = new ArrayList<>();

// Getters and setters
public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public List<Todo> getTodos() {
return todos;
}

public void setTodos(List<Todo> todos) {
this.todos = todos;
}

public List<User> getFollowing() {
return following;
}

public void setFollowing(List<User> following) {
this.following = following;
}

public List<User> getFollowers() {
return followers;
}

public void setFollowers(List<User> followers) {
this.followers = followers;
}
}
8 changes: 8 additions & 0 deletions src/main/java/com/todolist/repository/UserRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.todolist.repository;

import com.todolist.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
17 changes: 17 additions & 0 deletions src/main/java/com/todolist/service/TodoService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.todolist.entity.Todo;
import com.todolist.entity.TodoStatus;
import com.todolist.repository.TodoRepository;
import com.todolist.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Expand All @@ -16,6 +17,7 @@
public class TodoService {

private final TodoRepository todoRepository;
private final UserRepository userRepository;

public Todo createTodo(TodoCreateRequest request) {
Todo todo = new Todo();
Expand Down Expand Up @@ -59,4 +61,19 @@ public void deleteTodos(java.util.List<Long> ids) {
}
todoRepository.deleteAll(todos);
}

public java.util.List<Todo> getTodosByUserId(Long userId, boolean increaseViewCount) {
var user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("해당 유저가 존재하지 않습니다."));

java.util.List<Todo> todos = user.getTodos();
if (increaseViewCount) {
for (Todo todo : todos) {
todo.increaseViewCount();
}
todoRepository.saveAll(todos);
}

return todos;
}
}
99 changes: 99 additions & 0 deletions src/main/java/com/todolist/service/UserService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.todolist.service;

import com.todolist.entity.Todo;

import com.todolist.entity.User;
import com.todolist.repository.UserRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
public class UserService {

private final UserRepository userRepository;

public List<User> findAllUsers() {
return userRepository.findAll();
}

public User findUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("User not found with id: " + id));
}

@Transactional
public void followUser(Long followerId, Long followeeId) {
if (followerId.equals(followeeId)) {
throw new IllegalArgumentException("You cannot follow yourself.");
}

User follower = findUserById(followerId);
User followee = findUserById(followeeId);

if (!follower.getFollowing().contains(followee)) {
follower.getFollowing().add(followee);
userRepository.save(follower);
}
}

@Transactional
public void unfollowUser(Long followerId, Long followeeId) {
User follower = findUserById(followerId);
User followee = findUserById(followeeId);

if (follower.getFollowing().contains(followee)) {
follower.getFollowing().remove(followee);
userRepository.save(follower);
}
}

public List<User> getFollowers(Long userId) {
return findUserById(userId).getFollowers();
}

public List<User> getFollowing(Long userId) {
return findUserById(userId).getFollowing();
}

public boolean isFollowing(Long followerId, Long followeeId) {
User follower = findUserById(followerId);
User followee = findUserById(followeeId);
return follower.getFollowing().contains(followee);
}
public boolean isMutualFollow(Long userId1, Long userId2) {
User user1 = findUserById(userId1);
User user2 = findUserById(userId2);
return user1.getFollowing().contains(user2) && user2.getFollowing().contains(user1);
}
public User createUser(String username) {
if (username == null || username.isBlank()) {
throw new IllegalArgumentException("Username must not be empty.");
}

User user = new User();
user.setUsername(username);
return userRepository.save(user);
}
public List<Todo> getUserTodos(Long id, boolean increaseView) {
User user = findUserById(id);
List<Todo> todos = user.getTodos();

if (increaseView) {
for (Todo todo : todos) {
todo.increaseViewCount();
if (todo.getSubTasks() != null) {
todo.getSubTasks().size();
}
}
}

return todos;
}
public List<Todo> getMyTodos(Long myId) {
return getUserTodos(myId, false);
}
}
Loading