diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 59affc0..03b0f36 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,9 +2,9 @@ name: CI/CD Pipeline on: push: - branches: [ week6 ] + branches: [ develop ] pull_request: - branches: [ week6 ] + branches: [ develop ] env: LIGHTSAIL_USERNAME: ubuntu @@ -53,9 +53,7 @@ jobs: client-secret: ${{ secrets.GOOGLE_CLIENT_SECRET }} - redirect-uri: "https://${{ - secrets.LIGHT_SAIL_IP - }}/login/oauth2/code/google" + redirect-uri: https://seamlessup.com/login/oauth2/code/google scope: - email - profile @@ -64,16 +62,6 @@ jobs: authorization-uri: https://accounts.google.com/o/oauth2/auth token-uri: https://oauth2.googleapis.com/token user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo - server: - port: 443 - ssl: - enabled: true - key-store: /etc/letsencrypt/live/seamlessup.com/keystore.p12 - key-store-password: ${{ - secrets.KEYSTORE_PASSWORD - }} - key-store-type: PKCS12 - key-alias: tomcat EOF # Gradle 캐시 설정 @@ -131,16 +119,17 @@ jobs: CURRENT_PID=$(pgrep -f $JAR_NAME) - if [ -z $CURRENT_PID ] + if [ -n $CURRENT_PID ] then sleep 1 else kill -15 $CURRENT_PID - sleep 5 + wait $CURRENT_PID + sleep 10 fi DEPLOY_PATH=/home/ubuntu/seamless/deploy/ - mkdir $DEPLOY_PATH + mkdir -p $DEPLOY_PATH cp $BUILD_PATH $DEPLOY_PATH cd $DEPLOY_PATH diff --git a/README.md b/README.md index 63be885..7da9dca 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@ --- -# 그라운드 룰 +# 프로젝트 구조도 + +![seamless](https://github.com/user-attachments/assets/98d62cf5-8755-4980-9bea-a5ada9719ee7) + -- 추가 요망 --- @@ -122,6 +124,7 @@ - [4주차 리뷰](https://github.com/kakao-tech-campus-2nd-step3/Team1_BE/issues/17) - [4주차 멘토링](https://quickest-asterisk-75d.notion.site/Back-end_-323b0e20ae2b405189ffe5b7c4242e00) - [5주차 리뷰](https://github.com/kakao-tech-campus-2nd-step3/Team1_BE/issues/31) +- [6주차 피드백](https://github.com/kakao-tech-campus-2nd-step3/Team1_BE/pull/58) --- # Issue - [week4 프로젝트 빌드 실패](https://github.com/kakao-tech-campus-2nd-step3/Team1_BE/issues/27) @@ -150,4 +153,4 @@ - week5 - softDelete의 구현 방법 - 연결괸 객체의 정보를 가져오는 방법 -- week6 \ No newline at end of file +- week6 diff --git a/src/main/java/team1/BE/seamless/DTO/InviteRequestDTO.java b/src/main/java/team1/BE/seamless/DTO/InviteRequestDTO.java index 3d0a775..f063c31 100644 --- a/src/main/java/team1/BE/seamless/DTO/InviteRequestDTO.java +++ b/src/main/java/team1/BE/seamless/DTO/InviteRequestDTO.java @@ -4,22 +4,32 @@ // 요청을 보낼 때의 DTO public class InviteRequestDTO { - private Integer projectId; + private Long projectId; private String email; + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } public InviteRequestDTO() { } - public InviteRequestDTO(Integer projectId, String email) { + public InviteRequestDTO(Long projectId, String email, String name) { this.projectId = projectId; this.email = email; + this.name = name; } - public Integer getProjectId() { + public Long getProjectId() { return projectId; } - public void setProjectId(Integer projectId) { + public void setProjectId(Long projectId) { this.projectId = projectId; } diff --git a/src/main/java/team1/BE/seamless/DTO/MemberDetailResponseDTO.java b/src/main/java/team1/BE/seamless/DTO/MemberDetailResponseDTO.java deleted file mode 100644 index 73ef5ce..0000000 --- a/src/main/java/team1/BE/seamless/DTO/MemberDetailResponseDTO.java +++ /dev/null @@ -1,73 +0,0 @@ -package team1.BE.seamless.DTO; - -import java.util.List; -import team1.BE.seamless.entity.ProjectEntity; -import team1.BE.seamless.entity.TaskEntity; - -public class MemberDetailResponseDTO { - - private String email; - private String role; - private String name; - private String imageURL; - private ProjectEntity projectEntity; - private List taskEntities; - - public MemberDetailResponseDTO(String email, String role, String name, - String imageURL, ProjectEntity projectEntity, List taskEntities) { - this.email = email; - this.role = role; - this.name = name; - this.imageURL = imageURL; - this.projectEntity = projectEntity; - this.taskEntities = taskEntities; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getRole() { - return role; - } - - public void setRole(String role) { - this.role = role; - } - - public String getImageURL() { - return imageURL; - } - - public void setImageURL(String imageURL) { - this.imageURL = imageURL; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public ProjectEntity getProjectEntity() { - return projectEntity; - } - - public void setProjectEntity(ProjectEntity projectEntity) { - this.projectEntity = projectEntity; - } - - public List getTaskEntities() { - return taskEntities; - } - - public void setTaskEntities(List taskEntities) { - this.taskEntities = taskEntities; - } -} diff --git a/src/main/java/team1/BE/seamless/DTO/MemberRequestDTO.java b/src/main/java/team1/BE/seamless/DTO/MemberRequestDTO.java index 5c58e4b..9d98a0f 100644 --- a/src/main/java/team1/BE/seamless/DTO/MemberRequestDTO.java +++ b/src/main/java/team1/BE/seamless/DTO/MemberRequestDTO.java @@ -11,46 +11,71 @@ public static class getMemberList extends PageParam { } - public static class CreateMember { - - @NotBlank(message = "이름은 필수 입력 사항입니다.") - @Size(max = 15, message = "이름은 공백 포함 최대 15글자까지 가능합니다.") - private String name; +// public static class CreateMember { +// +// @NotBlank(message = "이름은 필수 입력 사항입니다.") +// @Size(max = 15, message = "이름은 공백 포함 최대 15글자까지 가능합니다.") +// private String name; +// +// private String role; +// +// @Email(message = "유효한 이메일 주소를 입력해주세요.") +// @NotBlank(message = "이메일은 필수 입력 사항입니다.") +// private String email; +// +// private String imageURL; +// +// +// public CreateMember() { +// } +// +// public CreateMember(String name, String role, String email, String imageURL) { +// this.name = name; +// this.role = role; +// this.email = email; +// this.imageURL = imageURL; +// } +// +// public String getName() { +// return name; +// } +// +// public String getRole() { +// return role; +// } +// +// public String getEmail() { +// return email; +// } +// +// public String getImageURL() { +// return imageURL; +// } +// } - @NotBlank(message = "역할은 필수 입력 사항입니다.") - @Size(max = 15, message = "역할은 공백 포함 최대 15글자까지 가능합니다.") - private String role; + public static class CreateMember { @Email(message = "유효한 이메일 주소를 입력해주세요.") @NotBlank(message = "이메일은 필수 입력 사항입니다.") private String email; - private String imageURL; + private String code; + public CreateMember() { } - public CreateMember(String name, String role, String email, String imageURL) { - this.name = name; - this.role = role; + public CreateMember(String email, String code) { this.email = email; - this.imageURL = imageURL; - } - - public String getName() { - return name; - } - - public String getRole() { - return role; + this.code = code; } - public String getEmail() { + public @Email(message = "유효한 이메일 주소를 입력해주세요.") @NotBlank(message = "이메일은 필수 입력 사항입니다.") String getEmail() { return email; } - public String getImageURL() { - return imageURL; + public String getCode() { + return code; } } @@ -70,6 +95,14 @@ public static class UpdateMember { public UpdateMember() { } + public UpdateMember(String name, String role, String email, String imageURL, boolean test) { + this.name = name; + this.role = role; + this.email = email; + this.imageURL = imageURL; + + } + public UpdateMember(String name, String role, String email, String imageURL) { this.name = name; this.role = role; diff --git a/src/main/java/team1/BE/seamless/DTO/MemberResponseDTO.java b/src/main/java/team1/BE/seamless/DTO/MemberResponseDTO.java index 4268dd1..99d35a6 100644 --- a/src/main/java/team1/BE/seamless/DTO/MemberResponseDTO.java +++ b/src/main/java/team1/BE/seamless/DTO/MemberResponseDTO.java @@ -10,6 +10,8 @@ public class MemberResponseDTO { private String email; + private String code; + public MemberResponseDTO(String message, String name, String role, String email) { this.message = message; this.name = name; @@ -17,6 +19,14 @@ public MemberResponseDTO(String message, String name, String role, String email) this.email = email; } + public MemberResponseDTO(String message, String name, String role, String email, String code) { + this.message = message; + this.name = name; + this.role = role; + this.email = email; + this.code = code; + } + public String getRole() { return role; } @@ -48,4 +58,8 @@ public String getMessage() { public void setMessage(String message) { this.message = message; } + + public String getCode() { + return code; + } } diff --git a/src/main/java/team1/BE/seamless/DTO/OptionDTO.java b/src/main/java/team1/BE/seamless/DTO/OptionDTO.java index 59a542a..7264d4b 100644 --- a/src/main/java/team1/BE/seamless/DTO/OptionDTO.java +++ b/src/main/java/team1/BE/seamless/DTO/OptionDTO.java @@ -1,15 +1,23 @@ package team1.BE.seamless.DTO; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; import team1.BE.seamless.entity.enums.OptionType; +import team1.BE.seamless.util.page.PageParam; public class OptionDTO { + public static class getList extends PageParam { + + } public static class OptionCreate { + @NotNull private String name; private String description; + @NotNull private String optionType; public OptionCreate() { @@ -33,7 +41,111 @@ public String getDescription() { public String getOptionType() { return optionType; } + } + + public static class updateOption { + + private String name; + + private String description; + + private String optionType; + + public updateOption() { + + } + + public updateOption(String name, String description, String optionType) { + this.name = name; + this.description = description; + this.optionType = optionType; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getOptionType() { + return optionType; + } + + } + + public static class OptionSimple{ + private Long id; + private String name; + private String optionType; + + public OptionSimple() { + } + + public OptionSimple(Long id, String name, OptionType optionType) { + this.id = id; + this.name = name; + this.optionType = optionType.toString(); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getOptionType() { + return optionType; + } + } + + public static class OptionDetail{ + private Long id; + private String name; + private String description; + private String optionType; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + public OptionDetail() { + } + + public OptionDetail(Long id, String name, String description, OptionType optionType, + LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.name = name; + this.description = description; + this.optionType = optionType.toString(); + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getOptionType() { + return optionType; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + public LocalDateTime getUpdatedAt() { + return updatedAt; + } } } diff --git a/src/main/java/team1/BE/seamless/DTO/ProjectDTO.java b/src/main/java/team1/BE/seamless/DTO/ProjectDTO.java index 179eb4c..f58cc23 100644 --- a/src/main/java/team1/BE/seamless/DTO/ProjectDTO.java +++ b/src/main/java/team1/BE/seamless/DTO/ProjectDTO.java @@ -32,11 +32,11 @@ public static class ProjectCreate { private List<@Positive Long> optionIds = new ArrayList<>(); @NotNull - @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$") +// @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$") private LocalDateTime startDate; @NotNull - @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$") +// @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$") private LocalDateTime endDate; public ProjectCreate() { @@ -90,10 +90,10 @@ public static class ProjectUpdate { @Valid private List<@Positive Long> optionIds = new ArrayList<>(); - @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$") +// @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$") private LocalDateTime startDate; - @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$") +// @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$") private LocalDateTime endDate; public ProjectUpdate() { diff --git a/src/main/java/team1/BE/seamless/DTO/TaskDTO.java b/src/main/java/team1/BE/seamless/DTO/TaskDTO.java index c010de9..1ab3b67 100644 --- a/src/main/java/team1/BE/seamless/DTO/TaskDTO.java +++ b/src/main/java/team1/BE/seamless/DTO/TaskDTO.java @@ -13,7 +13,7 @@ public static class getList extends PageParam { } - public static class Create { + public static class TaskCreate { @NotBlank(message = "이름은 필수 입력 사항입니다.") private String name; @@ -28,10 +28,10 @@ public static class Create { @NotNull(message = "종료 시간은 필수 입력 사항입니다.") private LocalDateTime endDate; - public Create(String name, String remark, Long memberId, LocalDateTime startDate, + public TaskCreate(String name, String remark, Long memberId, LocalDateTime startDate, LocalDateTime endDate) { if (endDate.isBefore(startDate)) { - throw new BaseHandler(HttpStatus.FORBIDDEN, "종료시간은 시작시간보다 이전일 수 없습니다."); + throw new BaseHandler(HttpStatus.BAD_REQUEST, "종료시간은 시작시간보다 이전일 수 없습니다."); } this.name = name; this.remark = remark; @@ -61,7 +61,7 @@ public Long getMemberId() { } } - public static class Update { + public static class TaskUpdate { private String name; @@ -75,11 +75,11 @@ public static class Update { private LocalDateTime endDate; - public Update(String name, String remark, Integer progress, Long memberId, + public TaskUpdate(String name, String remark, Integer progress, Long memberId, LocalDateTime startDate, LocalDateTime endDate) { if (endDate.isBefore(startDate)) { - throw new BaseHandler(HttpStatus.FORBIDDEN, "종료시간은 시작시간보다 이전일 수 없습니다."); + throw new BaseHandler(HttpStatus.BAD_REQUEST, "종료시간은 시작시간보다 이전일 수 없습니다."); } this.name = name; this.remark = remark; diff --git a/src/main/java/team1/BE/seamless/DTO/UserDTO.java b/src/main/java/team1/BE/seamless/DTO/UserDTO.java index 8af7845..8377815 100644 --- a/src/main/java/team1/BE/seamless/DTO/UserDTO.java +++ b/src/main/java/team1/BE/seamless/DTO/UserDTO.java @@ -2,6 +2,7 @@ import jakarta.validation.constraints.Pattern; import java.time.LocalDateTime; +import org.hibernate.validator.constraints.URL; public class UserDTO { @@ -9,8 +10,7 @@ public static class UserUpdate { private String username; - @Pattern(regexp = "^(https?:\\/\\/)?([a-zA-Z0-9_-]+\\.)+[a-zA-Z]{2,6}(:[0-9]{1,5})?(\\/.*)?$", - message = "사진은 URL이여야 합니다.") + @URL(protocol = "https", message = "사진은 URL이여야 합니다.") private String picture; public UserUpdate() { diff --git a/src/main/java/team1/BE/seamless/controller/AttendUrlController.java b/src/main/java/team1/BE/seamless/controller/AttendUrlController.java index 0d21292..026fd22 100644 --- a/src/main/java/team1/BE/seamless/controller/AttendUrlController.java +++ b/src/main/java/team1/BE/seamless/controller/AttendUrlController.java @@ -35,14 +35,12 @@ public AttendUrlController(AttendURLService attendURLService) { /** * 팉장의 토큰과 프로젝트id로 프로젝트 존재 검증 프로젝트id + " " + exp로 코드 생성 코드를 양방향 암호화 ex) - * http://localhost:8080/api/memverInvite?code="123456" 검증시 코드를 복호화 해서 프로젝트id와 exp를 검증(server - * less) */ - @Operation(summary = "팀원초대 링크 생성") - @PostMapping("/api/project/{project_id}/invite-link/{user-id}") + @Operation(summary = "팀원초대 코드 생성") + @PostMapping("/api/project/{projectId}/invite-link/{userId}") public SingleResult generateInviteLink(HttpServletRequest req, - @Valid @PathVariable("project_id") Long projectId, - @Valid @PathVariable("user-id") Long userId) { + @Valid @PathVariable("projectId") Long projectId, + @Valid @PathVariable("userId") Long userId) { return new SingleResult<>(attendURLService.generateAttendURL(req, projectId, userId)); } } \ No newline at end of file diff --git a/src/main/java/team1/BE/seamless/controller/ProjectInviteController.java b/src/main/java/team1/BE/seamless/controller/InviteCodeByEmailController.java similarity index 65% rename from src/main/java/team1/BE/seamless/controller/ProjectInviteController.java rename to src/main/java/team1/BE/seamless/controller/InviteCodeByEmailController.java index 8a3b139..6428f7e 100644 --- a/src/main/java/team1/BE/seamless/controller/ProjectInviteController.java +++ b/src/main/java/team1/BE/seamless/controller/InviteCodeByEmailController.java @@ -5,33 +5,32 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import team1.BE.seamless.DTO.InviteRequestDTO; -import team1.BE.seamless.service.ProjectInviteService; +import team1.BE.seamless.service.InviteCodeByEmailService; +import team1.BE.seamless.util.errorException.BaseHandler; import team1.BE.seamless.util.page.SingleResult; @Tag(name = "이메일 전송") @RestController @RequestMapping("/api/project") -public class ProjectInviteController { +public class InviteCodeByEmailController { @Autowired - private ProjectInviteService inviteService; + private InviteCodeByEmailService inviteService; @Operation(summary = "이메일로 참여코드 전송하기") @PostMapping("/invite") public SingleResult inviteMemberToProject(@RequestBody InviteRequestDTO inviteRequest) { try { - String message = - "You have been invited to join the project with ID: " + inviteRequest.getProjectId() - + "\nAnd Participation code: "; - inviteService.sendProjectInvite(inviteRequest.getEmail(), message); - return new SingleResult<>("프로젝트 초대 요청이 성공적으로 처리되었습니다."); + inviteService.sendProjectInvite(inviteRequest.getEmail(), inviteRequest.getProjectId()); + return new SingleResult<>("팀원의 이메일로 프로젝트 초대코드 전송이 성공적으로 처리되었습니다."); } catch (Exception e) { - return new SingleResult<>("이메일 초대 실패: " + e.getMessage()); + throw new BaseHandler(HttpStatus.BAD_REQUEST,"이메일로 프로젝트 초대코드 전송이 실패되었습니다. : " + e.getMessage()); } } } \ No newline at end of file diff --git a/src/main/java/team1/BE/seamless/controller/MemberController.java b/src/main/java/team1/BE/seamless/controller/MemberController.java index a0099ab..8ec33fd 100644 --- a/src/main/java/team1/BE/seamless/controller/MemberController.java +++ b/src/main/java/team1/BE/seamless/controller/MemberController.java @@ -56,11 +56,8 @@ public PageResult getMemberList(@Valid @PathVariable("project_id") @Operation(summary = "새 팀원 추가") @PostMapping - public SingleResult createMember( - @PathVariable("project_id") Long projectId, - @Valid @RequestBody MemberRequestDTO.CreateMember Create, - HttpServletRequest req) { - return new SingleResult<>(memberService.createMember(projectId, Create, req)); + public SingleResult createMember(@Valid @RequestBody MemberRequestDTO.CreateMember Create) { + return new SingleResult<>(memberService.createMember(Create)); } @Operation(summary = "팀원 정보 수정") diff --git a/src/main/java/team1/BE/seamless/controller/ProjectController.java b/src/main/java/team1/BE/seamless/controller/ProjectController.java index 214124b..ece1b2e 100644 --- a/src/main/java/team1/BE/seamless/controller/ProjectController.java +++ b/src/main/java/team1/BE/seamless/controller/ProjectController.java @@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import team1.BE.seamless.DTO.MemberResponseDTO; import team1.BE.seamless.DTO.ProjectDTO; import team1.BE.seamless.DTO.ProjectDTO.ProjectDetail; import team1.BE.seamless.DTO.ProjectDTO.ProjectPeriod; @@ -62,7 +63,7 @@ public PageResult getProjectPeriod(@Valid ProjectDTO.getList para @Operation(summary = "프로젝트 멤버 조회") @GetMapping("/{project-id}/members") - public ListResult getProjectMembers(@Valid @PathVariable("project-id") Long id) { + public ListResult getProjectMembers(@Valid @PathVariable("project-id") Long id) { return new ListResult<>(projectService.getProjectMembers(id)); } diff --git a/src/main/java/team1/BE/seamless/controller/ProjectOptionController.java b/src/main/java/team1/BE/seamless/controller/ProjectOptionController.java new file mode 100644 index 0000000..130bd66 --- /dev/null +++ b/src/main/java/team1/BE/seamless/controller/ProjectOptionController.java @@ -0,0 +1,76 @@ +package team1.BE.seamless.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import team1.BE.seamless.DTO.OptionDTO; +import team1.BE.seamless.DTO.OptionDTO.OptionDetail; +import team1.BE.seamless.DTO.OptionDTO.OptionSimple; +import team1.BE.seamless.service.OptionService; +import team1.BE.seamless.util.auth.ParsingPram; +import team1.BE.seamless.util.page.PageMapper; +import team1.BE.seamless.util.page.PageResult; +import team1.BE.seamless.util.page.SingleResult; + +@Tag(name = "프로젝트 옵션") +@RestController +@RequestMapping("/api/project/option") +public class ProjectOptionController { + + private final OptionService optionService; + private final ParsingPram parsingPram; + + @Autowired + public ProjectOptionController(OptionService optionService, ParsingPram parsingPram) { + this.optionService = optionService; + this.parsingPram = parsingPram; + } + + @Operation(summary = "프로젝트 옵션 리스트 조회") + @GetMapping + public PageResult getOptionList(HttpServletRequest req, + @Valid OptionDTO.getList param) { + return PageMapper.toPageResult( + optionService.getProjectOptionList(param, parsingPram.getRole(req))); + } + + @Operation(summary = "프로젝트 옵션 조회") + @GetMapping("/{optionId}") + public SingleResult getOption(HttpServletRequest req, + @Valid @PathVariable("optionId") Long id) { + return new SingleResult<>(optionService.getOption(id, parsingPram.getRole(req))); + } + + @Operation(summary = "옵션 생성") + @PostMapping + public SingleResult createOption(HttpServletRequest req, + @Valid @RequestBody OptionDTO.OptionCreate create) { + return new SingleResult<>(optionService.createOption(create, parsingPram.getRole(req))); + } + + @Operation(summary = "옵션 수정") + @PutMapping("/{optionId}") + public SingleResult updateOption(HttpServletRequest req, + @Valid @PathVariable("optionId") Long id, + @Valid @RequestBody OptionDTO.updateOption update) { + return new SingleResult<>(optionService.updateOption(id, update, parsingPram.getRole(req))); + } + + @Operation(summary = "옵션 수정") + @DeleteMapping("/{optionId}") + public SingleResult deleteOption(HttpServletRequest req, + @Valid @PathVariable("optionId") Long id) { + return new SingleResult<>(optionService.deleteOption(id, parsingPram.getRole(req))); + } + +} diff --git a/src/main/java/team1/BE/seamless/controller/TaskController.java b/src/main/java/team1/BE/seamless/controller/TaskController.java index bd80f71..8ea4309 100644 --- a/src/main/java/team1/BE/seamless/controller/TaskController.java +++ b/src/main/java/team1/BE/seamless/controller/TaskController.java @@ -14,8 +14,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import team1.BE.seamless.DTO.TaskDTO; +import team1.BE.seamless.DTO.TaskDTO.TaskCreate; +import team1.BE.seamless.DTO.TaskDTO.TaskUpdate; import team1.BE.seamless.DTO.TaskDTO.TaskDetail; -import team1.BE.seamless.entity.TaskEntity; import team1.BE.seamless.service.TaskService; import team1.BE.seamless.util.page.PageMapper; import team1.BE.seamless.util.page.PageResult; @@ -52,8 +53,8 @@ public PageResult getTaskList(@PathVariable Long projectId, @Operation(summary = "태스크 생성") @PostMapping("/{projectId}/task") public SingleResult createTask(HttpServletRequest req, - @Valid @PathVariable Long projectId, @Valid @RequestBody TaskDTO.Create create) { - return new SingleResult<>(taskService.createTask(req, projectId, create)); + @Valid @PathVariable Long projectId, @Valid @RequestBody TaskCreate taskCreate) { + return new SingleResult<>(taskService.createTask(req, projectId, taskCreate)); } /** @@ -63,7 +64,7 @@ public SingleResult createTask(HttpServletRequest req, @PutMapping("/task/{taskId}") public SingleResult updateTask(HttpServletRequest req, @Valid @PathVariable Long taskId, - @Valid @RequestBody TaskDTO.Update update) { + @Valid @RequestBody TaskUpdate update) { return new SingleResult<>(taskService.updateTask(req, taskId, update)); } diff --git a/src/main/java/team1/BE/seamless/entity/MemberEntity.java b/src/main/java/team1/BE/seamless/entity/MemberEntity.java index 20544e8..adcf0dd 100644 --- a/src/main/java/team1/BE/seamless/entity/MemberEntity.java +++ b/src/main/java/team1/BE/seamless/entity/MemberEntity.java @@ -12,7 +12,7 @@ import java.util.ArrayList; import java.util.List; -@Entity(name = "member") +@Entity(name = "memberss") public class MemberEntity extends BaseEntity { public MemberEntity() { diff --git a/src/main/java/team1/BE/seamless/entity/OptionEntity.java b/src/main/java/team1/BE/seamless/entity/OptionEntity.java index f822604..f2ceba7 100644 --- a/src/main/java/team1/BE/seamless/entity/OptionEntity.java +++ b/src/main/java/team1/BE/seamless/entity/OptionEntity.java @@ -9,10 +9,13 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; +import java.time.LocalDateTime; import java.util.List; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; import team1.BE.seamless.entity.enums.OptionType; -@Entity(name = "option") +@Entity(name = "optionss") public class OptionEntity extends BaseEntity { public OptionEntity() { @@ -47,6 +50,12 @@ public OptionEntity(String name, String description, OptionType optionType) { @OneToMany(mappedBy = "optionEntity", cascade = CascadeType.ALL) private List options; + @CreatedDate + private LocalDateTime createdAt; + + @LastModifiedDate + private LocalDateTime updatedAt; + public Long getId() { return id; } @@ -71,6 +80,20 @@ public List getOptions() { return options; } + public boolean isDeleted() { + return isDeleted; + } + + @Override + public LocalDateTime getCreatedAt() { + return createdAt; + } + + @Override + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + public void setOptions(List options) { this.options = options; } @@ -79,4 +102,11 @@ public void setIsDeleted(boolean isDeleted) { this.isDeleted = isDeleted; } + public OptionEntity Update(String name, String description, OptionType optionType) { + this.name = name; + this.description=description; + this.optionType = optionType; + return this; + } + } diff --git a/src/main/java/team1/BE/seamless/entity/ProjectEntity.java b/src/main/java/team1/BE/seamless/entity/ProjectEntity.java index c9a9afc..bb37a63 100644 --- a/src/main/java/team1/BE/seamless/entity/ProjectEntity.java +++ b/src/main/java/team1/BE/seamless/entity/ProjectEntity.java @@ -14,7 +14,7 @@ import java.util.ArrayList; import java.util.List; -@Entity(name = "project") +@Entity(name = "projectss") public class ProjectEntity extends BaseEntity { public ProjectEntity() { diff --git a/src/main/java/team1/BE/seamless/entity/ProjectOption.java b/src/main/java/team1/BE/seamless/entity/ProjectOption.java index ba45c6c..097287d 100644 --- a/src/main/java/team1/BE/seamless/entity/ProjectOption.java +++ b/src/main/java/team1/BE/seamless/entity/ProjectOption.java @@ -9,7 +9,7 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; -@Entity(name = "project_option") +@Entity(name = "project_optionss") public class ProjectOption extends BaseEntity { public ProjectOption() { diff --git a/src/main/java/team1/BE/seamless/entity/TaskEntity.java b/src/main/java/team1/BE/seamless/entity/TaskEntity.java index a9356b7..7bc1cce 100644 --- a/src/main/java/team1/BE/seamless/entity/TaskEntity.java +++ b/src/main/java/team1/BE/seamless/entity/TaskEntity.java @@ -10,7 +10,7 @@ import jakarta.persistence.ManyToOne; import java.time.LocalDateTime; -@Entity(name = "task") +@Entity(name = "taskss") public class TaskEntity { public TaskEntity() { diff --git a/src/main/java/team1/BE/seamless/entity/UserEntity.java b/src/main/java/team1/BE/seamless/entity/UserEntity.java index 618ed85..cb9fea0 100644 --- a/src/main/java/team1/BE/seamless/entity/UserEntity.java +++ b/src/main/java/team1/BE/seamless/entity/UserEntity.java @@ -18,7 +18,7 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener; import team1.BE.seamless.entity.enums.Role; -@Entity(name = "user_table") +@Entity(name = "userss") @EntityListeners(AuditingEntityListener.class) public class UserEntity { diff --git a/src/main/java/team1/BE/seamless/init/InitData.java b/src/main/java/team1/BE/seamless/init/InitData.java index 81ae9bf..b8c0756 100644 --- a/src/main/java/team1/BE/seamless/init/InitData.java +++ b/src/main/java/team1/BE/seamless/init/InitData.java @@ -14,15 +14,17 @@ public class InitData { private final UserCreator userCreator; private final OptionCreator optionCreator; private final MemberCreator memberCreator; + private final TaskCreator taskCreator; @Autowired public InitData(TestCreator testCreator, ProjectCreator projectCreator, UserCreator userCreator, - OptionCreator optionCreator, MemberCreator memberCreator) { + OptionCreator optionCreator, MemberCreator memberCreator, TaskCreator taskCreator) { this.testCreator = testCreator; this.projectCreator = projectCreator; this.userCreator = userCreator; this.optionCreator = optionCreator; this.memberCreator = memberCreator; + this.taskCreator = taskCreator; } @PostConstruct @@ -33,5 +35,6 @@ public void init() { optionCreator.creator(); projectCreator.creator(); memberCreator.creator(); + taskCreator.creator(); } } diff --git a/src/main/java/team1/BE/seamless/init/MemberCreator.java b/src/main/java/team1/BE/seamless/init/MemberCreator.java index 07da476..97ab2d3 100644 --- a/src/main/java/team1/BE/seamless/init/MemberCreator.java +++ b/src/main/java/team1/BE/seamless/init/MemberCreator.java @@ -16,15 +16,24 @@ public MemberCreator(MemberService memberService) { } public void creator() { - // 테스트용으로 request데이터 생성 - MemberRequestDTO.CreateMember member1 = new MemberRequestDTO.CreateMember("권순호","MEMBER","ex1@gmail.com","exURL1"); - memberService.createMember(1L, member1); // HttpServletRequest는 null 처리가 안돼서 테스트용으로 새로운 create만듦 - - MemberRequestDTO.CreateMember member2 = new MemberRequestDTO.CreateMember("김동혁","USER","ex2@gmail.com","exURL2"); - memberService.createMember(1L, member2); // HttpServletRequest는 null 처리가 안돼서 테스트용으로 새로운 create만듦 - - MemberRequestDTO.CreateMember member3 = new MemberRequestDTO.CreateMember("김도헌","MEMBER","ex3@gmail.com","exURL3"); - memberService.createMember(1L, member3); // HttpServletRequest는 null 처리가 안돼서 테스트용으로 새로운 create만듦 +// // 테스트용으로 request데이터 생성 +// MemberRequestDTO.CreateMember member1 = new MemberRequestDTO.CreateMember("권순호","MEMBER","ex1@gmail.com","ZG4zyIfK/i2BNPoL4pYjbaasMQ9kZu2kuzj9VVcMuAD1g/vSWs+gt2doo4UpJmPR"); +// memberService.createMember(1L, member1); // HttpServletRequest는 null 처리가 안돼서 테스트용으로 새로운 create만듦 +// +// MemberRequestDTO.CreateMember member2 = new MemberRequestDTO.CreateMember("김동혁","USER","ex2@gmail.com","ZG4zyIfK/i2BNPoL4pYjbaasMQ9kZu2kuzj9VVcMuAD1g/vSWs+gt2doo4UpJmPR"); +// memberService.createMember(1L, member2); // HttpServletRequest는 null 처리가 안돼서 테스트용으로 새로운 create만듦 +// +// MemberRequestDTO.CreateMember member3 = new MemberRequestDTO.CreateMember("김도헌","MEMBER","ex3@gmail.com","ZG4zyIfK/i2BNPoL4pYjbaasMQ9kZu2kuzj9VVcMuAD1g/vSWs+gt2doo4UpJmPR"); +// memberService.createMember(1L, member3); // HttpServletRequest는 null 처리가 안돼서 테스트용으로 새로운 create만듦 + + MemberRequestDTO.CreateMember member1 = new MemberRequestDTO.CreateMember("ex1@gmail.com","cCeJvA99H7bV2ctvVIpM4Bh3ZJvawh3JnX3tREWGtNA="); + memberService.createMember(member1); + + MemberRequestDTO.CreateMember member2 = new MemberRequestDTO.CreateMember("ex2@gmail.com","cCeJvA99H7bV2ctvVIpM4Bh3ZJvawh3JnX3tREWGtNA="); + memberService.createMember(member2); + + MemberRequestDTO.CreateMember member3 = new MemberRequestDTO.CreateMember("ex3@gmail.com","cCeJvA99H7bV2ctvVIpM4Bh3ZJvawh3JnX3tREWGtNA="); + memberService.createMember(member3); } } diff --git a/src/main/java/team1/BE/seamless/init/TaskCreator.java b/src/main/java/team1/BE/seamless/init/TaskCreator.java new file mode 100644 index 0000000..2ab369a --- /dev/null +++ b/src/main/java/team1/BE/seamless/init/TaskCreator.java @@ -0,0 +1,27 @@ +package team1.BE.seamless.init; + +import java.time.LocalDateTime; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import team1.BE.seamless.DTO.TaskDTO.TaskCreate; +import team1.BE.seamless.service.TaskService; + +@Component +public class TaskCreator { + + private final TaskService taskService; + + @Autowired + public TaskCreator(TaskService taskService) { + this.taskService = taskService; + } + + public void creator() { + + TaskCreate task1 = new TaskCreate("태스크1", "첫번째 태스크입니다.", 1L, + LocalDateTime.of(2024, 10, 10, 0, 0), + LocalDateTime.of(2025, 9, 3, 0, 0)); + + taskService.createTask(1L, task1); + } +} diff --git a/src/main/java/team1/BE/seamless/mapper/MemberMapper.java b/src/main/java/team1/BE/seamless/mapper/MemberMapper.java index 56f6ce5..add4ac6 100644 --- a/src/main/java/team1/BE/seamless/mapper/MemberMapper.java +++ b/src/main/java/team1/BE/seamless/mapper/MemberMapper.java @@ -13,10 +13,10 @@ public class MemberMapper { public MemberEntity toEntity(CreateMember create, ProjectEntity project) { return new MemberEntity( - create.getName(), - create.getRole(), + "guest", + "", create.getEmail(), - create.getImageURL(), + "", project ); } @@ -43,11 +43,12 @@ public MemberResponseDTO toDeleteResponseDTO(MemberEntity memberEntity) { memberEntity.getEmail()); } - public MemberResponseDTO toCreateResponseDTO(MemberEntity memberEntity) { + public MemberResponseDTO toCreateResponseDTO(MemberEntity memberEntity, String code) { return new MemberResponseDTO("성공적으로 생성되었습니다.", memberEntity.getName(), memberEntity.getRole(), - memberEntity.getEmail()); + memberEntity.getEmail(), + code); } public MemberResponseDTO toPutResponseDTO(MemberEntity memberEntity) { diff --git a/src/main/java/team1/BE/seamless/mapper/OptionMapper.java b/src/main/java/team1/BE/seamless/mapper/OptionMapper.java index e00976a..de95b8a 100644 --- a/src/main/java/team1/BE/seamless/mapper/OptionMapper.java +++ b/src/main/java/team1/BE/seamless/mapper/OptionMapper.java @@ -3,8 +3,12 @@ import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import team1.BE.seamless.DTO.OptionDTO.OptionCreate; +import team1.BE.seamless.DTO.OptionDTO.OptionDetail; +import team1.BE.seamless.DTO.OptionDTO.OptionSimple; +import team1.BE.seamless.DTO.OptionDTO.updateOption; import team1.BE.seamless.entity.OptionEntity; import team1.BE.seamless.entity.enums.OptionType; +import team1.BE.seamless.util.Util; import team1.BE.seamless.util.errorException.BaseHandler; @Component @@ -18,12 +22,29 @@ public OptionEntity toEntity(OptionCreate create) { ); } + public OptionEntity toUpdate(OptionEntity entity, updateOption update) { + return entity.Update( + Util.isNull(update.getName()) ? entity.getName() : update.getName(), + Util.isNull(update.getDescription())? entity.getDescription() : update.getDescription(), + Util.isNull(update.getOptionType().toString())?entity.getOptionType():toOptionType(update.getOptionType()) + ); + } + public OptionType toOptionType(String optionType) { for (OptionType type : OptionType.values()) { if (type.getKey().equals(optionType)) { return type; } } - throw new BaseHandler(HttpStatus.NOT_FOUND,"잘못된 옵션 타입입니다."); + throw new BaseHandler(HttpStatus.NOT_FOUND, "잘못된 옵션 타입입니다."); + } + + public OptionSimple toSimple(OptionEntity entity) { + return new OptionSimple(entity.getId(), entity.getName(), entity.getOptionType()); + } + + public OptionDetail toDetail(OptionEntity entity) { + return new OptionDetail(entity.getId(), entity.getName(), entity.getDescription(), + entity.getOptionType(), entity.getCreatedAt(), entity.getUpdatedAt()); } } diff --git a/src/main/java/team1/BE/seamless/mapper/ProjectMapper.java b/src/main/java/team1/BE/seamless/mapper/ProjectMapper.java index ccc75aa..875b220 100644 --- a/src/main/java/team1/BE/seamless/mapper/ProjectMapper.java +++ b/src/main/java/team1/BE/seamless/mapper/ProjectMapper.java @@ -6,6 +6,7 @@ import team1.BE.seamless.DTO.ProjectDTO; import team1.BE.seamless.DTO.ProjectDTO.ProjectDetail; import team1.BE.seamless.DTO.ProjectDTO.ProjectPeriod; +import team1.BE.seamless.entity.OptionEntity; import team1.BE.seamless.entity.ProjectEntity; import team1.BE.seamless.entity.ProjectOption; import team1.BE.seamless.entity.UserEntity; @@ -30,7 +31,7 @@ public ProjectDetail toDetail(ProjectEntity projectEntity) { projectEntity.getName(), projectEntity.getStartDate(), projectEntity.getEndDate(), - projectEntity.getProjectOptions().stream().map(ProjectOption::getId).toList() + projectEntity.getProjectOptions().stream().map(ProjectOption::getOptionEntity).map(OptionEntity::getId).toList() ); } diff --git a/src/main/java/team1/BE/seamless/mapper/TaskMapper.java b/src/main/java/team1/BE/seamless/mapper/TaskMapper.java index 51ab0bf..c79398a 100644 --- a/src/main/java/team1/BE/seamless/mapper/TaskMapper.java +++ b/src/main/java/team1/BE/seamless/mapper/TaskMapper.java @@ -1,10 +1,9 @@ package team1.BE.seamless.mapper; -import java.time.LocalDateTime; import org.springframework.stereotype.Component; -import team1.BE.seamless.DTO.TaskDTO.Create; +import team1.BE.seamless.DTO.TaskDTO.TaskCreate; import team1.BE.seamless.DTO.TaskDTO.TaskDetail; -import team1.BE.seamless.DTO.TaskDTO.Update; +import team1.BE.seamless.DTO.TaskDTO.TaskUpdate; import team1.BE.seamless.entity.MemberEntity; import team1.BE.seamless.entity.ProjectEntity; import team1.BE.seamless.entity.TaskEntity; @@ -13,17 +12,17 @@ @Component public class TaskMapper { - public TaskEntity toEntity(ProjectEntity project, MemberEntity member, Create create) { + public TaskEntity toEntity(ProjectEntity project, MemberEntity member, TaskCreate taskCreate) { return new TaskEntity( - create.getName(), - create.getRemark(), + taskCreate.getName(), + taskCreate.getRemark(), project, member, - create.getStartDate(), - create.getEndDate()); + taskCreate.getStartDate(), + taskCreate.getEndDate()); } - public TaskEntity toUpdate(TaskEntity task, Update update) { + public TaskEntity toUpdate(TaskEntity task, TaskUpdate update) { task.setName(Util.isNull(update.getName()) ? task.getName() : update.getName()); task.setRemark(Util.isNull(update.getRemark()) ? task.getRemark() : update.getRemark()); task.setProgress(Util.isNull(update.getProgress().toString()) ? task.getProgress() diff --git a/src/main/java/team1/BE/seamless/repository/MemberRepository.java b/src/main/java/team1/BE/seamless/repository/MemberRepository.java index 0cac24b..d2ca7d1 100644 --- a/src/main/java/team1/BE/seamless/repository/MemberRepository.java +++ b/src/main/java/team1/BE/seamless/repository/MemberRepository.java @@ -17,4 +17,6 @@ public interface MemberRepository extends JpaRepository { Optional findByProjectEntityIdAndIdAndIsDeleteFalse(Long projectId, Long memberId); + Optional findByEmailAndIsDeleteFalse(String email); + } diff --git a/src/main/java/team1/BE/seamless/repository/OptionRepository.java b/src/main/java/team1/BE/seamless/repository/OptionRepository.java index 434e80d..89f4516 100644 --- a/src/main/java/team1/BE/seamless/repository/OptionRepository.java +++ b/src/main/java/team1/BE/seamless/repository/OptionRepository.java @@ -2,6 +2,9 @@ import java.util.List; +import java.util.Optional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import team1.BE.seamless.entity.OptionEntity; @@ -11,4 +14,8 @@ public interface OptionRepository extends JpaRepository { List findByIdIn(List ids); + Page findAllByIsDeletedFalse(Pageable pageable); + + Optional findByIdAndIsDeletedFalse(Long id); + } diff --git a/src/main/java/team1/BE/seamless/service/AttendURLService.java b/src/main/java/team1/BE/seamless/service/AttendURLService.java index 25744e8..44ff40e 100644 --- a/src/main/java/team1/BE/seamless/service/AttendURLService.java +++ b/src/main/java/team1/BE/seamless/service/AttendURLService.java @@ -50,7 +50,7 @@ public String generateAttendURL(HttpServletRequest req, @Valid Long projectId, @ // 코드는 프로젝트id + exp로 구성 // exp는 1일로 가정 String code = aesEncrypt.encrypt( - project.getId() + " " + LocalDateTime.now().plusDays(1).toString()); + project.getId() + " " + LocalDateTime.now().plusDays(1)); return DEFAULTURL + "invite?code=" + code; } diff --git a/src/main/java/team1/BE/seamless/service/AuthService.java b/src/main/java/team1/BE/seamless/service/AuthService.java index ac80bba..c304edb 100644 --- a/src/main/java/team1/BE/seamless/service/AuthService.java +++ b/src/main/java/team1/BE/seamless/service/AuthService.java @@ -1,6 +1,7 @@ package team1.BE.seamless.service; import jakarta.validation.Valid; +import java.time.LocalDateTime; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -90,7 +91,7 @@ protected UserEntity saveOrUpdate(OAuthAttributes attributes) { return userRepository.save(user); } - public Token memberCodeJoin(@Valid String memberCode) { + public Token memberCodeJoin(String memberCode) { // decode String code = aesEncrypt.decrypt(memberCode); @@ -104,9 +105,10 @@ public Token memberCodeJoin(@Valid String memberCode) { return new Token(token); } - public String memberCodeCreate(@Valid String memberCode) { + public String memberCodeCreate(String memberCode) { // ENCODE - String code = aesEncrypt.encrypt(memberCode); + String code = aesEncrypt.encrypt( + 1 + "_" + LocalDateTime.now().plusDays(1000)); return code; } } \ No newline at end of file diff --git a/src/main/java/team1/BE/seamless/service/InviteCodeByEmailService.java b/src/main/java/team1/BE/seamless/service/InviteCodeByEmailService.java new file mode 100644 index 0000000..b8a57f0 --- /dev/null +++ b/src/main/java/team1/BE/seamless/service/InviteCodeByEmailService.java @@ -0,0 +1,68 @@ +package team1.BE.seamless.service; +// 팀원이 초대링크에 해당하는 페이지에서 이름, 이메일을 작성하여 +// 요청을 보낼 때의 서비스 계층 + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; +import team1.BE.seamless.entity.ProjectEntity; +import team1.BE.seamless.repository.ProjectRepository; +import team1.BE.seamless.util.errorException.BaseHandler; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.UUID; + +@Service +public class InviteCodeByEmailService { + + private JavaMailSender mailSender; + private ProjectRepository projectRepository; + + @Autowired + InviteCodeByEmailService(JavaMailSender mailSender, ProjectRepository projectRepository) { + this.mailSender = mailSender; + this.projectRepository = projectRepository; + } + + public void sendProjectInvite(String email, Long projectId) { + // 프로젝트 존재 검증 + ProjectEntity project = projectRepository.findById(projectId) + .orElseThrow(() -> new BaseHandler(HttpStatus.NOT_FOUND, "해당 프로젝트가 존재하지 않습니다.")); + +// 프로젝트 종료 기간 검증 +// if (project.getEndDate().isBefore(LocalDateTime.now())) { +// throw new BaseHandler(HttpStatus.BAD_REQUEST, "프로젝트는 종료되었습니다."); +// } // 프로젝트 initData에 EndDate 설정이 안되어있어서 지금 테스트하면 오류걸림 그래서 주석처리 해놓음ㅇㅇ + + // 팀원인지 팀장인지 검증은 필요없음.(어차피 이 post요청은 아무 권한 없는 사람이 보내는 것 취급임) ㄴ + + + // 참여코드 생성 (UUID 기반 + 현재 시간) + String participationCode = generateParticipationCode(); + + // 이메일 메시지 내용 생성 + String message = "안녕하세요,\n\n" + + "프로젝트 '" + project.getName() + "'에 초대되었습니다.\n" + + "참여 코드는 다음과 같습니다: " + participationCode + "\n\n" + + "프로젝트에 참여하려면 초대 코드를 사용하여 입장해주세요.\n\n" + + "감사합니다."; + + + SimpleMailMessage mailMessage = new SimpleMailMessage(); + mailMessage.setTo(email); + mailMessage.setSubject("[프로젝트 초대] 프로젝트 '" + project.getName() + "'에 참여하세요!"); // 이메일 제목 설정임. + mailMessage.setText(message); + mailSender.send(mailMessage); + } + + + private String generateParticipationCode() { + String uniqueId = UUID.randomUUID().toString().substring(0, 8); // 8자리 코드임 + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmm")); + return uniqueId + "-" + timestamp; // 참여코드 예: 1234abcd-202410161530 와 같이 생성됨. + // 이렇게 시간을 기준으로 만들면, 이전 or 다른 팀장의 프로젝트의 참여코드와 겹칠 일이 없게됨. + } +} \ No newline at end of file diff --git a/src/main/java/team1/BE/seamless/service/MemberService.java b/src/main/java/team1/BE/seamless/service/MemberService.java index 4102347..9ff998b 100644 --- a/src/main/java/team1/BE/seamless/service/MemberService.java +++ b/src/main/java/team1/BE/seamless/service/MemberService.java @@ -17,6 +17,8 @@ import team1.BE.seamless.mapper.MemberMapper; import team1.BE.seamless.repository.MemberRepository; import team1.BE.seamless.repository.ProjectRepository; +import team1.BE.seamless.util.Util; +import team1.BE.seamless.util.auth.AesEncrypt; import team1.BE.seamless.util.auth.ParsingPram; import team1.BE.seamless.util.errorException.BaseHandler; @@ -29,14 +31,16 @@ public class MemberService { private final MemberMapper memberMapper; private final ProjectRepository projectRepository; private final ParsingPram parsingPram; + private final AesEncrypt aesEncrypt; @Autowired public MemberService(MemberRepository memberRepository, MemberMapper memberMapper, - ProjectRepository projectRepository, ParsingPram parsingPram) { + ProjectRepository projectRepository, ParsingPram parsingPram, AesEncrypt aesEncrypt) { this.memberRepository = memberRepository; this.memberMapper = memberMapper; this.projectRepository = projectRepository; this.parsingPram = parsingPram; + this.aesEncrypt = aesEncrypt; } public MemberResponseDTO getMember(Long projectId, Long memberId, HttpServletRequest req) { @@ -70,27 +74,41 @@ public Page getMemberList(@Valid Long projectId, memberListRequestDTO.toPageable()); } - public MemberResponseDTO createMember(Long projectId, CreateMember create, HttpServletRequest req) { - // 팀원인지 확인.. 삭제함 - - ProjectEntity project = projectRepository.findById(projectId) - .orElseThrow(() -> new BaseHandler(HttpStatus.NOT_FOUND, "해당 프로젝트가 존재하지 않습니다.")); +// public MemberResponseDTO createMember(Long projectId, CreateMember create, HttpServletRequest req) { +// // 팀원인지 확인.. 삭제함 +// +// ProjectEntity project = projectRepository.findById(projectId) +// .orElseThrow(() -> new BaseHandler(HttpStatus.NOT_FOUND, "해당 프로젝트가 존재하지 않습니다.")); +// +// // 아래는 프로젝트가 종료됐는데, 그 후에 팀원이 참여링크를 통해 프로젝트 참여를 했을 때 걸러내는거임ㅇㅇ +//// if (project.getEndDate().isBefore(LocalDateTime.now())) { +//// throw new BaseHandler(HttpStatus.BAD_REQUEST, "프로젝트는 종료되었습니다."); +//// } 프로젝트 initData에 EndDate 설정이 안되어있어서 지금 테스트하면 오류걸림 그래서 주석처리 해놓음ㅇㅇ +// +// MemberEntity member = memberMapper.toEntity(create, project); +// memberRepository.save(member); +// +// return memberMapper.toCreateResponseDTO(member); +// } - // 아래는 프로젝트가 종료됐는데, 그 후에 팀원이 참여링크를 통해 프로젝트 참여를 했을 때 걸러내는거임ㅇㅇ -// if (project.getEndDate().isBefore(LocalDateTime.now())) { -// throw new BaseHandler(HttpStatus.BAD_REQUEST, "프로젝트는 종료되었습니다."); -// } 프로젝트 initData에 EndDate 설정이 안되어있어서 지금 테스트하면 오류걸림 그래서 주석처리 해놓음ㅇㅇ + @Transactional + public MemberResponseDTO createMember(CreateMember create) { - MemberEntity member = memberMapper.toEntity(create, project); - memberRepository.save(member); +// 프로젝트id, exp + String[] temp = aesEncrypt.decrypt(create.getCode()).split("_"); - return memberMapper.toCreateResponseDTO(member); - } +// exp검사 + if (Util.parseDate(temp[1]).isBefore(LocalDateTime.now())) { + throw new BaseHandler(HttpStatus.FORBIDDEN,"초대 코드가 만료되었습니다."); + } - public MemberResponseDTO createMember(Long projectId, CreateMember create) { - // 테스트용 오버로딩임. 삭제 금지 +// 멤버 이메일 중복 여부 검사 + if(memberRepository.findByEmailAndIsDeleteFalse(create.getEmail()).isPresent()){ + throw new BaseHandler(HttpStatus.UNAUTHORIZED,"이메일이 중복 됩니다."); + }; - ProjectEntity project = projectRepository.findById(projectId) +// 프로젝트 조회 + ProjectEntity project = projectRepository.findById(Long.parseLong(temp[0])) .orElseThrow(() -> new BaseHandler(HttpStatus.NOT_FOUND, "해당 프로젝트가 존재하지 않습니다.")); // if (project.getEndDate().isBefore(LocalDateTime.now())) { @@ -100,7 +118,13 @@ public MemberResponseDTO createMember(Long projectId, CreateMember create) { MemberEntity member = memberMapper.toEntity(create, project); memberRepository.save(member); - return memberMapper.toCreateResponseDTO(member); +// 코드 생성 + String code = aesEncrypt.encrypt(member.getId().toString()); + System.out.println(code); + +// 이메일로 코드 전달(추가 요망) + + return memberMapper.toCreateResponseDTO(member, code); } @Transactional @@ -134,8 +158,8 @@ public MemberResponseDTO deleteMember(Long projectId, Long memberId, HttpServlet // } 프로젝트 initData에 EndDate 설정이 안되어있어서 지금 테스트하면 오류걸림 그래서 주석처리 해놓음ㅇㅇ // 팀장인지 확인(팀원인지 굳이 한번 더 확인하지 않음. 팀장인지만 검증.) - if (parsingPram.getRole(req).equals(Role.USER.toString())) { - throw new BaseHandler(HttpStatus.UNAUTHORIZED,"수정 권한이 없습니다."); + if (parsingPram.getRole(req).equals(Role.MEMBER.toString())) { + throw new BaseHandler(HttpStatus.FORBIDDEN,"수정 권한이 없습니다."); } MemberEntity member = memberRepository.findByProjectEntityIdAndIdAndIsDeleteFalse( diff --git a/src/main/java/team1/BE/seamless/service/OptionService.java b/src/main/java/team1/BE/seamless/service/OptionService.java index d984f56..ceb917d 100644 --- a/src/main/java/team1/BE/seamless/service/OptionService.java +++ b/src/main/java/team1/BE/seamless/service/OptionService.java @@ -1,12 +1,20 @@ package team1.BE.seamless.service; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import team1.BE.seamless.DTO.OptionDTO.OptionCreate; +import team1.BE.seamless.DTO.OptionDTO.OptionDetail; +import team1.BE.seamless.DTO.OptionDTO.OptionSimple; +import team1.BE.seamless.DTO.OptionDTO.getList; +import team1.BE.seamless.DTO.OptionDTO.updateOption; import team1.BE.seamless.entity.OptionEntity; +import team1.BE.seamless.entity.enums.Role; import team1.BE.seamless.mapper.OptionMapper; import team1.BE.seamless.repository.OptionRepository; +import team1.BE.seamless.util.errorException.BaseHandler; @Service public class OptionService { @@ -21,8 +29,67 @@ public OptionService(OptionRepository optionRepository, OptionMapper optionMappe this.optionMapper = optionMapper; } + public Page getProjectOptionList(getList param, String role) { + if (!role.equals(Role.USER.getKey())) { + throw new BaseHandler(HttpStatus.FORBIDDEN, "로그인한 유저만 조회 가능합니다."); + } + + return optionRepository.findAllByIsDeletedFalse(param.toPageable()).map(optionMapper::toSimple); + } + + public OptionDetail getOption(Long id, String role) { + if (!role.equals(Role.USER.getKey())) { + throw new BaseHandler(HttpStatus.FORBIDDEN, "로그인한 유저만 조회 가능합니다."); + } + + return optionMapper.toDetail(optionRepository.findByIdAndIsDeletedFalse(id) + .orElseThrow(() -> new BaseHandler(HttpStatus.NOT_FOUND, "해당하는 옵션이 존재하지 않습니다."))); + } + + @Transactional + public OptionDetail createOption(OptionCreate create, String role) { + if (!role.equals(Role.USER.getKey())) { + throw new BaseHandler(HttpStatus.FORBIDDEN, "로그인한 유저만 생성 가능합니다."); + } + OptionEntity optionEntity = optionMapper.toEntity(create); + optionRepository.save(optionEntity); + + return optionMapper.toDetail(optionEntity); + } + + @Transactional + public OptionDetail updateOption(Long id, updateOption update, String role) { + if (!role.equals(Role.USER.getKey())) { + throw new BaseHandler(HttpStatus.FORBIDDEN, "로그인한 유저만 수정 가능합니다."); + } + OptionEntity option = optionRepository.findByIdAndIsDeletedFalse(id) + .orElseThrow(() -> new BaseHandler(HttpStatus.NOT_FOUND, "해당하는 옵션이 존재하지 않습니다.")); + + optionMapper.toUpdate(option, update); + + return optionMapper.toDetail(option); + } + + @Transactional + public OptionDetail deleteOption(Long id, String role) { + if (!role.equals(Role.USER.getKey())) { + throw new BaseHandler(HttpStatus.FORBIDDEN, "로그인한 유저만 삭제 가능합니다."); + } + + OptionEntity option = optionRepository.findByIdAndIsDeletedFalse(id) + .orElseThrow(() -> new BaseHandler(HttpStatus.NOT_FOUND, "해당하는 옵션이 존재하지 않습니다.")); + + option.setIsDeleted(true); + + return optionMapper.toDetail(option); + } + + /** + * 테스트용 + */ @Transactional public OptionEntity createOption(OptionCreate create) { return optionRepository.save(optionMapper.toEntity(create)); } + } diff --git a/src/main/java/team1/BE/seamless/service/ProjectInviteService.java b/src/main/java/team1/BE/seamless/service/ProjectInviteService.java deleted file mode 100644 index 816b8c4..0000000 --- a/src/main/java/team1/BE/seamless/service/ProjectInviteService.java +++ /dev/null @@ -1,23 +0,0 @@ -package team1.BE.seamless.service; -// 팀원이 초대링크에 해당하는 페이지에서 이름, 이메일을 작성하여 -// 요청을 보낼 때의 서비스 계층 - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.mail.SimpleMailMessage; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.stereotype.Service; - -@Service -public class ProjectInviteService { - - @Autowired - private JavaMailSender mailSender; - - public void sendProjectInvite(String email, String message) { - SimpleMailMessage mailMessage = new SimpleMailMessage(); - mailMessage.setTo(email); - mailMessage.setSubject("Project participation code"); - mailMessage.setText(message); - mailSender.send(mailMessage); - } -} diff --git a/src/main/java/team1/BE/seamless/service/ProjectService.java b/src/main/java/team1/BE/seamless/service/ProjectService.java index 3c2d468..38f963b 100644 --- a/src/main/java/team1/BE/seamless/service/ProjectService.java +++ b/src/main/java/team1/BE/seamless/service/ProjectService.java @@ -6,16 +6,17 @@ import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import team1.BE.seamless.DTO.MemberResponseDTO; import team1.BE.seamless.DTO.ProjectDTO; import team1.BE.seamless.DTO.ProjectDTO.ProjectCreate; import team1.BE.seamless.DTO.ProjectDTO.ProjectDetail; import team1.BE.seamless.DTO.ProjectDTO.ProjectPeriod; import team1.BE.seamless.DTO.ProjectDTO.ProjectUpdate; -import team1.BE.seamless.entity.MemberEntity; import team1.BE.seamless.entity.OptionEntity; import team1.BE.seamless.entity.ProjectEntity; import team1.BE.seamless.entity.ProjectOption; import team1.BE.seamless.entity.UserEntity; +import team1.BE.seamless.mapper.MemberMapper; import team1.BE.seamless.mapper.ProjectMapper; import team1.BE.seamless.repository.OptionRepository; import team1.BE.seamless.repository.ProjectRepository; @@ -29,14 +30,16 @@ public class ProjectService { private final UserRepository userRepository; private final OptionRepository optionRepository; private final ProjectMapper projectMapper; + private final MemberMapper memberMapper; @Autowired public ProjectService(ProjectRepository projectRepository, UserRepository userRepository, - OptionRepository optionRepository, ProjectMapper projectMapper) { + OptionRepository optionRepository, ProjectMapper projectMapper, MemberMapper memberMapper) { this.projectRepository = projectRepository; this.userRepository = userRepository; this.optionRepository = optionRepository; this.projectMapper = projectMapper; + this.memberMapper = memberMapper; } /** @@ -65,10 +68,10 @@ public ProjectDetail getProject(long id) { * @param id : 프로젝트 Id * @return : 해당 id를 가진 프로젝트에 참여한 팀원들의 목록 * */ - public List getProjectMembers(long id) { + public List getProjectMembers(long id) { ProjectEntity projectEntity = projectRepository.findById(id) .orElseThrow(() -> new BaseHandler(HttpStatus.NOT_FOUND, "프로젝트가 존재하지 않음")); - return projectEntity.getMemberEntities(); + return projectEntity.getMemberEntities().stream().map( entity -> memberMapper.toGetResponseDTO(entity)).toList(); } /** @@ -154,6 +157,9 @@ public Long deleteProject(long id) { ProjectEntity projectEntity = projectRepository.findById(id) .orElseThrow(() -> new BaseHandler(HttpStatus.NOT_FOUND, "프로젝트가 존재하지 않음")); + if(projectEntity.getIsDeleted()) { + throw new BaseHandler(HttpStatus.BAD_REQUEST, "해당 프로젝트는 지워진 상태 입니다."); + } projectEntity.setIsDeleted(true); return projectEntity.getId(); } diff --git a/src/main/java/team1/BE/seamless/service/TaskService.java b/src/main/java/team1/BE/seamless/service/TaskService.java index 5ca6828..eb59232 100644 --- a/src/main/java/team1/BE/seamless/service/TaskService.java +++ b/src/main/java/team1/BE/seamless/service/TaskService.java @@ -7,10 +7,7 @@ import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import team1.BE.seamless.DTO.TaskDTO.Create; -import team1.BE.seamless.DTO.TaskDTO.TaskDetail; -import team1.BE.seamless.DTO.TaskDTO.Update; -import team1.BE.seamless.DTO.TaskDTO.getList; +import team1.BE.seamless.DTO.TaskDTO.*; import team1.BE.seamless.entity.MemberEntity; import team1.BE.seamless.entity.ProjectEntity; import team1.BE.seamless.entity.TaskEntity; @@ -45,30 +42,59 @@ public TaskDetail getTask(Long taskId) { TaskEntity taskEntity = taskRepository.findByIdAndIsDeletedFalse(taskId) .orElseThrow(() -> new BaseHandler(HttpStatus.NOT_FOUND, "존재하지 않는 태스크")); + Long projectId = taskEntity.getProject().getId(); + + ProjectEntity project = projectRepository.findByIdAndIsDeletedFalse(projectId).orElseThrow(() -> new BaseHandler(HttpStatus.NOT_FOUND, "존재하지 않는 프로젝트")); + return taskMapper.toDetail(taskEntity); } public Page getTaskList(Long projectId, getList param) { + ProjectEntity project = projectRepository.findByIdAndIsDeletedFalse(projectId).orElseThrow(() -> new BaseHandler(HttpStatus.NOT_FOUND, "존재하지 않는 프로젝트")); + Page taskEntities = taskRepository.findAllByProjectEntityIdAndIsDeletedFalse(projectId, param.toPageable()); return taskEntities.map(taskMapper::toDetail); } - public TaskDetail createTask(HttpServletRequest req, @Valid Long projectId, Create create) { + public TaskDetail createTask(HttpServletRequest req, Long projectId, TaskCreate taskCreate) { ProjectEntity project = projectRepository.findByIdAndUserEntityEmailAndIsDeletedFalse( projectId, parsingPram.getEmail(req)) .orElseThrow(() -> new BaseHandler(HttpStatus.NOT_FOUND, "존재하지 않는 프로젝트")); // 태스크의 일정 검증 - if (project.getStartDate().isAfter(create.getStartDate()) || project.getEndDate() - .isBefore(create.getEndDate())) { - throw new BaseHandler(HttpStatus.FORBIDDEN, "태스크는 프로젝트의 기한을 넘어설 수 없습니다."); + if (project.getStartDate().isAfter(taskCreate.getStartDate()) || project.getEndDate() + .isBefore(taskCreate.getEndDate())) { + throw new BaseHandler(HttpStatus.BAD_REQUEST, "태스크는 프로젝트의 기한을 넘어설 수 없습니다."); + } + + MemberEntity member = memberRepository.findById(taskCreate.getMemberId()) + .orElseThrow(() -> new BaseHandler(HttpStatus.NOT_FOUND, "존재하지 않는 멤버")); + + TaskEntity taskEntity = taskMapper.toEntity(project, member, taskCreate); + + taskRepository.save(taskEntity); + + return taskMapper.toDetail(taskEntity); + } + + // 테스트용 오버로딩 + public TaskDetail createTask(Long projectId, TaskCreate taskCreate) { + + ProjectEntity project = projectRepository.findByIdAndUserEntityEmailAndIsDeletedFalse( + projectId, "user1@google.com") + .orElseThrow(() -> new BaseHandler(HttpStatus.NOT_FOUND, "존재하지 않는 프로젝트")); + +// 태스크의 일정 검증 + if (project.getStartDate().isAfter(taskCreate.getStartDate()) || project.getEndDate() + .isBefore(taskCreate.getEndDate())) { + throw new BaseHandler(HttpStatus.BAD_REQUEST, "태스크는 프로젝트의 기한을 넘어설 수 없습니다."); } - MemberEntity member = memberRepository.findById(create.getMemberId()) + MemberEntity member = memberRepository.findById(taskCreate.getMemberId()) .orElseThrow(() -> new BaseHandler(HttpStatus.NOT_FOUND, "존재하지 않는 멤버")); - TaskEntity taskEntity = taskMapper.toEntity(project, member, create); + TaskEntity taskEntity = taskMapper.toEntity(project, member, taskCreate); taskRepository.save(taskEntity); @@ -76,14 +102,14 @@ public TaskDetail createTask(HttpServletRequest req, @Valid Long projectId, Crea } @Transactional - public TaskDetail updateTask(HttpServletRequest req, @Valid Long taskId, @Valid Update update) { + public TaskDetail updateTask(HttpServletRequest req, Long taskId, TaskUpdate update) { TaskEntity task = taskRepository.findByIdAndIsDeletedFalse(taskId) .orElseThrow(() -> new BaseHandler(HttpStatus.NOT_FOUND, "존재하지 않는 태스크")); // 태스크의 일정 검증 if (task.getProject().getStartDate().isAfter(update.getStartDate()) || task.getProject() .getEndDate().isBefore(update.getEndDate())) { - throw new BaseHandler(HttpStatus.FORBIDDEN, "태스크는 프로젝트의 기한을 넘어설 수 없습니다."); + throw new BaseHandler(HttpStatus.BAD_REQUEST, "태스크는 프로젝트의 기한을 넘어설 수 없습니다."); } // 수정 권한이 있는지 검증 diff --git a/src/main/java/team1/BE/seamless/service/UserService.java b/src/main/java/team1/BE/seamless/service/UserService.java index 791b69e..261d1d3 100644 --- a/src/main/java/team1/BE/seamless/service/UserService.java +++ b/src/main/java/team1/BE/seamless/service/UserService.java @@ -37,7 +37,7 @@ public UserDetails getUser(HttpServletRequest req) { } @Transactional - public UserSimple updateUser(HttpServletRequest req, @Valid UserUpdate update) { + public UserSimple updateUser(HttpServletRequest req, UserUpdate update) { UserEntity user = userRepository.findByEmailAndIsDeleteFalse(parsingPram.getEmail(req)) .orElseThrow(() -> new BaseHandler(HttpStatus.NOT_FOUND, "해당 유저가 존재하지 않습니다.")); @@ -57,7 +57,7 @@ public UserSimple deleteUser(HttpServletRequest req) { } @Transactional - public UserEntity createUser(@Valid UserSimple simple) { + public UserEntity createUser( UserSimple simple) { return userRepository.findByEmailAndIsDeleteFalse(simple.getEmail()) .orElseGet(() -> userRepository.save( userMapper.toEntity( diff --git a/src/main/java/team1/BE/seamless/util/Util.java b/src/main/java/team1/BE/seamless/util/Util.java index 94d8cfd..58bba7b 100644 --- a/src/main/java/team1/BE/seamless/util/Util.java +++ b/src/main/java/team1/BE/seamless/util/Util.java @@ -1,5 +1,8 @@ package team1.BE.seamless.util; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + public class Util { public Util() { @@ -8,4 +11,11 @@ public Util() { public static boolean isNull(String str) { return str == null || str.isBlank(); } + + public static LocalDateTime parseDate(String date) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS"); + LocalDateTime dateTime = LocalDateTime.parse(date, formatter); + + return dateTime; + } } diff --git a/src/main/java/team1/BE/seamless/util/auth/CookieUtils.java b/src/main/java/team1/BE/seamless/util/auth/CookieUtils.java new file mode 100644 index 0000000..3f4ca7b --- /dev/null +++ b/src/main/java/team1/BE/seamless/util/auth/CookieUtils.java @@ -0,0 +1,57 @@ +package team1.BE.seamless.util.auth; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.Base64; +import java.util.Optional; +import org.springframework.util.SerializationUtils; + + +public class CookieUtils { + + public static Optional getCookie(HttpServletRequest request, String name) { + Cookie[] cookies = request.getCookies(); + if (cookies != null && cookies.length > 0) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals(name)) { + return Optional.of(cookie); + } + } + } + return Optional.empty(); + } + + public static void addCookie(HttpServletResponse response, String name, String value, + int maxAge) { + Cookie cookie = new Cookie(name, value); + cookie.setPath("/"); + cookie.setHttpOnly(true); + cookie.setMaxAge(maxAge); + response.addCookie(cookie); + } + + public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, + String name) { + Cookie[] cookies = request.getCookies(); + if (cookies != null && cookies.length > 0) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals(name)) { + cookie.setValue(""); + cookie.setPath("/"); + cookie.setMaxAge(0); + response.addCookie(cookie); + } + } + } + } + + public static String serialize(Object object) { + return Base64.getUrlEncoder().encodeToString(SerializationUtils.serialize(object)); + } + + public static T deserialize(Cookie cookie, Class cls) { + return cls.cast( + SerializationUtils.deserialize(Base64.getUrlDecoder().decode(cookie.getValue()))); + } +} \ No newline at end of file diff --git a/src/main/java/team1/BE/seamless/util/auth/HttpCookieOAuth2AuthorizationRequestRepository.java b/src/main/java/team1/BE/seamless/util/auth/HttpCookieOAuth2AuthorizationRequestRepository.java new file mode 100644 index 0000000..0c5ccd1 --- /dev/null +++ b/src/main/java/team1/BE/seamless/util/auth/HttpCookieOAuth2AuthorizationRequestRepository.java @@ -0,0 +1,50 @@ +package team1.BE.seamless.util.auth; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.stereotype.Component; + +@Component +public class HttpCookieOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository { + + public static final String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request"; + public static final String REDIRECT_URI_PARAM_COOKIE_NAME = "redirect_uri"; + private static final int cookieExpireSeconds = 180; + + @Override + public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) { + OAuth2AuthorizationRequest oAuth2AuthorizationRequest = CookieUtils.getCookie(request, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME) + .map(cookie -> CookieUtils.deserialize(cookie, OAuth2AuthorizationRequest.class)) + .orElse(null); + return oAuth2AuthorizationRequest; + } + + @Override + public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) { + if (authorizationRequest == null) { + removeAuthorizationRequest(request, response); + return; + } + + CookieUtils.addCookie(response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, CookieUtils.serialize(authorizationRequest), cookieExpireSeconds); + String redirectUriAfterLogin = request.getParameter(REDIRECT_URI_PARAM_COOKIE_NAME); + if (StringUtils.isNotBlank(redirectUriAfterLogin)) { + CookieUtils.addCookie(response, REDIRECT_URI_PARAM_COOKIE_NAME, redirectUriAfterLogin, cookieExpireSeconds); + } + } + + @Override + public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request, + HttpServletResponse response) { + return this.loadAuthorizationRequest(request); + } + + public void removeAuthorizationRequestCookies(HttpServletRequest request, HttpServletResponse response) { + CookieUtils.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME); + CookieUtils.deleteCookie(request, response, REDIRECT_URI_PARAM_COOKIE_NAME); + } + +} \ No newline at end of file diff --git a/src/main/java/team1/BE/seamless/util/auth/JwtToken.java b/src/main/java/team1/BE/seamless/util/auth/JwtToken.java index bf93b4e..c596b7e 100644 --- a/src/main/java/team1/BE/seamless/util/auth/JwtToken.java +++ b/src/main/java/team1/BE/seamless/util/auth/JwtToken.java @@ -83,7 +83,7 @@ public String createUserToken(UserEntity user) { ZonedDateTime expirationDateTime = now.plusSeconds(tokenExpTime); Claims claims = Jwts.claims(); - claims.put("authentication", Role.MEMBER.toString()); + claims.put("authentication", Role.USER.toString()); claims.put("email", user.getEmail()); return Jwts.builder() .setClaims(claims) diff --git a/src/main/java/team1/BE/seamless/util/auth/SecurityConfig.java b/src/main/java/team1/BE/seamless/util/auth/SecurityConfig.java index 5777bc2..6afb5e2 100644 --- a/src/main/java/team1/BE/seamless/util/auth/SecurityConfig.java +++ b/src/main/java/team1/BE/seamless/util/auth/SecurityConfig.java @@ -24,22 +24,26 @@ public class SecurityConfig { private final TokenAuthenticationFilter tokenAuthenticationFilter; private final TokenExceptionFilter tokenExceptionFilter; private final SecurityEntryPoint SecurityException; + private final HttpCookieOAuth2AuthorizationRequestRepository authorizationRequestRepository; @Autowired public SecurityConfig(AuthService authService, OAuth2SuccessHandler successHandler, TokenAuthenticationFilter tokenAuthenticationFilter, TokenExceptionFilter tokenExceptionFilter, - SecurityEntryPoint SecurityException) { + SecurityEntryPoint securityException, + HttpCookieOAuth2AuthorizationRequestRepository authorizationRequestRepository) { this.authService = authService; this.successHandler = successHandler; this.tokenAuthenticationFilter = tokenAuthenticationFilter; this.tokenExceptionFilter = tokenExceptionFilter; - this.SecurityException = SecurityException; + SecurityException = securityException; + this.authorizationRequestRepository = authorizationRequestRepository; } @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + public SecurityFilterChain securityFilterChain(HttpSecurity http, + HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .cors(AbstractHttpConfigurer::disable) @@ -62,9 +66,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti // 인증, h2 .requestMatchers("/h2-console/**", "/auth/**","/api/test/**").permitAll() // 멤버 생성 - .requestMatchers(HttpMethod.POST,"/api/project/{project_id}/member/**").permitAll() + .requestMatchers(HttpMethod.POST,"/api/project/**/member/**").permitAll() // 멤버 조회 - .requestMatchers(HttpMethod.GET,"/api/project/{project_id}/member/**").permitAll() + .requestMatchers(HttpMethod.GET,"/api/project/**/member/**").permitAll() .anyRequest() .authenticated() ) @@ -72,8 +76,12 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .oauth2Login(oauth -> oauth .userInfoEndpoint(c -> c.userService(authService)) .successHandler(successHandler) + .authorizationEndpoint() + .baseUri("/login/oauth2/code/*") + .authorizationRequestRepository(httpCookieOAuth2AuthorizationRequestRepository) ) + // .exceptionHandling(handler -> handler.authenticationEntryPoint(SecurityException)) .addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6079bcb..a349acf 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -13,18 +13,18 @@ spring.jpa.database=h2 spring.jpa.defer-datasource-initialization=true spring.jpa.properties.hibernate.format_sql=true spring.jpa.show-sql=true -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect jwt.secretKey=KO)$*"g+zWechI9]KE|'irIM?ky--x/|p.K&>bA>Ef,gfD/)ekd/UcBE2kQ@BhE jwt.tokenExpTime=3600 code.secretKey=ae2af8cfb8721e39189f6f5edc928589 -spring.profiles.active=build +spring.profiles.active=dev server.forward-headers-strategy=framework spring.profiles.include=oauth spring.mvc.pathmatch.matching-strategy=ant_path_matcher spring.mail.host=smtp.gmail.com spring.mail.port=587 -spring.mail.username=example@gmail.com -spring.mail.password=123 +spring.mail.username=${mail.username} +spring.mail.password=${mail.password} spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true \ No newline at end of file diff --git a/src/test/java/team1/BE/seamless/ProjectServiceTest.java b/src/test/java/team1/BE/seamless/ProjectServiceTest.java new file mode 100644 index 0000000..8c9f12d --- /dev/null +++ b/src/test/java/team1/BE/seamless/ProjectServiceTest.java @@ -0,0 +1,157 @@ +package team1.BE.seamless; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.http.HttpMethod.DELETE; +import static org.springframework.http.HttpMethod.POST; +import static org.springframework.http.HttpMethod.PUT; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.OK; + +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import team1.BE.seamless.DTO.ProjectDTO.ProjectCreate; +import team1.BE.seamless.DTO.ProjectDTO.ProjectUpdate; +import team1.BE.seamless.service.ProjectService; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class ProjectServiceTest { + @LocalServerPort + private int port; + private String url = "http://localhost:"; + private final TestRestTemplate restTemplate; + private String token; + private HttpHeaders headers = new HttpHeaders(); + private final ProjectService projectService; + @Autowired + public ProjectServiceTest(TestRestTemplate restTemplate, ProjectService projectService) { + this.restTemplate = restTemplate; + this.projectService = projectService; + } + /** + * 특정 유저id의 토큰 파싱 + */ + @BeforeEach + public void setUp() { + HttpEntity requestEntity = new HttpEntity<>(null); + ResponseEntity responseEntity = restTemplate.exchange( + url + port + "/api/test/userToken/1", + POST, + requestEntity, String.class); + int startIndex = responseEntity.getBody().indexOf("\"token\":\"") + "\"token\":\"".length(); + int endIndex = responseEntity.getBody().indexOf("\"", startIndex); + token = responseEntity.getBody().substring(startIndex, endIndex); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(token); + } + + @Test + void 프로젝트_생성_성공() { + ProjectCreate body = new ProjectCreate("프로젝트 이름2", + LocalDateTime.of(2024, 11, 1, 1, 1, 1), + LocalDateTime.of(2024, 11, 4, 4, 4, 4), + List.of(2L, 3L)); + HttpEntity requestEntity = new HttpEntity(body, headers); + ResponseEntity responseEntity = restTemplate.exchange(url + port + "/api/project", + POST, + requestEntity, + String.class); + System.out.println(responseEntity.getBody()); + assertThat(responseEntity.getStatusCode()).isEqualTo(OK); + } + + @Test + void 프로젝트_날짜_수정_성공() { + ProjectUpdate body = new ProjectUpdate("프로젝트 이름1", List.of(1L, 2L, 3L), + LocalDateTime.of(2024, 10, 1, 0, 0, 0), + LocalDateTime.of(2024, 10, 4, 4, 4, 4)); + HttpEntity requestEntity = new HttpEntity(body, headers); + ResponseEntity responseEntity = restTemplate.exchange(url + port + "/api/project/1", + PUT, + requestEntity, + String.class); + System.out.println(responseEntity.getBody()); + assertThat(responseEntity.getStatusCode()).isEqualTo(OK); + } + + /** + * + * 이 부분 전에 ProjectDTO에서 startDate, endDate 생성 시 현재 이전 값도 되야 되지 않나? + * 라고 말했던 적 있어서 일단 만들어 놓고 주석처리 + * 현재는 @Future가 설정 안되어 잇어서 과거의 값도 생성 가능한 상태 + */ +// @Test +// void 프로젝트_날짜_현재_이전_불가() { +// // 현재보다 이전인 날짜 설정 +// ProjectUpdate body = new ProjectUpdate("프로젝트 이름", List.of(1L, 2L, 3L), +// LocalDateTime.of(2022, 10, 1, 0, 0), +// LocalDateTime.of(2022, 10, 5, 0, 0)); +// +// HttpEntity requestEntity = new HttpEntity<>(body, headers); +// ResponseEntity responseEntity = restTemplate.exchange(url + port + "/api/project/1", +// PUT, requestEntity, String.class); +// +// assertThat(responseEntity.getStatusCode()).isEqualTo(BAD_REQUEST); +// } + + @Test + void 프로젝트_종료일_시작일_이전_불가() { + // 종료일이 시작일보다 이전인 날짜 설정 + ProjectUpdate body = new ProjectUpdate("프로젝트 이름", List.of(1L, 2L, 3L), + LocalDateTime.of(2024, 10, 10, 0, 0), + LocalDateTime.of(2024, 10, 5, 0, 0)); + + HttpEntity requestEntity = new HttpEntity<>(body, headers); + ResponseEntity responseEntity = restTemplate.exchange(url + port + "/api/project/1", + PUT, requestEntity, String.class); + + assertThat(responseEntity.getStatusCode()).isEqualTo(BAD_REQUEST); + } + + @Test + void 프로젝트_생성시_시작일과_종료일_간견은_1일_이상_필수() { + // 1일보다 작은 갭을 설정 + ProjectUpdate body = new ProjectUpdate("프로젝트 이름", List.of(1L, 2L, 3L), + LocalDateTime.of(2024, 10, 10, 0, 0), + LocalDateTime.of(2024, 10, 10, 23, 59)); + + HttpEntity requestEntity = new HttpEntity<>(body, headers); + ResponseEntity responseEntity = restTemplate.exchange(url + port + "/api/project/1", + PUT, requestEntity, String.class); + + assertThat(responseEntity.getStatusCode()).isEqualTo(BAD_REQUEST); + } + + /** + * 이 부분도 지금은 그냥 Service가 delete시 isDeleted = true로 변경하고 + * 그냥 id를 반환하는 구조인데, 이를 체크하기 위해서 isDeleted를 포함한 + * DTO를 반환하는 방법은 좋지 않은 것 같아서 일단을 이렇게 구현*/ + @Test + void 삭제된_프로젝트_조회_불가() { + HttpEntity requestEntity = new HttpEntity(null, headers); + + restTemplate.exchange(url + port + "/api/project/1", + DELETE, + requestEntity, String.class); + + ResponseEntity responseEntity = restTemplate.exchange(url + port + "/api/project/1", + DELETE, + requestEntity, String.class); + + System.out.println(responseEntity); + assertThat(responseEntity.getStatusCode()).isEqualTo(BAD_REQUEST); + } + + +} + + + diff --git a/src/test/java/team1/BE/seamless/service/MemberServiceTest.java b/src/test/java/team1/BE/seamless/service/MemberServiceTest.java new file mode 100644 index 0000000..384c04b --- /dev/null +++ b/src/test/java/team1/BE/seamless/service/MemberServiceTest.java @@ -0,0 +1,143 @@ +package team1.BE.seamless.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.http.HttpMethod.DELETE; +import static org.springframework.http.HttpMethod.GET; +import static org.springframework.http.HttpMethod.POST; +import static org.springframework.http.HttpMethod.PUT; +import static org.springframework.http.HttpStatus.FORBIDDEN; +import static org.springframework.http.HttpStatus.NOT_FOUND; +import static org.springframework.http.HttpStatus.OK; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import team1.BE.seamless.DTO.MemberRequestDTO.CreateMember; +import team1.BE.seamless.DTO.MemberRequestDTO.UpdateMember; +import team1.BE.seamless.entity.MemberEntity; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class MemberServiceTest { + + @LocalServerPort + private int port; + private String url = "http://localhost:"; + private final TestRestTemplate restTemplate; + private String token; + private String memberToken; + private HttpHeaders headers = new HttpHeaders(); + + @Autowired + public MemberServiceTest(TestRestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + @BeforeEach + public void setUp() { +// 새로운 멤버 생성 + CreateMember member = new CreateMember("ex@gmail.com","cCeJvA99H7bV2ctvVIpM4Bh3ZJvawh3JnX3tREWGtNA="); + HttpEntity request1 = new HttpEntity<>(member); + ResponseEntity response1 = restTemplate.exchange( + url + port + "/api/project/1/member", + POST, + request1, String.class); + + int startIndex = response1.getBody().indexOf("\"code\":\"") + "\"code\":\"".length(); + int endIndex = response1.getBody().indexOf("\"", startIndex); + +// 멤버 생성시 반환되는 코드 추출 + String code = response1.getBody().substring(startIndex, endIndex); + +// 코드로 멤버 토큰 요청 + HttpEntity request2 = new HttpEntity<>(null); + ResponseEntity response2 = restTemplate.exchange( + url + port + "/api/auth/memberCode?memberCode=" + code, + GET, + request2, String.class); + + startIndex = response2.getBody().indexOf("\"token\":\"") + "\"token\":\"".length(); + endIndex = response2.getBody().indexOf("\"", startIndex); + + memberToken = response2.getBody().substring(startIndex, endIndex); + +// 팀장의 토큰 요청 + HttpEntity requestEntity = new HttpEntity<>(null); + ResponseEntity responseEntity = restTemplate.exchange( + url + port + "/api/test/userToken/1", + POST, + requestEntity, String.class); + + startIndex = responseEntity.getBody().indexOf("\"token\":\"") + "\"token\":\"".length(); + endIndex = responseEntity.getBody().indexOf("\"", startIndex); + + token = responseEntity.getBody().substring(startIndex, endIndex); + + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(memberToken); + + this.url += port + "/api/project/1/member/"; + } + + + //"멤버 이메일 빈 칸으로 정보 수정시 반영이 안되거나 예외처리 되는가?" 에 대한 테스트 + @Test + void updateMemberWithEmptyEmailShouldFail() { + UpdateMember updateInfo = new UpdateMember("새로운 이름", "팀원", "", "http://example.com/"); + HttpEntity requestEntity = new HttpEntity<>(updateInfo, headers); + + ResponseEntity response = restTemplate.exchange( + url + "1", // 테스트 Member ID + PUT, + requestEntity, + String.class); + + assertThat(response.getStatusCode()).isEqualTo(OK); + } + + // 멤버가 멤버를 삭제 가능한에 대한 테스트 + @Test + void softDeleteMember() { + headers.setBearerAuth(memberToken); + + // Soft delete member + HttpEntity requestEntity = new HttpEntity<>(null, headers); + ResponseEntity response = restTemplate.exchange( + url + "1", // 테스트 Member ID + DELETE, + requestEntity, + String.class); + + assertThat(response.getStatusCode()).isEqualTo(FORBIDDEN); + } + + // "멤버 삭제(softdelete로 되는가, 다시 조회하면 조회 되는가)" 에 대한 테스트 + @Test + void softDeleteMemberAndRequery() { + headers.setBearerAuth(token); + + HttpEntity requestEntity = new HttpEntity<>(null, headers); + + // Soft delete member + ResponseEntity response1 = restTemplate.exchange( + url + "2", // 테스트 Member ID + DELETE, + requestEntity, + String.class); + assertThat(response1.getStatusCode()).isEqualTo(OK); + + ResponseEntity response2 = restTemplate.exchange( + url + "2", + GET, + new HttpEntity<>(headers), + MemberEntity.class); + + assertThat(response2.getStatusCode()).isEqualTo(NOT_FOUND); + } +} diff --git a/src/test/java/team1/BE/seamless/service/ProjectServiceTest.java b/src/test/java/team1/BE/seamless/service/ProjectServiceTest.java new file mode 100644 index 0000000..1a73275 --- /dev/null +++ b/src/test/java/team1/BE/seamless/service/ProjectServiceTest.java @@ -0,0 +1,58 @@ +package team1.BE.seamless.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.http.HttpMethod.DELETE; +import static org.springframework.http.HttpMethod.POST; +import static org.springframework.http.HttpMethod.PUT; +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import team1.BE.seamless.DTO.UserDTO.UserUpdate; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class ProjectServiceTest { + + @LocalServerPort + private int port; + private String url = "http://localhost:"; + private final TestRestTemplate restTemplate; + private String token; + private HttpHeaders headers = new HttpHeaders(); + + private final ProjectService projectService; + + + @Autowired + public ProjectServiceTest(TestRestTemplate restTemplate, ProjectService projectService) { + this.restTemplate = restTemplate; + this.projectService = projectService; + } + + /** + * 특정 유저id의 토큰 파싱 + */ + @BeforeEach + public void setUp() { + HttpEntity requestEntity = new HttpEntity<>(null); + ResponseEntity responseEntity = restTemplate.exchange( + url + port + "/api/test/userToken/1", + POST, + requestEntity, String.class); + + int startIndex = responseEntity.getBody().indexOf("\"token\":\"") + "\"token\":\"".length(); + int endIndex = responseEntity.getBody().indexOf("\"", startIndex); + + token = responseEntity.getBody().substring(startIndex, endIndex); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(token); + } +} \ No newline at end of file diff --git a/src/test/java/team1/BE/seamless/service/TaskServiceTest.java b/src/test/java/team1/BE/seamless/service/TaskServiceTest.java new file mode 100644 index 0000000..7b9c44d --- /dev/null +++ b/src/test/java/team1/BE/seamless/service/TaskServiceTest.java @@ -0,0 +1,114 @@ +package team1.BE.seamless.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.http.HttpMethod.GET; +import static org.springframework.http.HttpMethod.POST; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.NOT_FOUND; + +import java.time.LocalDateTime; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import team1.BE.seamless.DTO.TaskDTO.TaskCreate; +import team1.BE.seamless.repository.MemberRepository; +import team1.BE.seamless.repository.ProjectRepository; +import team1.BE.seamless.repository.UserRepository; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class TaskServiceTest { + + @LocalServerPort + private int port; + private String url = "http://localhost:"; + private final TestRestTemplate restTemplate; + private String token; + private HttpHeaders headers = new HttpHeaders(); + + private TaskService taskService; + private ProjectService projectService; + private ProjectRepository projectRepository; + private UserRepository userRepository; + private MemberRepository memberRepository; + + @Autowired + public TaskServiceTest(TestRestTemplate restTemplate, TaskService taskService, ProjectRepository projectRepository, UserRepository userRepository, MemberRepository memberRepository, ProjectService projectService) { + this.restTemplate = restTemplate; + this.taskService = taskService; + this.projectRepository = projectRepository; + this.userRepository = userRepository; + this.memberRepository = memberRepository; + this.projectService = projectService; + } + + @BeforeEach + public void setUp() { + HttpEntity requestEntity = new HttpEntity<>(null); + ResponseEntity responseEntity = restTemplate.exchange( + url + port + "/api/test/userToken/1", + POST, + requestEntity, String.class); + + int startIndex = responseEntity.getBody().indexOf("\"token\":\"") + "\"token\":\"".length(); + int endIndex = responseEntity.getBody().indexOf("\"", startIndex); + + token = responseEntity.getBody().substring(startIndex, endIndex); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(token); + } + + @Test + public void 태스크_시작_시간이_프로젝트_일정_범위보다_이를_경우_실패() { + TaskCreate body = new TaskCreate("태스크1", "첫번째 태스크입니다.", 1L, LocalDateTime.of(2001, 10, 10, 0, 0), LocalDateTime.of(2025, 5, 3, 1, 0, 0)); + + HttpEntity requestEntity = new HttpEntity(body, headers); + + ResponseEntity responseEntity = restTemplate.exchange(url + port + "/api/project/1/task", POST, requestEntity, String.class); + + assertThat(responseEntity.getStatusCode()).isEqualTo(BAD_REQUEST); + } + + @Test + public void 태스크_마감_시간이_프로젝트_일정_범위보다_늦을_경우_실패() { + TaskCreate body = new TaskCreate("태스크1", "첫번째 태스크입니다.", 1L, LocalDateTime.of(2024, 12, 1, 0, 0), LocalDateTime.of(2100, 5, 3, 1, 0, 0)); + + HttpEntity requestEntity = new HttpEntity(body, headers); + + ResponseEntity responseEntity = restTemplate.exchange(url + port + "/api/project/1/task", POST, requestEntity, String.class); + + assertThat(responseEntity.getStatusCode()).isEqualTo(BAD_REQUEST); + } + + @Test + public void 프로젝트_삭제시_태스크_조회_실패() { + // 프로젝트 삭제 + projectService.deleteProject(1L); + + Exception exception = assertThrows(RuntimeException.class, () -> { + taskService.getTask(1L); + }); + + String expectedMessage = "존재하지 않는 프로젝트"; + String actualMessage = exception.getMessage(); + + assertTrue(actualMessage.contains(expectedMessage)); + + HttpEntity requestEntity = new HttpEntity<>(headers); + + ResponseEntity responseEntity = restTemplate.exchange(url + port + "/api/project/task/1", GET, requestEntity, String.class); + + assertThat(responseEntity.getStatusCode()).isEqualTo(NOT_FOUND); + } + +} \ No newline at end of file diff --git a/src/test/java/team1/BE/seamless/service/UserServiceTest.java b/src/test/java/team1/BE/seamless/service/UserServiceTest.java new file mode 100644 index 0000000..6feb277 --- /dev/null +++ b/src/test/java/team1/BE/seamless/service/UserServiceTest.java @@ -0,0 +1,89 @@ +package team1.BE.seamless.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.http.HttpMethod.DELETE; +import static org.springframework.http.HttpMethod.POST; +import static org.springframework.http.HttpMethod.PUT; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.NOT_FOUND; +import static org.springframework.http.HttpStatus.OK; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import team1.BE.seamless.DTO.UserDTO.UserUpdate; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class UserServiceTest { + + @LocalServerPort + private int port; + private String url = "http://localhost:"; + private final TestRestTemplate restTemplate; + private String token; + private HttpHeaders headers = new HttpHeaders(); + + private final UserService userService; + + + @Autowired + public UserServiceTest(TestRestTemplate restTemplate, UserService userService) { + this.restTemplate = restTemplate; + this.userService = userService; + } + + /** + * 특정 유저id의 토큰 파싱 + */ + @BeforeEach + public void setUp() { + HttpEntity requestEntity = new HttpEntity<>(null); + ResponseEntity responseEntity = restTemplate.exchange( + url + port + "/api/test/userToken/1", + POST, + requestEntity, String.class); + + int startIndex = responseEntity.getBody().indexOf("\"token\":\"") + "\"token\":\"".length(); + int endIndex = responseEntity.getBody().indexOf("\"", startIndex); + + token = responseEntity.getBody().substring(startIndex, endIndex); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(token); + } + + @Test + void 유저정보_수정_테스트_실패_url() { + UserUpdate body = new UserUpdate("name1","qwer1234"); + + HttpEntity requestEntity = new HttpEntity(body, headers); + ResponseEntity responseEntity = restTemplate.exchange(url + port + "/api/user", + PUT, + requestEntity, String.class); + + System.out.println(responseEntity); + assertThat(responseEntity.getStatusCode()).isEqualTo(BAD_REQUEST); + } + + @Test + void 유저정보_삭제_실패() { + HttpEntity requestEntity = new HttpEntity(null, headers); +// 삭제 두 번 시도 + restTemplate.exchange(url + port + "/api/user", + DELETE, + requestEntity, String.class); + + ResponseEntity responseEntity = restTemplate.exchange(url + port + "/api/user", + DELETE, + requestEntity, String.class); + + System.out.println(responseEntity); + assertThat(responseEntity.getStatusCode()).isEqualTo(BAD_REQUEST); + } +} \ No newline at end of file