diff --git a/src/main/java/com/goormdari/domain/routine/application/RoutineService.java b/src/main/java/com/goormdari/domain/routine/application/RoutineService.java new file mode 100644 index 0000000..10695df --- /dev/null +++ b/src/main/java/com/goormdari/domain/routine/application/RoutineService.java @@ -0,0 +1,58 @@ +package com.goormdari.domain.routine.application; + +import com.amazonaws.services.kms.model.NotFoundException; +import com.goormdari.domain.routine.domain.Routine; +import com.goormdari.domain.routine.dto.request.CompleteRoutineRequest; +import com.goormdari.domain.routine.domain.repository.RoutineRepository; +import com.goormdari.domain.user.domain.User; +import com.goormdari.domain.user.domain.repository.UserRepository; +import com.goormdari.global.payload.Message; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class RoutineService { + + final RoutineRepository routineRepository; + final UserRepository userRepository; + + @Transactional + public Message completeRoutine (Long userId, CompleteRoutineRequest completeRoutineRequest, String imgURL) { + User user = userRepository.findById(userId) + .orElseThrow(()->new NotFoundException("User Not Found")); + Routine routine = Routine.builder() + .user(user) + .routineImg(imgURL) + .routineIndex(completeRoutineRequest.routineIndex()) + .routineName(completeRoutineRequest.routineName()) + .build(); + + user.updateCurrentStep(user.getCurrentStep()+1); + routineRepository.save(routine); + + return Message.builder() + .message("루틴 완수 성공") + .build(); + } + + @Transactional + public Message deleteRoutineByUserIdAndRoutineIndex (Long userId, Long routineIndex) { + User user = userRepository.findById(userId) + .orElseThrow(()->new NotFoundException("User Not Found")); + Routine routine = routineRepository.findByRoutineIndexAndUserId(userId,routineIndex); + routineRepository.deleteById(routine.getId()); + return Message.builder() + .message("루틴 삭제 성공") + .build(); + } + + @Transactional + public List findAllRoutineByUserId (Long userId) { + return routineRepository.findAllByUserId(userId); + } +} diff --git a/src/main/java/com/goormdari/domain/routine/domain/Routine.java b/src/main/java/com/goormdari/domain/routine/domain/Routine.java new file mode 100644 index 0000000..a0cf05e --- /dev/null +++ b/src/main/java/com/goormdari/domain/routine/domain/Routine.java @@ -0,0 +1,45 @@ +package com.goormdari.domain.routine.domain; + +import com.goormdari.domain.common.BaseEntity; +import com.goormdari.domain.user.domain.User; +import jakarta.persistence.*; +import lombok.*; +import org.springframework.data.annotation.CreatedDate; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Routine extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", updatable = false) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + private String routineImg; + + private Long routineIndex; + + private String routineName; + + @CreatedDate + @Column(name= "create_at") + private LocalDateTime createdAt; + + @Builder + public Routine(User user, String routineImg, Long routineIndex, String routineName) { + this.user=user; + this.routineImg=routineImg; + this.routineIndex=routineIndex; + this.routineName=routineName; + } + +} diff --git a/src/main/java/com/goormdari/domain/routine/domain/repository/RoutineRepository.java b/src/main/java/com/goormdari/domain/routine/domain/repository/RoutineRepository.java new file mode 100644 index 0000000..b6de152 --- /dev/null +++ b/src/main/java/com/goormdari/domain/routine/domain/repository/RoutineRepository.java @@ -0,0 +1,16 @@ +package com.goormdari.domain.routine.domain.repository; + +import com.goormdari.domain.routine.domain.Routine; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; +import java.util.Optional; + +public interface RoutineRepository extends JpaRepository { + @Query("SELECT r FROM Routine r WHERE r.routineIndex = :routineIndex AND r.user.id = :userId") + Routine findByRoutineIndexAndUserId(Long userId, Long routineIndex); + + @Query("SELECT r FROM Routine r WHERE r.user.id = :userId") + List findAllByUserId(Long userId); +} diff --git a/src/main/java/com/goormdari/domain/routine/dto/request/CompleteRoutineRequest.java b/src/main/java/com/goormdari/domain/routine/dto/request/CompleteRoutineRequest.java new file mode 100644 index 0000000..6327241 --- /dev/null +++ b/src/main/java/com/goormdari/domain/routine/dto/request/CompleteRoutineRequest.java @@ -0,0 +1,14 @@ +package com.goormdari.domain.routine.dto.request; + +import lombok.Builder; + +@Builder +public record CompleteRoutineRequest ( + + Long routineIndex, + + String routineName + + ) { + +} diff --git a/src/main/java/com/goormdari/domain/routine/presentation/RoutineController.java b/src/main/java/com/goormdari/domain/routine/presentation/RoutineController.java new file mode 100644 index 0000000..17a94c6 --- /dev/null +++ b/src/main/java/com/goormdari/domain/routine/presentation/RoutineController.java @@ -0,0 +1,100 @@ +package com.goormdari.domain.routine.presentation; + + +import com.goormdari.domain.calendar.dto.response.CheckGoalProgressResponse; +import com.goormdari.domain.calendar.exception.InvalidTokenException; +import com.goormdari.domain.routine.application.RoutineService; +import com.goormdari.domain.routine.domain.Routine; +import com.goormdari.domain.routine.dto.request.CompleteRoutineRequest; +import com.goormdari.global.config.security.jwt.JWTUtil; +import com.goormdari.global.config.s3.S3Service; +import com.goormdari.global.payload.ErrorResponse; +import com.goormdari.global.payload.Message; +import com.goormdari.global.payload.ResponseCustom; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@Tag(name = "Routine", description = "Routine API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/routine") +public class RoutineController { + + private final S3Service s3Service; + private final RoutineService routineService; + + private final JWTUtil jwtUtil; + + @Operation(summary = "루틴 완수", description = "사진을 업로드하여 루틴을 완수합니다") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "루틴 완수 성공", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = Message.class))}), + @ApiResponse(responseCode = "400", description = "루틴 완수 실패", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))}), + }) + @PostMapping("/upload") + public ResponseCustom uploadRoutine( + @Parameter(description = "Accesstoken을 입력해주세요.", required = true) @RequestHeader("Authorization") String token, + @Parameter(description = "Schemas의 completeRoutineRequest를 참고해주세요.", required = true) @Valid @RequestBody CompleteRoutineRequest completeRoutineRequest, + @Parameter(description = "file 이미지 업로드", required = true) @Valid @RequestParam("file") MultipartFile file + ) { + if (token == null) { + throw new InvalidTokenException(); + } + + String jwt = token.startsWith("Bearer ") ? token.substring(7) : token; + if (!jwtUtil.validateToken(jwt)) { + throw new IllegalArgumentException("Invalid token"); + } + Long userId = jwtUtil.extractId(jwt); + String imgURL = s3Service.uploadImageToS3(file); + return ResponseCustom.OK(routineService.completeRoutine(userId, completeRoutineRequest, imgURL)); + } + + @Operation(summary = "루틴 삭제", description = "이미지 url, routineIndex로 삭제") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "루틴 완수 성공", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = Message.class))}), + @ApiResponse(responseCode = "400", description = "루틴 완수 실패", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))}), + }) + @DeleteMapping("/upload") + public ResponseCustom deleteRoutine( + @Parameter(description = "Accesstoken을 입력해주세요.", required = true) @RequestHeader("Authorization") String token, + @Parameter(description = "이미지 url", required = true) @Valid @RequestParam("imgURL") String imgURL, + @Parameter(description = "루틴 Index", required = true) @Valid @RequestParam("routineIndex") Long routineIndex + ) { + if (token == null) { + throw new InvalidTokenException(); + } + + String jwt = token.startsWith("Bearer ") ? token.substring(7) : token; + if (!jwtUtil.validateToken(jwt)) { + throw new IllegalArgumentException("Invalid token"); + } + Long userId = jwtUtil.extractId(jwt); + s3Service.deleteImageOnS3(imgURL); + return ResponseCustom.OK(routineService.deleteRoutineByUserIdAndRoutineIndex(userId,routineIndex)); + } + + @Operation(summary = "유저별 루틴 전체 조회", description = "userId로 해당 유저 루틴 전체 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "루틴 완수 성공", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = Message.class))}), + @ApiResponse(responseCode = "400", description = "루틴 완수 실패", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))}), + }) + @GetMapping("/{userId}") + public ResponseCustom> getAllRoutineByUserId( + @Parameter(description = "조회 할 userId", required = true) @PathVariable("userId") Long userId + ) { + return ResponseCustom.OK(routineService.findAllRoutineByUserId(userId)); + } + + +} diff --git a/src/main/java/com/goormdari/domain/user/domain/User.java b/src/main/java/com/goormdari/domain/user/domain/User.java index 5ff0cd2..8ac00ef 100644 --- a/src/main/java/com/goormdari/domain/user/domain/User.java +++ b/src/main/java/com/goormdari/domain/user/domain/User.java @@ -35,11 +35,6 @@ public class User extends BaseEntity { private String profileUrl; - private String routinImg1; - private String routinImg2; - private String routinImg3; - private String routinImg4; - private String role; private String goal; @@ -61,6 +56,10 @@ public void updateDeadLine(LocalDate deadLine) { this.deadLine = deadLine; } + public void updateCurrentStep(int currentStep) { + this.currentStep = currentStep; + } + @Builder public User(String nickname, String username, String password, String role) { this.nickname = nickname; diff --git a/src/main/java/com/goormdari/domain/user/domain/dto/response/findCurrentStepResponse.java b/src/main/java/com/goormdari/domain/user/domain/dto/response/findCurrentStepResponse.java new file mode 100644 index 0000000..e2f4425 --- /dev/null +++ b/src/main/java/com/goormdari/domain/user/domain/dto/response/findCurrentStepResponse.java @@ -0,0 +1,7 @@ +package com.goormdari.domain.user.dto.response; + +import lombok.Builder; + +@Builder +public record findCurrentStepResponse (int currentStep) { +} diff --git a/src/main/java/com/goormdari/domain/user/domain/service/UserService.java b/src/main/java/com/goormdari/domain/user/domain/service/UserService.java index 1eca3fc..0f05331 100644 --- a/src/main/java/com/goormdari/domain/user/domain/service/UserService.java +++ b/src/main/java/com/goormdari/domain/user/domain/service/UserService.java @@ -1,5 +1,7 @@ package com.goormdari.domain.user.domain.service; +import com.amazonaws.services.kms.model.NotFoundException; +import com.goormdari.domain.user.dto.response.findCurrentStepResponse; import com.goormdari.domain.user.domain.User; import com.goormdari.domain.user.domain.dto.AddUserRequest; import com.goormdari.domain.user.domain.dto.JwtResponse; @@ -27,6 +29,13 @@ public class UserService { private final AuthenticationManager authenticationManager; private final JWTUtil jwtUtil; + @Transactional + public findCurrentStepResponse findCurrentStepById(Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(()->new NotFoundException("User Not Found")); + + return findCurrentStepResponse.builder().currentStep(user.getCurrentStep()).build(); + } @Transactional public Long save(AddUserRequest dto) { // 사용자 이름 중복 체크 diff --git a/src/main/java/com/goormdari/domain/user/presentation/UserController.java b/src/main/java/com/goormdari/domain/user/presentation/UserController.java new file mode 100644 index 0000000..ec693ec --- /dev/null +++ b/src/main/java/com/goormdari/domain/user/presentation/UserController.java @@ -0,0 +1,52 @@ +package com.goormdari.domain.user.presentation; + +import com.goormdari.domain.calendar.exception.InvalidTokenException; +import com.goormdari.domain.user.domain.service.UserService; +import com.goormdari.domain.user.dto.response.findCurrentStepResponse; +import com.goormdari.global.config.security.jwt.JWTUtil; +import com.goormdari.global.payload.ErrorResponse; +import com.goormdari.global.payload.Message; +import com.goormdari.global.payload.ResponseCustom; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@Tag(name = "Routine", description = "Routine API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/user") +public class UserController { + + private final UserService userService; + private final JWTUtil jwtUtil; + + @Operation(summary = "현재 루틴 수 조회", description = "사용자가 완수한 루틴의 개수") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공 ", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = findCurrentStepResponse.class))}), + @ApiResponse(responseCode = "400", description = "조회 실패", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))}), + }) + @GetMapping("/current-step") + public ResponseCustom getCurrentStep( + @Parameter(description = "Accesstoken을 입력해주세요.", required = true) @RequestHeader("Authorization") String token + ) { + if (token == null) { + throw new InvalidTokenException(); + } + + String jwt = token.startsWith("Bearer ") ? token.substring(7) : token; + if (!jwtUtil.validateToken(jwt)) { + throw new IllegalArgumentException("Invalid token"); + } + Long userId = jwtUtil.extractId(jwt); + return ResponseCustom.OK(userService.findCurrentStepById(userId)); + } +} diff --git a/src/main/java/com/goormdari/global/config/s3/S3Service.java b/src/main/java/com/goormdari/global/config/s3/S3Service.java index 78e0223..126ad2e 100644 --- a/src/main/java/com/goormdari/global/config/s3/S3Service.java +++ b/src/main/java/com/goormdari/global/config/s3/S3Service.java @@ -1,6 +1,7 @@ package com.goormdari.global.config.s3; import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.DeleteObjectRequest; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; import com.amazonaws.services.s3.model.PutObjectResult; @@ -21,7 +22,7 @@ public class S3Service { private String changedImageName(String originName) { //이미지 이름 중복 방지를 위해 랜덤으로 생성 String random = UUID.randomUUID().toString(); - return random + originName; + return random +'-' + originName; } public String uploadImageToS3(MultipartFile image) { //이미지를 S3에 업로드하고 이미지의 url을 반환 @@ -40,4 +41,11 @@ public String uploadImageToS3(MultipartFile image) { //이미지를 S3에 업로 } return amazonS3.getUrl(bucket, changedName).toString(); //데이터베이스에 저장할 이미지가 저장된 주소 } -} + + //삭제는 일단 보류 + public void deleteImageOnS3(String imgURL) { + String fileName = imgURL.substring(imgURL.lastIndexOf("-"));; + + amazonS3.deleteObject(new DeleteObjectRequest(bucket, fileName)); + } +} \ No newline at end of file diff --git a/src/main/java/com/goormdari/global/config/security/jwt/JWTUtil.java b/src/main/java/com/goormdari/global/config/security/jwt/JWTUtil.java index fdcad69..7f536c1 100644 --- a/src/main/java/com/goormdari/global/config/security/jwt/JWTUtil.java +++ b/src/main/java/com/goormdari/global/config/security/jwt/JWTUtil.java @@ -110,4 +110,4 @@ public String getRole(String token) { public Boolean isExpired(String token) { return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date()); } -} +} \ No newline at end of file