diff --git a/src/main/java/dongguk/osori/OsoriApplication.java b/src/main/java/dongguk/osori/OsoriApplication.java index b74a3e2..37a148a 100644 --- a/src/main/java/dongguk/osori/OsoriApplication.java +++ b/src/main/java/dongguk/osori/OsoriApplication.java @@ -7,5 +7,6 @@ public class OsoriApplication { public static void main(String[] args) { SpringApplication.run(OsoriApplication.class, args); + } } diff --git a/src/main/java/dongguk/osori/config/SecurityConfig.java b/src/main/java/dongguk/osori/config/SecurityConfig.java index 1681e41..ca54f59 100644 --- a/src/main/java/dongguk/osori/config/SecurityConfig.java +++ b/src/main/java/dongguk/osori/config/SecurityConfig.java @@ -36,7 +36,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti // 권한 설정 .authorizeHttpRequests(authz -> authz // TODO: 이후 권한 추가해서 수정 필요 - .requestMatchers("/api/users/**").permitAll() + .requestMatchers("/api/**").permitAll() //.requestMatchers("/api/users/signup", "/api/users/login", "/api/users/logout").permitAll() // 회원가입, 로그인, 로그아웃은 인증 불필요 //.requestMatchers("/api/**").authenticated() // 그 외 /api/**는 인증 필요 .anyRequest().permitAll() // 그 외 모든 요청은 허용 diff --git a/src/main/java/dongguk/osori/domain/goal/GoalController.java b/src/main/java/dongguk/osori/domain/goal/GoalController.java new file mode 100644 index 0000000..b1bbe1e --- /dev/null +++ b/src/main/java/dongguk/osori/domain/goal/GoalController.java @@ -0,0 +1,80 @@ +package dongguk.osori.domain.goal; + +import dongguk.osori.domain.goal.dto.GoalCompletionDto; +import dongguk.osori.domain.goal.dto.GoalDto; +import dongguk.osori.domain.goal.entity.Goal; +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; + +@Slf4j +@RequiredArgsConstructor +@CrossOrigin(origins = "http://localhost:3000") +@RestController +@RequestMapping("/api/goals") +public class GoalController { + + private final GoalService goalService; + + // 로그인한 사용자의 목표 조회 + @GetMapping + public ResponseEntity> getUserGoals(HttpSession session) { + Long userId = (Long) session.getAttribute("userId"); + if (userId == null) { + return ResponseEntity.status(401).build(); + } + List goals = goalService.getUserGoals(userId); + return ResponseEntity.ok(goals); + } + + // 로그인한 사용자의 목표 생성 + @PostMapping + public ResponseEntity createGoal(@RequestBody GoalDto goalDto, HttpSession session) { + Long userId = (Long) session.getAttribute("userId"); + if (userId == null) { + return ResponseEntity.status(401).build(); + } + Goal createdGoal = goalService.createGoal(goalDto, userId); + return ResponseEntity.ok(createdGoal); + } + + // 로그인한 사용자의 목표 수정 + @PatchMapping("/{goalId}") + public ResponseEntity updateGoal(@PathVariable("goalId") Long goalId, @RequestBody GoalDto goalDto, HttpSession session) { + Long userId = (Long) session.getAttribute("userId"); + if (userId == null) { + return ResponseEntity.status(401).build(); + } + Optional updatedGoal = goalService.updateGoal(goalId, goalDto, userId); + return updatedGoal.map(ResponseEntity::ok) + .orElseGet(() -> ResponseEntity.notFound().build()); + } + + // 로그인한 사용자의 목표 삭제 + @DeleteMapping("/{goalId}") + public ResponseEntity deleteGoal(@PathVariable("goalId") Long goalId, HttpSession session) { + Long userId = (Long) session.getAttribute("userId"); + if (userId == null) { + return ResponseEntity.status(401).build(); + } + goalService.deleteGoal(goalId, userId); + return ResponseEntity.noContent().build(); + } + + // 로그인한 사용자의 목표 달성 여부 수정 + @PatchMapping("/{goalId}/completion") + public ResponseEntity updateGoalCompletion(@PathVariable("goalId") Long goalId, @RequestBody GoalCompletionDto goalCompletionDto, HttpSession session) { + Long userId = (Long) session.getAttribute("userId"); + if (userId == null) { + return ResponseEntity.status(401).build(); + } + Optional updatedGoal = goalService.updateGoalCompletionStatus(goalId, goalCompletionDto, userId); + return updatedGoal.map(ResponseEntity::ok) + .orElseGet(() -> ResponseEntity.notFound().build()); + } +} diff --git a/src/main/java/dongguk/osori/domain/goal/GoalRepository.java b/src/main/java/dongguk/osori/domain/goal/GoalRepository.java new file mode 100644 index 0000000..e4c8c47 --- /dev/null +++ b/src/main/java/dongguk/osori/domain/goal/GoalRepository.java @@ -0,0 +1,13 @@ +package dongguk.osori.domain.goal; + +import dongguk.osori.domain.goal.entity.Goal; +import dongguk.osori.domain.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface GoalRepository extends JpaRepository { + Optional findByGoalIdAndUser(Long goalId, User user); +} diff --git a/src/main/java/dongguk/osori/domain/goal/GoalService.java b/src/main/java/dongguk/osori/domain/goal/GoalService.java new file mode 100644 index 0000000..6b81840 --- /dev/null +++ b/src/main/java/dongguk/osori/domain/goal/GoalService.java @@ -0,0 +1,106 @@ +package dongguk.osori.domain.goal; + +import dongguk.osori.domain.goal.dto.GoalCompletionDto; +import dongguk.osori.domain.goal.dto.GoalDto; +import dongguk.osori.domain.goal.entity.Goal; +import dongguk.osori.domain.user.entity.User; +import dongguk.osori.domain.user.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class GoalService { + + private final GoalRepository goalRepository; + private final UserRepository userRepository; + + // 로그인된 사용자 가져오기 + private User getLoggedInUser(Long userId) { + log.debug("Fetching user with ID: {}", userId); // 디버깅용 + return userRepository.findById(userId) + .orElseThrow(() -> new RuntimeException("User not found")); + } + + + // 로그인된 사용자의 모든 목표 조회 + public List getUserGoals(Long userId) { + User user = getLoggedInUser(userId); + log.debug("Goals retrieved: {}", user.getGoals()); // 디버깅용 + return user.getGoals(); + } + + // 로그인된 사용자의 목표 생성 + @Transactional + public Goal createGoal(GoalDto goalDto, Long userId) { + log.debug("Creating goal for user ID: {}", userId); // 디버깅용 + User user = getLoggedInUser(userId); + log.debug("User found: {}", user); // 디버깅용 + + // Goal Builder 사용하여 새로운 목표 생성 + Goal goal = Goal.builder() + .context(goalDto.getContext()) + .user(user) + .build(); + log.debug("New goal created: {}", goal); // 디버깅용 + + user.getGoals().add(goal); + return goalRepository.save(goal); + } + + // 로그인된 사용자의 목표 수정 + @Transactional + public Optional updateGoal(Long goalId, GoalDto goalDto, Long userId) { + User user = getLoggedInUser(userId); + return goalRepository.findByGoalIdAndUser(goalId, user) + .map(goal -> { + goal.updateContext(goalDto.getContext()); + return goal; + }); + } + + + // 로그인된 사용자의 목표 달성 여부 업데이트 + @Transactional + public Optional updateGoalCompletionStatus(Long goalId, GoalCompletionDto goalCompletionDto, Long userId) { + User user = getLoggedInUser(userId); + return goalRepository.findById(goalId) + .filter(goal -> user.getGoals().contains(goal)) + .map(goal -> { + if (goalCompletionDto.isCompleted()) { + goal.markAsCompleted(); + } else { + goal.markAsIncomplete(); + } + return goal; + }); + } + + // 목표 삭제 + @Transactional + public String deleteGoal(Long userId, Long goalId) { + log.debug("Deleting goal ID: {} for user ID: {}", goalId, userId); // 디버깅용 + Goal goal = goalRepository.findById(goalId) + .orElseThrow(() -> new RuntimeException("존재하지 않는 목표")); + + User user = getLoggedInUser(userId); + log.debug("User found: {}, Goal user: {}", user, goal.getUser()); // 디버깅용 + + if(!user.equals(goal.getUser())) { + log.warn("User mismatch: {} is not the owner of goal ID: {}", userId, goalId); // 디버깅용 + return "잘못된 사용자, 삭제 실패"; + } + user.getGoals().remove(goal); // User 엔티티에서 해당 목표를 제거 + goalRepository.deleteById(goalId); // Goal에서 목표 삭제 + log.debug("Goal deleted successfully: {}", goalId); // 디버깅용 + return "아코자국 삭제 완료"; + } + + +} diff --git a/src/main/java/dongguk/osori/domain/goal/dto/GoalCompletionDto.java b/src/main/java/dongguk/osori/domain/goal/dto/GoalCompletionDto.java new file mode 100644 index 0000000..8f0d0bf --- /dev/null +++ b/src/main/java/dongguk/osori/domain/goal/dto/GoalCompletionDto.java @@ -0,0 +1,12 @@ +package dongguk.osori.domain.goal.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GoalCompletionDto { + private boolean completed; +} diff --git a/src/main/java/dongguk/osori/domain/goal/dto/GoalDto.java b/src/main/java/dongguk/osori/domain/goal/dto/GoalDto.java new file mode 100644 index 0000000..1894437 --- /dev/null +++ b/src/main/java/dongguk/osori/domain/goal/dto/GoalDto.java @@ -0,0 +1,12 @@ +package dongguk.osori.domain.goal.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GoalDto { + private String context; +} diff --git a/src/main/java/dongguk/osori/domain/goal/entity/Goal.java b/src/main/java/dongguk/osori/domain/goal/entity/Goal.java new file mode 100644 index 0000000..969f412 --- /dev/null +++ b/src/main/java/dongguk/osori/domain/goal/entity/Goal.java @@ -0,0 +1,51 @@ +package dongguk.osori.domain.goal.entity; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import dongguk.osori.domain.user.entity.User; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@Getter +@Entity +public class Goal { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long goalId; + + @Column(nullable = false) + private String context; + + @Column(nullable = false) + private LocalDateTime createdAt; + + @Column(nullable = false) + private boolean completed = false; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + @JsonIgnore + private User user; + + + public void updateContext(String context) { this.context = context; } + public void markAsCompleted() { this.completed = true; } + public void markAsIncomplete() { this.completed = false; } + + @PrePersist + public void prePersist() { + this.createdAt = LocalDateTime.now(); + this.completed = false; + } + + @Builder + public Goal(String context, User user) { + this.context = context; + this.user = user; + } + +} diff --git a/src/main/java/dongguk/osori/domain/user/UserController.java b/src/main/java/dongguk/osori/domain/user/UserController.java index 742e239..e2eebcb 100644 --- a/src/main/java/dongguk/osori/domain/user/UserController.java +++ b/src/main/java/dongguk/osori/domain/user/UserController.java @@ -84,7 +84,7 @@ public ResponseEntity login(@RequestBody LoginRequestDto loginRequest, H log.info("로그인 성공, 세션에 userId 저장: {}", userId); return ResponseEntity.ok("로그인 성공"); } catch (IllegalArgumentException e) { - log.error("Login failed: {}", e.getMessage()); + log.error("로그인 실패: {}", e.getMessage()); return ResponseEntity.status(401).body(e.getMessage()); } catch (Exception e) { log.error("Unexpected error during login", e); diff --git a/src/main/java/dongguk/osori/domain/user/entity/User.java b/src/main/java/dongguk/osori/domain/user/entity/User.java index de620d9..e839a62 100644 --- a/src/main/java/dongguk/osori/domain/user/entity/User.java +++ b/src/main/java/dongguk/osori/domain/user/entity/User.java @@ -1,8 +1,13 @@ package dongguk.osori.domain.user.entity; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import dongguk.osori.domain.goal.entity.Goal; import jakarta.persistence.*; import lombok.*; +import java.util.ArrayList; +import java.util.List; + @AllArgsConstructor @NoArgsConstructor @EqualsAndHashCode(onlyExplicitlyIncluded = true) @@ -50,6 +55,9 @@ public void updateBalance(int amount) { this.balance += amount; } + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + @JsonManagedReference + private List goals = new ArrayList<>(); } diff --git a/src/main/java/dongguk/osori/domain/user/service/UserService.java b/src/main/java/dongguk/osori/domain/user/service/UserService.java index 2f6f18a..a8295ca 100644 --- a/src/main/java/dongguk/osori/domain/user/service/UserService.java +++ b/src/main/java/dongguk/osori/domain/user/service/UserService.java @@ -1,11 +1,11 @@ package dongguk.osori.domain.user.service; import dongguk.osori.domain.user.UserRepository; +import dongguk.osori.domain.user.dto.UserProfileDto; +import dongguk.osori.domain.user.entity.User; import dongguk.osori.domain.user.dto.PasswordDto; import dongguk.osori.domain.user.dto.SignupUserDto; -import dongguk.osori.domain.user.dto.UserProfileDto; import dongguk.osori.domain.user.dto.UserProfileEditDto; -import dongguk.osori.domain.user.entity.User; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.transaction.annotation.Transactional;