From de2a813389a66548bfde39fdad8242b18ad36427 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Thu, 9 Jan 2025 18:12:09 +0900 Subject: [PATCH 01/76] test --- README.md | 2 ++ src/main/java/stackpot/stackpot/StackpotApplication.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fed0aa2c..edc65d6d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # STACKPOT-BE STACKPOT-BE + +### git test \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/StackpotApplication.java b/src/main/java/stackpot/stackpot/StackpotApplication.java index bd79a86e..a72aa2af 100644 --- a/src/main/java/stackpot/stackpot/StackpotApplication.java +++ b/src/main/java/stackpot/stackpot/StackpotApplication.java @@ -7,7 +7,7 @@ public class StackpotApplication { public static void main(String[] args) { - SpringApplication.run(StackpotApplication.class, args); + SpringApplication.run(StackpotApplication.class, args);; } } From ccfc9c456969554c176d8630603daf2f3eb9a309 Mon Sep 17 00:00:00 2001 From: MiN <81948815+leesumin0526@users.noreply.github.com> Date: Fri, 10 Jan 2025 01:04:04 +0900 Subject: [PATCH 02/76] Update issue templates --- .github/ISSUE_TEMPLATE/bug-report-template.md | 22 +++++++++++++++++++ .github/ISSUE_TEMPLATE/feature-template.md | 20 +++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug-report-template.md create mode 100644 .github/ISSUE_TEMPLATE/feature-template.md diff --git a/.github/ISSUE_TEMPLATE/bug-report-template.md b/.github/ISSUE_TEMPLATE/bug-report-template.md new file mode 100644 index 00000000..c8286e51 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report-template.md @@ -0,0 +1,22 @@ +--- +name: Bug Report Template +about: 버그 리포트 이슈 템플릿 +title: "[BUG/#이슈번호] 이슈 내용" +labels: '' +assignees: '' + +--- + +## 어떤 버그인가요? + +> 어떤 버그인지 간결하게 설명해주세요 + +## 어떤 상황에서 발생한 버그인가요? + +> (가능하면) Given-When-Then 형식으로 서술해주세요 + +## 예상 결과 + +> 예상했던 정상적인 결과가 어떤 것이었는지 설명해주세요 + +## 참고할만한 자료(선택) diff --git a/.github/ISSUE_TEMPLATE/feature-template.md b/.github/ISSUE_TEMPLATE/feature-template.md new file mode 100644 index 00000000..f8088862 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-template.md @@ -0,0 +1,20 @@ +--- +name: Feature Template +about: 기능 추가 이슈 템플릿 +title: "[타입/#이슈번호] 이슈 내용" +labels: '' +assignees: '' + +--- + +## 어떤 기능인가요? + +> 추가하려는 기능에 대해 간결하게 설명해주세요 + +## 작업 상세 내용 + +- [ ] TODO +- [ ] TODO +- [ ] TODO + +## 참고할만한 자료(선택) From b87db66a3d9112b459852fb13bbd38d4a4b832bd Mon Sep 17 00:00:00 2001 From: MiN <81948815+leesumin0526@users.noreply.github.com> Date: Fri, 10 Jan 2025 01:08:28 +0900 Subject: [PATCH 03/76] Create PULL_REQUEST_TEMPLATE --- .github/PULL_REQUEST_TEMPLATE | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE new file mode 100644 index 00000000..420e398c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE @@ -0,0 +1,14 @@ +### PR 타입(하나 이상의 PR 타입을 선택해주세요) +-[] 기능 추가 +-[] 기능 삭제 +-[] 버그 수정 +-[] 의존성, 환경 변수, 빌드 관련 코드 업데이트 + +### 반영 브랜치 +ex) feat/login -> dev + +### 작업 내용 +ex) 로그인 시, 구글 소셜 로그인 기능을 추가했습니다. + +### 테스트 결과 +ex) 베이스 브랜치에 포함되기 위한 코드는 모두 정상적으로 동작해야 합니다. 결과물에 대한 스크린샷, GIF, 혹은 라이브 From ee60770bf2380173ce5aafa7e09d0e4edb8bc9e6 Mon Sep 17 00:00:00 2001 From: MiN <81948815+leesumin0526@users.noreply.github.com> Date: Fri, 10 Jan 2025 01:09:17 +0900 Subject: [PATCH 04/76] Rename PULL_REQUEST_TEMPLATE to PULL_REQUEST_TEMPLATE.md --- .github/{PULL_REQUEST_TEMPLATE => PULL_REQUEST_TEMPLATE.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{PULL_REQUEST_TEMPLATE => PULL_REQUEST_TEMPLATE.md} (100%) diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE rename to .github/PULL_REQUEST_TEMPLATE.md From 47d1c26556564848bbdc01e3b706546d94a5499a Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Fri, 10 Jan 2025 14:18:16 +0900 Subject: [PATCH 05/76] =?UTF-8?q?[feat/#4]=20=EB=94=94=EB=A0=89=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=EA=B5=AC=EC=A1=B0=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/stackpot/stackpot/apiPayload/ApiResponse.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/main/java/stackpot/stackpot/apiPayload/ApiResponse.java diff --git a/src/main/java/stackpot/stackpot/apiPayload/ApiResponse.java b/src/main/java/stackpot/stackpot/apiPayload/ApiResponse.java new file mode 100644 index 00000000..3d0c920c --- /dev/null +++ b/src/main/java/stackpot/stackpot/apiPayload/ApiResponse.java @@ -0,0 +1,4 @@ +package stackpot.stackpot.apiPayload; + +public class ApiResponse { +} From c20cc3c0030ef033ace79588fbec1161bcfbbb75 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Fri, 10 Jan 2025 17:58:57 +0900 Subject: [PATCH 06/76] =?UTF-8?q?[feat/#4]=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../stackpot/apiPayload/ApiResponse.java | 33 ++++- .../stackpot/apiPayload/code/BaseCode.java | 7 + .../apiPayload/code/BaseErrorCode.java | 6 + .../apiPayload/code/ErrorReasonDTO.java | 17 +++ .../stackpot/apiPayload/code/ReasonDTO.java | 16 +++ .../apiPayload/code/status/ErrorStatus.java | 42 ++++++ .../apiPayload/code/status/SuccessStatus.java | 37 ++++++ .../apiPayload/exception/ExceptionAdvice.java | 120 ++++++++++++++++++ .../exception/GeneralException.java | 20 +++ 10 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 src/main/java/stackpot/stackpot/apiPayload/code/BaseCode.java create mode 100644 src/main/java/stackpot/stackpot/apiPayload/code/BaseErrorCode.java create mode 100644 src/main/java/stackpot/stackpot/apiPayload/code/ErrorReasonDTO.java create mode 100644 src/main/java/stackpot/stackpot/apiPayload/code/ReasonDTO.java create mode 100644 src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java create mode 100644 src/main/java/stackpot/stackpot/apiPayload/code/status/SuccessStatus.java create mode 100644 src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java create mode 100644 src/main/java/stackpot/stackpot/apiPayload/exception/GeneralException.java diff --git a/build.gradle b/build.gradle index ea0071c4..b82dcd44 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-validation' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/stackpot/stackpot/apiPayload/ApiResponse.java b/src/main/java/stackpot/stackpot/apiPayload/ApiResponse.java index 3d0c920c..e0627a2a 100644 --- a/src/main/java/stackpot/stackpot/apiPayload/ApiResponse.java +++ b/src/main/java/stackpot/stackpot/apiPayload/ApiResponse.java @@ -1,4 +1,35 @@ package stackpot.stackpot.apiPayload; -public class ApiResponse { +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.AllArgsConstructor; +import lombok.Getter; +import stackpot.stackpot.apiPayload.code.BaseCode; +import stackpot.stackpot.apiPayload.code.status.SuccessStatus; + +@Getter +@AllArgsConstructor +@JsonPropertyOrder({"isSuccess", "code", " message", "result"}) +public class ApiResponse { + + @JsonProperty("isSuccess") + private final Boolean isSuccsee; + private final String code; + private final String message; + @JsonInclude(JsonInclude.Include.NON_NULL) + private T result; + + public static ApiResponse onSuccess(T result){ + return new ApiResponse<>(true, SuccessStatus._OK.getCode() , SuccessStatus._OK.getMessage(), result); + } + + public static ApiResponse of(BaseCode code, T result){ + return new ApiResponse<>(true, code.getReasonHttpStatus().getCode() , code.getReasonHttpStatus().getMessage(), result); + } + + public static ApiResponse onFailure(String code, String message, T data){ + return new ApiResponse<>(false, code, message, data); + } + } diff --git a/src/main/java/stackpot/stackpot/apiPayload/code/BaseCode.java b/src/main/java/stackpot/stackpot/apiPayload/code/BaseCode.java new file mode 100644 index 00000000..8c47b59f --- /dev/null +++ b/src/main/java/stackpot/stackpot/apiPayload/code/BaseCode.java @@ -0,0 +1,7 @@ +package stackpot.stackpot.apiPayload.code; + +public interface BaseCode { + + ReasonDTO getReason(); + ReasonDTO getReasonHttpStatus(); +} diff --git a/src/main/java/stackpot/stackpot/apiPayload/code/BaseErrorCode.java b/src/main/java/stackpot/stackpot/apiPayload/code/BaseErrorCode.java new file mode 100644 index 00000000..53838aea --- /dev/null +++ b/src/main/java/stackpot/stackpot/apiPayload/code/BaseErrorCode.java @@ -0,0 +1,6 @@ +package stackpot.stackpot.apiPayload.code; + +public interface BaseErrorCode { + ErrorReasonDTO getReason(); + ErrorReasonDTO getReasonHttpStatus(); +} diff --git a/src/main/java/stackpot/stackpot/apiPayload/code/ErrorReasonDTO.java b/src/main/java/stackpot/stackpot/apiPayload/code/ErrorReasonDTO.java new file mode 100644 index 00000000..d544cc93 --- /dev/null +++ b/src/main/java/stackpot/stackpot/apiPayload/code/ErrorReasonDTO.java @@ -0,0 +1,17 @@ +package stackpot.stackpot.apiPayload.code; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@Builder +public class ErrorReasonDTO { + private HttpStatus httpStatus; + + private final boolean isSuccess; + private final String code; + private final String message; + + public boolean getIsSuccess(){return isSuccess;} +} diff --git a/src/main/java/stackpot/stackpot/apiPayload/code/ReasonDTO.java b/src/main/java/stackpot/stackpot/apiPayload/code/ReasonDTO.java new file mode 100644 index 00000000..c27d9254 --- /dev/null +++ b/src/main/java/stackpot/stackpot/apiPayload/code/ReasonDTO.java @@ -0,0 +1,16 @@ +package stackpot.stackpot.apiPayload.code; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.http.HttpStatus; +@Getter +@Builder +public class ReasonDTO { + private HttpStatus httpStatus; + + private final boolean isSuccess; + private final String code; + private final String message; + + public boolean getIsSuccess(){return isSuccess;} +} diff --git a/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java b/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java new file mode 100644 index 00000000..e9aad23a --- /dev/null +++ b/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java @@ -0,0 +1,42 @@ +package stackpot.stackpot.apiPayload.code.status; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import stackpot.stackpot.apiPayload.code.BaseErrorCode; +import stackpot.stackpot.apiPayload.code.ErrorReasonDTO; + +@Getter +@AllArgsConstructor +public enum ErrorStatus implements BaseErrorCode { + // 가장 일반적인 응답 + _INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON500", "서버 에러, 관리자에게 문의 바랍니다."), + _BAD_REQUEST(HttpStatus.BAD_REQUEST,"COMMON400","잘못된 요청입니다."), + _UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"COMMON401","인증이 필요합니다."), + _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."); + + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public ErrorReasonDTO getReason() { + return ErrorReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(false) + .build(); + } + + @Override + public ErrorReasonDTO getReasonHttpStatus() { + return ErrorReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(false) + .httpStatus(httpStatus) + .build() + ; + } +} diff --git a/src/main/java/stackpot/stackpot/apiPayload/code/status/SuccessStatus.java b/src/main/java/stackpot/stackpot/apiPayload/code/status/SuccessStatus.java new file mode 100644 index 00000000..92f85501 --- /dev/null +++ b/src/main/java/stackpot/stackpot/apiPayload/code/status/SuccessStatus.java @@ -0,0 +1,37 @@ +package stackpot.stackpot.apiPayload.code.status; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import stackpot.stackpot.apiPayload.code.BaseCode; +import stackpot.stackpot.apiPayload.code.ReasonDTO; + +@Getter +@AllArgsConstructor +public enum SuccessStatus implements BaseCode { + + _OK(HttpStatus.OK, "COMMON200", "성공입니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public ReasonDTO getReason() { + return ReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(true) + .build(); + } + + @Override + public ReasonDTO getReasonHttpStatus() { + return ReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(true) + .httpStatus(httpStatus) + .build(); + } +} diff --git a/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java b/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java new file mode 100644 index 00000000..29765be8 --- /dev/null +++ b/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java @@ -0,0 +1,120 @@ +package stackpot.stackpot.apiPayload.exception; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolationException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import stackpot.stackpot.apiPayload.ApiResponse; +import stackpot.stackpot.apiPayload.code.ErrorReasonDTO; +import stackpot.stackpot.apiPayload.code.status.ErrorStatus; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestControllerAdvice(annotations = {RestController.class}) +public class ExceptionAdvice extends ResponseEntityExceptionHandler { + + + @ExceptionHandler + public ResponseEntity validation(ConstraintViolationException e, WebRequest request) { + String errorMessage = e.getConstraintViolations().stream() + .map(constraintViolation -> constraintViolation.getMessage()) + .findFirst() + .orElseThrow(() -> new RuntimeException("ConstraintViolationException 추출 도중 에러 발생")); + + return handleExceptionInternalConstraint(e, ErrorStatus.valueOf(errorMessage), HttpHeaders.EMPTY,request); + } + + @Override + public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException e, HttpHeaders headers, HttpStatusCode status, WebRequest request) { + + Map errors = new LinkedHashMap<>(); + + e.getBindingResult().getFieldErrors().stream() + .forEach(fieldError -> { + String fieldName = fieldError.getField(); + String errorMessage = Optional.ofNullable(fieldError.getDefaultMessage()).orElse(""); + errors.merge(fieldName, errorMessage, (existingErrorMessage, newErrorMessage) -> existingErrorMessage + ", " + newErrorMessage); + }); + + return handleExceptionInternalArgs(e,HttpHeaders.EMPTY,ErrorStatus.valueOf("_BAD_REQUEST"),request,errors); + } + + @ExceptionHandler + public ResponseEntity exception(Exception e, WebRequest request) { + e.printStackTrace(); + + return handleExceptionInternalFalse(e, ErrorStatus._INTERNAL_SERVER_ERROR, HttpHeaders.EMPTY, ErrorStatus._INTERNAL_SERVER_ERROR.getHttpStatus(),request, e.getMessage()); + } + + @ExceptionHandler(value = GeneralException.class) + public ResponseEntity onThrowException(GeneralException generalException, HttpServletRequest request) { + ErrorReasonDTO errorReasonHttpStatus = generalException.getErrorReasonHttpStatus(); + return handleExceptionInternal(generalException,errorReasonHttpStatus,null,request); + } + + private ResponseEntity handleExceptionInternal(Exception e, ErrorReasonDTO reason, + HttpHeaders headers, HttpServletRequest request) { + + ApiResponse body = ApiResponse.onFailure(reason.getCode(),reason.getMessage(),null); +// e.printStackTrace(); + + WebRequest webRequest = new ServletWebRequest(request); + return super.handleExceptionInternal( + e, + body, + headers, + reason.getHttpStatus(), + webRequest + ); + } + + private ResponseEntity handleExceptionInternalFalse(Exception e, ErrorStatus errorCommonStatus, + HttpHeaders headers, HttpStatus status, WebRequest request, String errorPoint) { + ApiResponse body = ApiResponse.onFailure(errorCommonStatus.getCode(),errorCommonStatus.getMessage(),errorPoint); + return super.handleExceptionInternal( + e, + body, + headers, + status, + request + ); + } + + private ResponseEntity handleExceptionInternalArgs(Exception e, HttpHeaders headers, ErrorStatus errorCommonStatus, + WebRequest request, Map errorArgs) { + ApiResponse body = ApiResponse.onFailure(errorCommonStatus.getCode(),errorCommonStatus.getMessage(),errorArgs); + return super.handleExceptionInternal( + e, + body, + headers, + errorCommonStatus.getHttpStatus(), + request + ); + } + + private ResponseEntity handleExceptionInternalConstraint(Exception e, ErrorStatus errorCommonStatus, + HttpHeaders headers, WebRequest request) { + ApiResponse body = ApiResponse.onFailure(errorCommonStatus.getCode(), errorCommonStatus.getMessage(), null); + return super.handleExceptionInternal( + e, + body, + headers, + errorCommonStatus.getHttpStatus(), + request + ); + } +} diff --git a/src/main/java/stackpot/stackpot/apiPayload/exception/GeneralException.java b/src/main/java/stackpot/stackpot/apiPayload/exception/GeneralException.java new file mode 100644 index 00000000..c08440c4 --- /dev/null +++ b/src/main/java/stackpot/stackpot/apiPayload/exception/GeneralException.java @@ -0,0 +1,20 @@ +package stackpot.stackpot.apiPayload.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import stackpot.stackpot.apiPayload.code.BaseErrorCode; +import stackpot.stackpot.apiPayload.code.ErrorReasonDTO; + +@Getter +@AllArgsConstructor +public class GeneralException extends RuntimeException{ + + private BaseErrorCode code; + + public ErrorReasonDTO getErrorReason(){ + return this.code.getReason(); + } + public ErrorReasonDTO getErrorReasonHttpStatus(){ + return this.code.getReasonHttpStatus(); + } +} From d4f57d52e30e64062972380cfdf53f75a342fde6 Mon Sep 17 00:00:00 2001 From: isumin Date: Sat, 11 Jan 2025 00:51:07 +0900 Subject: [PATCH 07/76] =?UTF-8?q?[FEAT/#5]=20Domain=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/stackpot/stackpot/domain/Badge.java | 23 ++++++++ .../java/stackpot/stackpot/domain/Feed.java | 37 ++++++++++++ .../stackpot/stackpot/domain/FeedFile.java | 27 +++++++++ .../java/stackpot/stackpot/domain/Pot.java | 56 +++++++++++++++++++ .../stackpot/domain/PotOpeningPhoto.java | 28 ++++++++++ .../domain/PotRecruitmentDetails.java | 33 +++++++++++ .../stackpot/stackpot/domain/Taskboard.java | 42 ++++++++++++++ .../java/stackpot/stackpot/domain/User.java | 47 ++++++++++++++++ .../stackpot/domain/common/BaseEntity.java | 22 ++++++++ .../domain/enums/ApplicationStatus.java | 5 ++ .../domain/enums/ModeOfOperation.java | 5 ++ .../domain/enums/TaskboardStatus.java | 5 ++ .../stackpot/domain/enums/TodoStatus.java | 5 ++ .../stackpot/domain/enums/Visibility.java | 5 ++ .../stackpot/domain/mapping/FeedLike.java | 26 +++++++++ .../stackpot/domain/mapping/FeedSave.java | 28 ++++++++++ .../domain/mapping/PotApplication.java | 41 ++++++++++++++ .../stackpot/domain/mapping/PotMember.java | 28 ++++++++++ .../stackpot/domain/mapping/PotSave.java | 28 ++++++++++ .../stackpot/domain/mapping/Task.java | 27 +++++++++ .../stackpot/domain/mapping/TaskComment.java | 30 ++++++++++ .../stackpot/domain/mapping/UserBadge.java | 27 +++++++++ .../stackpot/domain/mapping/UserTodo.java | 36 ++++++++++++ src/main/resources/application.yml | 19 +++++++ 24 files changed, 630 insertions(+) create mode 100644 src/main/java/stackpot/stackpot/domain/Badge.java create mode 100644 src/main/java/stackpot/stackpot/domain/Feed.java create mode 100644 src/main/java/stackpot/stackpot/domain/FeedFile.java create mode 100644 src/main/java/stackpot/stackpot/domain/Pot.java create mode 100644 src/main/java/stackpot/stackpot/domain/PotOpeningPhoto.java create mode 100644 src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java create mode 100644 src/main/java/stackpot/stackpot/domain/Taskboard.java create mode 100644 src/main/java/stackpot/stackpot/domain/User.java create mode 100644 src/main/java/stackpot/stackpot/domain/common/BaseEntity.java create mode 100644 src/main/java/stackpot/stackpot/domain/enums/ApplicationStatus.java create mode 100644 src/main/java/stackpot/stackpot/domain/enums/ModeOfOperation.java create mode 100644 src/main/java/stackpot/stackpot/domain/enums/TaskboardStatus.java create mode 100644 src/main/java/stackpot/stackpot/domain/enums/TodoStatus.java create mode 100644 src/main/java/stackpot/stackpot/domain/enums/Visibility.java create mode 100644 src/main/java/stackpot/stackpot/domain/mapping/FeedLike.java create mode 100644 src/main/java/stackpot/stackpot/domain/mapping/FeedSave.java create mode 100644 src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java create mode 100644 src/main/java/stackpot/stackpot/domain/mapping/PotMember.java create mode 100644 src/main/java/stackpot/stackpot/domain/mapping/PotSave.java create mode 100644 src/main/java/stackpot/stackpot/domain/mapping/Task.java create mode 100644 src/main/java/stackpot/stackpot/domain/mapping/TaskComment.java create mode 100644 src/main/java/stackpot/stackpot/domain/mapping/UserBadge.java create mode 100644 src/main/java/stackpot/stackpot/domain/mapping/UserTodo.java create mode 100644 src/main/resources/application.yml diff --git a/src/main/java/stackpot/stackpot/domain/Badge.java b/src/main/java/stackpot/stackpot/domain/Badge.java new file mode 100644 index 00000000..d1d691a7 --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/Badge.java @@ -0,0 +1,23 @@ +package stackpot.stackpot.domain; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.common.BaseEntity; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Badge extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long badgeId; + + @Column(nullable = false, length = 30) + private String name; + + @Column(nullable = false, length = 50) + private String description; +} diff --git a/src/main/java/stackpot/stackpot/domain/Feed.java b/src/main/java/stackpot/stackpot/domain/Feed.java new file mode 100644 index 00000000..3844b687 --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/Feed.java @@ -0,0 +1,37 @@ +package stackpot.stackpot.domain; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.common.BaseEntity; +import stackpot.stackpot.domain.enums.Visibility; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Feed extends BaseEntity{ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long feedId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "creator_id", nullable = false) + private User creator; + + @Column(nullable = false, length = 50) + private String title; + + @Column(nullable = false, columnDefinition = "TEXT") + private String content; + + @Column(nullable = false, length = 10) + private String mainPart; + + @Column(nullable = false, length = 10) + private String interest; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Visibility visibility; +} diff --git a/src/main/java/stackpot/stackpot/domain/FeedFile.java b/src/main/java/stackpot/stackpot/domain/FeedFile.java new file mode 100644 index 00000000..ba1381a6 --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/FeedFile.java @@ -0,0 +1,27 @@ +package stackpot.stackpot.domain; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.common.BaseEntity; +import stackpot.stackpot.domain.enums.Visibility; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class FeedFile extends BaseEntity{ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long fileId; + + @Column(length = 255) + private String originalFilename; + + @Column(length = 255) + private String savedFilename; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "feed_id", nullable = false) + private Feed feed; +} diff --git a/src/main/java/stackpot/stackpot/domain/Pot.java b/src/main/java/stackpot/stackpot/domain/Pot.java new file mode 100644 index 00000000..2cbbb1bb --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/Pot.java @@ -0,0 +1,56 @@ +package stackpot.stackpot.domain; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.common.BaseEntity; +import stackpot.stackpot.domain.enums.ModeOfOperation; + +import java.time.LocalDate; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Pot extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long potId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "creator_id", nullable = false) + private User creator; + + @Column(nullable = false, length = 255) + private String potName; + + @Column(nullable = true) + private LocalDate startDate; + + @Column(nullable = true) + private LocalDate endDate; + + @Column(nullable = false, length = 255) + private String expectedDuration; + + @Column(nullable = false, length = 255) + private String languagesUsed; + + @Column(nullable = true, columnDefinition = "TEXT") + private String description; + + @Column(nullable = false, length = 255) + private String potStatus; + + @Column(nullable = false, columnDefinition = "TEXT") + private String potImage; + + @Column(nullable = true, length = 100) + private String oneLineSummary; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private ModeOfOperation modeOfOperation; //팟 진행 방식 +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/domain/PotOpeningPhoto.java b/src/main/java/stackpot/stackpot/domain/PotOpeningPhoto.java new file mode 100644 index 00000000..7871320b --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/PotOpeningPhoto.java @@ -0,0 +1,28 @@ +package stackpot.stackpot.domain; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.common.BaseEntity; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class PotOpeningPhoto extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long photoId; + + @Column(nullable = false, length = 255) + private String originalFilename; + + @Column(nullable = false, length = 255) + private String savedFilename; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "pot_id", nullable = false) + private Pot pot; +} diff --git a/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java b/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java new file mode 100644 index 00000000..37885523 --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java @@ -0,0 +1,33 @@ +package stackpot.stackpot.domain; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.common.BaseEntity; + +import java.time.LocalDate; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class PotRecruitmentDetails extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long recruitmentId; + + @Column(nullable = true, length = 255) + private String recruitmentRole; + + @Column(nullable = true) + private Integer recruitmentCount; + + @Column(nullable = false) + private LocalDate recruitmentDeadline; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "pot_id", nullable = false) + private Pot pot; +} diff --git a/src/main/java/stackpot/stackpot/domain/Taskboard.java b/src/main/java/stackpot/stackpot/domain/Taskboard.java new file mode 100644 index 00000000..3364a538 --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/Taskboard.java @@ -0,0 +1,42 @@ +package stackpot.stackpot.domain; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.common.BaseEntity; +import stackpot.stackpot.domain.enums.TaskboardStatus; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Taskboard extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long taskboardId; + + @Column(nullable = false, length = 20) + private String title; + + @Column(nullable = true, columnDefinition = "TEXT") + private String description; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private TaskboardStatus status; + + @Column(nullable = false) + private LocalDateTime startDate; + + @Column(nullable = false) + private LocalDateTime endDate; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "pot_id", nullable = false) + private Pot pot; + +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/domain/User.java b/src/main/java/stackpot/stackpot/domain/User.java new file mode 100644 index 00000000..159edbb4 --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/User.java @@ -0,0 +1,47 @@ +package stackpot.stackpot.domain; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.common.BaseEntity; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class User extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // Primary Key + + @Column(nullable = false, length = 255) + private String loginId; // 로그인 아이디 + + @Column(nullable = true, length = 255) + private String userName; // 유저 이름 + + @Column(nullable = false, length = 255) + private String snsKey; // SNS 키 + + @Column(nullable = false, length = 255) + private String nickname; // 닉네임 + + @Column(nullable = false, length = 255) + private String role; // 역할 + + @Column(nullable = false, length = 255) + private String kakaoId; // 카카오 아이디 + + @Column(nullable = false, length = 255) + private String interest; // 관심사 + + @Column(nullable = true, columnDefinition = "TEXT") + private String introduction; // 한 줄 소개 + + @Column(nullable = false) + private Integer userTemperature; // 유저 온도 + + @Column(nullable = false, unique = true) + private String email; // 이메일 +} diff --git a/src/main/java/stackpot/stackpot/domain/common/BaseEntity.java b/src/main/java/stackpot/stackpot/domain/common/BaseEntity.java new file mode 100644 index 00000000..cdf0ee14 --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/common/BaseEntity.java @@ -0,0 +1,22 @@ +package stackpot.stackpot.domain.common; + +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +@Getter +public abstract class BaseEntity { + @CreatedDate + private LocalDateTime createdAt; + + @LastModifiedDate + private LocalDateTime updatedAt; +} + diff --git a/src/main/java/stackpot/stackpot/domain/enums/ApplicationStatus.java b/src/main/java/stackpot/stackpot/domain/enums/ApplicationStatus.java new file mode 100644 index 00000000..4577b06e --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/enums/ApplicationStatus.java @@ -0,0 +1,5 @@ +package stackpot.stackpot.domain.enums; + +public enum ApplicationStatus { + PENDING, APPROVED, REJECTED +} diff --git a/src/main/java/stackpot/stackpot/domain/enums/ModeOfOperation.java b/src/main/java/stackpot/stackpot/domain/enums/ModeOfOperation.java new file mode 100644 index 00000000..a8aada34 --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/enums/ModeOfOperation.java @@ -0,0 +1,5 @@ +package stackpot.stackpot.domain.enums; + +public enum ModeOfOperation { + ONLINE, OFFLINE +} diff --git a/src/main/java/stackpot/stackpot/domain/enums/TaskboardStatus.java b/src/main/java/stackpot/stackpot/domain/enums/TaskboardStatus.java new file mode 100644 index 00000000..5130f34a --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/enums/TaskboardStatus.java @@ -0,0 +1,5 @@ +package stackpot.stackpot.domain.enums; + +public enum TaskboardStatus { + OPEN, IN_PROGRESS, CLOSED +} diff --git a/src/main/java/stackpot/stackpot/domain/enums/TodoStatus.java b/src/main/java/stackpot/stackpot/domain/enums/TodoStatus.java new file mode 100644 index 00000000..df5315a4 --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/enums/TodoStatus.java @@ -0,0 +1,5 @@ +package stackpot.stackpot.domain.enums; + +public enum TodoStatus { + NOT_STARTED, COMPLETED +} diff --git a/src/main/java/stackpot/stackpot/domain/enums/Visibility.java b/src/main/java/stackpot/stackpot/domain/enums/Visibility.java new file mode 100644 index 00000000..6deea23d --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/enums/Visibility.java @@ -0,0 +1,5 @@ +package stackpot.stackpot.domain.enums; + +public enum Visibility { + PRIVATE, PUBLIC +} diff --git a/src/main/java/stackpot/stackpot/domain/mapping/FeedLike.java b/src/main/java/stackpot/stackpot/domain/mapping/FeedLike.java new file mode 100644 index 00000000..f1bab33a --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/mapping/FeedLike.java @@ -0,0 +1,26 @@ +package stackpot.stackpot.domain.mapping; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.common.BaseEntity; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class FeedLike extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long likeId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "feed_id", nullable = false) + private Feed feed; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; +} diff --git a/src/main/java/stackpot/stackpot/domain/mapping/FeedSave.java b/src/main/java/stackpot/stackpot/domain/mapping/FeedSave.java new file mode 100644 index 00000000..8b37062c --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/mapping/FeedSave.java @@ -0,0 +1,28 @@ +package stackpot.stackpot.domain.mapping; + +import jakarta.persistence.*; +import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.common.BaseEntity; + +import lombok.*; +import java.time.LocalDateTime; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class FeedSave extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long feedSaveId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "feed_id", nullable = false) + private Feed feed; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; +} diff --git a/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java b/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java new file mode 100644 index 00000000..4dd216ec --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java @@ -0,0 +1,41 @@ +package stackpot.stackpot.domain.mapping; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.common.BaseEntity; +import stackpot.stackpot.domain.enums.ApplicationStatus; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class PotApplication extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long applicationId; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private ApplicationStatus status; + + @Column(nullable = true) + private LocalDateTime appliedAt; + + @Column(nullable = false, columnDefinition = "BOOLEAN DEFAULT false") + private Boolean liked; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "pot_id", nullable = false) + private Pot pot; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; +} diff --git a/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java b/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java new file mode 100644 index 00000000..dad2738e --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java @@ -0,0 +1,28 @@ +package stackpot.stackpot.domain.mapping; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.common.BaseEntity; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class PotMember extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long teamId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "pot_id", nullable = false) + private Pot pot; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; +} diff --git a/src/main/java/stackpot/stackpot/domain/mapping/PotSave.java b/src/main/java/stackpot/stackpot/domain/mapping/PotSave.java new file mode 100644 index 00000000..13e86afd --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/mapping/PotSave.java @@ -0,0 +1,28 @@ +package stackpot.stackpot.domain.mapping; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.common.BaseEntity; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class PotSave extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long potSaveId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "pot_id", nullable = false) + private Pot pot; +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/domain/mapping/Task.java b/src/main/java/stackpot/stackpot/domain/mapping/Task.java new file mode 100644 index 00000000..32cd5a7c --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/mapping/Task.java @@ -0,0 +1,27 @@ +package stackpot.stackpot.domain.mapping; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.Taskboard; +import stackpot.stackpot.domain.common.BaseEntity; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Task extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long taskId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "taskboard_id", nullable = false) + private Taskboard taskboard; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "potMember_id", nullable = false) + private PotMember potMember; +} diff --git a/src/main/java/stackpot/stackpot/domain/mapping/TaskComment.java b/src/main/java/stackpot/stackpot/domain/mapping/TaskComment.java new file mode 100644 index 00000000..fa3c6d63 --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/mapping/TaskComment.java @@ -0,0 +1,30 @@ +package stackpot.stackpot.domain.mapping; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.Taskboard; +import stackpot.stackpot.domain.common.BaseEntity; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class TaskComment extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long commentId; + + @Column(nullable = false, columnDefinition = "TEXT") + private String content; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "potMember_id", nullable = false) + private PotMember potMember; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "taskboard_id", nullable = false) + private Taskboard taskboard; +} diff --git a/src/main/java/stackpot/stackpot/domain/mapping/UserBadge.java b/src/main/java/stackpot/stackpot/domain/mapping/UserBadge.java new file mode 100644 index 00000000..522655b6 --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/mapping/UserBadge.java @@ -0,0 +1,27 @@ +package stackpot.stackpot.domain.mapping; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.Badge; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.common.BaseEntity; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class UserBadge extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long userBadgeId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "badge_id", nullable = false) + private Badge badge; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; +} diff --git a/src/main/java/stackpot/stackpot/domain/mapping/UserTodo.java b/src/main/java/stackpot/stackpot/domain/mapping/UserTodo.java new file mode 100644 index 00000000..0304f639 --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/mapping/UserTodo.java @@ -0,0 +1,36 @@ +package stackpot.stackpot.domain.mapping; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.common.BaseEntity; +import stackpot.stackpot.domain.enums.TodoStatus; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class UserTodo extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long todoId; + + @Column(nullable = false, length = 50) + private String content; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private TodoStatus status; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "pot_id", nullable = false) + private Pot pot; +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 00000000..26d1cd36 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,19 @@ +spring: + datasource: + url: jdbc:mysql://localhost:3306/stackpot + username: root + password: dltnals125! + driver-class-name: com.mysql.cj.jdbc.Driver + sql: + init: + mode: never + jpa: + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL8Dialect + show_sql: true + format_sql: true + use_sql_comments: true + hbm2ddl: + auto: update + default_batch_fetch_size: 1000 \ No newline at end of file From 49fe1866b7d28671561e591bedf4865ed83d544d Mon Sep 17 00:00:00 2001 From: isumin Date: Sat, 11 Jan 2025 01:28:54 +0900 Subject: [PATCH 08/76] =?UTF-8?q?[FEAT/#5]=20Domain=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20&=20s3=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../java/stackpot/stackpot/domain/Pot.java | 8 +++--- ...{PotOpeningPhoto.java => PotDocument.java} | 9 +++---- .../stackpot/domain/PotOpeningImg.java | 25 +++++++++++++++++++ .../domain/mapping/PotApplication.java | 7 ++++++ .../stackpot/domain/mapping/PotMember.java | 10 ++++++++ src/main/resources/application.yml | 6 ++--- 7 files changed, 53 insertions(+), 13 deletions(-) rename src/main/java/stackpot/stackpot/domain/{PotOpeningPhoto.java => PotDocument.java} (71%) create mode 100644 src/main/java/stackpot/stackpot/domain/PotOpeningImg.java diff --git a/build.gradle b/build.gradle index ea0071c4..eee31667 100644 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,7 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' } tasks.named('test') { diff --git a/src/main/java/stackpot/stackpot/domain/Pot.java b/src/main/java/stackpot/stackpot/domain/Pot.java index 2cbbb1bb..08533c36 100644 --- a/src/main/java/stackpot/stackpot/domain/Pot.java +++ b/src/main/java/stackpot/stackpot/domain/Pot.java @@ -44,13 +44,13 @@ public class Pot extends BaseEntity { @Column(nullable = false, length = 255) private String potStatus; - @Column(nullable = false, columnDefinition = "TEXT") - private String potImage; - @Column(nullable = true, length = 100) private String oneLineSummary; @Enumerated(EnumType.STRING) @Column(nullable = false) - private ModeOfOperation modeOfOperation; //팟 진행 방식 + private ModeOfOperation modeOfOperation; // 팟 진행 방식 + + @Column(nullable = true, length = 700) + private String potSummary; // 팟 요약 } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/domain/PotOpeningPhoto.java b/src/main/java/stackpot/stackpot/domain/PotDocument.java similarity index 71% rename from src/main/java/stackpot/stackpot/domain/PotOpeningPhoto.java rename to src/main/java/stackpot/stackpot/domain/PotDocument.java index 7871320b..9ee29a52 100644 --- a/src/main/java/stackpot/stackpot/domain/PotOpeningPhoto.java +++ b/src/main/java/stackpot/stackpot/domain/PotDocument.java @@ -9,18 +9,15 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor -public class PotOpeningPhoto extends BaseEntity { +public class PotDocument extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(nullable = false) - private Long photoId; + private Long documentId; @Column(nullable = false, length = 255) - private String originalFilename; - - @Column(nullable = false, length = 255) - private String savedFilename; + private String documentUrl; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "pot_id", nullable = false) diff --git a/src/main/java/stackpot/stackpot/domain/PotOpeningImg.java b/src/main/java/stackpot/stackpot/domain/PotOpeningImg.java new file mode 100644 index 00000000..0cc44869 --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/PotOpeningImg.java @@ -0,0 +1,25 @@ +package stackpot.stackpot.domain; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.common.BaseEntity; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class PotOpeningImg extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long imgId; + + @Column(nullable = false, length = 255) + private String imgUrl; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "pot_id", nullable = false) + private Pot pot; +} diff --git a/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java b/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java index 4dd216ec..0ce393d1 100644 --- a/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java +++ b/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java @@ -31,6 +31,9 @@ public class PotApplication extends BaseEntity { @Column(nullable = false, columnDefinition = "BOOLEAN DEFAULT false") private Boolean liked; + @Column(nullable = false, length = 10) + private String potRole; // 팟 역할 + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "pot_id", nullable = false) private Pot pot; @@ -38,4 +41,8 @@ public class PotApplication extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "pot_member_id", nullable = false) + private PotMember potMember; } diff --git a/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java b/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java index dad2738e..9658c5ad 100644 --- a/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java +++ b/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java @@ -25,4 +25,14 @@ public class PotMember extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "pot_application_id", nullable = false) + private PotApplication potApplication; + + @Column(nullable = false, length = 10) + private String potRoleName; + + @Column(nullable = true, length = 10) + private String awards; } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 26d1cd36..e0829dad 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,8 +1,8 @@ spring: datasource: - url: jdbc:mysql://localhost:3306/stackpot + url: jdbc:mysql://stackpot-db.cp2quse6utig.ap-northeast-2.rds.amazonaws.com:3306/stackpot_server username: root - password: dltnals125! + password: stackpot1234 driver-class-name: com.mysql.cj.jdbc.Driver sql: init: @@ -15,5 +15,5 @@ spring: format_sql: true use_sql_comments: true hbm2ddl: - auto: update + auto: create default_batch_fetch_size: 1000 \ No newline at end of file From 847d2ce57da3fbbb0e1658966f61c1ab504b7d8e Mon Sep 17 00:00:00 2001 From: starday119 Date: Sat, 11 Jan 2025 01:41:23 +0900 Subject: [PATCH 09/76] =?UTF-8?q?[Feat]=20Swagger=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 + .../stackpot/config/SwaggerConfig.java | 39 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/main/java/stackpot/stackpot/config/SwaggerConfig.java diff --git a/build.gradle b/build.gradle index b82dcd44..89c1f923 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,8 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' + implementation 'mysql:mysql-connector-java:8.0.33' } tasks.named('test') { diff --git a/src/main/java/stackpot/stackpot/config/SwaggerConfig.java b/src/main/java/stackpot/stackpot/config/SwaggerConfig.java new file mode 100644 index 00000000..8f3c8055 --- /dev/null +++ b/src/main/java/stackpot/stackpot/config/SwaggerConfig.java @@ -0,0 +1,39 @@ +package stackpot.stackpot.config; + +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI StackPotAPI() { + Info info = new Info() + .title("StackPot API") + .description("StackPotAPI API 명세서") + .version("1.0.0"); + + String jwtSchemeName = "JWT TOKEN"; + // API 요청헤더에 인증정보 포함 + SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName); + // SecuritySchemes 등록 + Components components = new Components() + .addSecuritySchemes(jwtSchemeName, new SecurityScheme() + .name(jwtSchemeName) + .type(SecurityScheme.Type.HTTP) // HTTP 방식 + .scheme("bearer") + .bearerFormat("JWT")); + + return new OpenAPI() + .addServersItem(new Server().url("/api")) + .info(info) + .addSecurityItem(securityRequirement) + .components(components); + } +} \ No newline at end of file From b59949e9e586f535b73bcbbe29b7d67a86471907 Mon Sep 17 00:00:00 2001 From: rudeore-098 <75055749+rudeore-098@users.noreply.github.com> Date: Sat, 11 Jan 2025 01:47:18 +0900 Subject: [PATCH 10/76] Update README.md --- README.md | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index edc65d6d..6615599c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,77 @@ # STACKPOT-BE STACKPOT-BE -### git test \ No newline at end of file +## Commit Convention +- **[FEAT]** : 새로운 기능 구현 +- **[MOD]** : 코드 수정 및 내부 파일 수정 +- **[ADD]** : 부수적인 코드 추가 및 라이브러리 추가, 새로운 파일 생성 +- **[CHORE]** : 버전 코드 수정, 패키지 구조 변경, 타입 및 변수명 변경 등의 작은 작업 +- **[DEL]** : 쓸모없는 코드나 파일 삭제 +- **[UI]** : UI 작업 +- **[FIX]** : 버그 및 오류 해결 +- **[HOTFIX]** : issue나 QA에서 문의된 급한 버그 및 오류 해결 +- **[MERGE]** : 다른 브랜치와의 MERGE +- **[MOVE]** : 프로젝트 내 파일이나 코드의 이동 +- **[RENAME]** : 파일 이름 변경 +- **[REFACTOR]** : 전면 수정 +- **[DOCS]** : README나 WIKI 등의 문서 개정 + +- --- + +**📌 형식**: + +- `[커밋 타입/#이슈번호] 커밋 내용` + +**📌 예시** + +- `[feat/#32] User 도메인 구현` +- `[feat/#32] User 필드값 annotation 추가` + +## Branch Convention +1. **이슈 파기** + + **📌 형식** + + `[타입/#이슈번호] 이슈 내용` + + **📌 예시** + + - `[Feat/#11] User 도메인 구현` + - `[Refactor/#2] User 관련 DTO 수정` +2. **브랜치 파기** + + **📌 형식** + + - `유형/#이슈번호-what` + + **📌 예시** + + - `feat/#11-login-view-ui` +1. **PR 올리기** + + **📌 형식** + + - `[유형] where / what` + + **📌 예시** + + - `[FEAT] 로그인 뷰 / UI 구현` + + **📌 PR Convention** + + ``` + ### PR 타입(하나 이상의 PR 타입을 선택해주세요) + -[] 기능 추가 + -[] 기능 삭제 + -[] 버그 수정 + -[] 의존성, 환경 변수, 빌드 관련 코드 업데이트 + + ### 반영 브랜치 + ex) feat/login -> dev + + ### 변경 사항 + ex) 로그인 시, 구글 소셜 로그인 기능을 추가했습니다. + + ### 테스트 결과 + ex) 베이스 브랜치에 포함되기 위한 코드는 모두 정상적으로 동작해야 합니다. 결과물에 대한 스크린샷, GIF, 혹은 라이브 + ``` From cf2aaeee1a36bc198f7d377231219c7485791315 Mon Sep 17 00:00:00 2001 From: rudeore-098 <75055749+rudeore-098@users.noreply.github.com> Date: Sat, 11 Jan 2025 01:47:57 +0900 Subject: [PATCH 11/76] Update README.md --- README.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6615599c..9a9dc411 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # STACKPOT-BE -STACKPOT-BE ## Commit Convention - **[FEAT]** : 새로운 기능 구현 @@ -75,3 +74,39 @@ STACKPOT-BE ### 테스트 결과 ex) 베이스 브랜치에 포함되기 위한 코드는 모두 정상적으로 동작해야 합니다. 결과물에 대한 스크린샷, GIF, 혹은 라이브 ``` +## PR Convention +**📌 형식** + +- `[유형/#이슈번호] where / what` + +**📌 예시** + +- `[FEAT/#3] 로그인 뷰 / UI 구현` + +**📌 PR 프로세스** + +1. **PR 생성**: 작업을 완료한 후, 변경 사항을 설명하는 PR을 생성합니다. +2. **코드 리뷰 요청**: PR이 생성되면 팀원들에게 코드 리뷰를 요청합니다. +3. **코드 리뷰 진행**: 리뷰어는 코드를 검토하고 피드백을 제공합니다. +4. **피드백 대응**: PR 작성자는 리뷰어의 피드백을 반영하여 코드를 수정합니다. +5. **리뷰어 동의**: 리뷰어는 수정된 코드를 다시 검토하고 동의합니다. +6. **PR 병합**: 필요한 승인 수가 충족되면, PR을 메인 브랜치에 병합합니다. + +**📌 PR 템플릿** + +``` +### PR 타입(하나 이상의 PR 타입을 선택해주세요) +-[] 기능 추가 +-[] 기능 삭제 +-[] 버그 수정 +-[] 의존성, 환경 변수, 빌드 관련 코드 업데이트 + +### 반영 브랜치 +ex) feat/login -> dev + +### 작업 내용 +ex) 로그인 시, 구글 소셜 로그인 기능을 추가했습니다. + +### 테스트 결과 +ex) 베이스 브랜치에 포함되기 위한 코드는 모두 정상적으로 동작해야 합니다. 결과물에 대한 스크린샷, GIF, 혹은 라이브 +``` From c6bca4f93f476640fe1dea58efc8f791c1d2b6ae Mon Sep 17 00:00:00 2001 From: isumin Date: Sat, 11 Jan 2025 02:50:48 +0900 Subject: [PATCH 12/76] =?UTF-8?q?[FEAT/#5]=20S3=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stackpot/aws/s3/AmazonS3Manager.java | 40 ++++++++++++++ .../stackpot/aws/s3/UuidRepository.java | 4 ++ .../stackpot/config/AmazonConfig.java | 55 +++++++++++++++++++ .../java/stackpot/stackpot/domain/Uuid.java | 21 +++++++ src/main/resources/application.yml | 21 +++++-- 5 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 src/main/java/stackpot/stackpot/aws/s3/AmazonS3Manager.java create mode 100644 src/main/java/stackpot/stackpot/aws/s3/UuidRepository.java create mode 100644 src/main/java/stackpot/stackpot/config/AmazonConfig.java create mode 100644 src/main/java/stackpot/stackpot/domain/Uuid.java diff --git a/src/main/java/stackpot/stackpot/aws/s3/AmazonS3Manager.java b/src/main/java/stackpot/stackpot/aws/s3/AmazonS3Manager.java new file mode 100644 index 00000000..da2ecdc8 --- /dev/null +++ b/src/main/java/stackpot/stackpot/aws/s3/AmazonS3Manager.java @@ -0,0 +1,40 @@ +package stackpot.stackpot.aws.s3; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; +import stackpot.stackpot.config.AmazonConfig; +import stackpot.stackpot.domain.Uuid; + +import java.io.IOException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class AmazonS3Manager{ + + private final AmazonS3 amazonS3; + + private final AmazonConfig amazonConfig; + + + public String uploadFile(String keyName, MultipartFile file){ + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(file.getSize()); + try { + amazonS3.putObject(new PutObjectRequest(amazonConfig.getBucket(), keyName, file.getInputStream(), metadata)); + } catch (IOException e){ + log.error("error at AmazonS3Manager uploadFile : {}", (Object) e.getStackTrace()); + } + + return amazonS3.getUrl(amazonConfig.getBucket(), keyName).toString(); + } + + public String generateFeedFileKeyName(Uuid uuid) { + return amazonConfig.getFeedFilePath() + '/' + uuid.getUuid(); + } +} diff --git a/src/main/java/stackpot/stackpot/aws/s3/UuidRepository.java b/src/main/java/stackpot/stackpot/aws/s3/UuidRepository.java new file mode 100644 index 00000000..0988d425 --- /dev/null +++ b/src/main/java/stackpot/stackpot/aws/s3/UuidRepository.java @@ -0,0 +1,4 @@ +package stackpot.stackpot.aws.s3; + +public class UuidRepository { +} diff --git a/src/main/java/stackpot/stackpot/config/AmazonConfig.java b/src/main/java/stackpot/stackpot/config/AmazonConfig.java new file mode 100644 index 00000000..0904a396 --- /dev/null +++ b/src/main/java/stackpot/stackpot/config/AmazonConfig.java @@ -0,0 +1,55 @@ +package stackpot.stackpot.config; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import jakarta.annotation.PostConstruct; +import lombok.Getter; +import lombok.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Getter +public class AmazonConfig { + + + private AWSCredentials awsCredentials; + + @org.springframework.beans.factory.annotation.Value("${cloud.aws.credentials.accessKey}") + private String accessKey; + + @org.springframework.beans.factory.annotation.Value("${cloud.aws.credentials.secretKey}") + private String secretKey; + + @org.springframework.beans.factory.annotation.Value("${cloud.aws.region.static}") + private String region; + + @org.springframework.beans.factory.annotation.Value("${cloud.aws.s3.bucket}") + private String bucket; + + @org.springframework.beans.factory.annotation.Value("${cloud.aws.s3.path.FeedFile}") + private String FeedFilePath; + + @PostConstruct + public void init() { + this.awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + } + + @Bean + public AmazonS3 amazonS3() { + AWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + return AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); + } + + @Bean + public AWSCredentialsProvider awsCredentialsProvider() { + return new AWSStaticCredentialsProvider(awsCredentials); + } +} diff --git a/src/main/java/stackpot/stackpot/domain/Uuid.java b/src/main/java/stackpot/stackpot/domain/Uuid.java new file mode 100644 index 00000000..52c81fec --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/Uuid.java @@ -0,0 +1,21 @@ +package stackpot.stackpot.domain; + +import stackpot.stackpot.domain.common.BaseEntity; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.common.BaseEntity; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Uuid extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // Primary Key + + @Column(unique = true) + private String uuid; +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e0829dad..80b467e7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,8 +1,8 @@ spring: datasource: - url: jdbc:mysql://stackpot-db.cp2quse6utig.ap-northeast-2.rds.amazonaws.com:3306/stackpot_server - username: root - password: stackpot1234 + url: ${URL} + username: ${USERNAME} + password: ${PASSWORD} driver-class-name: com.mysql.cj.jdbc.Driver sql: init: @@ -16,4 +16,17 @@ spring: use_sql_comments: true hbm2ddl: auto: create - default_batch_fetch_size: 1000 \ No newline at end of file + default_batch_fetch_size: 1000 + cloud: + aws: + s3: + bucket: stackpot + path: + FeedFile: FeedFile + region: + static: ap-northeast-2 + stack: + auto: false + credentials: + accessKey: ${AWS_ACCESS_KEY_ID} + secretKey: ${AWS_SECRET_ACCESS_KEY} \ No newline at end of file From d805aae1e6c0e72d2fcc20cd4ab810662d323760 Mon Sep 17 00:00:00 2001 From: isumin Date: Sat, 11 Jan 2025 03:21:10 +0900 Subject: [PATCH 13/76] =?UTF-8?q?[FIX]=20AmazonConfig=20import=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/stackpot/stackpot/config/AmazonConfig.java | 11 +++++------ src/main/resources/application.properties | 1 - 2 files changed, 5 insertions(+), 7 deletions(-) delete mode 100644 src/main/resources/application.properties diff --git a/src/main/java/stackpot/stackpot/config/AmazonConfig.java b/src/main/java/stackpot/stackpot/config/AmazonConfig.java index 0904a396..07f9a26d 100644 --- a/src/main/java/stackpot/stackpot/config/AmazonConfig.java +++ b/src/main/java/stackpot/stackpot/config/AmazonConfig.java @@ -8,7 +8,6 @@ import com.amazonaws.services.s3.AmazonS3ClientBuilder; import jakarta.annotation.PostConstruct; import lombok.Getter; -import lombok.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -19,19 +18,19 @@ public class AmazonConfig { private AWSCredentials awsCredentials; - @org.springframework.beans.factory.annotation.Value("${cloud.aws.credentials.accessKey}") + @org.springframework.beans.factory.annotation.Value("${spring.cloud.aws.credentials.accessKey}") private String accessKey; - @org.springframework.beans.factory.annotation.Value("${cloud.aws.credentials.secretKey}") + @org.springframework.beans.factory.annotation.Value("${spring.cloud.aws.credentials.secretKey}") private String secretKey; - @org.springframework.beans.factory.annotation.Value("${cloud.aws.region.static}") + @org.springframework.beans.factory.annotation.Value("${spring.cloud.aws.region.static}") private String region; - @org.springframework.beans.factory.annotation.Value("${cloud.aws.s3.bucket}") + @org.springframework.beans.factory.annotation.Value("${spring.cloud.aws.s3.bucket}") private String bucket; - @org.springframework.beans.factory.annotation.Value("${cloud.aws.s3.path.FeedFile}") + @org.springframework.beans.factory.annotation.Value("${spring.cloud.aws.s3.path.FeedFile}") private String FeedFilePath; @PostConstruct diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 40cd959d..00000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=stackpot From 63708377594e1d5aa13f529d46864f8c5d42adc6 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Sun, 12 Jan 2025 15:29:29 +0900 Subject: [PATCH 14/76] =?UTF-8?q?[feat/#9]=20security=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index d393d63a..f6a2aae2 100644 --- a/build.gradle +++ b/build.gradle @@ -35,6 +35,10 @@ dependencies { implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' implementation 'mysql:mysql-connector-java:8.0.33' + + implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'org.springframework.security:spring-security-test' + } tasks.named('test') { From 2c4684dc6d3986672e936326841fc944580b84a1 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Wed, 15 Jan 2025 21:38:07 +0900 Subject: [PATCH 15/76] =?UTF-8?q?[feat/#1o]=20config=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/stackpot/stackpot/config/security/SecurityConfig.java | 2 ++ .../stackpot/stackpot/web/controller/MemberViewController.java | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 src/main/java/stackpot/stackpot/config/security/SecurityConfig.java create mode 100644 src/main/java/stackpot/stackpot/web/controller/MemberViewController.java diff --git a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java new file mode 100644 index 00000000..9c6055de --- /dev/null +++ b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java @@ -0,0 +1,2 @@ +package stackpot.stackpot.config.security;public class SecurityConfig { +} diff --git a/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java b/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java new file mode 100644 index 00000000..0d97c0c2 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java @@ -0,0 +1,2 @@ +package stackpot.stackpot.web.controller;public class MemberViewController { +} From 44ec08d9cf5290185e7f1c3688402d5e12bbb00d Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Wed, 15 Jan 2025 21:38:23 +0900 Subject: [PATCH 16/76] =?UTF-8?q?[feat/#10]=20config=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/security/SecurityConfig.java | 33 ++++++++++++++++++- .../web/controller/MemberViewController.java | 19 ++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java index 9c6055de..acf3d22a 100644 --- a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java +++ b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java @@ -1,2 +1,33 @@ -package stackpot.stackpot.config.security;public class SecurityConfig { +package stackpot.stackpot.config.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.web.SecurityFilterChain; + +@EnableWebSecurity +@Configuration +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests((requests) -> requests + .requestMatchers("/", "/home", "/signup", "/swagger-ui/**", "/v3/api-docs/**").permitAll() + .anyRequest().authenticated() + ) + .formLogin((form) -> form + .loginPage("/login") + .defaultSuccessUrl("/home", true) + .permitAll() + ) + .logout((logout) -> logout + .logoutUrl("/logout") + .logoutSuccessUrl("/login?logout") + .permitAll() + ); + + return http.build(); + } } diff --git a/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java b/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java index 0d97c0c2..855677e7 100644 --- a/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java +++ b/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java @@ -1,2 +1,19 @@ -package stackpot.stackpot.web.controller;public class MemberViewController { +package stackpot.stackpot.web.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class MemberViewController { + @GetMapping("/login") + public String loginPage() { + return "login"; + } + @GetMapping("/signup") + public String signupPage(Model model) { +// model.addAttribute("memberJoinDto", new MemberRequestDTO.JoinDto()); + return "signup"; + } + } From 8e3b649d40b4ac3a73890b1ba3e7581ed0adc84f Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Thu, 16 Jan 2025 01:43:27 +0900 Subject: [PATCH 17/76] =?UTF-8?q?[feat/#10]=20oauth2=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index f6a2aae2..85e9cdd0 100644 --- a/build.gradle +++ b/build.gradle @@ -39,6 +39,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' testImplementation 'org.springframework.security:spring-security-test' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + } tasks.named('test') { From b8a65e0eda9a494f4094fd5802e0ae1af11fa64a Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Fri, 17 Jan 2025 13:41:55 +0900 Subject: [PATCH 18/76] =?UTF-8?q?[feat/#10]=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20DTO=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 + .../apiPayload/code/status/ErrorStatus.java | 4 +- .../security/CustomOAuth2UserService.java | 58 +++++++++++++++++++ .../config/security/SecurityConfig.java | 6 ++ .../stackpot/stackpot/domain/enums/Role.java | 5 ++ .../UserRepository/UserRepository.java | 10 ++++ .../web/controller/MemberViewController.java | 1 + .../stackpot/web/dto/UserRequestDTO.java | 30 ++++++++++ src/main/resources/application.yml | 20 ++++++- 9 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java create mode 100644 src/main/java/stackpot/stackpot/domain/enums/Role.java create mode 100644 src/main/java/stackpot/stackpot/repository/UserRepository/UserRepository.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/UserRequestDTO.java diff --git a/build.gradle b/build.gradle index 85e9cdd0..81c4ba5a 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:3.1.1.RELEASE' + } tasks.named('test') { diff --git a/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java b/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java index e9aad23a..1993f063 100644 --- a/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java @@ -13,7 +13,9 @@ public enum ErrorStatus implements BaseErrorCode { _INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON500", "서버 에러, 관리자에게 문의 바랍니다."), _BAD_REQUEST(HttpStatus.BAD_REQUEST,"COMMON400","잘못된 요청입니다."), _UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"COMMON401","인증이 필요합니다."), - _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."); + _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."), + + MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBER4001", "등록된 사용자가 없습니다."),; private final HttpStatus httpStatus; diff --git a/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java b/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java new file mode 100644 index 00000000..e9aa01a8 --- /dev/null +++ b/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java @@ -0,0 +1,58 @@ +package stackpot.stackpot.config.security; + + +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.repository.UserRepository.UserRepository; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class CustomOAuth2UserService extends DefaultOAuth2UserService { + + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User oAuth2User = super.loadUser(userRequest); + + Map attributes = oAuth2User.getAttributes(); + Map properties = (Map) attributes.get("properties"); + + String email = (String) properties.get("email"); + + // 사용자 정보 저장 또는 업데이트 + User member = saveOrUpdateUser(email); + + // 이메일을 Principal로 사용하기 위해 attributes 수정 + Map modifiedAttributes = new HashMap<>(attributes); + modifiedAttributes.put("email", email); + + return new DefaultOAuth2User( + oAuth2User.getAuthorities(), + modifiedAttributes, + "email" // email Principal로 설정 + ); + } + + private User saveOrUpdateUser(String email) { + User user = userRepository.findByEmail(email) + .orElse(User.builder() + .email(email) + .kakaoId(passwordEncoder.encode("OAUTH_USER_" + UUID.randomUUID())) + .build()); + + return userRepository.save(user); + } +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java index acf3d22a..fbf803ab 100644 --- a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java +++ b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java @@ -4,6 +4,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @EnableWebSecurity @@ -30,4 +32,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti return http.build(); } + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } } diff --git a/src/main/java/stackpot/stackpot/domain/enums/Role.java b/src/main/java/stackpot/stackpot/domain/enums/Role.java new file mode 100644 index 00000000..fc3989a3 --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/enums/Role.java @@ -0,0 +1,5 @@ +package stackpot.stackpot.domain.enums; + +public enum Role { + Design, Beckend, Frontend, PM; +} diff --git a/src/main/java/stackpot/stackpot/repository/UserRepository/UserRepository.java b/src/main/java/stackpot/stackpot/repository/UserRepository/UserRepository.java new file mode 100644 index 00000000..bd3de0c7 --- /dev/null +++ b/src/main/java/stackpot/stackpot/repository/UserRepository/UserRepository.java @@ -0,0 +1,10 @@ +package stackpot.stackpot.repository.UserRepository; + +import org.springframework.data.jpa.repository.JpaRepository; +import stackpot.stackpot.domain.User; + +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + Optional findByEmail(String email); +} diff --git a/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java b/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java index 855677e7..d277a813 100644 --- a/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java +++ b/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java @@ -10,6 +10,7 @@ public class MemberViewController { public String loginPage() { return "login"; } + @GetMapping("/signup") public String signupPage(Model model) { // model.addAttribute("memberJoinDto", new MemberRequestDTO.JoinDto()); diff --git a/src/main/java/stackpot/stackpot/web/dto/UserRequestDTO.java b/src/main/java/stackpot/stackpot/web/dto/UserRequestDTO.java new file mode 100644 index 00000000..8efe9726 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/UserRequestDTO.java @@ -0,0 +1,30 @@ +package stackpot.stackpot.web.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; + +import javax.management.relation.Role; + +public class UserRequestDTO { + + @Getter + @Setter + public static class JoinDto { + @NotNull + Role role; + @NotNull + String interest; + @NotBlank + String nickname; + @NotBlank + @Email + String email; // 이메일 + @NotNull + String kakaoId; // 카카오 아이디 + + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 80b467e7..48c12666 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -29,4 +29,22 @@ spring: auto: false credentials: accessKey: ${AWS_ACCESS_KEY_ID} - secretKey: ${AWS_SECRET_ACCESS_KEY} \ No newline at end of file + secretKey: ${AWS_SECRET_ACCESS_KEY} + security: + oauth2: + client: + registration: + kakao: + client-authentication-method: client_secret_post + client-id: d84efa2760475c897588429fbac5d1e6 + client-secret: Kz1nktBgxUxKYEIlGz2kmwHIGkxRggY3 + redirect-uri: http://localhost:8080/login/oauth2/code/kakao + authorization-grant-type: authorization_code + scope: profile_nickname + client-name: Kakao + provider: + kakao: + authorization-uri: https://kauth.kakao.com/oauth/authorize + token-uri: https://kauth.kakao.com/oauth/token + user-info-uri: https://kapi.kakao.com/v2/user/me + user-name-attribute: id \ No newline at end of file From 54f82a96acfbc7103ce267c534d28a30223736b0 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Fri, 17 Jan 2025 18:12:52 +0900 Subject: [PATCH 19/76] =?UTF-8?q?[feat/#10]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 ++ .../security/CustomOAuth2UserService.java | 15 ++++-- .../config/security/SecurityConfig.java | 2 +- .../stackpot/converter/UserConverter.java | 19 +++++++ .../java/stackpot/stackpot/domain/User.java | 1 + .../stackpot/service/UserCommandService.java | 9 ++++ .../service/UserCommandServiceImpl.java | 24 +++++++++ .../web/controller/MemberViewController.java | 35 +++++++++++-- .../stackpot/web/dto/UserRequestDTO.java | 2 +- src/main/resources/application.yml | 4 +- .../controller/MemberViewControllerTest.java | 51 +++++++++++++++++++ 11 files changed, 154 insertions(+), 11 deletions(-) create mode 100644 src/main/java/stackpot/stackpot/converter/UserConverter.java create mode 100644 src/main/java/stackpot/stackpot/service/UserCommandService.java create mode 100644 src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java create mode 100644 src/test/java/stackpot/stackpot/controller/MemberViewControllerTest.java diff --git a/.gitignore b/.gitignore index c2065bc2..91ef0b97 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ out/ ### VS Code ### .vscode/ + +.env +application-secrets.yml \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java b/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java index e9aa01a8..3864e14e 100644 --- a/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java +++ b/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java @@ -32,17 +32,24 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic String email = (String) properties.get("email"); - // 사용자 정보 저장 또는 업데이트 - User member = saveOrUpdateUser(email); + // 사용자 정보 조회 + User user = userRepository.findByEmail(email).orElse(null); - // 이메일을 Principal로 사용하기 위해 attributes 수정 + // 리다이렉션 상태 플래그 추가 Map modifiedAttributes = new HashMap<>(attributes); modifiedAttributes.put("email", email); + if (user == null) { + // 회원가입 페이지로 리다이렉션 + modifiedAttributes.put("redirect", String.format("/signup?email=%s", email)); + } else { + // 홈 페이지로 리다이렉션 + modifiedAttributes.put("redirect", "/home"); + } return new DefaultOAuth2User( oAuth2User.getAuthorities(), modifiedAttributes, - "email" // email Principal로 설정 + "email" // Principal로 설정 ); } diff --git a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java index fbf803ab..8ddf5c33 100644 --- a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java +++ b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java @@ -16,7 +16,7 @@ public class SecurityConfig { public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests((requests) -> requests - .requestMatchers("/", "/home", "/signup", "/swagger-ui/**", "/v3/api-docs/**").permitAll() + .requestMatchers("/", "/home", "/signup", "/user/profile","/swagger-ui/**", "/v3/api-docs/**").permitAll() .anyRequest().authenticated() ) .formLogin((form) -> form diff --git a/src/main/java/stackpot/stackpot/converter/UserConverter.java b/src/main/java/stackpot/stackpot/converter/UserConverter.java new file mode 100644 index 00000000..84c09257 --- /dev/null +++ b/src/main/java/stackpot/stackpot/converter/UserConverter.java @@ -0,0 +1,19 @@ +package stackpot.stackpot.converter; + +import stackpot.stackpot.domain.User; +import stackpot.stackpot.web.dto.UserRequestDTO; + +public class UserConverter { + public static User toUser(UserRequestDTO.JoinDto request) { + + return User.builder() + .nickname(request.getNickname()) + .email(request.getEmail()) // 추가된 코드 + .kakaoId(request.getKakaoId()) + .interest(request.getInterest()) + .role(request.getRole()) + .userTemperature((int)35.5) + .build(); + } +} + diff --git a/src/main/java/stackpot/stackpot/domain/User.java b/src/main/java/stackpot/stackpot/domain/User.java index 159edbb4..49fb84fb 100644 --- a/src/main/java/stackpot/stackpot/domain/User.java +++ b/src/main/java/stackpot/stackpot/domain/User.java @@ -3,6 +3,7 @@ import jakarta.persistence.*; import lombok.*; import stackpot.stackpot.domain.common.BaseEntity; +import stackpot.stackpot.domain.enums.Role; @Entity @Getter diff --git a/src/main/java/stackpot/stackpot/service/UserCommandService.java b/src/main/java/stackpot/stackpot/service/UserCommandService.java new file mode 100644 index 00000000..2b5cfdad --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/UserCommandService.java @@ -0,0 +1,9 @@ +package stackpot.stackpot.service; + +import stackpot.stackpot.domain.User; +import stackpot.stackpot.web.dto.UserRequestDTO; + +public interface UserCommandService { + + public User joinUser(UserRequestDTO.JoinDto request); +} diff --git a/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java b/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java new file mode 100644 index 00000000..a3e39957 --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java @@ -0,0 +1,24 @@ +package stackpot.stackpot.service; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import stackpot.stackpot.converter.UserConverter; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.repository.UserRepository.UserRepository; +import stackpot.stackpot.web.dto.UserRequestDTO; + +@Service +@RequiredArgsConstructor +public class UserCommandServiceImpl implements UserCommandService{ + + private final UserRepository userRepository; + + @Override + @Transactional + public User joinUser(UserRequestDTO.JoinDto request) { + + User newUser = UserConverter.toUser(request); + return userRepository.save(newUser); + } +} diff --git a/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java b/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java index d277a813..39b43634 100644 --- a/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java +++ b/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java @@ -1,20 +1,49 @@ package stackpot.stackpot.web.controller; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import stackpot.stackpot.service.UserCommandService; +import stackpot.stackpot.web.dto.UserRequestDTO; @Controller +@RequiredArgsConstructor public class MemberViewController { + + private final UserCommandService userCommandService; + + @PostMapping("/user/profile") + public String joinUser(@ModelAttribute("UserJoinDto") UserRequestDTO.JoinDto request, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + return "signup"; + } + + try { + userCommandService.joinUser(request); + return "redirect:/login"; + } catch (Exception e) { + model.addAttribute("error", e.getMessage()); + return "signup"; + } + } + @GetMapping("/login") public String loginPage() { return "login"; } - @GetMapping("/signup") - public String signupPage(Model model) { -// model.addAttribute("memberJoinDto", new MemberRequestDTO.JoinDto()); + @GetMapping("/user/profile") + public String signupPage(@RequestParam(required = false) String email, Model model) { + model.addAttribute("email", email); // 카카오 이메일 전달 return "signup"; } + } diff --git a/src/main/java/stackpot/stackpot/web/dto/UserRequestDTO.java b/src/main/java/stackpot/stackpot/web/dto/UserRequestDTO.java index 8efe9726..445644cf 100644 --- a/src/main/java/stackpot/stackpot/web/dto/UserRequestDTO.java +++ b/src/main/java/stackpot/stackpot/web/dto/UserRequestDTO.java @@ -15,7 +15,7 @@ public class UserRequestDTO { @Setter public static class JoinDto { @NotNull - Role role; + String role; @NotNull String interest; @NotBlank diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 48c12666..5ca28f07 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -36,8 +36,8 @@ spring: registration: kakao: client-authentication-method: client_secret_post - client-id: d84efa2760475c897588429fbac5d1e6 - client-secret: Kz1nktBgxUxKYEIlGz2kmwHIGkxRggY3 + client-id: ${KAKAO_CLIENT_ID} + client-secret: ${KAKAO_CLIENT_SECRET} redirect-uri: http://localhost:8080/login/oauth2/code/kakao authorization-grant-type: authorization_code scope: profile_nickname diff --git a/src/test/java/stackpot/stackpot/controller/MemberViewControllerTest.java b/src/test/java/stackpot/stackpot/controller/MemberViewControllerTest.java new file mode 100644 index 00000000..1bb091e0 --- /dev/null +++ b/src/test/java/stackpot/stackpot/controller/MemberViewControllerTest.java @@ -0,0 +1,51 @@ +package stackpot.stackpot.controller; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; +import stackpot.stackpot.service.UserCommandService; +import stackpot.stackpot.web.controller.MemberViewController; +import stackpot.stackpot.web.dto.UserRequestDTO; + +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(MemberViewController.class) +class MemberViewControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private UserCommandService userCommandService; + + @Test + void testJoinUser_Success() throws Exception { + // Mock request data + UserRequestDTO.JoinDto joinDto = new UserRequestDTO.JoinDto(); + joinDto.setEmail("test@example.com"); + joinDto.setNickname("TestUser"); + joinDto.setKakaoId("12345"); + + doNothing().when(userCommandService).joinUser(any(UserRequestDTO.JoinDto.class)); + + mockMvc.perform(post("/user/profile") + .flashAttr("UserJoinDto", joinDto)) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/login")); + } + + @Test + void testJoinUser_ValidationError() throws Exception { + mockMvc.perform(post("/user/profile") + .param("email", "") // 잘못된 데이터 + .param("nickname", "") + .param("kakaoId", "")) + .andExpect(status().isOk()) + .andExpect(view().name("signup")) + .andExpect(model().attributeExists("error")); + } +} \ No newline at end of file From 012552630b83e0f625d7317bc315263cfcf34fd6 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Fri, 17 Jan 2025 18:28:05 +0900 Subject: [PATCH 20/76] =?UTF-8?q?[feat/#9]=20jwt=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.gradle b/build.gradle index 81c4ba5a..3824ce57 100644 --- a/build.gradle +++ b/build.gradle @@ -36,6 +36,7 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' implementation 'mysql:mysql-connector-java:8.0.33' + //spring security implementation 'org.springframework.boot:spring-boot-starter-security' testImplementation 'org.springframework.security:spring-security-test' @@ -43,6 +44,11 @@ dependencies { implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:3.1.1.RELEASE' + //JWT + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + } tasks.named('test') { From 96d2646f237b3746de8f649e2ff897953501980a Mon Sep 17 00:00:00 2001 From: MiN <81948815+leesumin0526@users.noreply.github.com> Date: Sat, 18 Jan 2025 15:33:34 +0900 Subject: [PATCH 21/76] =?UTF-8?q?[MOD]=20domain=20=EC=88=98=EC=A0=95=20(#1?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [MOD/#10] Domain들 연관 관계 및 필드명 수정 --- .../config/security/SecurityConfig.java | 40 +++++++++++++++++++ .../java/stackpot/stackpot/domain/Feed.java | 4 +- .../stackpot/stackpot/domain/FeedFile.java | 7 +--- .../java/stackpot/stackpot/domain/Pot.java | 27 ++++++------- .../java/stackpot/stackpot/domain/User.java | 22 +++++----- ...Operation.java => PotModeOfOperation.java} | 2 +- .../domain/mapping/PotApplication.java | 4 -- .../stackpot/domain/mapping/PotMember.java | 11 +++-- .../{UserBadge.java => PotMemberBadge.java} | 8 ++-- .../stackpot/domain/mapping/TaskComment.java | 2 +- 10 files changed, 80 insertions(+), 47 deletions(-) create mode 100644 src/main/java/stackpot/stackpot/config/security/SecurityConfig.java rename src/main/java/stackpot/stackpot/domain/enums/{ModeOfOperation.java => PotModeOfOperation.java} (65%) rename src/main/java/stackpot/stackpot/domain/mapping/{UserBadge.java => PotMemberBadge.java} (75%) diff --git a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java new file mode 100644 index 00000000..e755fc0a --- /dev/null +++ b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java @@ -0,0 +1,40 @@ +package stackpot.stackpot.config.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) // CSRF 비활성화 + .authorizeHttpRequests(auth -> auth + .requestMatchers("/public/**").permitAll() // 공개 경로 + .requestMatchers("/admin/**").hasRole("ADMIN") // ADMIN 권한 필요 + .anyRequest().authenticated() // 그 외 요청은 인증 필요 + ) + .formLogin(form -> form + .loginPage("/login") // 커스텀 로그인 페이지 + .defaultSuccessUrl("/home") // 로그인 성공 후 이동할 페이지 + .permitAll() // 로그인 페이지는 인증 없이 접근 가능 + ) + .logout(logout -> logout + .logoutUrl("/logout") // 로그아웃 URL + .logoutSuccessUrl("/login?logout") // 로그아웃 성공 후 이동할 페이지 + .permitAll() + ); + + return http.build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/domain/Feed.java b/src/main/java/stackpot/stackpot/domain/Feed.java index 3844b687..f886040f 100644 --- a/src/main/java/stackpot/stackpot/domain/Feed.java +++ b/src/main/java/stackpot/stackpot/domain/Feed.java @@ -16,8 +16,8 @@ public class Feed extends BaseEntity{ private Long feedId; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "creator_id", nullable = false) - private User creator; + @JoinColumn(name = "user_id", nullable = false) + private User user; @Column(nullable = false, length = 50) private String title; diff --git a/src/main/java/stackpot/stackpot/domain/FeedFile.java b/src/main/java/stackpot/stackpot/domain/FeedFile.java index ba1381a6..6555d4b3 100644 --- a/src/main/java/stackpot/stackpot/domain/FeedFile.java +++ b/src/main/java/stackpot/stackpot/domain/FeedFile.java @@ -15,11 +15,8 @@ public class FeedFile extends BaseEntity{ @GeneratedValue(strategy = GenerationType.IDENTITY) private Long fileId; - @Column(length = 255) - private String originalFilename; - - @Column(length = 255) - private String savedFilename; + @Column(length = 255, nullable = false) + private String fileUrl; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "feed_id", nullable = false) diff --git a/src/main/java/stackpot/stackpot/domain/Pot.java b/src/main/java/stackpot/stackpot/domain/Pot.java index 08533c36..f7de0ed1 100644 --- a/src/main/java/stackpot/stackpot/domain/Pot.java +++ b/src/main/java/stackpot/stackpot/domain/Pot.java @@ -3,7 +3,7 @@ import jakarta.persistence.*; import lombok.*; import stackpot.stackpot.domain.common.BaseEntity; -import stackpot.stackpot.domain.enums.ModeOfOperation; +import stackpot.stackpot.domain.enums.PotModeOfOperation; import java.time.LocalDate; @@ -20,37 +20,34 @@ public class Pot extends BaseEntity { private Long potId; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "creator_id", nullable = false) - private User creator; + @JoinColumn(name = "user_id", nullable = false) + private User user; @Column(nullable = false, length = 255) private String potName; - @Column(nullable = true) - private LocalDate startDate; + @Column(nullable = false) + private LocalDate potStartDate; - @Column(nullable = true) - private LocalDate endDate; + @Column(nullable = false) + private LocalDate potEndDate; @Column(nullable = false, length = 255) - private String expectedDuration; + private String potDuration; @Column(nullable = false, length = 255) - private String languagesUsed; + private String potLan; @Column(nullable = true, columnDefinition = "TEXT") - private String description; + private String potContent; @Column(nullable = false, length = 255) private String potStatus; - @Column(nullable = true, length = 100) - private String oneLineSummary; - @Enumerated(EnumType.STRING) @Column(nullable = false) - private ModeOfOperation modeOfOperation; // 팟 진행 방식 + private PotModeOfOperation potModeOfOperation; // 팟 진행 방식 - @Column(nullable = true, length = 700) + @Column(nullable = true, length = 400) private String potSummary; // 팟 요약 } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/domain/User.java b/src/main/java/stackpot/stackpot/domain/User.java index 159edbb4..67110ce4 100644 --- a/src/main/java/stackpot/stackpot/domain/User.java +++ b/src/main/java/stackpot/stackpot/domain/User.java @@ -18,30 +18,30 @@ public class User extends BaseEntity { @Column(nullable = false, length = 255) private String loginId; // 로그인 아이디 - @Column(nullable = true, length = 255) + @Column(nullable = false, length = 255) private String userName; // 유저 이름 @Column(nullable = false, length = 255) private String snsKey; // SNS 키 + @Column(nullable = false, unique = true) + private String email; // 이메일 + @Column(nullable = false, length = 255) + private String kakaoId; // 카카오 아이디 + + @Column(nullable = true, length = 255) private String nickname; // 닉네임 - @Column(nullable = false, length = 255) + @Column(nullable = true, length = 255) private String role; // 역할 - @Column(nullable = false, length = 255) - private String kakaoId; // 카카오 아이디 - - @Column(nullable = false, length = 255) + @Column(nullable = true, length = 255) private String interest; // 관심사 @Column(nullable = true, columnDefinition = "TEXT") - private String introduction; // 한 줄 소개 + private String userIntroduction; // 한 줄 소개 - @Column(nullable = false) + @Column(nullable = true) private Integer userTemperature; // 유저 온도 - - @Column(nullable = false, unique = true) - private String email; // 이메일 } diff --git a/src/main/java/stackpot/stackpot/domain/enums/ModeOfOperation.java b/src/main/java/stackpot/stackpot/domain/enums/PotModeOfOperation.java similarity index 65% rename from src/main/java/stackpot/stackpot/domain/enums/ModeOfOperation.java rename to src/main/java/stackpot/stackpot/domain/enums/PotModeOfOperation.java index a8aada34..c07b1e07 100644 --- a/src/main/java/stackpot/stackpot/domain/enums/ModeOfOperation.java +++ b/src/main/java/stackpot/stackpot/domain/enums/PotModeOfOperation.java @@ -1,5 +1,5 @@ package stackpot.stackpot.domain.enums; -public enum ModeOfOperation { +public enum PotModeOfOperation { ONLINE, OFFLINE } diff --git a/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java b/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java index 0ce393d1..988250cf 100644 --- a/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java +++ b/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java @@ -41,8 +41,4 @@ public class PotApplication extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; - - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "pot_member_id", nullable = false) - private PotMember potMember; } diff --git a/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java b/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java index 9658c5ad..4dd8c547 100644 --- a/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java +++ b/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java @@ -16,7 +16,7 @@ public class PotMember extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(nullable = false) - private Long teamId; + private Long potMemberId; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "pot_id", nullable = false) @@ -31,8 +31,11 @@ public class PotMember extends BaseEntity { private PotApplication potApplication; @Column(nullable = false, length = 10) - private String potRoleName; + private String roleName; - @Column(nullable = true, length = 10) - private String awards; + @Column(nullable = false) + private String owner; + + @Column(nullable = false) + private String appealContent; } diff --git a/src/main/java/stackpot/stackpot/domain/mapping/UserBadge.java b/src/main/java/stackpot/stackpot/domain/mapping/PotMemberBadge.java similarity index 75% rename from src/main/java/stackpot/stackpot/domain/mapping/UserBadge.java rename to src/main/java/stackpot/stackpot/domain/mapping/PotMemberBadge.java index 522655b6..da7e1022 100644 --- a/src/main/java/stackpot/stackpot/domain/mapping/UserBadge.java +++ b/src/main/java/stackpot/stackpot/domain/mapping/PotMemberBadge.java @@ -11,17 +11,17 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor -public class UserBadge extends BaseEntity { +public class PotMemberBadge extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long userBadgeId; + private Long potMemberBadgeId; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "badge_id", nullable = false) private Badge badge; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; + @JoinColumn(name = "pot_member_id", nullable = false) + private PotMember potMember; } diff --git a/src/main/java/stackpot/stackpot/domain/mapping/TaskComment.java b/src/main/java/stackpot/stackpot/domain/mapping/TaskComment.java index fa3c6d63..a0ec30b5 100644 --- a/src/main/java/stackpot/stackpot/domain/mapping/TaskComment.java +++ b/src/main/java/stackpot/stackpot/domain/mapping/TaskComment.java @@ -18,7 +18,7 @@ public class TaskComment extends BaseEntity { private Long commentId; @Column(nullable = false, columnDefinition = "TEXT") - private String content; + private String comment_content; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "potMember_id", nullable = false) From 4066c2cc8fa08b5e29d02e944616e80a0dbbc8aa Mon Sep 17 00:00:00 2001 From: isumin Date: Sun, 19 Jan 2025 00:52:17 +0900 Subject: [PATCH 22/76] =?UTF-8?q?[FIX]=20=ED=8C=9F=20=EB=AA=A8=EC=A7=91=20?= =?UTF-8?q?=EA=B8=B0=ED=95=9C=20=ED=95=84=EB=93=9C=20PotRecruitmentDetails?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99=20&=20User=20=ED=95=84=EB=93=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/stackpot/stackpot/domain/Pot.java | 3 +++ .../domain/PotRecruitmentDetails.java | 3 --- .../java/stackpot/stackpot/domain/User.java | 22 +++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/stackpot/stackpot/domain/Pot.java b/src/main/java/stackpot/stackpot/domain/Pot.java index f7de0ed1..b2e80050 100644 --- a/src/main/java/stackpot/stackpot/domain/Pot.java +++ b/src/main/java/stackpot/stackpot/domain/Pot.java @@ -50,4 +50,7 @@ public class Pot extends BaseEntity { @Column(nullable = true, length = 400) private String potSummary; // 팟 요약 + + @Column(nullable = false) + private LocalDate recruitmentDeadline; } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java b/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java index 37885523..51414d09 100644 --- a/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java +++ b/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java @@ -24,9 +24,6 @@ public class PotRecruitmentDetails extends BaseEntity { @Column(nullable = true) private Integer recruitmentCount; - @Column(nullable = false) - private LocalDate recruitmentDeadline; - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "pot_id", nullable = false) private Pot pot; diff --git a/src/main/java/stackpot/stackpot/domain/User.java b/src/main/java/stackpot/stackpot/domain/User.java index 67110ce4..6d038687 100644 --- a/src/main/java/stackpot/stackpot/domain/User.java +++ b/src/main/java/stackpot/stackpot/domain/User.java @@ -18,30 +18,30 @@ public class User extends BaseEntity { @Column(nullable = false, length = 255) private String loginId; // 로그인 아이디 - @Column(nullable = false, length = 255) - private String userName; // 유저 이름 + @Column(nullable = true, length = 12) + private String userName; // 유저 카톡 설정 이름 @Column(nullable = false, length = 255) private String snsKey; // SNS 키 - @Column(nullable = false, unique = true) - private String email; // 이메일 - @Column(nullable = false, length = 255) - private String kakaoId; // 카카오 아이디 - - @Column(nullable = true, length = 255) private String nickname; // 닉네임 - @Column(nullable = true, length = 255) + @Column(nullable = false, length = 255) private String role; // 역할 @Column(nullable = true, length = 255) + private String kakaoId; // 카카오 아이디 + + @Column(nullable = false, length = 255) private String interest; // 관심사 @Column(nullable = true, columnDefinition = "TEXT") - private String userIntroduction; // 한 줄 소개 + private String introduction; // 한 줄 소개 - @Column(nullable = true) + @Column(nullable = false) private Integer userTemperature; // 유저 온도 + + @Column(nullable = false, unique = true) + private String email; // 이메일 } From e0ad439881904e64fc72af577e7577859549ebc4 Mon Sep 17 00:00:00 2001 From: isumin Date: Sun, 19 Jan 2025 00:59:20 +0900 Subject: [PATCH 23/76] =?UTF-8?q?[FIX]=20SecurityConfig=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 ++ .../java/stackpot/stackpot/config/security/SecurityConfig.java | 1 + 2 files changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index d393d63a..e80e5bc9 100644 --- a/build.gradle +++ b/build.gradle @@ -35,6 +35,8 @@ dependencies { implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' implementation 'mysql:mysql-connector-java:8.0.33' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.security:spring-security-crypto' } tasks.named('test') { diff --git a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java index e755fc0a..1c9871de 100644 --- a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java +++ b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java @@ -7,6 +7,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; + @Configuration public class SecurityConfig { From fec99f9252b75fe3524ecfbeadf47058ca8f9b11 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Sun, 19 Jan 2025 01:13:11 +0900 Subject: [PATCH 24/76] =?UTF-8?q?[feat/#9]=20jwt=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20(=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85?= =?UTF-8?q?=20=EB=AF=B8=EC=99=84=EC=84=B1=20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/CustomOAuth2UserService.java | 105 ++++++++++++------ .../config/security/JwtTokenFilter.java | 0 .../config/security/JwtTokenProvider.java | 48 ++++++++ .../config/security/SecurityConfig.java | 90 +++++++++++++-- .../stackpot/converter/UserConverter.java | 8 +- .../service/JwtUserDetailsService.java | 4 + .../web/controller/MemberViewController.java | 57 ++++++---- .../web/controller/UserController.java | 11 ++ .../stackpot/stackpot/web/dto/JwtDto.java | 5 + .../stackpot/web/dto/LoginResponseDTO.java | 4 + src/main/resources/application.yml | 2 +- src/main/resources/templates/home.html | 17 +++ src/main/resources/templates/login.html | 14 +++ src/main/resources/templates/signup.html | 16 +++ .../controller/MemberViewControllerTest.java | 102 ++++++++--------- 15 files changed, 359 insertions(+), 124 deletions(-) create mode 100644 src/main/java/stackpot/stackpot/config/security/JwtTokenFilter.java create mode 100644 src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java create mode 100644 src/main/java/stackpot/stackpot/service/JwtUserDetailsService.java create mode 100644 src/main/java/stackpot/stackpot/web/controller/UserController.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/JwtDto.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/LoginResponseDTO.java create mode 100644 src/main/resources/templates/home.html create mode 100644 src/main/resources/templates/login.html create mode 100644 src/main/resources/templates/signup.html diff --git a/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java b/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java index 3864e14e..d7bcede3 100644 --- a/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java +++ b/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java @@ -1,8 +1,6 @@ package stackpot.stackpot.config.security; - import lombok.RequiredArgsConstructor; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; @@ -14,52 +12,87 @@ import java.util.HashMap; import java.util.Map; -import java.util.UUID; @Service @RequiredArgsConstructor public class CustomOAuth2UserService extends DefaultOAuth2UserService { private final UserRepository userRepository; - private final PasswordEncoder passwordEncoder; - @Override - public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { - OAuth2User oAuth2User = super.loadUser(userRequest); +// @Override +// public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { +// OAuth2User oAuth2User = super.loadUser(userRequest); +// +// // 사용자 정보 가져오기 +// Map attributes = oAuth2User.getAttributes(); +// Map kakaoAccount = (Map) attributes.get("kakao_account"); +// +// if (kakaoAccount == null) { +// throw new OAuth2AuthenticationException("Failed to retrieve kakao_account from attributes."); +// } +// +// // 이메일 가져오기 +// String email = (String) kakaoAccount.get("email"); +// if (email == null) { +// throw new OAuth2AuthenticationException("Email not found in kakao_account."); +// } +// // 사용자 정보 조회 (DB에 저장 여부 확인) +// userRepository.findByEmail(email).ifPresentOrElse( +// user -> System.out.println("Existing user found: " + email), +// () -> System.out.println("No user found. Redirecting to signup.") +// ); +// +// Map modifiedAttributes = new HashMap<>(attributes); +// modifiedAttributes.put("email", email); // 확실히 email 필드가 포함되도록 보장 +// +// System.out.println("modifiedAttributes : " + modifiedAttributes); +// +// if (!modifiedAttributes.containsKey("email")) { +// throw new IllegalStateException("Email is not present in modifiedAttributes!"); +// } +// // 사용자 정보를 DefaultOAuth2User로 반환 +// return new DefaultOAuth2User( +// oAuth2User.getAuthorities(), +// attributes, +// "email" // Principal로 사용할 필드 +// ); +// } +@Override +public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User oAuth2User = super.loadUser(userRequest); - Map attributes = oAuth2User.getAttributes(); - Map properties = (Map) attributes.get("properties"); + // 사용자 정보 가져오기 + Map attributes = oAuth2User.getAttributes(); + Map kakaoAccount = (Map) attributes.get("kakao_account"); - String email = (String) properties.get("email"); + if (kakaoAccount == null) { + throw new OAuth2AuthenticationException("Failed to retrieve kakao_account from attributes."); + } - // 사용자 정보 조회 - User user = userRepository.findByEmail(email).orElse(null); + // 이메일 가져오기 + String email = (String) kakaoAccount.get("email"); + if (email == null) { + throw new OAuth2AuthenticationException("Email not found in kakao_account."); + } - // 리다이렉션 상태 플래그 추가 - Map modifiedAttributes = new HashMap<>(attributes); - modifiedAttributes.put("email", email); - if (user == null) { - // 회원가입 페이지로 리다이렉션 - modifiedAttributes.put("redirect", String.format("/signup?email=%s", email)); - } else { - // 홈 페이지로 리다이렉션 - modifiedAttributes.put("redirect", "/home"); - } + // 사용자 정보 확인 + userRepository.findByEmail(email).ifPresentOrElse( + user -> System.out.println("Existing user found: " + email), + () -> System.out.println("No user found. Redirecting to signup.") + ); - return new DefaultOAuth2User( - oAuth2User.getAuthorities(), - modifiedAttributes, - "email" // Principal로 설정 - ); - } + // attributes에 email 추가 + Map modifiedAttributes = new HashMap<>(attributes); + modifiedAttributes.put("email", email); - private User saveOrUpdateUser(String email) { - User user = userRepository.findByEmail(email) - .orElse(User.builder() - .email(email) - .kakaoId(passwordEncoder.encode("OAUTH_USER_" + UUID.randomUUID())) - .build()); + // 디버깅: modifiedAttributes 확인 + System.out.println("Final Modified Attributes: " + modifiedAttributes); - return userRepository.save(user); - } + // DefaultOAuth2User 생성 + return new DefaultOAuth2User( + oAuth2User.getAuthorities(), + modifiedAttributes, + "email" // Principal로 사용할 필드 이름 + ); +} } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/config/security/JwtTokenFilter.java b/src/main/java/stackpot/stackpot/config/security/JwtTokenFilter.java new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java b/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java new file mode 100644 index 00000000..ad994b9a --- /dev/null +++ b/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java @@ -0,0 +1,48 @@ +package stackpot.stackpot.config.security; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.Date; + +@Component +public class JwtTokenProvider { + + private final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256); + private final long validityInMilliseconds = 3600000; // 1시간 + + // JWT 생성 (이메일 포함) + public String createToken(String email) { + Claims claims = Jwts.claims().setSubject(email); + + Date now = new Date(); + Date validity = new Date(now.getTime() + validityInMilliseconds); + + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(now) + .setExpiration(validity) + .signWith(key) + .compact(); + } + + public boolean validateToken(String token) { + try { + Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); + return true; + } catch (JwtException | IllegalArgumentException e) { + return false; + } + } + + public String getEmailFromToken(String token) { + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody() + .getSubject(); + } +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java index 8ddf5c33..27686b6a 100644 --- a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java +++ b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java @@ -1,3 +1,47 @@ +//package stackpot.stackpot.config.security; +// +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.security.config.annotation.web.builders.HttpSecurity; +//import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +//import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +//import org.springframework.security.crypto.password.PasswordEncoder; +//import org.springframework.security.web.SecurityFilterChain; +// +//@EnableWebSecurity +//@Configuration +//public class SecurityConfig { +// +// @Bean +// public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { +// http +// .authorizeHttpRequests((requests) -> requests +// .requestMatchers("/", "/home", "/signup","/swagger-ui/**", "/v3/api-docs/**").permitAll() +// .anyRequest().authenticated() +// ) +// .formLogin((form) -> form +// .loginPage("/login") +// .defaultSuccessUrl("/home", true) +// .permitAll() +// ) +// .logout((logout) -> logout +// .logoutUrl("/logout") +// .logoutSuccessUrl("/login?logout") +// .permitAll() +// ) +// .oauth2Login(oauth2 -> oauth2 +// .loginPage("/login") +// .permitAll() +// ); +// +// return http.build(); +// } +// @Bean +// public PasswordEncoder passwordEncoder() { +// return new BCryptPasswordEncoder(); +// } +//} + package stackpot.stackpot.config.security; import org.springframework.context.annotation.Bean; @@ -6,34 +50,58 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import stackpot.stackpot.repository.UserRepository.UserRepository; @EnableWebSecurity @Configuration public class SecurityConfig { @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtTokenProvider jwtTokenProvider, UserRepository userRepository) throws Exception { http - .authorizeHttpRequests((requests) -> requests - .requestMatchers("/", "/home", "/signup", "/user/profile","/swagger-ui/**", "/v3/api-docs/**").permitAll() + .authorizeHttpRequests(auth -> auth + .requestMatchers("/", "/home", "/signup","user/signup", "/login", "/oauth2/**").permitAll() .anyRequest().authenticated() ) - .formLogin((form) -> form + .oauth2Login(oauth2 -> oauth2 .loginPage("/login") - .defaultSuccessUrl("/home", true) - .permitAll() + .successHandler(successHandler(jwtTokenProvider, userRepository)) // SuccessHandler 등록 ) - .logout((logout) -> logout - .logoutUrl("/logout") - .logoutSuccessUrl("/login?logout") - .permitAll() + .csrf(csrf -> csrf.ignoringRequestMatchers("/signup")) // CSRF 예외 처리 + .formLogin(form -> form + .loginPage("/login").permitAll() ); return http.build(); } + @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } -} + + @Bean + public AuthenticationSuccessHandler successHandler(JwtTokenProvider jwtTokenProvider, UserRepository userRepository) { + + + + return (request, response, authentication) -> { + OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); + String email = (String) oAuth2User.getAttributes().get("email"); + + // JWT 토큰 생성 + String token = jwtTokenProvider.createToken(email); + + if (userRepository.findByEmail(email).isPresent()) { + // 이메일이 존재하면 홈으로 리다이렉트 + response.sendRedirect("/home?token=" + token); + } else { + // 이메일이 없으면 회원가입 페이지로 리다이렉트 + response.sendRedirect("/signup?token=" + token); + } + }; + } +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/converter/UserConverter.java b/src/main/java/stackpot/stackpot/converter/UserConverter.java index 84c09257..5b6865a1 100644 --- a/src/main/java/stackpot/stackpot/converter/UserConverter.java +++ b/src/main/java/stackpot/stackpot/converter/UserConverter.java @@ -7,11 +7,11 @@ public class UserConverter { public static User toUser(UserRequestDTO.JoinDto request) { return User.builder() - .nickname(request.getNickname()) +// .nickname(request.getNickname()) .email(request.getEmail()) // 추가된 코드 - .kakaoId(request.getKakaoId()) - .interest(request.getInterest()) - .role(request.getRole()) +// .kakaoId(request.getKakaoId()) +// .interest(request.getInterest()) +// .role(request.getRole()) .userTemperature((int)35.5) .build(); } diff --git a/src/main/java/stackpot/stackpot/service/JwtUserDetailsService.java b/src/main/java/stackpot/stackpot/service/JwtUserDetailsService.java new file mode 100644 index 00000000..fc172b43 --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/JwtUserDetailsService.java @@ -0,0 +1,4 @@ +package stackpot.stackpot.service; + +public class JwtUserDetailsService { +} diff --git a/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java b/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java index 39b43634..48954ff3 100644 --- a/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java +++ b/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java @@ -1,13 +1,14 @@ package stackpot.stackpot.web.controller; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestHeader; +import stackpot.stackpot.config.security.JwtTokenProvider; import stackpot.stackpot.service.UserCommandService; import stackpot.stackpot.web.dto.UserRequestDTO; @@ -16,34 +17,48 @@ public class MemberViewController { private final UserCommandService userCommandService; + private final JwtTokenProvider jwtTokenProvider; - @PostMapping("/user/profile") - public String joinUser(@ModelAttribute("UserJoinDto") UserRequestDTO.JoinDto request, + // 로그인 페이지 + @GetMapping("/login") + public String loginPage() { + return "login"; + } + +// 회원가입 페이지 랜더링 + @GetMapping("/signup") + public String signupPage() { + // 단순히 회원가입 페이지를 렌더링 + return "signup"; + } + + // 회원가입 처리 + @PostMapping("/user/signup") + public String joinUser(@Valid @ModelAttribute("UserJoinDTO") UserRequestDTO.JoinDto request, BindingResult bindingResult, + @RequestHeader(value = "Authorization", required = false) String token, Model model) { if (bindingResult.hasErrors()) { - return "signup"; + return "signup"; // 유효성 검사 실패 시 다시 회원가입 페이지로 } try { + if (token == null || !token.startsWith("Bearer ")) { + throw new IllegalArgumentException("Authorization header is missing or invalid."); + } + + // JWT에서 이메일 추출 + String email = jwtTokenProvider.getEmailFromToken(token.replace("Bearer ", "")); + request.setEmail(email); // DTO에 이메일 설정 + System.out.println("Extracted Email: " + email); + + + // 회원가입 처리 userCommandService.joinUser(request); - return "redirect:/login"; + return "redirect:/home"; // 성공 시 홈 페이지로 리다이렉트 } catch (Exception e) { model.addAttribute("error", e.getMessage()); - return "signup"; + return "signup"; // 에러 발생 시 다시 회원가입 페이지로 } } - - @GetMapping("/login") - public String loginPage() { - return "login"; - } - - @GetMapping("/user/profile") - public String signupPage(@RequestParam(required = false) String email, Model model) { - model.addAttribute("email", email); // 카카오 이메일 전달 - return "signup"; - } - - } diff --git a/src/main/java/stackpot/stackpot/web/controller/UserController.java b/src/main/java/stackpot/stackpot/web/controller/UserController.java new file mode 100644 index 00000000..c924d018 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/controller/UserController.java @@ -0,0 +1,11 @@ +package stackpot.stackpot.web.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class UserController { + +} diff --git a/src/main/java/stackpot/stackpot/web/dto/JwtDto.java b/src/main/java/stackpot/stackpot/web/dto/JwtDto.java new file mode 100644 index 00000000..9a30707d --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/JwtDto.java @@ -0,0 +1,5 @@ +package stackpot.stackpot.web.dto; + +public record JwtDto(String accessToken, + String refreshToken) { +} diff --git a/src/main/java/stackpot/stackpot/web/dto/LoginResponseDTO.java b/src/main/java/stackpot/stackpot/web/dto/LoginResponseDTO.java new file mode 100644 index 00000000..7362c16e --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/LoginResponseDTO.java @@ -0,0 +1,4 @@ +package stackpot.stackpot.web.dto; + +public class LoginResponseDTO { +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 5ca28f07..131b1557 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -40,7 +40,7 @@ spring: client-secret: ${KAKAO_CLIENT_SECRET} redirect-uri: http://localhost:8080/login/oauth2/code/kakao authorization-grant-type: authorization_code - scope: profile_nickname + scope: account_email client-name: Kakao provider: kakao: diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html new file mode 100644 index 00000000..529b72c5 --- /dev/null +++ b/src/main/resources/templates/home.html @@ -0,0 +1,17 @@ + + + + Home + + +

Welcome to Home Page!

+

+ + + +
+ +
+ \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 00000000..2f69e8fa --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,14 @@ + + + + Login + + +카카오로 로그인 + +

사용자 이름 또는 비밀번호가 잘못되었습니다.

+

로그아웃되었습니다.

+ +

계정이 없나요? Sign up

+ + \ No newline at end of file diff --git a/src/main/resources/templates/signup.html b/src/main/resources/templates/signup.html new file mode 100644 index 00000000..06f6c292 --- /dev/null +++ b/src/main/resources/templates/signup.html @@ -0,0 +1,16 @@ + + + + 회원가입 + + + +

회원가입

+
+
+ +
+ + \ No newline at end of file diff --git a/src/test/java/stackpot/stackpot/controller/MemberViewControllerTest.java b/src/test/java/stackpot/stackpot/controller/MemberViewControllerTest.java index 1bb091e0..2d9757d8 100644 --- a/src/test/java/stackpot/stackpot/controller/MemberViewControllerTest.java +++ b/src/test/java/stackpot/stackpot/controller/MemberViewControllerTest.java @@ -1,51 +1,51 @@ -package stackpot.stackpot.controller; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.test.web.servlet.MockMvc; -import stackpot.stackpot.service.UserCommandService; -import stackpot.stackpot.web.controller.MemberViewController; -import stackpot.stackpot.web.dto.UserRequestDTO; - -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -@WebMvcTest(MemberViewController.class) -class MemberViewControllerTest { - - @Autowired - private MockMvc mockMvc; - - @MockBean - private UserCommandService userCommandService; - - @Test - void testJoinUser_Success() throws Exception { - // Mock request data - UserRequestDTO.JoinDto joinDto = new UserRequestDTO.JoinDto(); - joinDto.setEmail("test@example.com"); - joinDto.setNickname("TestUser"); - joinDto.setKakaoId("12345"); - - doNothing().when(userCommandService).joinUser(any(UserRequestDTO.JoinDto.class)); - - mockMvc.perform(post("/user/profile") - .flashAttr("UserJoinDto", joinDto)) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/login")); - } - - @Test - void testJoinUser_ValidationError() throws Exception { - mockMvc.perform(post("/user/profile") - .param("email", "") // 잘못된 데이터 - .param("nickname", "") - .param("kakaoId", "")) - .andExpect(status().isOk()) - .andExpect(view().name("signup")) - .andExpect(model().attributeExists("error")); - } -} \ No newline at end of file +//package stackpot.stackpot.controller; +// +//import org.junit.jupiter.api.Test; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +//import org.springframework.boot.test.mock.mockito.MockBean; +//import org.springframework.test.web.servlet.MockMvc; +//import stackpot.stackpot.service.UserCommandService; +//import stackpot.stackpot.web.controller.MemberViewController; +//import stackpot.stackpot.web.dto.UserRequestDTO; +// +//import static org.mockito.Mockito.*; +//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +// +//@WebMvcTest(MemberViewController.class) +//class MemberViewControllerTest { +// +// @Autowired +// private MockMvc mockMvc; +// +// @MockBean +// private UserCommandService userCommandService; +// +// @Test +// void testJoinUser_Success() throws Exception { +// // Mock request data +// UserRequestDTO.JoinDto joinDto = new UserRequestDTO.JoinDto(); +// joinDto.setEmail("test@example.com"); +// joinDto.setNickname("TestUser"); +// joinDto.setKakaoId("12345"); +// +// doNothing().when(userCommandService).joinUser(any(UserRequestDTO.JoinDto.class)); +// +// mockMvc.perform(post("/user/profile") +// .flashAttr("UserJoinDto", joinDto)) +// .andExpect(status().is3xxRedirection()) +// .andExpect(redirectedUrl("/login")); +// } +// +// @Test +// void testJoinUser_ValidationError() throws Exception { +// mockMvc.perform(post("/user/profile") +// .param("email", "") // 잘못된 데이터 +// .param("nickname", "") +// .param("kakaoId", "")) +// .andExpect(status().isOk()) +// .andExpect(view().name("signup")) +// .andExpect(model().attributeExists("error")); +// } +//} \ No newline at end of file From c9593bb80a23e403e2919dc3162efcefe2b72a36 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Sun, 19 Jan 2025 02:40:15 +0900 Subject: [PATCH 25/76] =?UTF-8?q?[feat/#9]=20jwt=20token=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 --- .../security/JwtAuthenticationFilter.java | 41 +++++++++++++++++++ .../config/security/JwtTokenFilter.java | 0 .../config/security/JwtTokenProvider.java | 15 ++++++- .../config/security/SecurityConfig.java | 11 +++-- .../java/stackpot/stackpot/domain/User.java | 21 +++++++++- .../service/CustomUserDetailService.java | 25 +++++++++++ .../web/controller/MemberViewController.java | 3 +- .../stackpot/stackpot/web/dto/JwtDto.java | 5 --- .../stackpot/web/dto/LoginResponseDTO.java | 4 -- 10 files changed, 110 insertions(+), 20 deletions(-) create mode 100644 src/main/java/stackpot/stackpot/config/security/JwtAuthenticationFilter.java delete mode 100644 src/main/java/stackpot/stackpot/config/security/JwtTokenFilter.java create mode 100644 src/main/java/stackpot/stackpot/service/CustomUserDetailService.java delete mode 100644 src/main/java/stackpot/stackpot/web/dto/JwtDto.java delete mode 100644 src/main/java/stackpot/stackpot/web/dto/LoginResponseDTO.java diff --git a/build.gradle b/build.gradle index 25c32158..3824ce57 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,6 @@ dependencies { implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' implementation 'mysql:mysql-connector-java:8.0.33' -<<<<<<< HEAD //spring security implementation 'org.springframework.boot:spring-boot-starter-security' @@ -50,10 +49,6 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' -======= - implementation 'org.springframework.boot:spring-boot-starter-security' - implementation 'org.springframework.security:spring-security-crypto' ->>>>>>> e0ad439881904e64fc72af577e7577859549ebc4 } tasks.named('test') { diff --git a/src/main/java/stackpot/stackpot/config/security/JwtAuthenticationFilter.java b/src/main/java/stackpot/stackpot/config/security/JwtAuthenticationFilter.java new file mode 100644 index 00000000..ef33947a --- /dev/null +++ b/src/main/java/stackpot/stackpot/config/security/JwtAuthenticationFilter.java @@ -0,0 +1,41 @@ +package stackpot.stackpot.config.security; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.security.core.Authentication; + +import java.io.IOException; + +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtTokenProvider jwtTokenProvider; + + public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { + this.jwtTokenProvider = jwtTokenProvider; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + String token = resolveToken(request); + + if (token != null && jwtTokenProvider.validateToken(token)) { + Authentication authentication = jwtTokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + filterChain.doFilter(request, response); + } + + private String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (bearerToken != null && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/config/security/JwtTokenFilter.java b/src/main/java/stackpot/stackpot/config/security/JwtTokenFilter.java deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java b/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java index ad994b9a..a33a7d40 100644 --- a/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java +++ b/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java @@ -2,16 +2,23 @@ import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Component; - +import org.springframework.security.core.Authentication; import java.security.Key; import java.util.Date; +@RequiredArgsConstructor @Component public class JwtTokenProvider { + // key 하드코딩 되어 있음 수정필요. private final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256); private final long validityInMilliseconds = 3600000; // 1시간 + private final UserDetailsService userDetailsService; // JWT 생성 (이메일 포함) public String createToken(String email) { @@ -45,4 +52,10 @@ public String getEmailFromToken(String token) { .getBody() .getSubject(); } + + public Authentication getAuthentication(String token) { + String email = getEmailFromToken(token); + UserDetails userDetails = userDetailsService.loadUserByUsername(email); // 이메일로 사용자 로드 + return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); + } } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java index c95635cb..ce5befcf 100644 --- a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java +++ b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java @@ -57,11 +57,14 @@ import stackpot.stackpot.repository.UserRepository.UserRepository; @EnableWebSecurity +@Configuration public class SecurityConfig { + + @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtTokenProvider jwtTokenProvider, UserRepository userRepository) throws Exception { http .authorizeHttpRequests(auth -> auth - .requestMatchers("/", "/home", "/signup", "/user/signup", "/login", "/oauth2/**").permitAll() + .requestMatchers("/", "/home", "/signup", "/user/profile","/swagger-ui/**", "/v3/api-docs/**").permitAll() .anyRequest().authenticated() ) .oauth2Login(oauth2 -> oauth2 @@ -91,12 +94,14 @@ public AuthenticationSuccessHandler successHandler(JwtTokenProvider jwtTokenProv // JWT 토큰 생성 String token = jwtTokenProvider.createToken(email); + response.setHeader("Authorization", "Bearer " + token); + if (userRepository.findByEmail(email).isPresent()) { // 이메일이 존재하면 홈으로 리다이렉트 - response.sendRedirect("/home?token=" + token); + response.sendRedirect("/home"); } else { // 이메일이 없으면 회원가입 페이지로 리다이렉트 - response.sendRedirect("/signup?token=" + token); + response.sendRedirect("/signup"); } }; } diff --git a/src/main/java/stackpot/stackpot/domain/User.java b/src/main/java/stackpot/stackpot/domain/User.java index 3f25016f..ca21cbd6 100644 --- a/src/main/java/stackpot/stackpot/domain/User.java +++ b/src/main/java/stackpot/stackpot/domain/User.java @@ -2,15 +2,19 @@ import jakarta.persistence.*; import lombok.*; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; import stackpot.stackpot.domain.common.BaseEntity; import stackpot.stackpot.domain.enums.Role; +import java.util.Collection; + @Entity @Getter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor -public class User extends BaseEntity { +public class User extends BaseEntity implements UserDetails{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -45,4 +49,19 @@ public class User extends BaseEntity { @Column(nullable = false, unique = true) private String email; // 이메일 + + @Override + public Collection getAuthorities() { + return null; + } + + @Override + public String getPassword() { + return null; + } + + @Override + public String getUsername() { + return email; // 사용자 식별자로 이메일을 사용 + } } diff --git a/src/main/java/stackpot/stackpot/service/CustomUserDetailService.java b/src/main/java/stackpot/stackpot/service/CustomUserDetailService.java new file mode 100644 index 00000000..15e0a6e0 --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/CustomUserDetailService.java @@ -0,0 +1,25 @@ +package stackpot.stackpot.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.repository.UserRepository.UserRepository; + +@RequiredArgsConstructor +@Service +public class CustomUserDetailService implements UserDetailsService { + + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + + // UserDetails 반환 + return userRepository.findByEmail(email) + .orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다.")); + } +} diff --git a/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java b/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java index 48954ff3..30864f83 100644 --- a/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java +++ b/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java @@ -25,7 +25,7 @@ public String loginPage() { return "login"; } -// 회원가입 페이지 랜더링 + // 회원가입 페이지 랜더링 @GetMapping("/signup") public String signupPage() { // 단순히 회원가입 페이지를 렌더링 @@ -33,6 +33,7 @@ public String signupPage() { } // 회원가입 처리 + // 미완성 @PostMapping("/user/signup") public String joinUser(@Valid @ModelAttribute("UserJoinDTO") UserRequestDTO.JoinDto request, BindingResult bindingResult, diff --git a/src/main/java/stackpot/stackpot/web/dto/JwtDto.java b/src/main/java/stackpot/stackpot/web/dto/JwtDto.java deleted file mode 100644 index 9a30707d..00000000 --- a/src/main/java/stackpot/stackpot/web/dto/JwtDto.java +++ /dev/null @@ -1,5 +0,0 @@ -package stackpot.stackpot.web.dto; - -public record JwtDto(String accessToken, - String refreshToken) { -} diff --git a/src/main/java/stackpot/stackpot/web/dto/LoginResponseDTO.java b/src/main/java/stackpot/stackpot/web/dto/LoginResponseDTO.java deleted file mode 100644 index 7362c16e..00000000 --- a/src/main/java/stackpot/stackpot/web/dto/LoginResponseDTO.java +++ /dev/null @@ -1,4 +0,0 @@ -package stackpot.stackpot.web.dto; - -public class LoginResponseDTO { -} From b3a3fae6618e02120af99dd126b5198fc8e44265 Mon Sep 17 00:00:00 2001 From: starday119 Date: Sun, 19 Jan 2025 02:54:35 +0900 Subject: [PATCH 26/76] =?UTF-8?q?[Feat]=20=ED=8C=9F=20=EC=82=AD=EC=A0=9C/?= =?UTF-8?q?=EC=88=98=EC=A0=95=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apiPayload/exception/ExceptionAdvice.java | 2 +- .../stackpot/config/SwaggerConfig.java | 17 +++--- .../stackpot/converter/PotConverter.java | 14 +++++ .../stackpot/converter/PotConverterImpl.java | 55 +++++++++++++++++++ .../PotRecruitmentDetailsRepository.java | 7 +++ .../stackpot/repository/PotRepository.java | 9 +++ .../stackpot/stackpot/service/PotService.java | 48 ++++++++++++++++ .../web/controller/PotController.java | 23 ++++++++ .../web/dto/PotRecruitmentRequestDto.java | 13 +++++ .../web/dto/PotRecruitmentResponseDto.java | 15 +++++ .../stackpot/web/dto/PotRequestDto.java | 41 ++++++++++++++ .../stackpot/web/dto/PotResponseDto.java | 27 +++++++++ 12 files changed, 263 insertions(+), 8 deletions(-) create mode 100644 src/main/java/stackpot/stackpot/converter/PotConverter.java create mode 100644 src/main/java/stackpot/stackpot/converter/PotConverterImpl.java create mode 100644 src/main/java/stackpot/stackpot/repository/PotRecruitmentDetailsRepository.java create mode 100644 src/main/java/stackpot/stackpot/repository/PotRepository.java create mode 100644 src/main/java/stackpot/stackpot/service/PotService.java create mode 100644 src/main/java/stackpot/stackpot/web/controller/PotController.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/PotRecruitmentRequestDto.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/PotRecruitmentResponseDto.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/PotRequestDto.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/PotResponseDto.java diff --git a/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java b/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java index 29765be8..781afea5 100644 --- a/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java +++ b/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java @@ -24,7 +24,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j -@RestControllerAdvice(annotations = {RestController.class}) +//@RestControllerAdvice(annotations = {RestController.class}) public class ExceptionAdvice extends ResponseEntityExceptionHandler { diff --git a/src/main/java/stackpot/stackpot/config/SwaggerConfig.java b/src/main/java/stackpot/stackpot/config/SwaggerConfig.java index 8f3c8055..985672ee 100644 --- a/src/main/java/stackpot/stackpot/config/SwaggerConfig.java +++ b/src/main/java/stackpot/stackpot/config/SwaggerConfig.java @@ -1,5 +1,6 @@ package stackpot.stackpot.config; +import com.amazonaws.services.cloudformation.model.Tag; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; @@ -9,6 +10,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import java.util.List; + @Configuration public class SwaggerConfig { @@ -16,24 +19,24 @@ public class SwaggerConfig { public OpenAPI StackPotAPI() { Info info = new Info() .title("StackPot API") - .description("StackPotAPI API 명세서") + .description("StackPot API 명세서") .version("1.0.0"); String jwtSchemeName = "JWT TOKEN"; - // API 요청헤더에 인증정보 포함 SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName); - // SecuritySchemes 등록 Components components = new Components() .addSecuritySchemes(jwtSchemeName, new SecurityScheme() .name(jwtSchemeName) - .type(SecurityScheme.Type.HTTP) // HTTP 방식 + .type(SecurityScheme.Type.HTTP) .scheme("bearer") - .bearerFormat("JWT")); + .bearerFormat("JWT") + .in(SecurityScheme.In.HEADER)); return new OpenAPI() - .addServersItem(new Server().url("/api")) + .addServersItem(new Server().url("http://localhost:8080")) .info(info) .addSecurityItem(securityRequirement) .components(components); + } -} \ No newline at end of file +} diff --git a/src/main/java/stackpot/stackpot/converter/PotConverter.java b/src/main/java/stackpot/stackpot/converter/PotConverter.java new file mode 100644 index 00000000..2aa4b1ba --- /dev/null +++ b/src/main/java/stackpot/stackpot/converter/PotConverter.java @@ -0,0 +1,14 @@ +package stackpot.stackpot.converter; + +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.PotRecruitmentDetails; +import stackpot.stackpot.web.dto.PotRequestDto; +import stackpot.stackpot.web.dto.PotResponseDto; + +import java.util.List; + +public interface PotConverter { + Pot toEntity(PotRequestDto dto); + + PotResponseDto toDto(Pot entity, List recruitmentDetails); +} diff --git a/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java b/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java new file mode 100644 index 00000000..dfe525c1 --- /dev/null +++ b/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java @@ -0,0 +1,55 @@ +package stackpot.stackpot.converter; + +import org.springframework.stereotype.Component; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.PotRecruitmentDetails; +import stackpot.stackpot.domain.enums.PotModeOfOperation; +import stackpot.stackpot.web.dto.PotRequestDto; +import stackpot.stackpot.web.dto.PotResponseDto; +import stackpot.stackpot.web.dto.PotRecruitmentResponseDto; + +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class PotConverterImpl implements PotConverter { + + @Override + public Pot toEntity(PotRequestDto dto) { + return Pot.builder() + .user(user) // 사용자 정보 설정 + .potName(dto.getPotName()) + .potStartDate(dto.getPotStartDate()) + .potEndDate(dto.getPotEndDate()) + .potDuration(dto.getPotDuration()) + .potLan(dto.getPotLan()) + .potContent(dto.getPotContent()) + .potStatus(dto.getPotStatus()) + .potModeOfOperation(PotModeOfOperation.valueOf(dto.getPotModeOfOperation())) + .potSummary(dto.getPotSummary()) + .recruitmentDeadline(dto.getRecruitmentDeadline()) + .build(); + } + + @Override + public PotResponseDto toDto(Pot entity, List recruitmentDetails) { + return PotResponseDto.builder() + .potId(entity.getPotId()) + .potName(entity.getPotName()) + .potStartDate(entity.getPotStartDate()) + .potEndDate(entity.getPotEndDate()) + .potDuration(entity.getPotDuration()) + .potLan(entity.getPotLan()) + .potContent(entity.getPotContent()) + .potStatus(entity.getPotStatus()) + .potModeOfOperation(entity.getPotModeOfOperation().name()) + .potSummary(entity.getPotSummary()) + .recruitmentDeadline(entity.getRecruitmentDeadline()) + .recruitmentDetails(recruitmentDetails.stream().map(r -> PotRecruitmentResponseDto.builder() + .recruitmentId(r.getRecruitmentId()) + .recruitmentRole(r.getRecruitmentRole()) + .recruitmentCount(r.getRecruitmentCount()) + .build()).collect(Collectors.toList())) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/repository/PotRecruitmentDetailsRepository.java b/src/main/java/stackpot/stackpot/repository/PotRecruitmentDetailsRepository.java new file mode 100644 index 00000000..9ed08ad5 --- /dev/null +++ b/src/main/java/stackpot/stackpot/repository/PotRecruitmentDetailsRepository.java @@ -0,0 +1,7 @@ +package stackpot.stackpot.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import stackpot.stackpot.domain.PotRecruitmentDetails; + +public interface PotRecruitmentDetailsRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/repository/PotRepository.java b/src/main/java/stackpot/stackpot/repository/PotRepository.java new file mode 100644 index 00000000..b29a5c62 --- /dev/null +++ b/src/main/java/stackpot/stackpot/repository/PotRepository.java @@ -0,0 +1,9 @@ +package stackpot.stackpot.repository; + + + +import org.springframework.data.jpa.repository.JpaRepository; +import stackpot.stackpot.domain.Pot; + +public interface PotRepository extends JpaRepository { +} diff --git a/src/main/java/stackpot/stackpot/service/PotService.java b/src/main/java/stackpot/stackpot/service/PotService.java new file mode 100644 index 00000000..ec633e1b --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/PotService.java @@ -0,0 +1,48 @@ +// Service +package stackpot.stackpot.service; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import stackpot.stackpot.converter.PotConverter; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.PotRecruitmentDetails; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.repository.PotRecruitmentDetailsRepository; +import stackpot.stackpot.repository.PotRepository; +import stackpot.stackpot.web.dto.PotRequestDto; +import stackpot.stackpot.web.dto.PotResponseDto; +import stackpot.stackpot.web.dto.PotRecruitmentRequestDto; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class PotService { + + private final PotRepository potRepository; + private final PotRecruitmentDetailsRepository recruitmentDetailsRepository; + private final PotConverter potConverter; + + @Transactional + public PotResponseDto createPotWithRecruitments(PotRequestDto requestDto, User user) { + // Create Pot entity + Pot pot = potConverter.toEntity(requestDto, user); + Pot savedPot = potRepository.save(pot); + + // Create and save recruitment details + List recruitmentDetails = requestDto.getRecruitmentDetails().stream() + .map(recruitmentDto -> PotRecruitmentDetails.builder() + .recruitmentRole(recruitmentDto.getRecruitmentRole()) + .recruitmentCount(recruitmentDto.getRecruitmentCount()) + .pot(savedPot) + .build()) + .collect(Collectors.toList()); + + recruitmentDetailsRepository.saveAll(recruitmentDetails); + + // Convert and return response DTO + return potConverter.toDto(savedPot, recruitmentDetails); + } +} diff --git a/src/main/java/stackpot/stackpot/web/controller/PotController.java b/src/main/java/stackpot/stackpot/web/controller/PotController.java new file mode 100644 index 00000000..84555b34 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/controller/PotController.java @@ -0,0 +1,23 @@ +package stackpot.stackpot.web.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stackpot.stackpot.service.PotService; +import stackpot.stackpot.web.dto.PotRequestDto; +import stackpot.stackpot.web.dto.PotResponseDto; + +@RestController +@RequestMapping("/pots") +@RequiredArgsConstructor +public class PotController { + + private final PotService potService; + + @PostMapping + public ResponseEntity createPot(@RequestBody @Valid PotRequestDto requestDto) { + PotResponseDto responseDto = potService.createPotWithRecruitments(requestDto); + return ResponseEntity.ok(responseDto); + } +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/dto/PotRecruitmentRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/PotRecruitmentRequestDto.java new file mode 100644 index 00000000..a4ab9f65 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/PotRecruitmentRequestDto.java @@ -0,0 +1,13 @@ +package stackpot.stackpot.web.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class PotRecruitmentRequestDto { + private String recruitmentRole; + private Integer recruitmentCount; +} diff --git a/src/main/java/stackpot/stackpot/web/dto/PotRecruitmentResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/PotRecruitmentResponseDto.java new file mode 100644 index 00000000..a1d4de7d --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/PotRecruitmentResponseDto.java @@ -0,0 +1,15 @@ +package stackpot.stackpot.web.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class PotRecruitmentResponseDto { + private Long recruitmentId; + private String recruitmentRole; + private Integer recruitmentCount; + +} diff --git a/src/main/java/stackpot/stackpot/web/dto/PotRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/PotRequestDto.java new file mode 100644 index 00000000..04bbe638 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/PotRequestDto.java @@ -0,0 +1,41 @@ +package stackpot.stackpot.web.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +@Builder +public class PotRequestDto { + + @NotBlank(message = "팟 이름은 필수입니다.") + private String potName; + + private LocalDate potStartDate; + + private LocalDate potEndDate; + + @NotBlank(message = "예상 기간은 필수입니다.") + private String potDuration; + + @NotBlank(message = "사용 언어는 필수입니다.") + private String potLan; + + private String potContent; + + @NotBlank(message = "팟 상태는 필수입니다.") + private String potStatus; + + private String potModeOfOperation; + + private String potSummary; + + private LocalDate recruitmentDeadline; + + private List recruitmentDetails; +} diff --git a/src/main/java/stackpot/stackpot/web/dto/PotResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/PotResponseDto.java new file mode 100644 index 00000000..727bb86f --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/PotResponseDto.java @@ -0,0 +1,27 @@ +package stackpot.stackpot.web.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import stackpot.stackpot.domain.Pot; + +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +@Builder +public class PotResponseDto { + private Long potId; + private String potName; + private LocalDate potStartDate; + private LocalDate potEndDate; + private String potDuration; + private String potLan; + private String potContent; + private String potStatus; + private String potModeOfOperation; + private String potSummary; + private LocalDate recruitmentDeadline; + private List recruitmentDetails; +} From 07471a34ca8708f064e3859e0e3027e219198f18 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Sun, 19 Jan 2025 03:04:16 +0900 Subject: [PATCH 27/76] =?UTF-8?q?[ADD]=20SecurityConfig=20JwtAuthenticatio?= =?UTF-8?q?nFilter=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/security/SecurityConfig.java | 23 +++++++++++++++---- .../service/JwtUserDetailsService.java | 4 ---- 2 files changed, 18 insertions(+), 9 deletions(-) delete mode 100644 src/main/java/stackpot/stackpot/service/JwtUserDetailsService.java diff --git a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java index ce5befcf..4e523deb 100644 --- a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java +++ b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java @@ -54,6 +54,7 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import stackpot.stackpot.repository.UserRepository.UserRepository; @EnableWebSecurity @@ -67,14 +68,26 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtTokenProvid .requestMatchers("/", "/home", "/signup", "/user/profile","/swagger-ui/**", "/v3/api-docs/**").permitAll() .anyRequest().authenticated() ) - .oauth2Login(oauth2 -> oauth2 - .loginPage("/login") - .successHandler(successHandler(jwtTokenProvider, userRepository)) // SuccessHandler 등록 - ) +// .oauth2Login(oauth2 -> oauth2 +// .loginPage("/login") +// .successHandler(successHandler(jwtTokenProvider, userRepository)) // SuccessHandler 등록 +// ) +// .csrf(csrf -> csrf.ignoringRequestMatchers("/signup")) // CSRF 예외 처리 +// .formLogin(form -> form +// .loginPage("/login").permitAll() +// ); +// +// return http.build(); .csrf(csrf -> csrf.ignoringRequestMatchers("/signup")) // CSRF 예외 처리 .formLogin(form -> form .loginPage("/login").permitAll() - ); + ) + .oauth2Login(oauth2 -> oauth2 + .loginPage("/login") + .successHandler(successHandler(jwtTokenProvider, userRepository)) + ) + // JWT 필터 추가 + .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); return http.build(); } diff --git a/src/main/java/stackpot/stackpot/service/JwtUserDetailsService.java b/src/main/java/stackpot/stackpot/service/JwtUserDetailsService.java deleted file mode 100644 index fc172b43..00000000 --- a/src/main/java/stackpot/stackpot/service/JwtUserDetailsService.java +++ /dev/null @@ -1,4 +0,0 @@ -package stackpot.stackpot.service; - -public class JwtUserDetailsService { -} From a1a3f19d1876ae81fa24ddfc382af8738a2c4002 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Sun, 19 Jan 2025 03:14:55 +0900 Subject: [PATCH 28/76] =?UTF-8?q?[ADD]=20domain=20user=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/stackpot/stackpot/domain/User.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main/java/stackpot/stackpot/domain/User.java b/src/main/java/stackpot/stackpot/domain/User.java index ca21cbd6..8dc78cf2 100644 --- a/src/main/java/stackpot/stackpot/domain/User.java +++ b/src/main/java/stackpot/stackpot/domain/User.java @@ -5,7 +5,6 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import stackpot.stackpot.domain.common.BaseEntity; -import stackpot.stackpot.domain.enums.Role; import java.util.Collection; @@ -15,7 +14,6 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class User extends BaseEntity implements UserDetails{ - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // Primary Key @@ -29,22 +27,19 @@ public class User extends BaseEntity implements UserDetails{ @Column(nullable = false, length = 255) private String snsKey; // SNS 키 - @Column(nullable = false, length = 255) + @Column(nullable = true, length = 255) private String nickname; // 닉네임 - @Column(nullable = false, length = 255) + @Column(nullable = true, length = 255) private String role; // 역할 @Column(nullable = true, length = 255) - private String kakaoId; // 카카오 아이디 - - @Column(nullable = false, length = 255) private String interest; // 관심사 @Column(nullable = true, columnDefinition = "TEXT") - private String introduction; // 한 줄 소개 + private String userIntroduction; // 한 줄 소개 - @Column(nullable = false) + @Column(nullable = true) private Integer userTemperature; // 유저 온도 @Column(nullable = false, unique = true) From d518230692ac46e6b8a811cb9480d419ab9da695 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Sun, 19 Jan 2025 03:17:01 +0900 Subject: [PATCH 29/76] =?UTF-8?q?[ADD]=20config=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stackpot/config/security/SecurityConfig.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java index 4e523deb..04a9efab 100644 --- a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java +++ b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java @@ -68,16 +68,6 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtTokenProvid .requestMatchers("/", "/home", "/signup", "/user/profile","/swagger-ui/**", "/v3/api-docs/**").permitAll() .anyRequest().authenticated() ) -// .oauth2Login(oauth2 -> oauth2 -// .loginPage("/login") -// .successHandler(successHandler(jwtTokenProvider, userRepository)) // SuccessHandler 등록 -// ) -// .csrf(csrf -> csrf.ignoringRequestMatchers("/signup")) // CSRF 예외 처리 -// .formLogin(form -> form -// .loginPage("/login").permitAll() -// ); -// -// return http.build(); .csrf(csrf -> csrf.ignoringRequestMatchers("/signup")) // CSRF 예외 처리 .formLogin(form -> form .loginPage("/login").permitAll() From 5b1f3e1d3fb34e8b96e13a0de04ec45b2bb103b3 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Tue, 21 Jan 2025 00:01:07 +0900 Subject: [PATCH 30/76] =?UTF-8?q?[ADD]=20access=20token=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 28 ++-- .../apiPayload/exception/ExceptionAdvice.java | 2 +- .../stackpot/config/SwaggerConfig.java | 3 +- .../security/CustomOAuth2UserService.java | 124 +++++++++--------- .../security/JwtAuthenticationFilter.java | 28 +++- .../config/security/JwtTokenProvider.java | 15 ++- .../config/security/SecurityConfig.java | 101 +++++++++----- .../java/stackpot/stackpot/domain/User.java | 4 +- .../web/controller/MemberViewController.java | 65 --------- .../web/controller/UserController.java | 16 ++- .../web/dto/TokenServiceResponse.java | 30 +++++ src/main/resources/templates/home.html | 17 --- src/main/resources/templates/login.html | 14 -- src/main/resources/templates/signup.html | 16 --- 14 files changed, 235 insertions(+), 228 deletions(-) delete mode 100644 src/main/java/stackpot/stackpot/web/controller/MemberViewController.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/TokenServiceResponse.java delete mode 100644 src/main/resources/templates/home.html delete mode 100644 src/main/resources/templates/login.html delete mode 100644 src/main/resources/templates/signup.html diff --git a/build.gradle b/build.gradle index 3824ce57..6744c379 100644 --- a/build.gradle +++ b/build.gradle @@ -24,31 +24,41 @@ repositories { } dependencies { + // Spring Boot Core implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' + + // Lombok compileOnly 'org.projectlombok:lombok' - runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' - implementation 'mysql:mysql-connector-java:8.0.33' - //spring security + // Database + runtimeOnly 'com.mysql:mysql-connector-j' + runtimeOnly 'mysql:mysql-connector-java:8.0.33' + + // Spring Security implementation 'org.springframework.boot:spring-boot-starter-security' testImplementation 'org.springframework.security:spring-security-test' - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + // Thymeleaf Security Integration implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:3.1.1.RELEASE' - //JWT + // Swagger/OpenAPI + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' + + // AWS Integration + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + + // JWT implementation 'io.jsonwebtoken:jjwt-api:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + // Test Dependencies + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { diff --git a/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java b/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java index 29765be8..781afea5 100644 --- a/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java +++ b/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java @@ -24,7 +24,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j -@RestControllerAdvice(annotations = {RestController.class}) +//@RestControllerAdvice(annotations = {RestController.class}) public class ExceptionAdvice extends ResponseEntityExceptionHandler { diff --git a/src/main/java/stackpot/stackpot/config/SwaggerConfig.java b/src/main/java/stackpot/stackpot/config/SwaggerConfig.java index 8f3c8055..a59267f2 100644 --- a/src/main/java/stackpot/stackpot/config/SwaggerConfig.java +++ b/src/main/java/stackpot/stackpot/config/SwaggerConfig.java @@ -20,6 +20,7 @@ public OpenAPI StackPotAPI() { .version("1.0.0"); String jwtSchemeName = "JWT TOKEN"; + // API 요청헤더에 인증정보 포함 SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName); // SecuritySchemes 등록 @@ -31,7 +32,7 @@ public OpenAPI StackPotAPI() { .bearerFormat("JWT")); return new OpenAPI() - .addServersItem(new Server().url("/api")) + .addServersItem(new Server().url("")) .info(info) .addSecurityItem(securityRequirement) .components(components); diff --git a/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java b/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java index d7bcede3..456bf287 100644 --- a/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java +++ b/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java @@ -1,5 +1,6 @@ package stackpot.stackpot.config.security; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; @@ -12,6 +13,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.UUID; @Service @RequiredArgsConstructor @@ -19,80 +21,80 @@ public class CustomOAuth2UserService extends DefaultOAuth2UserService { private final UserRepository userRepository; +// @Transactional // @Override // public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { -// OAuth2User oAuth2User = super.loadUser(userRequest); +// // 1. 유저 정보(attributes) 가져오기 +// Map oAuth2UserAttributes = super.loadUser(userRequest).getAttributes(); // -// // 사용자 정보 가져오기 -// Map attributes = oAuth2User.getAttributes(); -// Map kakaoAccount = (Map) attributes.get("kakao_account"); +// // 2. resistrationId 가져오기 (third-party id) +// String registrationId = userRequest.getClientRegistration().getRegistrationId(); // -// if (kakaoAccount == null) { -// throw new OAuth2AuthenticationException("Failed to retrieve kakao_account from attributes."); -// } +// // 3. userNameAttributeName 가져오기 +// String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails() +// .getUserInfoEndpoint().getUserNameAttributeName(); // -// // 이메일 가져오기 -// String email = (String) kakaoAccount.get("email"); -// if (email == null) { -// throw new OAuth2AuthenticationException("Email not found in kakao_account."); -// } -// // 사용자 정보 조회 (DB에 저장 여부 확인) -// userRepository.findByEmail(email).ifPresentOrElse( -// user -> System.out.println("Existing user found: " + email), -// () -> System.out.println("No user found. Redirecting to signup.") -// ); +// // 4. 유저 정보 dto 생성 +// OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfo.of(registrationId, oAuth2UserAttributes); // -// Map modifiedAttributes = new HashMap<>(attributes); -// modifiedAttributes.put("email", email); // 확실히 email 필드가 포함되도록 보장 +// // 5. 회원가입 및 로그인 +// User user = saveOrUpdateUser(oAuth2UserInfo); // -// System.out.println("modifiedAttributes : " + modifiedAttributes); -// -// if (!modifiedAttributes.containsKey("email")) { -// throw new IllegalStateException("Email is not present in modifiedAttributes!"); -// } -// // 사용자 정보를 DefaultOAuth2User로 반환 -// return new DefaultOAuth2User( -// oAuth2User.getAuthorities(), -// attributes, -// "email" // Principal로 사용할 필드 -// ); +// // 6. OAuth2User로 반환 +// return new PrincipalDetails(user, oAuth2UserAttributes, userNameAttributeName); // } -@Override -public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { - OAuth2User oAuth2User = super.loadUser(userRequest); + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User oAuth2User = super.loadUser(userRequest); + System.out.println("loadUser"); - // 사용자 정보 가져오기 - Map attributes = oAuth2User.getAttributes(); - Map kakaoAccount = (Map) attributes.get("kakao_account"); + // 사용자 정보 가져오기 + Map attributes = oAuth2User.getAttributes(); + Map kakaoAccount = (Map) attributes.get("kakao_account"); - if (kakaoAccount == null) { - throw new OAuth2AuthenticationException("Failed to retrieve kakao_account from attributes."); - } + if (kakaoAccount == null) { + throw new OAuth2AuthenticationException("Failed to retrieve kakao_account from attributes."); + } - // 이메일 가져오기 - String email = (String) kakaoAccount.get("email"); - if (email == null) { - throw new OAuth2AuthenticationException("Email not found in kakao_account."); - } + // 이메일 가져오기 + String email = (String) kakaoAccount.get("email"); + if (email == null) { + throw new OAuth2AuthenticationException("Email not found in kakao_account."); + } + + // 사용자 정보 확인 + userRepository.findByEmail(email).ifPresentOrElse( + user -> System.out.println("Existing user found: " + email), + () -> System.out.println("No user found. Redirecting to signup.") + ); - // 사용자 정보 확인 - userRepository.findByEmail(email).ifPresentOrElse( - user -> System.out.println("Existing user found: " + email), - () -> System.out.println("No user found. Redirecting to signup.") - ); + // attributes에 email 추가 + Map modifiedAttributes = new HashMap<>(attributes); + modifiedAttributes.put("email", email); - // attributes에 email 추가 - Map modifiedAttributes = new HashMap<>(attributes); - modifiedAttributes.put("email", email); + // 디버깅: modifiedAttributes 확인 + System.out.println("Final Modified Attributes: " + modifiedAttributes); - // 디버깅: modifiedAttributes 확인 - System.out.println("Final Modified Attributes: " + modifiedAttributes); + saveOrUpdateUser(email); - // DefaultOAuth2User 생성 - return new DefaultOAuth2User( - oAuth2User.getAuthorities(), - modifiedAttributes, - "email" // Principal로 사용할 필드 이름 - ); -} + + // DefaultOAuth2User 생성 + return new DefaultOAuth2User( + oAuth2User.getAuthorities(), + modifiedAttributes, + "email" // Principal로 사용할 필드 이름 + ); + } + + private void saveOrUpdateUser(String email) { + System.out.println("saveOrUpdateUser 실행"); + userRepository.findByEmail(email) + .orElseGet(() -> { + User user = User.builder() + .email(email) + .loginId(UUID.randomUUID().toString()) // 랜덤 로그인 ID 생성 + .build(); + return userRepository.save(user); // 저장된 사용자 반환 + }); + } } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/config/security/JwtAuthenticationFilter.java b/src/main/java/stackpot/stackpot/config/security/JwtAuthenticationFilter.java index ef33947a..2c74e7d6 100644 --- a/src/main/java/stackpot/stackpot/config/security/JwtAuthenticationFilter.java +++ b/src/main/java/stackpot/stackpot/config/security/JwtAuthenticationFilter.java @@ -9,7 +9,6 @@ import org.springframework.security.core.Authentication; import java.io.IOException; - public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; @@ -23,12 +22,28 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse throws ServletException, IOException { String token = resolveToken(request); - if (token != null && jwtTokenProvider.validateToken(token)) { - Authentication authentication = jwtTokenProvider.getAuthentication(token); - SecurityContextHolder.getContext().setAuthentication(authentication); + try { + if (token != null) { + System.out.println("Token found: " + token); + if (jwtTokenProvider.validateToken(token)) { + Authentication authentication = jwtTokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + System.out.println("Authentication set in SecurityContext: " + authentication.getName()); + } else { + System.out.println("Invalid or expired token."); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("Invalid or expired token."); + return; + } + } else { + System.out.println("No token found in the request."); + } + filterChain.doFilter(request, response); + } catch (Exception ex) { + System.out.println("Exception in JwtAuthenticationFilter: " + ex.getMessage()); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + response.getWriter().write("Internal server error occurred."); } - - filterChain.doFilter(request, response); } private String resolveToken(HttpServletRequest request) { @@ -36,6 +51,7 @@ private String resolveToken(HttpServletRequest request) { if (bearerToken != null && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } + System.out.println("Authorization header is missing or does not start with 'Bearer '."); return null; } } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java b/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java index a33a7d40..115181ba 100644 --- a/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java +++ b/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java @@ -8,6 +8,8 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Component; import org.springframework.security.core.Authentication; +import stackpot.stackpot.web.dto.TokenServiceResponse; + import java.security.Key; import java.util.Date; @@ -21,18 +23,27 @@ public class JwtTokenProvider { private final UserDetailsService userDetailsService; // JWT 생성 (이메일 포함) - public String createToken(String email) { + public TokenServiceResponse createToken(String email) { Claims claims = Jwts.claims().setSubject(email); Date now = new Date(); Date validity = new Date(now.getTime() + validityInMilliseconds); - return Jwts.builder() + String accessToken = Jwts.builder() + .setClaims(claims) + .setIssuedAt(now) + .setExpiration(validity) + .signWith(key) + .compact(); + + String refreshToken = Jwts.builder() .setClaims(claims) .setIssuedAt(now) .setExpiration(validity) .signWith(key) .compact(); +// return accessToken; + return TokenServiceResponse.of(accessToken, refreshToken); } public boolean validateToken(String token) { diff --git a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java index 04a9efab..c9d88676 100644 --- a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java +++ b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java @@ -43,12 +43,24 @@ // } //} +// if (userRepository.findByEmail(email).isPresent()) { +// // 이메일이 존재하면 홈으로 리다이렉트 +// response.sendRedirect("/home"); +// } else { +// // 이메일이 없으면 회원가입 페이지로 리다이렉트 +// response.sendRedirect("/signup"); + package stackpot.stackpot.config.security; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.user.OAuth2User; @@ -56,56 +68,79 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import stackpot.stackpot.repository.UserRepository.UserRepository; +import stackpot.stackpot.web.dto.TokenServiceResponse; + +import static org.springframework.security.config.Customizer.withDefaults; @EnableWebSecurity @Configuration +@RequiredArgsConstructor public class SecurityConfig { - @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtTokenProvider jwtTokenProvider, UserRepository userRepository) throws Exception { - http - .authorizeHttpRequests(auth -> auth - .requestMatchers("/", "/home", "/signup", "/user/profile","/swagger-ui/**", "/v3/api-docs/**").permitAll() - .anyRequest().authenticated() - ) - .csrf(csrf -> csrf.ignoringRequestMatchers("/signup")) // CSRF 예외 처리 - .formLogin(form -> form - .loginPage("/login").permitAll() - ) - .oauth2Login(oauth2 -> oauth2 - .loginPage("/login") - .successHandler(successHandler(jwtTokenProvider, userRepository)) - ) - // JWT 필터 추가 - .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); + private final CustomOAuth2UserService customOAuth2UserService; + private final CustomOAuth2UserService oAuth2UserService; - return http.build(); - } -// @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); + public WebSecurityCustomizer webSecurityCustomizer() { // security를 적용하지 않을 리소스 + return web -> web.ignoring() + // error endpoint를 열어줘야 함, favicon.ico 추가! + .requestMatchers("/error", "/favicon.ico"); } +@Bean +public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtTokenProvider jwtTokenProvider) throws Exception { + http.formLogin(AbstractHttpConfigurer::disable) + .httpBasic(AbstractHttpConfigurer::disable) + .csrf(AbstractHttpConfigurer::disable) + .cors(withDefaults()) + .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)) + .sessionManagement( + session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .oauth2Login( + oauth -> oauth.userInfoEndpoint(config -> config.userService(customOAuth2UserService)) + .successHandler(successHandler(jwtTokenProvider))) + .authorizeHttpRequests(request -> request + .requestMatchers("/", "/home", "/swagger-ui/**", "/v3/api-docs/**", "/v3/api-docs/**").permitAll() + .anyRequest().authenticated() + ) + .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); +// http +// .authorizeHttpRequests(auth -> auth +// .requestMatchers("/", "/home", "/swagger-ui/**", "/v3/api-docs/**").permitAll() +// .requestMatchers("/login/token").authenticated() +// .anyRequest().permitAll() +// ) +// .csrf(csrf -> csrf.ignoringRequestMatchers("/login/token")) +// .oauth2Login(oauth2 -> oauth2 +// .userInfoEndpoint().userService(customOAuth2UserService) +// .and() +// .successHandler(successHandler(jwtTokenProvider)) +// ) +// .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); + return http.build(); +} +// @Bean +// public PasswordEncoder passwordEncoder() { +// return new BCryptPasswordEncoder(); +// } @Bean - public AuthenticationSuccessHandler successHandler(JwtTokenProvider jwtTokenProvider, UserRepository userRepository) { - + public AuthenticationSuccessHandler successHandler(JwtTokenProvider jwtTokenProvider) { return (request, response, authentication) -> { OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); String email = (String) oAuth2User.getAttributes().get("email"); // JWT 토큰 생성 - String token = jwtTokenProvider.createToken(email); + TokenServiceResponse token = jwtTokenProvider.createToken(email); + System.out.println("STACKPOT TOKEN : " + token.getAccessToken()); - response.setHeader("Authorization", "Bearer " + token); + // 응답에 JWT 추가 + response.setHeader("Authorization", "Bearer " + token.getAccessToken()); + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); - if (userRepository.findByEmail(email).isPresent()) { - // 이메일이 존재하면 홈으로 리다이렉트 - response.sendRedirect("/home"); - } else { - // 이메일이 없으면 회원가입 페이지로 리다이렉트 - response.sendRedirect("/signup"); - } + // JSON 응답 반환 + response.getWriter().write("{\"accessTokendf\": \"" + token.getAccessToken() + "\"}"); + response.getWriter().flush(); }; } } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/domain/User.java b/src/main/java/stackpot/stackpot/domain/User.java index 8dc78cf2..c4f01f30 100644 --- a/src/main/java/stackpot/stackpot/domain/User.java +++ b/src/main/java/stackpot/stackpot/domain/User.java @@ -24,8 +24,8 @@ public class User extends BaseEntity implements UserDetails{ @Column(nullable = true, length = 12) private String userName; // 유저 카톡 설정 이름 - @Column(nullable = false, length = 255) - private String snsKey; // SNS 키 +// @Column(nullable = false, length = 255) +// private String snsKey; // SNS 키 @Column(nullable = true, length = 255) private String nickname; // 닉네임 diff --git a/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java b/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java deleted file mode 100644 index 30864f83..00000000 --- a/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java +++ /dev/null @@ -1,65 +0,0 @@ -package stackpot.stackpot.web.controller; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.RequestHeader; -import stackpot.stackpot.config.security.JwtTokenProvider; -import stackpot.stackpot.service.UserCommandService; -import stackpot.stackpot.web.dto.UserRequestDTO; - -@Controller -@RequiredArgsConstructor -public class MemberViewController { - - private final UserCommandService userCommandService; - private final JwtTokenProvider jwtTokenProvider; - - // 로그인 페이지 - @GetMapping("/login") - public String loginPage() { - return "login"; - } - - // 회원가입 페이지 랜더링 - @GetMapping("/signup") - public String signupPage() { - // 단순히 회원가입 페이지를 렌더링 - return "signup"; - } - - // 회원가입 처리 - // 미완성 - @PostMapping("/user/signup") - public String joinUser(@Valid @ModelAttribute("UserJoinDTO") UserRequestDTO.JoinDto request, - BindingResult bindingResult, - @RequestHeader(value = "Authorization", required = false) String token, - Model model) { - if (bindingResult.hasErrors()) { - return "signup"; // 유효성 검사 실패 시 다시 회원가입 페이지로 - } - - try { - if (token == null || !token.startsWith("Bearer ")) { - throw new IllegalArgumentException("Authorization header is missing or invalid."); - } - - // JWT에서 이메일 추출 - String email = jwtTokenProvider.getEmailFromToken(token.replace("Bearer ", "")); - request.setEmail(email); // DTO에 이메일 설정 - System.out.println("Extracted Email: " + email); - - - // 회원가입 처리 - userCommandService.joinUser(request); - return "redirect:/home"; // 성공 시 홈 페이지로 리다이렉트 - } catch (Exception e) { - model.addAttribute("error", e.getMessage()); - return "signup"; // 에러 발생 시 다시 회원가입 페이지로 - } - } -} diff --git a/src/main/java/stackpot/stackpot/web/controller/UserController.java b/src/main/java/stackpot/stackpot/web/controller/UserController.java index c924d018..df254d40 100644 --- a/src/main/java/stackpot/stackpot/web/controller/UserController.java +++ b/src/main/java/stackpot/stackpot/web/controller/UserController.java @@ -1,11 +1,25 @@ package stackpot.stackpot.web.controller; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Controller; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +@Slf4j @RestController @RequiredArgsConstructor public class UserController { + @Operation(summary = "토큰 test api") + @GetMapping("/login/token") + public ResponseEntity testEndpoint(Authentication authentication) { + if (authentication == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("User is not authenticated"); + } + return ResponseEntity.ok("Authenticated user: " + authentication.getName()); + } } diff --git a/src/main/java/stackpot/stackpot/web/dto/TokenServiceResponse.java b/src/main/java/stackpot/stackpot/web/dto/TokenServiceResponse.java new file mode 100644 index 00000000..76bbadf3 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/TokenServiceResponse.java @@ -0,0 +1,30 @@ +package stackpot.stackpot.web.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Getter; + +@Getter +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TokenServiceResponse { + + private final String accessToken; + private final String refreshToken; + + public TokenServiceResponse(String accessToken) { + this.accessToken = accessToken; + this.refreshToken = null; + } + + public TokenServiceResponse(String accessToken, String refreshToken) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } + + public static TokenServiceResponse of(final String accessToken, final String refreshToken) { + return new TokenServiceResponse(accessToken, refreshToken); + } + + public TokenServiceResponse withoutRefreshToken() { + return new TokenServiceResponse(this.accessToken); + } +} \ No newline at end of file diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html deleted file mode 100644 index 529b72c5..00000000 --- a/src/main/resources/templates/home.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - Home - - -

Welcome to Home Page!

-

- - - -
- -
- \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html deleted file mode 100644 index 2f69e8fa..00000000 --- a/src/main/resources/templates/login.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - Login - - -카카오로 로그인 - -

사용자 이름 또는 비밀번호가 잘못되었습니다.

-

로그아웃되었습니다.

- -

계정이 없나요? Sign up

- - \ No newline at end of file diff --git a/src/main/resources/templates/signup.html b/src/main/resources/templates/signup.html deleted file mode 100644 index 06f6c292..00000000 --- a/src/main/resources/templates/signup.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - 회원가입 - - - -

회원가입

-
-
- -
- - \ No newline at end of file From 2fd88895a512da33df8218abcf3b318d3323ca3b Mon Sep 17 00:00:00 2001 From: starday119 Date: Tue, 21 Jan 2025 00:06:14 +0900 Subject: [PATCH 31/76] =?UTF-8?q?[FEAT]:=20=ED=8C=9F=20crud?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stackpot/config/SwaggerConfig.java | 6 +- .../config/security/SecurityConfig.java | 77 ++++------- .../stackpot/config/security/WebConfig.java | 25 ++++ .../PotApplicationConverter.java | 13 ++ .../PotApplicationConverterImpl.java | 40 ++++++ .../stackpot/converter/PotConverter.java | 3 +- .../stackpot/converter/PotConverterImpl.java | 3 +- .../java/stackpot/stackpot/domain/Pot.java | 25 ++++ .../java/stackpot/stackpot/domain/User.java | 3 + .../PotApplicationRepository.java | 10 ++ .../PotRecruitmentDetailsRepository.java | 7 - .../PotRecruitmentDetailsRepository.java | 15 +++ .../{ => PotRepository}/PotRepository.java | 2 +- .../PotApplicationService.java | 12 ++ .../PotApplicationServiceImpl.java | 70 ++++++++++ .../stackpot/stackpot/service/PotService.java | 45 +------ .../stackpot/service/PotServiceImpl.java | 124 ++++++++++++++++++ .../web/controller/MemberViewController.java | 8 +- .../controller/PotApplicationController.java | 35 +++++ .../web/controller/PotController.java | 28 +++- .../web/dto/ApplicationRequestDto.java | 16 +++ .../web/dto/ApplicationResponseDto.java | 20 +++ 22 files changed, 472 insertions(+), 115 deletions(-) create mode 100644 src/main/java/stackpot/stackpot/config/security/WebConfig.java create mode 100644 src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverter.java create mode 100644 src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverterImpl.java create mode 100644 src/main/java/stackpot/stackpot/repository/PotApplicationRepository/PotApplicationRepository.java delete mode 100644 src/main/java/stackpot/stackpot/repository/PotRecruitmentDetailsRepository.java create mode 100644 src/main/java/stackpot/stackpot/repository/PotRepository/PotRecruitmentDetailsRepository.java rename src/main/java/stackpot/stackpot/repository/{ => PotRepository}/PotRepository.java (76%) create mode 100644 src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationService.java create mode 100644 src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java create mode 100644 src/main/java/stackpot/stackpot/service/PotServiceImpl.java create mode 100644 src/main/java/stackpot/stackpot/web/controller/PotApplicationController.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/ApplicationRequestDto.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/ApplicationResponseDto.java diff --git a/src/main/java/stackpot/stackpot/config/SwaggerConfig.java b/src/main/java/stackpot/stackpot/config/SwaggerConfig.java index 985672ee..df1c421f 100644 --- a/src/main/java/stackpot/stackpot/config/SwaggerConfig.java +++ b/src/main/java/stackpot/stackpot/config/SwaggerConfig.java @@ -32,11 +32,13 @@ public OpenAPI StackPotAPI() { .bearerFormat("JWT") .in(SecurityScheme.In.HEADER)); + + // 서버 URL 설정 return new OpenAPI() - .addServersItem(new Server().url("http://localhost:8080")) + .addServersItem(new Server().url("http://localhost:8080").description("Local server")) // 로컬 서버 + .addServersItem(new Server().url("https://api.stackpot.com").description("Production server")) // 프로덕션 서버 .info(info) .addSecurityItem(securityRequirement) .components(components); - } } diff --git a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java index 04a9efab..7a7c6075 100644 --- a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java +++ b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java @@ -1,47 +1,4 @@ -//package stackpot.stackpot.config.security; -// -//import org.springframework.context.annotation.Bean; -//import org.springframework.context.annotation.Configuration; -//import org.springframework.security.config.annotation.web.builders.HttpSecurity; -//import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -//import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -//import org.springframework.security.crypto.password.PasswordEncoder; -//import org.springframework.security.web.SecurityFilterChain; -// -//@EnableWebSecurity -//@Configuration -//public class SecurityConfig { -// -// @Bean -// public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { -// http -// .authorizeHttpRequests((requests) -> requests -// .requestMatchers("/", "/home", "/signup","/swagger-ui/**", "/v3/api-docs/**").permitAll() -// .anyRequest().authenticated() -// ) -// .formLogin((form) -> form -// .loginPage("/login") -// .defaultSuccessUrl("/home", true) -// .permitAll() -// ) -// .logout((logout) -> logout -// .logoutUrl("/logout") -// .logoutSuccessUrl("/login?logout") -// .permitAll() -// ) -// .oauth2Login(oauth2 -> oauth2 -// .loginPage("/login") -// .permitAll() -// ); -// -// return http.build(); -// } -// @Bean -// public PasswordEncoder passwordEncoder() { -// return new BCryptPasswordEncoder(); -// } -//} package stackpot.stackpot.config.security; @@ -59,29 +16,40 @@ @EnableWebSecurity @Configuration + public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtTokenProvider jwtTokenProvider, UserRepository userRepository) throws Exception { http .authorizeHttpRequests(auth -> auth - .requestMatchers("/", "/home", "/signup", "/user/profile","/swagger-ui/**", "/v3/api-docs/**").permitAll() + .requestMatchers( + "/", + "/home", + "/pots/**", + "/signup", + "/user/profile", + "/swagger-ui/**", + "/v3/api-docs/**" + ).permitAll() .anyRequest().authenticated() ) .csrf(csrf -> csrf.ignoringRequestMatchers("/signup")) // CSRF 예외 처리 - .formLogin(form -> form - .loginPage("/login").permitAll() - ) - .oauth2Login(oauth2 -> oauth2 - .loginPage("/login") - .successHandler(successHandler(jwtTokenProvider, userRepository)) - ) + .cors() // CORS 활성화 + .and() +// .formLogin(form -> form +// .loginPage("/login").permitAll() +// ) +// .oauth2Login(oauth2 -> oauth2 +// .loginPage("/login") +// .successHandler(successHandler(jwtTokenProvider, userRepository)) +// ) // JWT 필터 추가 .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); return http.build(); } -// + @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @@ -89,7 +57,6 @@ public PasswordEncoder passwordEncoder() { @Bean public AuthenticationSuccessHandler successHandler(JwtTokenProvider jwtTokenProvider, UserRepository userRepository) { - return (request, response, authentication) -> { OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); String email = (String) oAuth2User.getAttributes().get("email"); @@ -108,4 +75,6 @@ public AuthenticationSuccessHandler successHandler(JwtTokenProvider jwtTokenProv } }; } -} \ No newline at end of file + + +} diff --git a/src/main/java/stackpot/stackpot/config/security/WebConfig.java b/src/main/java/stackpot/stackpot/config/security/WebConfig.java new file mode 100644 index 00000000..ca4a84fe --- /dev/null +++ b/src/main/java/stackpot/stackpot/config/security/WebConfig.java @@ -0,0 +1,25 @@ +package stackpot.stackpot.config.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig { + + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") // 모든 엔드포인트에 대해 CORS 허용 + .allowedOrigins("http://localhost:3000", "https://stackpot.co.kr") // 허용할 도메인 + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 허용할 HTTP 메서드 + .allowedHeaders("Authorization", "Content-Type", "X-Requested-With") // 허용할 헤더 + .allowCredentials(true) // 인증 정보 포함 (쿠키, 헤더 등) + .maxAge(3600); // CORS 요청의 캐싱 시간 (1시간) + } + }; + } +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverter.java b/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverter.java new file mode 100644 index 00000000..1068cb43 --- /dev/null +++ b/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverter.java @@ -0,0 +1,13 @@ +package stackpot.stackpot.converter.PotApplicationConverter; + +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.mapping.PotApplication; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.web.dto.ApplicationRequestDto; +import stackpot.stackpot.web.dto.ApplicationResponseDto; + +public interface PotApplicationConverter { + PotApplication toEntity(ApplicationRequestDto dto, Pot pot, User user); + + ApplicationResponseDto toDto(PotApplication entity); +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverterImpl.java b/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverterImpl.java new file mode 100644 index 00000000..aa34a08b --- /dev/null +++ b/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverterImpl.java @@ -0,0 +1,40 @@ +package stackpot.stackpot.converter.PotApplicationConverter; + +import org.springframework.stereotype.Component; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.mapping.PotApplication; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.enums.ApplicationStatus; +import stackpot.stackpot.web.dto.ApplicationRequestDto; +import stackpot.stackpot.web.dto.ApplicationResponseDto; + +import java.time.LocalDateTime; + +@Component +public class PotApplicationConverterImpl implements PotApplicationConverter { + + @Override + public PotApplication toEntity(ApplicationRequestDto dto, Pot pot, User user) { + return PotApplication.builder() + .pot(pot) + .user(user) + .potRole(dto.getPotRole()) + .liked(dto.getLiked() != null ? dto.getLiked() : false) + .status(ApplicationStatus.PENDING) + .appliedAt(LocalDateTime.now()) + .build(); + } + + @Override + public ApplicationResponseDto toDto(PotApplication entity) { + return ApplicationResponseDto.builder() + .applicationId(entity.getApplicationId()) + .potRole(entity.getPotRole()) + .liked(entity.getLiked()) + .status(entity.getStatus().name()) + .appliedAt(entity.getAppliedAt()) + .potId(entity.getPot().getPotId()) + .userId(entity.getUser().getUserId()) + .build(); + } +} diff --git a/src/main/java/stackpot/stackpot/converter/PotConverter.java b/src/main/java/stackpot/stackpot/converter/PotConverter.java index 2aa4b1ba..55acd8e2 100644 --- a/src/main/java/stackpot/stackpot/converter/PotConverter.java +++ b/src/main/java/stackpot/stackpot/converter/PotConverter.java @@ -2,13 +2,14 @@ import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.PotRecruitmentDetails; +import stackpot.stackpot.domain.User; import stackpot.stackpot.web.dto.PotRequestDto; import stackpot.stackpot.web.dto.PotResponseDto; import java.util.List; public interface PotConverter { - Pot toEntity(PotRequestDto dto); + Pot toEntity(PotRequestDto dto, User user); PotResponseDto toDto(Pot entity, List recruitmentDetails); } diff --git a/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java b/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java index dfe525c1..21dd982f 100644 --- a/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java @@ -3,6 +3,7 @@ import org.springframework.stereotype.Component; import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.PotRecruitmentDetails; +import stackpot.stackpot.domain.User; import stackpot.stackpot.domain.enums.PotModeOfOperation; import stackpot.stackpot.web.dto.PotRequestDto; import stackpot.stackpot.web.dto.PotResponseDto; @@ -15,7 +16,7 @@ public class PotConverterImpl implements PotConverter { @Override - public Pot toEntity(PotRequestDto dto) { + public Pot toEntity(PotRequestDto dto, User user) { return Pot.builder() .user(user) // 사용자 정보 설정 .potName(dto.getPotName()) diff --git a/src/main/java/stackpot/stackpot/domain/Pot.java b/src/main/java/stackpot/stackpot/domain/Pot.java index b2e80050..ce89576c 100644 --- a/src/main/java/stackpot/stackpot/domain/Pot.java +++ b/src/main/java/stackpot/stackpot/domain/Pot.java @@ -6,6 +6,7 @@ import stackpot.stackpot.domain.enums.PotModeOfOperation; import java.time.LocalDate; +import java.util.Map; @Entity @Getter @@ -19,10 +20,12 @@ public class Pot extends BaseEntity { @Column(nullable = false) private Long potId; + @Setter @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; + @Column(nullable = false, length = 255) private String potName; @@ -53,4 +56,26 @@ public class Pot extends BaseEntity { @Column(nullable = false) private LocalDate recruitmentDeadline; + public void updateFields(Map updates) { + updates.forEach((key, value) -> { + if (value != null) { + switch (key) { + case "potName" -> this.potName = (String) value; + case "potStartDate" -> this.potStartDate = (LocalDate) value; + case "potEndDate" -> this.potEndDate = (LocalDate) value; + case "potDuration" -> this.potDuration = (String) value; + case "potLan" -> this.potLan = (String) value; + case "potContent" -> this.potContent = (String) value; + case "potStatus" -> this.potStatus = (String) value; + case "potModeOfOperation" -> this.potModeOfOperation = PotModeOfOperation.valueOf((String) value); + case "potSummary" -> this.potSummary = (String) value; + case "recruitmentDeadline" -> this.recruitmentDeadline = (LocalDate) value; + } + } + }); + } + + + + } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/domain/User.java b/src/main/java/stackpot/stackpot/domain/User.java index 8dc78cf2..940a711e 100644 --- a/src/main/java/stackpot/stackpot/domain/User.java +++ b/src/main/java/stackpot/stackpot/domain/User.java @@ -59,4 +59,7 @@ public String getPassword() { public String getUsername() { return email; // 사용자 식별자로 이메일을 사용 } + public Long getUserId() { + return id; + } } diff --git a/src/main/java/stackpot/stackpot/repository/PotApplicationRepository/PotApplicationRepository.java b/src/main/java/stackpot/stackpot/repository/PotApplicationRepository/PotApplicationRepository.java new file mode 100644 index 00000000..c7f8121d --- /dev/null +++ b/src/main/java/stackpot/stackpot/repository/PotApplicationRepository/PotApplicationRepository.java @@ -0,0 +1,10 @@ +package stackpot.stackpot.repository.PotApplicationRepository; + +import org.springframework.data.jpa.repository.JpaRepository; +import stackpot.stackpot.domain.mapping.PotApplication; + +import java.util.List; + +public interface PotApplicationRepository extends JpaRepository { + List findByPot_PotId(Long potId); +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/repository/PotRecruitmentDetailsRepository.java b/src/main/java/stackpot/stackpot/repository/PotRecruitmentDetailsRepository.java deleted file mode 100644 index 9ed08ad5..00000000 --- a/src/main/java/stackpot/stackpot/repository/PotRecruitmentDetailsRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package stackpot.stackpot.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import stackpot.stackpot.domain.PotRecruitmentDetails; - -public interface PotRecruitmentDetailsRepository extends JpaRepository { -} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/repository/PotRepository/PotRecruitmentDetailsRepository.java b/src/main/java/stackpot/stackpot/repository/PotRepository/PotRecruitmentDetailsRepository.java new file mode 100644 index 00000000..d3accf5d --- /dev/null +++ b/src/main/java/stackpot/stackpot/repository/PotRepository/PotRecruitmentDetailsRepository.java @@ -0,0 +1,15 @@ +package stackpot.stackpot.repository.PotRepository; + +import jakarta.transaction.Transactional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import stackpot.stackpot.domain.PotRecruitmentDetails; + +public interface PotRecruitmentDetailsRepository extends JpaRepository { + @Modifying + @Transactional + @Query("DELETE FROM PotRecruitmentDetails r WHERE r.pot.potId = :potId") + void deleteByPot_PotId(@Param("potId") Long potId); +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/repository/PotRepository.java b/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java similarity index 76% rename from src/main/java/stackpot/stackpot/repository/PotRepository.java rename to src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java index b29a5c62..b90e83b5 100644 --- a/src/main/java/stackpot/stackpot/repository/PotRepository.java +++ b/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java @@ -1,4 +1,4 @@ -package stackpot.stackpot.repository; +package stackpot.stackpot.repository.PotRepository; diff --git a/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationService.java b/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationService.java new file mode 100644 index 00000000..708de141 --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationService.java @@ -0,0 +1,12 @@ +package stackpot.stackpot.service.PotApplicationService; + +import stackpot.stackpot.web.dto.ApplicationRequestDto; +import stackpot.stackpot.web.dto.ApplicationResponseDto; + +import java.util.List; + +public interface PotApplicationService { + ApplicationResponseDto applyToPot(String token, Long potId, ApplicationRequestDto dto); + + List getApplicationsByPot(Long potId); +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java new file mode 100644 index 00000000..8c3161ba --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java @@ -0,0 +1,70 @@ +package stackpot.stackpot.service.PotApplicationService; + +import org.springframework.transaction.annotation.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import stackpot.stackpot.converter.PotApplicationConverter.PotApplicationConverter; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.enums.ApplicationStatus; +import stackpot.stackpot.domain.mapping.PotApplication; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.repository.PotApplicationRepository.PotApplicationRepository; +import stackpot.stackpot.repository.PotRepository.PotRepository; +import stackpot.stackpot.repository.UserRepository.UserRepository; +import stackpot.stackpot.config.security.JwtTokenProvider; +import stackpot.stackpot.web.dto.ApplicationRequestDto; +import stackpot.stackpot.web.dto.ApplicationResponseDto; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class PotApplicationServiceImpl implements PotApplicationService { + + private final PotApplicationRepository potApplicationRepository; + private final PotRepository potRepository; + private final UserRepository userRepository; + private final PotApplicationConverter potApplicationConverter; + private final JwtTokenProvider jwtTokenProvider; + + @Override + @Transactional + public ApplicationResponseDto applyToPot(String token, Long potId, ApplicationRequestDto dto) { + // 토큰에서 사용자 이메일 추출 + String email = jwtTokenProvider.getEmailFromToken(token); + + // 이메일로 사용자 조회 + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + + // 팟 조회 + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new IllegalArgumentException("해당 팟을 찾을 수 없습니다.")); + + // 중복 신청 방지 + boolean alreadyApplied = potApplicationRepository.existsByUserIdAndPotId(user.getId(), potId); + if (alreadyApplied) { + throw new IllegalStateException("이미 해당 팟에 지원하셨습니다."); + } + + // `PENDING` 상태로 지원 엔티티 생성 + PotApplication application = potApplicationConverter.toEntity(dto, pot, user); + application.setApplicationStatus(ApplicationStatus.PENDING); // 상태를 명시적으로 설정 + + // 지원 정보 저장 + PotApplication savedApplication = potApplicationRepository.save(application); + + // DTO로 변환하여 반환 + return potApplicationConverter.toDto(savedApplication); + } + + @Override + @Transactional(readOnly = true) + public List getApplicationsByPot(Long potId) { + List applications = potApplicationRepository.findByPot_PotId(potId); + return applications.stream() + .map(potApplicationConverter::toDto) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/stackpot/stackpot/service/PotService.java b/src/main/java/stackpot/stackpot/service/PotService.java index ec633e1b..81fa6bf1 100644 --- a/src/main/java/stackpot/stackpot/service/PotService.java +++ b/src/main/java/stackpot/stackpot/service/PotService.java @@ -1,48 +1,11 @@ -// Service package stackpot.stackpot.service; -import jakarta.transaction.Transactional; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import stackpot.stackpot.converter.PotConverter; -import stackpot.stackpot.domain.Pot; -import stackpot.stackpot.domain.PotRecruitmentDetails; -import stackpot.stackpot.domain.User; -import stackpot.stackpot.repository.PotRecruitmentDetailsRepository; -import stackpot.stackpot.repository.PotRepository; import stackpot.stackpot.web.dto.PotRequestDto; import stackpot.stackpot.web.dto.PotResponseDto; -import stackpot.stackpot.web.dto.PotRecruitmentRequestDto; -import java.util.List; -import java.util.stream.Collectors; +public interface PotService { + PotResponseDto createPotWithRecruitments(String token, PotRequestDto requestDto); + PotResponseDto updatePotWithRecruitments(String token, Long potId, PotRequestDto requestDto); -@Service -@RequiredArgsConstructor -public class PotService { - - private final PotRepository potRepository; - private final PotRecruitmentDetailsRepository recruitmentDetailsRepository; - private final PotConverter potConverter; - - @Transactional - public PotResponseDto createPotWithRecruitments(PotRequestDto requestDto, User user) { - // Create Pot entity - Pot pot = potConverter.toEntity(requestDto, user); - Pot savedPot = potRepository.save(pot); - - // Create and save recruitment details - List recruitmentDetails = requestDto.getRecruitmentDetails().stream() - .map(recruitmentDto -> PotRecruitmentDetails.builder() - .recruitmentRole(recruitmentDto.getRecruitmentRole()) - .recruitmentCount(recruitmentDto.getRecruitmentCount()) - .pot(savedPot) - .build()) - .collect(Collectors.toList()); - - recruitmentDetailsRepository.saveAll(recruitmentDetails); - - // Convert and return response DTO - return potConverter.toDto(savedPot, recruitmentDetails); - } + void deletePot(String token, Long potId); } diff --git a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java new file mode 100644 index 00000000..b1eb3a6b --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java @@ -0,0 +1,124 @@ +// Service +package stackpot.stackpot.service; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import stackpot.stackpot.converter.PotConverter; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.PotRecruitmentDetails; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.repository.PotRepository.PotRecruitmentDetailsRepository; +import stackpot.stackpot.repository.PotRepository.PotRepository; +import stackpot.stackpot.repository.UserRepository.UserRepository; +import stackpot.stackpot.service.PotService; +import stackpot.stackpot.web.dto.PotRequestDto; +import stackpot.stackpot.web.dto.PotResponseDto; +import stackpot.stackpot.config.security.JwtTokenProvider; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class PotServiceImpl implements PotService { + + private final PotRepository potRepository; + private final PotRecruitmentDetailsRepository recruitmentDetailsRepository; + private final PotConverter potConverter; + private final JwtTokenProvider jwtTokenProvider; + private final UserRepository userRepository; + + @Override + @Transactional + public PotResponseDto createPotWithRecruitments(String token, PotRequestDto requestDto) { + // JWT에서 이메일 추출 + String email = jwtTokenProvider.getEmailFromToken(token); + + // 이메일로 사용자 로드 + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + + // Pot 엔티티 생성 및 사용자 설정 + Pot pot = potConverter.toEntity(requestDto, user); + Pot savedPot = potRepository.save(pot); + + // RecruitmentDetails 저장 로직 + List recruitmentDetails = requestDto.getRecruitmentDetails().stream() + .map(recruitmentDto -> PotRecruitmentDetails.builder() + .recruitmentRole(recruitmentDto.getRecruitmentRole()) + .recruitmentCount(recruitmentDto.getRecruitmentCount()) + .pot(savedPot) + .build()) + .collect(Collectors.toList()); + + recruitmentDetailsRepository.saveAll(recruitmentDetails); + + // Convert and return response DTO + return potConverter.toDto(savedPot, recruitmentDetails); + + } + @Override + @Transactional + public PotResponseDto updatePotWithRecruitments(String token, Long potId, PotRequestDto requestDto) { + String email = jwtTokenProvider.getEmailFromToken(token); + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new IllegalArgumentException("해당 팟을 찾을 수 없습니다.")); + + if (!pot.getUser().equals(user)) { + throw new IllegalArgumentException("수정 권한이 없습니다."); + } + + // 업데이트 로직 + Map updates = new HashMap<>(); + updates.put("potName", requestDto.getPotName()); + updates.put("potStartDate", requestDto.getPotStartDate()); + updates.put("potEndDate", requestDto.getPotEndDate()); + updates.put("potDuration", requestDto.getPotDuration()); + updates.put("potLan", requestDto.getPotLan()); + updates.put("potContent", requestDto.getPotContent()); + updates.put("potStatus", requestDto.getPotStatus()); + updates.put("potModeOfOperation", requestDto.getPotModeOfOperation()); + updates.put("potSummary", requestDto.getPotSummary()); + updates.put("recruitmentDeadline", requestDto.getRecruitmentDeadline()); + + // 업데이트 메서드 호출 + pot.updateFields(updates); + + // 기존 모집 정보 삭제 및 새로운 모집 정보 추가 + recruitmentDetailsRepository.deleteByPot_PotId(potId); + List recruitmentDetails = requestDto.getRecruitmentDetails().stream() + .map(recruitmentDto -> PotRecruitmentDetails.builder() + .recruitmentRole(recruitmentDto.getRecruitmentRole()) + .recruitmentCount(recruitmentDto.getRecruitmentCount()) + .pot(pot) + .build()) + .collect(Collectors.toList()); + recruitmentDetailsRepository.saveAll(recruitmentDetails); + + return potConverter.toDto(pot, recruitmentDetails); + } + + @Override + @Transactional + public void deletePot(String token, Long potId) { + String email = jwtTokenProvider.getEmailFromToken(token); + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new IllegalArgumentException("해당 팟을 찾을 수 없습니다.")); + + if (!pot.getUser().equals(user)) { + throw new IllegalArgumentException("삭제 권한이 없습니다."); + } + + recruitmentDetailsRepository.deleteByPot_PotId(potId); + potRepository.delete(pot); + } +} diff --git a/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java b/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java index 30864f83..c26dfd11 100644 --- a/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java +++ b/src/main/java/stackpot/stackpot/web/controller/MemberViewController.java @@ -20,10 +20,10 @@ public class MemberViewController { private final JwtTokenProvider jwtTokenProvider; // 로그인 페이지 - @GetMapping("/login") - public String loginPage() { - return "login"; - } +// @GetMapping("/login") +// public String loginPage() { +// return "login"; +// } // 회원가입 페이지 랜더링 @GetMapping("/signup") diff --git a/src/main/java/stackpot/stackpot/web/controller/PotApplicationController.java b/src/main/java/stackpot/stackpot/web/controller/PotApplicationController.java new file mode 100644 index 00000000..2085f9c2 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/controller/PotApplicationController.java @@ -0,0 +1,35 @@ +package stackpot.stackpot.web.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stackpot.stackpot.service.PotApplicationService.PotApplicationService; +import stackpot.stackpot.web.dto.ApplicationRequestDto; +import stackpot.stackpot.web.dto.ApplicationResponseDto; + +import java.util.List; + +@RestController +@RequestMapping("/pots/{pot_id}/applications") +@RequiredArgsConstructor +public class PotApplicationController { + + private final PotApplicationService potApplicationService; + + @PostMapping + public ResponseEntity applyToPot( + @RequestHeader("Authorization") String token, + @PathVariable("pot_id") Long potId, // PathVariable 이름 변경 + @RequestBody @Valid ApplicationRequestDto requestDto) { + String parsedToken = token.replace("Bearer ", ""); + ApplicationResponseDto responseDto = potApplicationService.applyToPot(parsedToken, potId, requestDto); + return ResponseEntity.ok(responseDto); + } + + @GetMapping + public ResponseEntity> getApplications(@PathVariable("pot_id") Long potId) { + List applications = potApplicationService.getApplicationsByPot(potId); + return ResponseEntity.ok(applications); + } +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/controller/PotController.java b/src/main/java/stackpot/stackpot/web/controller/PotController.java index 84555b34..eab6af13 100644 --- a/src/main/java/stackpot/stackpot/web/controller/PotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/PotController.java @@ -4,7 +4,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import stackpot.stackpot.service.PotService; +import stackpot.stackpot.service.PotServiceImpl; import stackpot.stackpot.web.dto.PotRequestDto; import stackpot.stackpot.web.dto.PotResponseDto; @@ -13,11 +13,31 @@ @RequiredArgsConstructor public class PotController { - private final PotService potService; + private final PotServiceImpl potService; @PostMapping - public ResponseEntity createPot(@RequestBody @Valid PotRequestDto requestDto) { - PotResponseDto responseDto = potService.createPotWithRecruitments(requestDto); + public ResponseEntity createPot( + @RequestHeader(name = "Authorization", required = true) String token, + @RequestBody @Valid PotRequestDto requestDto) { + PotResponseDto responseDto = potService.createPotWithRecruitments(token, requestDto); return ResponseEntity.ok(responseDto); } + @PatchMapping("/{pot_id}") + public ResponseEntity updatePot( + @RequestHeader("Authorization") String token, + @PathVariable("pot_id") Long potId, // @PathVariable에 매핑된 이름을 명시적으로 설정 + @RequestBody @Valid PotRequestDto requestDto) { + PotResponseDto responseDto = potService.updatePotWithRecruitments(token, potId, requestDto); + return ResponseEntity.ok(responseDto); + } + + @DeleteMapping("/{pot_id}") + public ResponseEntity deletePot( + @RequestHeader("Authorization") String token, + @PathVariable("pot_id") Long potId) { // 동일하게 이름 매핑 + potService.deletePot(token, potId); + return ResponseEntity.noContent().build(); + } + + } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/dto/ApplicationRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/ApplicationRequestDto.java new file mode 100644 index 00000000..16402e74 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/ApplicationRequestDto.java @@ -0,0 +1,16 @@ +package stackpot.stackpot.web.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.*; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ApplicationRequestDto { + @NotBlank(message = "팟 역할은 필수입니다.") + private String potRole; + + private Boolean liked; +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/dto/ApplicationResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/ApplicationResponseDto.java new file mode 100644 index 00000000..c86c5649 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/ApplicationResponseDto.java @@ -0,0 +1,20 @@ +package stackpot.stackpot.web.dto; + +import lombok.*; + +import java.time.LocalDateTime; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ApplicationResponseDto { + private Long applicationId; + private String potRole; + private Boolean liked; + private String status; + private LocalDateTime appliedAt; + private Long potId; + private Long userId; +} From f0762763c53881ffb7ad945fa8f2207c305adf03 Mon Sep 17 00:00:00 2001 From: starday119 Date: Tue, 21 Jan 2025 02:33:58 +0900 Subject: [PATCH 32/76] [Feat] Pot Cud API --- .../stackpot/converter/PotConverterImpl.java | 24 ++++++------ .../stackpot/service/PotServiceImpl.java | 39 +++++++++++++------ .../web/controller/PotController.java | 27 +++++++++---- src/main/resources/application.yml | 2 +- 4 files changed, 60 insertions(+), 32 deletions(-) diff --git a/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java b/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java index 21dd982f..e9385566 100644 --- a/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java @@ -16,19 +16,19 @@ public class PotConverterImpl implements PotConverter { @Override - public Pot toEntity(PotRequestDto dto, User user) { + public Pot toEntity(PotRequestDto requestDto, User user) { return Pot.builder() - .user(user) // 사용자 정보 설정 - .potName(dto.getPotName()) - .potStartDate(dto.getPotStartDate()) - .potEndDate(dto.getPotEndDate()) - .potDuration(dto.getPotDuration()) - .potLan(dto.getPotLan()) - .potContent(dto.getPotContent()) - .potStatus(dto.getPotStatus()) - .potModeOfOperation(PotModeOfOperation.valueOf(dto.getPotModeOfOperation())) - .potSummary(dto.getPotSummary()) - .recruitmentDeadline(dto.getRecruitmentDeadline()) + .potName(requestDto.getPotName()) + .potStartDate(requestDto.getPotStartDate()) + .potEndDate(requestDto.getPotEndDate()) + .potDuration(requestDto.getPotDuration()) + .potLan(requestDto.getPotLan()) + .potContent(requestDto.getPotContent()) + .potStatus(requestDto.getPotStatus()) + .potModeOfOperation(PotModeOfOperation.valueOf(requestDto.getPotModeOfOperation())) + .potSummary(requestDto.getPotSummary()) + .recruitmentDeadline(requestDto.getRecruitmentDeadline()) + .user(user) // 사용자 설정 .build(); } diff --git a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java index b1eb3a6b..dbdeab57 100644 --- a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java @@ -37,14 +37,15 @@ public PotResponseDto createPotWithRecruitments(String token, PotRequestDto requ // JWT에서 이메일 추출 String email = jwtTokenProvider.getEmailFromToken(token); - // 이메일로 사용자 로드 + // 2. 이메일로 사용자 로드 User user = userRepository.findByEmail(email) .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); - // Pot 엔티티 생성 및 사용자 설정 + // 3. Pot 엔티티 생성 및 사용자 설정 Pot pot = potConverter.toEntity(requestDto, user); + pot.setUser(user); // 사용자 정보 설정 + // 4. Pot 저장 Pot savedPot = potRepository.save(pot); - // RecruitmentDetails 저장 로직 List recruitmentDetails = requestDto.getRecruitmentDetails().stream() .map(recruitmentDto -> PotRecruitmentDetails.builder() @@ -60,21 +61,28 @@ public PotResponseDto createPotWithRecruitments(String token, PotRequestDto requ return potConverter.toDto(savedPot, recruitmentDetails); } - @Override @Transactional public PotResponseDto updatePotWithRecruitments(String token, Long potId, PotRequestDto requestDto) { + // JWT에서 사용자 이메일 추출 String email = jwtTokenProvider.getEmailFromToken(token); + + // 이메일로 사용자 조회 User user = userRepository.findByEmail(email) .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + // 팟 조회 Pot pot = potRepository.findById(potId) - .orElseThrow(() -> new IllegalArgumentException("해당 팟을 찾을 수 없습니다.")); + .orElseThrow(() -> new IllegalArgumentException("수정할 팟을 찾을 수 없습니다.")); + // 권한 확인 if (!pot.getUser().equals(user)) { - throw new IllegalArgumentException("수정 권한이 없습니다."); + throw new IllegalArgumentException("해당 팟을 수정할 권한이 없습니다."); } - // 업데이트 로직 + // 기존 모집 정보 삭제 + recruitmentDetailsRepository.deleteByPot_PotId(potId); + + // 새로운 정보로 업데이트 Map updates = new HashMap<>(); updates.put("potName", requestDto.getPotName()); updates.put("potStartDate", requestDto.getPotStartDate()); @@ -86,12 +94,9 @@ public PotResponseDto updatePotWithRecruitments(String token, Long potId, PotReq updates.put("potModeOfOperation", requestDto.getPotModeOfOperation()); updates.put("potSummary", requestDto.getPotSummary()); updates.put("recruitmentDeadline", requestDto.getRecruitmentDeadline()); - - // 업데이트 메서드 호출 pot.updateFields(updates); - // 기존 모집 정보 삭제 및 새로운 모집 정보 추가 - recruitmentDetailsRepository.deleteByPot_PotId(potId); + // 새로운 모집 정보 추가 List recruitmentDetails = requestDto.getRecruitmentDetails().stream() .map(recruitmentDto -> PotRecruitmentDetails.builder() .recruitmentRole(recruitmentDto.getRecruitmentRole()) @@ -101,24 +106,34 @@ public PotResponseDto updatePotWithRecruitments(String token, Long potId, PotReq .collect(Collectors.toList()); recruitmentDetailsRepository.saveAll(recruitmentDetails); + // 수정된 데이터 반환 return potConverter.toDto(pot, recruitmentDetails); } + @Override @Transactional public void deletePot(String token, Long potId) { + // JWT에서 이메일 추출 String email = jwtTokenProvider.getEmailFromToken(token); + + // 이메일로 사용자 로드 User user = userRepository.findByEmail(email) .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + // 팟 로드 Pot pot = potRepository.findById(potId) - .orElseThrow(() -> new IllegalArgumentException("해당 팟을 찾을 수 없습니다.")); + .orElseThrow(() -> new IllegalArgumentException("삭제할 팟이 존재하지 않습니다.")); + // 사용자가 팟의 생성자인지 확인 if (!pot.getUser().equals(user)) { throw new IllegalArgumentException("삭제 권한이 없습니다."); } + // 연관된 모집 정보 삭제 recruitmentDetailsRepository.deleteByPot_PotId(potId); + + // 팟 삭제 potRepository.delete(pot); } } diff --git a/src/main/java/stackpot/stackpot/web/controller/PotController.java b/src/main/java/stackpot/stackpot/web/controller/PotController.java index eab6af13..a928cc6c 100644 --- a/src/main/java/stackpot/stackpot/web/controller/PotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/PotController.java @@ -17,25 +17,38 @@ public class PotController { @PostMapping public ResponseEntity createPot( - @RequestHeader(name = "Authorization", required = true) String token, + @RequestHeader("Authorization") String token, @RequestBody @Valid PotRequestDto requestDto) { - PotResponseDto responseDto = potService.createPotWithRecruitments(token, requestDto); + // Bearer 제거 후 토큰 전달 + String parsedToken = token.replace("Bearer ", ""); + PotResponseDto responseDto = potService.createPotWithRecruitments(parsedToken, requestDto); return ResponseEntity.ok(responseDto); } @PatchMapping("/{pot_id}") public ResponseEntity updatePot( @RequestHeader("Authorization") String token, - @PathVariable("pot_id") Long potId, // @PathVariable에 매핑된 이름을 명시적으로 설정 - @RequestBody @Valid PotRequestDto requestDto) { - PotResponseDto responseDto = potService.updatePotWithRecruitments(token, potId, requestDto); - return ResponseEntity.ok(responseDto); + @PathVariable("pot_id") Long potId, // 동일하게 이름 매핑 + @RequestBody @Valid PotRequestDto requestDto) { // 요청 DTO 검증 + // Bearer 제거 + String parsedToken = token.replace("Bearer ", ""); + + // 팟 수정 로직 호출 + PotResponseDto responseDto = potService.updatePotWithRecruitments(parsedToken, potId, requestDto); + + return ResponseEntity.ok(responseDto); // 수정된 팟 정보 반환 } + @DeleteMapping("/{pot_id}") public ResponseEntity deletePot( @RequestHeader("Authorization") String token, @PathVariable("pot_id") Long potId) { // 동일하게 이름 매핑 - potService.deletePot(token, potId); + // Bearer 제거 + String parsedToken = token.replace("Bearer ", ""); + + // 팟 삭제 로직 호출 + potService.deletePot(parsedToken, potId); + return ResponseEntity.noContent().build(); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 131b1557..7dc90ae0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -15,7 +15,7 @@ spring: format_sql: true use_sql_comments: true hbm2ddl: - auto: create + auto: update default_batch_fetch_size: 1000 cloud: aws: From 3d803213609d5dc36e76e73d28fcbd36f8739f83 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Tue, 21 Jan 2025 12:48:28 +0900 Subject: [PATCH 33/76] =?UTF-8?q?[ADD]=20token=20secret=20key=EC=A7=80?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/security/JwtTokenProvider.java | 16 ++--- .../config/security/SecurityConfig.java | 69 ------------------- src/main/resources/application.yml | 4 +- 3 files changed, 11 insertions(+), 78 deletions(-) diff --git a/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java b/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java index 115181ba..453e338e 100644 --- a/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java +++ b/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java @@ -3,6 +3,7 @@ import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -16,9 +17,9 @@ @RequiredArgsConstructor @Component public class JwtTokenProvider { - - // key 하드코딩 되어 있음 수정필요. - private final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256); + + @Value("${jwt.secret}") + private String secretKey; private final long validityInMilliseconds = 3600000; // 1시간 private final UserDetailsService userDetailsService; @@ -33,22 +34,21 @@ public TokenServiceResponse createToken(String email) { .setClaims(claims) .setIssuedAt(now) .setExpiration(validity) - .signWith(key) + .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); String refreshToken = Jwts.builder() .setClaims(claims) .setIssuedAt(now) .setExpiration(validity) - .signWith(key) + .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); -// return accessToken; return TokenServiceResponse.of(accessToken, refreshToken); } public boolean validateToken(String token) { try { - Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); + Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token); return true; } catch (JwtException | IllegalArgumentException e) { return false; @@ -57,7 +57,7 @@ public boolean validateToken(String token) { public String getEmailFromToken(String token) { return Jwts.parserBuilder() - .setSigningKey(key) + .setSigningKey(secretKey) .build() .parseClaimsJws(token) .getBody() diff --git a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java index c9d88676..3a0a1c1f 100644 --- a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java +++ b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java @@ -1,55 +1,3 @@ - -//package stackpot.stackpot.config.security; -// -//import org.springframework.context.annotation.Bean; -//import org.springframework.context.annotation.Configuration; -//import org.springframework.security.config.annotation.web.builders.HttpSecurity; -//import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -//import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -//import org.springframework.security.crypto.password.PasswordEncoder; -//import org.springframework.security.web.SecurityFilterChain; -// -//@EnableWebSecurity -//@Configuration -//public class SecurityConfig { -// -// @Bean -// public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { -// http -// .authorizeHttpRequests((requests) -> requests -// .requestMatchers("/", "/home", "/signup","/swagger-ui/**", "/v3/api-docs/**").permitAll() -// .anyRequest().authenticated() -// ) -// .formLogin((form) -> form -// .loginPage("/login") -// .defaultSuccessUrl("/home", true) -// .permitAll() -// ) -// .logout((logout) -> logout -// .logoutUrl("/logout") -// .logoutSuccessUrl("/login?logout") -// .permitAll() -// ) -// .oauth2Login(oauth2 -> oauth2 -// .loginPage("/login") -// .permitAll() -// ); -// -// return http.build(); -// } -// @Bean -// public PasswordEncoder passwordEncoder() { -// return new BCryptPasswordEncoder(); -// } -//} - -// if (userRepository.findByEmail(email).isPresent()) { -// // 이메일이 존재하면 홈으로 리다이렉트 -// response.sendRedirect("/home"); -// } else { -// // 이메일이 없으면 회원가입 페이지로 리다이렉트 -// response.sendRedirect("/signup"); - package stackpot.stackpot.config.security; import lombok.RequiredArgsConstructor; @@ -61,13 +9,10 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import stackpot.stackpot.repository.UserRepository.UserRepository; import stackpot.stackpot.web.dto.TokenServiceResponse; import static org.springframework.security.config.Customizer.withDefaults; @@ -78,7 +23,6 @@ public class SecurityConfig { private final CustomOAuth2UserService customOAuth2UserService; - private final CustomOAuth2UserService oAuth2UserService; @Bean public WebSecurityCustomizer webSecurityCustomizer() { // security를 적용하지 않을 리소스 @@ -104,19 +48,6 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtTokenProvid .anyRequest().authenticated() ) .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); -// http -// .authorizeHttpRequests(auth -> auth -// .requestMatchers("/", "/home", "/swagger-ui/**", "/v3/api-docs/**").permitAll() -// .requestMatchers("/login/token").authenticated() -// .anyRequest().permitAll() -// ) -// .csrf(csrf -> csrf.ignoringRequestMatchers("/login/token")) -// .oauth2Login(oauth2 -> oauth2 -// .userInfoEndpoint().userService(customOAuth2UserService) -// .and() -// .successHandler(successHandler(jwtTokenProvider)) -// ) -// .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); return http.build(); } // @Bean diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 131b1557..5cdf0739 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -47,4 +47,6 @@ spring: authorization-uri: https://kauth.kakao.com/oauth/authorize token-uri: https://kauth.kakao.com/oauth/token user-info-uri: https://kapi.kakao.com/v2/user/me - user-name-attribute: id \ No newline at end of file + user-name-attribute: id + jwt: + secret: ${JWT_SECRET} \ No newline at end of file From 452368ddf372f622fbc71a9762322a58870bf7a3 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Tue, 21 Jan 2025 12:56:29 +0900 Subject: [PATCH 34/76] =?UTF-8?q?dev=20=EB=B3=80=EA=B2=BD=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=B3=91=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 09e01ec5..6402270c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -49,4 +49,4 @@ spring: user-info-uri: https://kapi.kakao.com/v2/user/me user-name-attribute: id jwt: - secret: ${JWT_SECRET} \ No newline at end of file + secret: ${JW098654w3T_SECRET} \ No newline at end of file From 8b3d105e50e7867c1455bc8a102a6f681272dd99 Mon Sep 17 00:00:00 2001 From: starday119 Date: Tue, 21 Jan 2025 15:11:47 +0900 Subject: [PATCH 35/76] =?UTF-8?q?[MOD]=20=ED=97=A4=EB=8D=94=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stackpot/converter/PotConverter.java | 2 +- .../stackpot/stackpot/service/PotService.java | 6 +- .../stackpot/service/PotServiceImpl.java | 103 +++++++++--------- .../web/controller/PotController.java | 28 ++--- 4 files changed, 68 insertions(+), 71 deletions(-) diff --git a/src/main/java/stackpot/stackpot/converter/PotConverter.java b/src/main/java/stackpot/stackpot/converter/PotConverter.java index 55acd8e2..d0fba6e0 100644 --- a/src/main/java/stackpot/stackpot/converter/PotConverter.java +++ b/src/main/java/stackpot/stackpot/converter/PotConverter.java @@ -9,7 +9,7 @@ import java.util.List; public interface PotConverter { - Pot toEntity(PotRequestDto dto, User user); + Pot toEntity(PotRequestDto dto,User user); PotResponseDto toDto(Pot entity, List recruitmentDetails); } diff --git a/src/main/java/stackpot/stackpot/service/PotService.java b/src/main/java/stackpot/stackpot/service/PotService.java index 81fa6bf1..3367bcd7 100644 --- a/src/main/java/stackpot/stackpot/service/PotService.java +++ b/src/main/java/stackpot/stackpot/service/PotService.java @@ -4,8 +4,8 @@ import stackpot.stackpot.web.dto.PotResponseDto; public interface PotService { - PotResponseDto createPotWithRecruitments(String token, PotRequestDto requestDto); - PotResponseDto updatePotWithRecruitments(String token, Long potId, PotRequestDto requestDto); + PotResponseDto createPotWithRecruitments(PotRequestDto requestDto); + PotResponseDto updatePotWithRecruitments(Long potId, PotRequestDto requestDto); - void deletePot(String token, Long potId); + void deletePot(Long potId); } diff --git a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java index dbdeab57..4bebfa9b 100644 --- a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java @@ -3,6 +3,7 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import stackpot.stackpot.converter.PotConverter; import stackpot.stackpot.domain.Pot; @@ -15,7 +16,7 @@ import stackpot.stackpot.web.dto.PotRequestDto; import stackpot.stackpot.web.dto.PotResponseDto; import stackpot.stackpot.config.security.JwtTokenProvider; - +import org.springframework.security.core.context.SecurityContextHolder; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -31,22 +32,22 @@ public class PotServiceImpl implements PotService { private final JwtTokenProvider jwtTokenProvider; private final UserRepository userRepository; - @Override + @Transactional - public PotResponseDto createPotWithRecruitments(String token, PotRequestDto requestDto) { - // JWT에서 이메일 추출 - String email = jwtTokenProvider.getEmailFromToken(token); + public PotResponseDto createPotWithRecruitments(PotRequestDto requestDto) { + // 인증 정보에서 사용자 이메일 가져오기 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); - // 2. 이메일로 사용자 로드 + // 사용자 정보 조회 User user = userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); - // 3. Pot 엔티티 생성 및 사용자 설정 + // 팟 생성 Pot pot = potConverter.toEntity(requestDto, user); - pot.setUser(user); // 사용자 정보 설정 - // 4. Pot 저장 Pot savedPot = potRepository.save(pot); - // RecruitmentDetails 저장 로직 + + // 모집 정보 저장 List recruitmentDetails = requestDto.getRecruitmentDetails().stream() .map(recruitmentDto -> PotRecruitmentDetails.builder() .recruitmentRole(recruitmentDto.getRecruitmentRole()) @@ -54,49 +55,50 @@ public PotResponseDto createPotWithRecruitments(String token, PotRequestDto requ .pot(savedPot) .build()) .collect(Collectors.toList()); - recruitmentDetailsRepository.saveAll(recruitmentDetails); - // Convert and return response DTO + // DTO로 변환 후 반환 return potConverter.toDto(savedPot, recruitmentDetails); - } + @Transactional - public PotResponseDto updatePotWithRecruitments(String token, Long potId, PotRequestDto requestDto) { - // JWT에서 사용자 이메일 추출 - String email = jwtTokenProvider.getEmailFromToken(token); + @Override + public PotResponseDto updatePotWithRecruitments(Long potId, PotRequestDto requestDto) { + // 인증 정보에서 사용자 이메일 가져오기 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); - // 이메일로 사용자 조회 + // 사용자 정보 조회 User user = userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); // 팟 조회 Pot pot = potRepository.findById(potId) - .orElseThrow(() -> new IllegalArgumentException("수정할 팟을 찾을 수 없습니다.")); + .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); - // 권한 확인 + // 소유자 확인 if (!pot.getUser().equals(user)) { - throw new IllegalArgumentException("해당 팟을 수정할 권한이 없습니다."); + throw new IllegalArgumentException("You do not have permission to update this pot."); } + // 업데이트 로직 + pot.updateFields(Map.of( + "potName", requestDto.getPotName(), + "potStartDate", requestDto.getPotStartDate(), + "potEndDate", requestDto.getPotEndDate(), + "potDuration", requestDto.getPotDuration(), + "potLan", requestDto.getPotLan(), + "potContent", requestDto.getPotContent(), + "potStatus", requestDto.getPotStatus(), + "potModeOfOperation", requestDto.getPotModeOfOperation(), + "potSummary", requestDto.getPotSummary(), + "recruitmentDeadline", requestDto.getRecruitmentDeadline() + )); + // 기존 모집 정보 삭제 recruitmentDetailsRepository.deleteByPot_PotId(potId); - // 새로운 정보로 업데이트 - Map updates = new HashMap<>(); - updates.put("potName", requestDto.getPotName()); - updates.put("potStartDate", requestDto.getPotStartDate()); - updates.put("potEndDate", requestDto.getPotEndDate()); - updates.put("potDuration", requestDto.getPotDuration()); - updates.put("potLan", requestDto.getPotLan()); - updates.put("potContent", requestDto.getPotContent()); - updates.put("potStatus", requestDto.getPotStatus()); - updates.put("potModeOfOperation", requestDto.getPotModeOfOperation()); - updates.put("potSummary", requestDto.getPotSummary()); - updates.put("recruitmentDeadline", requestDto.getRecruitmentDeadline()); - pot.updateFields(updates); - - // 새로운 모집 정보 추가 + // 새로운 모집 정보 저장 List recruitmentDetails = requestDto.getRecruitmentDetails().stream() .map(recruitmentDto -> PotRecruitmentDetails.builder() .recruitmentRole(recruitmentDto.getRecruitmentRole()) @@ -106,34 +108,37 @@ public PotResponseDto updatePotWithRecruitments(String token, Long potId, PotReq .collect(Collectors.toList()); recruitmentDetailsRepository.saveAll(recruitmentDetails); - // 수정된 데이터 반환 + // DTO로 변환 후 반환 return potConverter.toDto(pot, recruitmentDetails); } - @Override + + @Transactional - public void deletePot(String token, Long potId) { - // JWT에서 이메일 추출 - String email = jwtTokenProvider.getEmailFromToken(token); + public void deletePot(Long potId) { + // 인증 정보에서 사용자 이메일 가져오기 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); - // 이메일로 사용자 로드 + // 사용자 정보 조회 User user = userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); - // 팟 로드 + // 팟 조회 Pot pot = potRepository.findById(potId) - .orElseThrow(() -> new IllegalArgumentException("삭제할 팟이 존재하지 않습니다.")); + .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); - // 사용자가 팟의 생성자인지 확인 + // 팟 소유자 확인 if (!pot.getUser().equals(user)) { - throw new IllegalArgumentException("삭제 권한이 없습니다."); + throw new IllegalArgumentException("You do not have permission to delete this pot."); } - // 연관된 모집 정보 삭제 + // 모집 정보 삭제 recruitmentDetailsRepository.deleteByPot_PotId(potId); // 팟 삭제 potRepository.delete(pot); } + } diff --git a/src/main/java/stackpot/stackpot/web/controller/PotController.java b/src/main/java/stackpot/stackpot/web/controller/PotController.java index a928cc6c..1e828824 100644 --- a/src/main/java/stackpot/stackpot/web/controller/PotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/PotController.java @@ -17,37 +17,29 @@ public class PotController { @PostMapping public ResponseEntity createPot( - @RequestHeader("Authorization") String token, + @RequestBody @Valid PotRequestDto requestDto) { - // Bearer 제거 후 토큰 전달 - String parsedToken = token.replace("Bearer ", ""); - PotResponseDto responseDto = potService.createPotWithRecruitments(parsedToken, requestDto); + // 팟 생성 로직 호출 + PotResponseDto responseDto = potService.createPotWithRecruitments(requestDto); + return ResponseEntity.ok(responseDto); } + @PatchMapping("/{pot_id}") public ResponseEntity updatePot( - @RequestHeader("Authorization") String token, - @PathVariable("pot_id") Long potId, // 동일하게 이름 매핑 - @RequestBody @Valid PotRequestDto requestDto) { // 요청 DTO 검증 - // Bearer 제거 - String parsedToken = token.replace("Bearer ", ""); - + @PathVariable("pot_id") Long potId, + @RequestBody @Valid PotRequestDto requestDto) { // 팟 수정 로직 호출 - PotResponseDto responseDto = potService.updatePotWithRecruitments(parsedToken, potId, requestDto); + PotResponseDto responseDto = potService.updatePotWithRecruitments(potId, requestDto); return ResponseEntity.ok(responseDto); // 수정된 팟 정보 반환 } @DeleteMapping("/{pot_id}") - public ResponseEntity deletePot( - @RequestHeader("Authorization") String token, - @PathVariable("pot_id") Long potId) { // 동일하게 이름 매핑 - // Bearer 제거 - String parsedToken = token.replace("Bearer ", ""); - + public ResponseEntity deletePot(@PathVariable("pot_id") Long potId) { // 팟 삭제 로직 호출 - potService.deletePot(parsedToken, potId); + potService.deletePot(potId); return ResponseEntity.noContent().build(); } From a306b55a35749da8d63436e8dfe4081cfff4788d Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Tue, 21 Jan 2025 15:35:36 +0900 Subject: [PATCH 36/76] =?UTF-8?q?[FEAT/#17]=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/CustomOAuth2UserService.java | 2 +- .../stackpot/converter/UserConverter.java | 26 +++++++---- .../java/stackpot/stackpot/domain/User.java | 15 ++++-- .../stackpot/service/UserCommandService.java | 4 +- .../service/UserCommandServiceImpl.java | 46 +++++++++++++++++-- .../web/controller/UserController.java | 37 +++++++++++++++ ...serRequestDTO.java => UserRequestDto.java} | 21 ++++----- .../stackpot/web/dto/UserResponseDto.java | 18 ++++++++ src/main/resources/application.yml | 2 +- 9 files changed, 138 insertions(+), 33 deletions(-) rename src/main/java/stackpot/stackpot/web/dto/{UserRequestDTO.java => UserRequestDto.java} (51%) create mode 100644 src/main/java/stackpot/stackpot/web/dto/UserResponseDto.java diff --git a/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java b/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java index 456bf287..676203ff 100644 --- a/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java +++ b/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java @@ -92,7 +92,7 @@ private void saveOrUpdateUser(String email) { .orElseGet(() -> { User user = User.builder() .email(email) - .loginId(UUID.randomUUID().toString()) // 랜덤 로그인 ID 생성 + .userTemperature(33) .build(); return userRepository.save(user); // 저장된 사용자 반환 }); diff --git a/src/main/java/stackpot/stackpot/converter/UserConverter.java b/src/main/java/stackpot/stackpot/converter/UserConverter.java index 5b6865a1..36418aae 100644 --- a/src/main/java/stackpot/stackpot/converter/UserConverter.java +++ b/src/main/java/stackpot/stackpot/converter/UserConverter.java @@ -1,18 +1,28 @@ package stackpot.stackpot.converter; import stackpot.stackpot.domain.User; -import stackpot.stackpot.web.dto.UserRequestDTO; +import stackpot.stackpot.web.dto.UserRequestDto; +import stackpot.stackpot.web.dto.UserResponseDto; public class UserConverter { - public static User toUser(UserRequestDTO.JoinDto request) { + public static User toUser(UserRequestDto.JoinDto request) { return User.builder() -// .nickname(request.getNickname()) - .email(request.getEmail()) // 추가된 코드 -// .kakaoId(request.getKakaoId()) -// .interest(request.getInterest()) -// .role(request.getRole()) - .userTemperature((int)35.5) + .nickname(request.getNickname()) + .kakaoId(request.getKakaoId()) + .interest(request.getInterest()) + .role(request.getRole()) + .build(); + } + + public static UserResponseDto toDto(User user) { + + return UserResponseDto.builder() + .nickname(user.getNickname()) + .email(user.getEmail()) // 추가된 코드 + .kakaoId(user.getKakaoId()) + .role(user.getRole()) + .userTemperature(user.getUserTemperature()) .build(); } } diff --git a/src/main/java/stackpot/stackpot/domain/User.java b/src/main/java/stackpot/stackpot/domain/User.java index 21f2e765..f6aae2dd 100644 --- a/src/main/java/stackpot/stackpot/domain/User.java +++ b/src/main/java/stackpot/stackpot/domain/User.java @@ -10,6 +10,7 @@ @Entity @Getter +@Setter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @@ -18,11 +19,11 @@ public class User extends BaseEntity implements UserDetails{ @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // Primary Key - @Column(nullable = false, length = 255) - private String loginId; // 로그인 아이디 - - @Column(nullable = true, length = 12) - private String userName; // 유저 카톡 설정 이름 +// @Column(nullable = false, length = 255) +// private String loginId; // 로그인 아이디 +// +//// @Column(nullable = true, length = 12) +// private String userName; // 유저 카톡 설정 이름 // @Column(nullable = false, length = 255) // private String snsKey; // SNS 키 @@ -45,6 +46,9 @@ public class User extends BaseEntity implements UserDetails{ @Column(nullable = false, unique = true) private String email; // 이메일 + @Column(nullable = true, unique = true) + private String kakaoId; + @Override public Collection getAuthorities() { return null; @@ -63,3 +67,4 @@ public Long getUserId() { return id; } } + diff --git a/src/main/java/stackpot/stackpot/service/UserCommandService.java b/src/main/java/stackpot/stackpot/service/UserCommandService.java index 2b5cfdad..af7250e3 100644 --- a/src/main/java/stackpot/stackpot/service/UserCommandService.java +++ b/src/main/java/stackpot/stackpot/service/UserCommandService.java @@ -1,9 +1,9 @@ package stackpot.stackpot.service; import stackpot.stackpot.domain.User; -import stackpot.stackpot.web.dto.UserRequestDTO; +import stackpot.stackpot.web.dto.UserRequestDto; public interface UserCommandService { - public User joinUser(UserRequestDTO.JoinDto request); + public User joinUser(UserRequestDto.JoinDto request); } diff --git a/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java b/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java index a3e39957..c105df06 100644 --- a/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java @@ -2,11 +2,13 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import stackpot.stackpot.converter.UserConverter; import stackpot.stackpot.domain.User; import stackpot.stackpot.repository.UserRepository.UserRepository; -import stackpot.stackpot.web.dto.UserRequestDTO; +import stackpot.stackpot.web.dto.UserRequestDto; @Service @RequiredArgsConstructor @@ -16,9 +18,45 @@ public class UserCommandServiceImpl implements UserCommandService{ @Override @Transactional - public User joinUser(UserRequestDTO.JoinDto request) { + public User joinUser(UserRequestDto.JoinDto request) { - User newUser = UserConverter.toUser(request); - return userRepository.save(newUser); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + + User addUser = UserConverter.toUser(request); + updateUserData(user, request); + + return userRepository.save(user); + } + + private void updateUserData(User user, UserRequestDto.JoinDto request) { +// if (addUser.getKakaoId() != null) { +// user.setKakaoId(addUser.getKakaoId()); +// } +// if(addUser.getNickname() != null){ +// user.setNickname(addUser.getNickname()); +// } +// if(addUser.getRole() != null){ +// user.setRole(addUser.getRole()); +// } +// if(addUser.getInterest() != null){ +// user.setInterest(addUser.getInterest()); +// } + if (request.getKakaoId() != null) { + user.setKakaoId(request.getKakaoId()); + System.out.println("request.getKakaoId() : " + request.getKakaoId()); + } + if (request.getNickname() != null) { + user.setNickname(request.getNickname()); + } + if (request.getRole() != null) { + user.setRole(request.getRole()); + } + if (request.getInterest() != null) { + user.setInterest(request.getInterest()); + } } } diff --git a/src/main/java/stackpot/stackpot/web/controller/UserController.java b/src/main/java/stackpot/stackpot/web/controller/UserController.java index df254d40..8e57ca7b 100644 --- a/src/main/java/stackpot/stackpot/web/controller/UserController.java +++ b/src/main/java/stackpot/stackpot/web/controller/UserController.java @@ -1,18 +1,33 @@ package stackpot.stackpot.web.controller; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; +import org.springframework.validation.BindingResult; +import org.springframework.validation.ObjectError; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; +import stackpot.stackpot.converter.UserConverter; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.service.UserCommandService; +import stackpot.stackpot.web.dto.UserRequestDto; + +import java.util.List; +import java.util.stream.Collectors; @Slf4j @RestController @RequiredArgsConstructor public class UserController { + + private final UserCommandService userCommandService; @Operation(summary = "토큰 test api") @GetMapping("/login/token") public ResponseEntity testEndpoint(Authentication authentication) { @@ -22,4 +37,26 @@ public ResponseEntity testEndpoint(Authentication authentication) { return ResponseEntity.ok("Authenticated user: " + authentication.getName()); } + @Operation(summary = "회원가입 api") + @PatchMapping("/users/profile") + public ResponseEntity signup(@RequestBody UserRequestDto.JoinDto request, + BindingResult bindingResult) { + // 유효성 검사 실패 처리 + if (bindingResult.hasErrors()) { + // 에러 메시지 수집 + List errors = bindingResult.getAllErrors() + .stream() + .map(ObjectError::getDefaultMessage) + .collect(Collectors.toList()); + return ResponseEntity.badRequest().body(errors); + } + + System.out.println("uuuuuds : " + request.getKakaoId()); + // 정상 처리 + User user = userCommandService.joinUser(request); + + + return ResponseEntity.status(HttpStatus.CREATED).body(UserConverter.toDto(user)); + } + } diff --git a/src/main/java/stackpot/stackpot/web/dto/UserRequestDTO.java b/src/main/java/stackpot/stackpot/web/dto/UserRequestDto.java similarity index 51% rename from src/main/java/stackpot/stackpot/web/dto/UserRequestDTO.java rename to src/main/java/stackpot/stackpot/web/dto/UserRequestDto.java index 445644cf..de286b39 100644 --- a/src/main/java/stackpot/stackpot/web/dto/UserRequestDTO.java +++ b/src/main/java/stackpot/stackpot/web/dto/UserRequestDto.java @@ -3,28 +3,25 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; import lombok.Getter; import lombok.Setter; -import javax.management.relation.Role; - -public class UserRequestDTO { +public class UserRequestDto { @Getter @Setter public static class JoinDto { - @NotNull + @NotBlank(message = "Role은 공백일 수 없습니다.") String role; - @NotNull + + @NotBlank(message = "Interest는 공백일 수 없습니다.") String interest; - @NotBlank + + @NotBlank(message = "Nickname은 공백일 수 없습니다.") String nickname; - @NotBlank - @Email - String email; // 이메일 - @NotNull - String kakaoId; // 카카오 아이디 + + @NotBlank(message = "KakaoId는 공백일 수 없습니다.") + String kakaoId; } } diff --git a/src/main/java/stackpot/stackpot/web/dto/UserResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/UserResponseDto.java new file mode 100644 index 00000000..6b9d23bc --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/UserResponseDto.java @@ -0,0 +1,18 @@ +package stackpot.stackpot.web.dto; + +import jakarta.persistence.Column; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class UserResponseDto { + private String email; // 이메일 + private String nickname; // 닉네임 + private String role; // 역할 + private String interest; // 관심사 + private Integer userTemperature; // 유저 온도 + private String kakaoId; +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6402270c..09e01ec5 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -49,4 +49,4 @@ spring: user-info-uri: https://kapi.kakao.com/v2/user/me user-name-attribute: id jwt: - secret: ${JW098654w3T_SECRET} \ No newline at end of file + secret: ${JWT_SECRET} \ No newline at end of file From 0dead004deb91e416b14facb1b3e6c0c764de5b7 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Tue, 21 Jan 2025 16:42:55 +0900 Subject: [PATCH 37/76] =?UTF-8?q?[FEAT/#17]=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stackpot/converter/UserConverter.java | 1 + .../service/UserCommandServiceImpl.java | 34 +++++-------------- .../web/controller/UserController.java | 11 +++--- .../stackpot/web/dto/UserRequestDto.java | 7 ++-- 4 files changed, 16 insertions(+), 37 deletions(-) diff --git a/src/main/java/stackpot/stackpot/converter/UserConverter.java b/src/main/java/stackpot/stackpot/converter/UserConverter.java index 36418aae..0b621aa7 100644 --- a/src/main/java/stackpot/stackpot/converter/UserConverter.java +++ b/src/main/java/stackpot/stackpot/converter/UserConverter.java @@ -22,6 +22,7 @@ public static UserResponseDto toDto(User user) { .email(user.getEmail()) // 추가된 코드 .kakaoId(user.getKakaoId()) .role(user.getRole()) + .interest(user.getInterest()) .userTemperature(user.getUserTemperature()) .build(); } diff --git a/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java b/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java index c105df06..013fa1b5 100644 --- a/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java @@ -26,37 +26,19 @@ public User joinUser(UserRequestDto.JoinDto request) { User user = userRepository.findByEmail(email) .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); - User addUser = UserConverter.toUser(request); updateUserData(user, request); return userRepository.save(user); } private void updateUserData(User user, UserRequestDto.JoinDto request) { -// if (addUser.getKakaoId() != null) { -// user.setKakaoId(addUser.getKakaoId()); -// } -// if(addUser.getNickname() != null){ -// user.setNickname(addUser.getNickname()); -// } -// if(addUser.getRole() != null){ -// user.setRole(addUser.getRole()); -// } -// if(addUser.getInterest() != null){ -// user.setInterest(addUser.getInterest()); -// } - if (request.getKakaoId() != null) { - user.setKakaoId(request.getKakaoId()); - System.out.println("request.getKakaoId() : " + request.getKakaoId()); - } - if (request.getNickname() != null) { - user.setNickname(request.getNickname()); - } - if (request.getRole() != null) { - user.setRole(request.getRole()); - } - if (request.getInterest() != null) { - user.setInterest(request.getInterest()); - } + // 카카오 id + user.setKakaoId(request.getKakaoId()); + // 닉네임 + user.setNickname(request.getNickname()); + // 역할군 + user.setRole(request.getRole()); + // 관심사 + user.setInterest(request.getInterest()); } } diff --git a/src/main/java/stackpot/stackpot/web/controller/UserController.java b/src/main/java/stackpot/stackpot/web/controller/UserController.java index 8e57ca7b..b1523885 100644 --- a/src/main/java/stackpot/stackpot/web/controller/UserController.java +++ b/src/main/java/stackpot/stackpot/web/controller/UserController.java @@ -1,8 +1,9 @@ package stackpot.stackpot.web.controller; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.parameters.RequestBody; +//import io.swagger.v3.oas.annotations.parameters.RequestBody; import jakarta.validation.Valid; + import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -12,7 +13,7 @@ import org.springframework.validation.ObjectError; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import stackpot.stackpot.converter.UserConverter; import stackpot.stackpot.domain.User; @@ -39,7 +40,7 @@ public ResponseEntity testEndpoint(Authentication authentication) { @Operation(summary = "회원가입 api") @PatchMapping("/users/profile") - public ResponseEntity signup(@RequestBody UserRequestDto.JoinDto request, + public ResponseEntity signup(@Valid @RequestBody UserRequestDto.JoinDto request, BindingResult bindingResult) { // 유효성 검사 실패 처리 if (bindingResult.hasErrors()) { @@ -50,12 +51,8 @@ public ResponseEntity signup(@RequestBody UserRequestDto.JoinDto request, .collect(Collectors.toList()); return ResponseEntity.badRequest().body(errors); } - - System.out.println("uuuuuds : " + request.getKakaoId()); // 정상 처리 User user = userCommandService.joinUser(request); - - return ResponseEntity.status(HttpStatus.CREATED).body(UserConverter.toDto(user)); } diff --git a/src/main/java/stackpot/stackpot/web/dto/UserRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/UserRequestDto.java index de286b39..7ff9fab0 100644 --- a/src/main/java/stackpot/stackpot/web/dto/UserRequestDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/UserRequestDto.java @@ -3,25 +3,24 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; public class UserRequestDto { @Getter @Setter + @NoArgsConstructor public static class JoinDto { @NotBlank(message = "Role은 공백일 수 없습니다.") String role; - @NotBlank(message = "Interest는 공백일 수 없습니다.") String interest; - @NotBlank(message = "Nickname은 공백일 수 없습니다.") String nickname; - @NotBlank(message = "KakaoId는 공백일 수 없습니다.") String kakaoId; - } } From 3dd3816c03be74ccbfcaa5aaf86246145b96563c Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Tue, 21 Jan 2025 17:25:11 +0900 Subject: [PATCH 38/76] =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=82=AC=ED=95=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stackpot/stackpot/web/controller/UserController.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/stackpot/stackpot/web/controller/UserController.java b/src/main/java/stackpot/stackpot/web/controller/UserController.java index b1523885..904fafb9 100644 --- a/src/main/java/stackpot/stackpot/web/controller/UserController.java +++ b/src/main/java/stackpot/stackpot/web/controller/UserController.java @@ -11,10 +11,7 @@ import org.springframework.security.core.Authentication; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import stackpot.stackpot.converter.UserConverter; import stackpot.stackpot.domain.User; import stackpot.stackpot.service.UserCommandService; @@ -26,6 +23,7 @@ @Slf4j @RestController @RequiredArgsConstructor +@RequestMapping("/users") public class UserController { private final UserCommandService userCommandService; @@ -39,7 +37,7 @@ public ResponseEntity testEndpoint(Authentication authentication) { } @Operation(summary = "회원가입 api") - @PatchMapping("/users/profile") + @PatchMapping("/profile") public ResponseEntity signup(@Valid @RequestBody UserRequestDto.JoinDto request, BindingResult bindingResult) { // 유효성 검사 실패 처리 @@ -56,4 +54,5 @@ public ResponseEntity signup(@Valid @RequestBody UserRequestDto.JoinDto reque return ResponseEntity.status(HttpStatus.CREATED).body(UserConverter.toDto(user)); } + } From 95959e066c1f327fcb2c2f1401df4344f8f7f666 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Tue, 21 Jan 2025 17:25:37 +0900 Subject: [PATCH 39/76] =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=82=AC=ED=95=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stackpot/stackpot/web/controller/UserController.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/stackpot/stackpot/web/controller/UserController.java b/src/main/java/stackpot/stackpot/web/controller/UserController.java index 904fafb9..960b0edf 100644 --- a/src/main/java/stackpot/stackpot/web/controller/UserController.java +++ b/src/main/java/stackpot/stackpot/web/controller/UserController.java @@ -54,5 +54,12 @@ public ResponseEntity signup(@Valid @RequestBody UserRequestDto.JoinDto reque return ResponseEntity.status(HttpStatus.CREATED).body(UserConverter.toDto(user)); } + @Operation(summary = "닉넴임 생성") + @GetMapping("/nickname") + public ResponseEntity nickname(){ + + return null; +// return ResponseEntity.ok(); + } } From 16aad72736ac03ae33789f68fd91c3eca164e50f Mon Sep 17 00:00:00 2001 From: starday119 Date: Tue, 21 Jan 2025 17:39:22 +0900 Subject: [PATCH 40/76] =?UTF-8?q?[MOD]=20=ED=8C=9F=20=EC=A7=80=EC=9B=90?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20API=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PotApplicationConverter.java | 8 ++-- .../PotApplicationConverterImpl.java | 41 +++++++++++++------ .../java/stackpot/stackpot/domain/User.java | 1 + .../domain/mapping/PotApplication.java | 10 +++-- .../PotApplicationService.java | 8 ++-- .../PotApplicationServiceImpl.java | 36 ++++++++-------- .../controller/PotApplicationController.java | 24 ++++++----- ...Dto.java => PotApplicationRequestDto.java} | 4 +- ...to.java => PotApplicationResponseDto.java} | 2 +- src/main/resources/application.yml | 2 +- 10 files changed, 79 insertions(+), 57 deletions(-) rename src/main/java/stackpot/stackpot/web/dto/{ApplicationRequestDto.java => PotApplicationRequestDto.java} (79%) rename src/main/java/stackpot/stackpot/web/dto/{ApplicationResponseDto.java => PotApplicationResponseDto.java} (89%) diff --git a/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverter.java b/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverter.java index 1068cb43..6f055ebb 100644 --- a/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverter.java +++ b/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverter.java @@ -3,11 +3,11 @@ import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.mapping.PotApplication; import stackpot.stackpot.domain.User; -import stackpot.stackpot.web.dto.ApplicationRequestDto; -import stackpot.stackpot.web.dto.ApplicationResponseDto; +import stackpot.stackpot.web.dto.PotApplicationRequestDto; +import stackpot.stackpot.web.dto.PotApplicationResponseDto; public interface PotApplicationConverter { - PotApplication toEntity(ApplicationRequestDto dto, Pot pot, User user); + PotApplication toEntity(PotApplicationRequestDto dto, Pot pot, User user); - ApplicationResponseDto toDto(PotApplication entity); + PotApplicationResponseDto toDto(PotApplication entity); } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverterImpl.java b/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverterImpl.java index aa34a08b..322c56bc 100644 --- a/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverterImpl.java @@ -5,8 +5,8 @@ import stackpot.stackpot.domain.mapping.PotApplication; import stackpot.stackpot.domain.User; import stackpot.stackpot.domain.enums.ApplicationStatus; -import stackpot.stackpot.web.dto.ApplicationRequestDto; -import stackpot.stackpot.web.dto.ApplicationResponseDto; +import stackpot.stackpot.web.dto.PotApplicationRequestDto; +import stackpot.stackpot.web.dto.PotApplicationResponseDto; import java.time.LocalDateTime; @@ -14,27 +14,44 @@ public class PotApplicationConverterImpl implements PotApplicationConverter { @Override - public PotApplication toEntity(ApplicationRequestDto dto, Pot pot, User user) { + public PotApplication toEntity(PotApplicationRequestDto dto, Pot pot, User user) { + // User와 Pot 객체가 null인지 확인 + if (pot == null || user == null) { + throw new IllegalArgumentException("Pot or User cannot be null"); + } + return PotApplication.builder() .pot(pot) .user(user) .potRole(dto.getPotRole()) - .liked(dto.getLiked() != null ? dto.getLiked() : false) - .status(ApplicationStatus.PENDING) - .appliedAt(LocalDateTime.now()) + .liked(false) // 기본값 false + .status(ApplicationStatus.PENDING) // 지원 상태 + .appliedAt(LocalDateTime.now()) // 지원 시간 .build(); } @Override - public ApplicationResponseDto toDto(PotApplication entity) { - return ApplicationResponseDto.builder() + public PotApplicationResponseDto toDto(PotApplication entity) { + // Null 검사 및 예외 처리 + if (entity == null) { + throw new IllegalArgumentException("PotApplication entity cannot be null"); + } + if (entity.getPot() == null) { + throw new IllegalArgumentException("Pot entity cannot be null in PotApplication"); + } + if (entity.getUser() == null) { + throw new IllegalArgumentException("User entity cannot be null in PotApplication"); + } + + return PotApplicationResponseDto.builder() .applicationId(entity.getApplicationId()) .potRole(entity.getPotRole()) .liked(entity.getLiked()) - .status(entity.getStatus().name()) - .appliedAt(entity.getAppliedAt()) - .potId(entity.getPot().getPotId()) - .userId(entity.getUser().getUserId()) + .status(entity.getStatus() != null ? entity.getStatus().name() : "UNKNOWN") // 상태가 null이면 기본값 설정 + .appliedAt(entity.getAppliedAt()) // null 가능성을 허용 + .potId(entity.getPot().getPotId()) // Pot 엔티티에서 potId 가져오기 + .userId(entity.getUser().getId()) // User 엔티티에서 id 가져오기 .build(); } + } diff --git a/src/main/java/stackpot/stackpot/domain/User.java b/src/main/java/stackpot/stackpot/domain/User.java index 21f2e765..ae1d884d 100644 --- a/src/main/java/stackpot/stackpot/domain/User.java +++ b/src/main/java/stackpot/stackpot/domain/User.java @@ -62,4 +62,5 @@ public String getUsername() { public Long getUserId() { return id; } + } diff --git a/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java b/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java index 55b7b3e5..5347d180 100644 --- a/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java +++ b/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java @@ -11,15 +11,16 @@ @Entity @Getter +@Setter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class PotApplication extends BaseEntity { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(nullable = false) - private Long applicationId; + @GeneratedValue(strategy = GenerationType.AUTO) // IDENTITY로 자동 증가 설정 + @Column(name = "application_id", nullable = false) + private Long applicationId; // Primary Key @Enumerated(EnumType.STRING) @Column(nullable = false) @@ -39,8 +40,9 @@ public class PotApplication extends BaseEntity { private Pot pot; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) + @JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false) private User user; + public void setApplicationStatus(ApplicationStatus status) { this.status = status; } diff --git a/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationService.java b/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationService.java index 708de141..0e7a197e 100644 --- a/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationService.java +++ b/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationService.java @@ -1,12 +1,12 @@ package stackpot.stackpot.service.PotApplicationService; -import stackpot.stackpot.web.dto.ApplicationRequestDto; -import stackpot.stackpot.web.dto.ApplicationResponseDto; +import stackpot.stackpot.web.dto.PotApplicationRequestDto; +import stackpot.stackpot.web.dto.PotApplicationResponseDto; import java.util.List; public interface PotApplicationService { - ApplicationResponseDto applyToPot(String token, Long potId, ApplicationRequestDto dto); + PotApplicationResponseDto applyToPot(PotApplicationRequestDto dto,Long potId); - List getApplicationsByPot(Long potId); + List getApplicationsByPot(Long potId); } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java index e7ec71c3..59bae28d 100644 --- a/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java @@ -1,5 +1,7 @@ package stackpot.stackpot.service.PotApplicationService; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -12,9 +14,10 @@ import stackpot.stackpot.repository.PotRepository.PotRepository; import stackpot.stackpot.repository.UserRepository.UserRepository; import stackpot.stackpot.config.security.JwtTokenProvider; -import stackpot.stackpot.web.dto.ApplicationRequestDto; -import stackpot.stackpot.web.dto.ApplicationResponseDto; +import stackpot.stackpot.web.dto.PotApplicationRequestDto; +import stackpot.stackpot.web.dto.PotApplicationResponseDto; +import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @@ -28,13 +31,13 @@ public class PotApplicationServiceImpl implements PotApplicationService { private final PotApplicationConverter potApplicationConverter; private final JwtTokenProvider jwtTokenProvider; - @Override @Transactional - public ApplicationResponseDto applyToPot(String token, Long potId, ApplicationRequestDto dto) { - // 토큰에서 사용자 이메일 추출 - String email = jwtTokenProvider.getEmailFromToken(token); + public PotApplicationResponseDto applyToPot(PotApplicationRequestDto dto, Long potId) { + // 인증된 사용자 이메일 가져오기 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); - // 이메일로 사용자 조회 + // 사용자 조회 User user = userRepository.findByEmail(email) .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); @@ -42,26 +45,23 @@ public ApplicationResponseDto applyToPot(String token, Long potId, ApplicationRe Pot pot = potRepository.findById(potId) .orElseThrow(() -> new IllegalArgumentException("해당 팟을 찾을 수 없습니다.")); - // 중복 신청 방지 - boolean alreadyApplied = potApplicationRepository.existsByUserIdAndPot_PotId(user.getId(), potId); - if (alreadyApplied) { + // 중복 지원 방지 + if (potApplicationRepository.existsByUserIdAndPot_PotId(user.getId(), potId)) { throw new IllegalStateException("이미 해당 팟에 지원하셨습니다."); } - // `PENDING` 상태로 지원 엔티티 생성 - PotApplication application = potApplicationConverter.toEntity(dto, pot, user); - application.setApplicationStatus(ApplicationStatus.PENDING); // 상태를 명시적으로 설정 - - // 지원 정보 저장 - PotApplication savedApplication = potApplicationRepository.save(application); + // 지원 엔티티 생성 및 저장 + PotApplication potApplication = potApplicationConverter.toEntity(dto, pot, user); + PotApplication savedApplication = potApplicationRepository.save(potApplication); - // DTO로 변환하여 반환 + // 저장된 지원 정보를 응답 DTO로 변환 return potApplicationConverter.toDto(savedApplication); } + @Override @Transactional(readOnly = true) - public List getApplicationsByPot(Long potId) { + public List getApplicationsByPot(Long potId) { List applications = potApplicationRepository.findByPot_PotId(potId); return applications.stream() .map(potApplicationConverter::toDto) diff --git a/src/main/java/stackpot/stackpot/web/controller/PotApplicationController.java b/src/main/java/stackpot/stackpot/web/controller/PotApplicationController.java index 2085f9c2..f975f8d1 100644 --- a/src/main/java/stackpot/stackpot/web/controller/PotApplicationController.java +++ b/src/main/java/stackpot/stackpot/web/controller/PotApplicationController.java @@ -5,8 +5,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import stackpot.stackpot.service.PotApplicationService.PotApplicationService; -import stackpot.stackpot.web.dto.ApplicationRequestDto; -import stackpot.stackpot.web.dto.ApplicationResponseDto; +import stackpot.stackpot.web.dto.PotApplicationRequestDto; +import stackpot.stackpot.web.dto.PotApplicationResponseDto; import java.util.List; @@ -18,18 +18,20 @@ public class PotApplicationController { private final PotApplicationService potApplicationService; @PostMapping - public ResponseEntity applyToPot( - @RequestHeader("Authorization") String token, - @PathVariable("pot_id") Long potId, // PathVariable 이름 변경 - @RequestBody @Valid ApplicationRequestDto requestDto) { - String parsedToken = token.replace("Bearer ", ""); - ApplicationResponseDto responseDto = potApplicationService.applyToPot(parsedToken, potId, requestDto); - return ResponseEntity.ok(responseDto); + public ResponseEntity applyToPot( + @PathVariable("pot_id") Long potId, + @RequestBody @Valid PotApplicationRequestDto requestDto) { + + // 팟 지원 로직 호출 + PotApplicationResponseDto responseDto = potApplicationService.applyToPot(requestDto, potId); + + return ResponseEntity.ok(responseDto); // 성공 시 응답 반환 } + @GetMapping - public ResponseEntity> getApplications(@PathVariable("pot_id") Long potId) { - List applications = potApplicationService.getApplicationsByPot(potId); + public ResponseEntity> getApplications(@PathVariable("pot_id") Long potId) { + List applications = potApplicationService.getApplicationsByPot(potId); return ResponseEntity.ok(applications); } } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/dto/ApplicationRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/PotApplicationRequestDto.java similarity index 79% rename from src/main/java/stackpot/stackpot/web/dto/ApplicationRequestDto.java rename to src/main/java/stackpot/stackpot/web/dto/PotApplicationRequestDto.java index 16402e74..60aff1bd 100644 --- a/src/main/java/stackpot/stackpot/web/dto/ApplicationRequestDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/PotApplicationRequestDto.java @@ -8,9 +8,9 @@ @NoArgsConstructor @AllArgsConstructor @Builder -public class ApplicationRequestDto { +public class PotApplicationRequestDto { @NotBlank(message = "팟 역할은 필수입니다.") private String potRole; - private Boolean liked; + } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/dto/ApplicationResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/PotApplicationResponseDto.java similarity index 89% rename from src/main/java/stackpot/stackpot/web/dto/ApplicationResponseDto.java rename to src/main/java/stackpot/stackpot/web/dto/PotApplicationResponseDto.java index c86c5649..090ee956 100644 --- a/src/main/java/stackpot/stackpot/web/dto/ApplicationResponseDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/PotApplicationResponseDto.java @@ -9,7 +9,7 @@ @NoArgsConstructor @AllArgsConstructor @Builder -public class ApplicationResponseDto { +public class PotApplicationResponseDto { private Long applicationId; private String potRole; private Boolean liked; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 7dc90ae0..131b1557 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -15,7 +15,7 @@ spring: format_sql: true use_sql_comments: true hbm2ddl: - auto: update + auto: create default_batch_fetch_size: 1000 cloud: aws: From aec13f0f504413bfd7ba9013dde9cd5c664dd552 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Tue, 21 Jan 2025 17:57:37 +0900 Subject: [PATCH 41/76] =?UTF-8?q?[FEAT/#20]=20Converter=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stackpot/converter/FeedConverter.java | 13 ++++++++++++ .../stackpot/converter/FeedConverterImpl.java | 18 +++++++++++++++++ .../web/controller/FeedController.java | 20 +++++++++++++++++++ .../stackpot/web/dto/FeedRequestDto.java | 4 ++++ .../stackpot/web/dto/FeedResponseDto.java | 14 +++++++++++++ 5 files changed, 69 insertions(+) create mode 100644 src/main/java/stackpot/stackpot/converter/FeedConverter.java create mode 100644 src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java create mode 100644 src/main/java/stackpot/stackpot/web/controller/FeedController.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/FeedRequestDto.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java diff --git a/src/main/java/stackpot/stackpot/converter/FeedConverter.java b/src/main/java/stackpot/stackpot/converter/FeedConverter.java new file mode 100644 index 00000000..77b654c4 --- /dev/null +++ b/src/main/java/stackpot/stackpot/converter/FeedConverter.java @@ -0,0 +1,13 @@ +package stackpot.stackpot.converter; + + +import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.web.dto.FeedResponseDto; + +import java.util.List; + +public interface FeedConverter { + FeedResponseDto.FeedPreViewDto feedPreViewDto(Feed feed); + + FeedResponseDto.FeedPreViewListDto feedPreViewListDTO(List feedList); +} diff --git a/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java b/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java new file mode 100644 index 00000000..62f640e3 --- /dev/null +++ b/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java @@ -0,0 +1,18 @@ +package stackpot.stackpot.converter; + +import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.web.dto.FeedResponseDto; + +import java.util.List; + +public class FeedConverterImpl implements FeedConverter{ + @Override + public FeedResponseDto.FeedPreViewDto feedPreViewDto(Feed feed) { + return null; + } + + @Override + public FeedResponseDto.FeedPreViewListDto feedPreViewListDTO(List feedList) { + return null; + } +} diff --git a/src/main/java/stackpot/stackpot/web/controller/FeedController.java b/src/main/java/stackpot/stackpot/web/controller/FeedController.java new file mode 100644 index 00000000..99cb7eb3 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/controller/FeedController.java @@ -0,0 +1,20 @@ +package stackpot.stackpot.web.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stackpot.stackpot.web.dto.PotRequestDto; +import stackpot.stackpot.web.dto.PotResponseDto; + +@RestController +@RequestMapping("/feeds") +@RequiredArgsConstructor +public class FeedController { + + @GetMapping + public ResponseEntity feedPreView() { +// return ResponseEntity.ok(); + return null; + } +} diff --git a/src/main/java/stackpot/stackpot/web/dto/FeedRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/FeedRequestDto.java new file mode 100644 index 00000000..623c28ed --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/FeedRequestDto.java @@ -0,0 +1,4 @@ +package stackpot.stackpot.web.dto; + +public class FeedRequestDto { +} diff --git a/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java new file mode 100644 index 00000000..002e9d56 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java @@ -0,0 +1,14 @@ +package stackpot.stackpot.web.dto; + +public class FeedResponseDto { + + public static class FeedPreViewDto{ + + } + + public static class FeedPreViewListDto{ + + } + + +} From 489e9e6a24cbdd125a30febcd7796ffd718ae96e Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Wed, 22 Jan 2025 23:58:52 +0900 Subject: [PATCH 42/76] =?UTF-8?q?[FEAT/#20]=20feed=20view=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stackpot/repository/FeedRepository/FeedRepository.java | 2 ++ src/main/java/stackpot/stackpot/service/FeedService.java | 2 ++ src/main/java/stackpot/stackpot/service/FeedServiceImpl.java | 2 ++ 3 files changed, 6 insertions(+) create mode 100644 src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java create mode 100644 src/main/java/stackpot/stackpot/service/FeedService.java create mode 100644 src/main/java/stackpot/stackpot/service/FeedServiceImpl.java diff --git a/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java b/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java new file mode 100644 index 00000000..d8288c83 --- /dev/null +++ b/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java @@ -0,0 +1,2 @@ +package stackpot.stackpot.repository.FeedRepository;public interface FeedRepository { +} diff --git a/src/main/java/stackpot/stackpot/service/FeedService.java b/src/main/java/stackpot/stackpot/service/FeedService.java new file mode 100644 index 00000000..a7791877 --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/FeedService.java @@ -0,0 +1,2 @@ +package stackpot.stackpot.service;public class FeedService { +} diff --git a/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java b/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java new file mode 100644 index 00000000..6385217a --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java @@ -0,0 +1,2 @@ +package stackpot.stackpot.service;public class FeedServiceImpl { +} From 315dcd83ec0f3ff224f2b62fbeba4ee826cc8f69 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Wed, 22 Jan 2025 23:59:48 +0900 Subject: [PATCH 43/76] =?UTF-8?q?[FEAT/#20]=20feed=20view=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stackpot/converter/FeedConverter.java | 3 +- .../stackpot/converter/FeedConverterImpl.java | 20 +++++-- .../java/stackpot/stackpot/domain/Feed.java | 1 + .../stackpot/stackpot/domain/enums/Role.java | 2 +- .../FeedRepository/FeedRepository.java | 40 ++++++++++++- .../stackpot/service/FeedService.java | 7 ++- .../stackpot/service/FeedServiceImpl.java | 56 ++++++++++++++++++- .../web/controller/FeedController.java | 19 +++++-- .../stackpot/web/dto/FeedResponseDto.java | 31 ++++++++-- 9 files changed, 157 insertions(+), 22 deletions(-) diff --git a/src/main/java/stackpot/stackpot/converter/FeedConverter.java b/src/main/java/stackpot/stackpot/converter/FeedConverter.java index 77b654c4..7d13bf98 100644 --- a/src/main/java/stackpot/stackpot/converter/FeedConverter.java +++ b/src/main/java/stackpot/stackpot/converter/FeedConverter.java @@ -7,7 +7,6 @@ import java.util.List; public interface FeedConverter { - FeedResponseDto.FeedPreViewDto feedPreViewDto(Feed feed); + FeedResponseDto.FeedDto feedDto(Feed feed, int popularity, int likeCount); - FeedResponseDto.FeedPreViewListDto feedPreViewListDTO(List feedList); } diff --git a/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java b/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java index 62f640e3..5b326766 100644 --- a/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java @@ -1,18 +1,26 @@ package stackpot.stackpot.converter; +import lombok.Builder; +import org.springframework.stereotype.Component; import stackpot.stackpot.domain.Feed; import stackpot.stackpot.web.dto.FeedResponseDto; import java.util.List; +@Component public class FeedConverterImpl implements FeedConverter{ - @Override - public FeedResponseDto.FeedPreViewDto feedPreViewDto(Feed feed) { - return null; - } @Override - public FeedResponseDto.FeedPreViewListDto feedPreViewListDTO(List feedList) { - return null; + public FeedResponseDto.FeedDto feedDto(Feed feed, int popularity, int likeCount) { + return FeedResponseDto.FeedDto.builder() + .id(feed.getFeedId()) + .writer(feed.getUser().getNickname()) + .category(feed.getMainPart()) + .title(feed.getTitle()) + .content(feed.getContent()) + .popularity(popularity) + .likeCount(likeCount) + .createdAt(feed.getCreatedAt()) + .build(); } } diff --git a/src/main/java/stackpot/stackpot/domain/Feed.java b/src/main/java/stackpot/stackpot/domain/Feed.java index f886040f..88f50643 100644 --- a/src/main/java/stackpot/stackpot/domain/Feed.java +++ b/src/main/java/stackpot/stackpot/domain/Feed.java @@ -7,6 +7,7 @@ @Entity @Getter +@Setter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor diff --git a/src/main/java/stackpot/stackpot/domain/enums/Role.java b/src/main/java/stackpot/stackpot/domain/enums/Role.java index fc3989a3..f4d36103 100644 --- a/src/main/java/stackpot/stackpot/domain/enums/Role.java +++ b/src/main/java/stackpot/stackpot/domain/enums/Role.java @@ -1,5 +1,5 @@ package stackpot.stackpot.domain.enums; public enum Role { - Design, Beckend, Frontend, PM; + Design, Backend, Frontend, PM; } diff --git a/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java b/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java index d8288c83..d1202f19 100644 --- a/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java +++ b/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java @@ -1,2 +1,38 @@ -package stackpot.stackpot.repository.FeedRepository;public interface FeedRepository { -} +package stackpot.stackpot.repository.FeedRepository; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import stackpot.stackpot.domain.Feed; + + +import java.time.LocalDateTime; +import java.util.List; +@Repository +public interface FeedRepository extends JpaRepository { + + @Query("SELECT f, " + + " (COALESCE(FL.likeCount, 0) + COALESCE(FS.saveCount, 0)) AS popularity, " + + " COALESCE(FL.likeCount, 0) AS likeCount " + + "FROM Feed f " + + "LEFT JOIN (SELECT fl.feed.id AS feedId, COUNT(fl) AS likeCount FROM FeedLike fl GROUP BY fl.feed.id) FL " + + "ON f.id = FL.feedId " + + "LEFT JOIN (SELECT fs.feed.id AS feedId, COUNT(fs) AS saveCount FROM FeedSave fs GROUP BY fs.feed.id) FS " + + "ON f.id = FS.feedId " + + "WHERE (:mainPart IS NULL OR :mainPart = '전체' OR f.mainPart = :mainPart) " + + "AND ((:sort = 'new' AND f.createdAt < :lastCreatedAt) " + + " OR (:sort = 'old' AND f.createdAt > :lastCreatedAt)) " + + "ORDER BY " + + "CASE WHEN :sort = 'popular' THEN (COALESCE(FL.likeCount, 0) + COALESCE(FS.saveCount, 0)) END DESC, " + + "CASE WHEN :sort = 'new' THEN f.createdAt END DESC, " + + "CASE WHEN :sort = 'old' THEN f.createdAt END ASC") + List findFeeds( + @Param("mainPart") String mainPart, + @Param("sort") String sort, + @Param("lastCreatedAt") LocalDateTime lastCreatedAt, + Pageable pageable); + + +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/service/FeedService.java b/src/main/java/stackpot/stackpot/service/FeedService.java index a7791877..27ac2298 100644 --- a/src/main/java/stackpot/stackpot/service/FeedService.java +++ b/src/main/java/stackpot/stackpot/service/FeedService.java @@ -1,2 +1,7 @@ -package stackpot.stackpot.service;public class FeedService { +package stackpot.stackpot.service; + +import stackpot.stackpot.web.dto.FeedResponseDto; + +public interface FeedService { + public FeedResponseDto.FeedResponse getPreViewFeeds(String category, String sort, String cursor, int limit); } diff --git a/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java b/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java index 6385217a..95f7cf7e 100644 --- a/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java @@ -1,2 +1,54 @@ -package stackpot.stackpot.service;public class FeedServiceImpl { -} +package stackpot.stackpot.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import stackpot.stackpot.converter.FeedConverter; +import stackpot.stackpot.converter.FeedConverterImpl; +import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.repository.FeedRepository.FeedRepository; +import stackpot.stackpot.web.dto.FeedResponseDto; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; +@Service +@RequiredArgsConstructor +public class FeedServiceImpl implements FeedService { + + private final FeedRepository feedRepository; + private final FeedConverter feedConverter; + + @Override + public FeedResponseDto.FeedResponse getPreViewFeeds(String mainPart, String sort, String cursor, int limit) { + // 커서가 없으면 현재 시간 사용 + LocalDateTime lastCreatedAt = cursor != null + ? LocalDateTime.parse(cursor) + : LocalDateTime.now(); + + // Pageable 생성 + Pageable pageable = PageRequest.of(0, limit); + + // 데이터 조회 + List feedResults = feedRepository.findFeeds(mainPart, sort, lastCreatedAt, pageable); + + // Feed와 인기 점수를 DTO로 변환 + List feedDtoList = feedResults.stream() + .map(result -> { + Feed feed = (Feed) result[0]; + int popularity = (int) result[1]; + int likeCount = (int) result[2]; + + return feedConverter.feedDto(feed, popularity, likeCount); + }) + .collect(Collectors.toList()); + + // 다음 커서 계산 + String nextCursor = feedResults.isEmpty() + ? null + : ((Feed) feedResults.get(feedResults.size() - 1)[0]).getCreatedAt().toString(); + + return new FeedResponseDto.FeedResponse(feedDtoList, nextCursor); + } +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/controller/FeedController.java b/src/main/java/stackpot/stackpot/web/controller/FeedController.java index 99cb7eb3..be85e253 100644 --- a/src/main/java/stackpot/stackpot/web/controller/FeedController.java +++ b/src/main/java/stackpot/stackpot/web/controller/FeedController.java @@ -1,9 +1,12 @@ package stackpot.stackpot.web.controller; +import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import stackpot.stackpot.service.FeedService; +import stackpot.stackpot.web.dto.FeedResponseDto; import stackpot.stackpot.web.dto.PotRequestDto; import stackpot.stackpot.web.dto.PotResponseDto; @@ -12,9 +15,17 @@ @RequiredArgsConstructor public class FeedController { - @GetMapping - public ResponseEntity feedPreView() { -// return ResponseEntity.ok(); - return null; + private final FeedService feedService; + + @Operation(summary = "피드 미리보기 api") + @GetMapping("") + public ResponseEntity getPreViewFeeds( + @RequestParam(value = "category", required = false, defaultValue = "전체") String category, + @RequestParam(value = "sort", required = false, defaultValue = "new") String sort, + @RequestParam(value = "cursor", required = false) String cursor, + @RequestParam(value = "limit", defaultValue = "10") int limit) { + + FeedResponseDto.FeedResponse response = feedService.getPreViewFeeds(category, sort, cursor, limit); + return ResponseEntity.ok(response); } } diff --git a/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java index 002e9d56..35476b63 100644 --- a/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java @@ -1,13 +1,36 @@ package stackpot.stackpot.web.dto; -public class FeedResponseDto { +import lombok.*; - public static class FeedPreViewDto{ +import java.time.LocalDateTime; +import java.util.List; - } +public class FeedResponseDto { - public static class FeedPreViewListDto{ + @Data + @Getter + @Setter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class FeedResponse { + private List feeds; + private String nextCursor; // 다음 커서 값 + } + @Data + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class FeedDto { + private Long id; + private String writer; + private String category; + private String title; + private String content; + private int likeCount; + private LocalDateTime createdAt; + private int popularity; // } From c4963a3f1a9ebf038380db2f3d3f47a40bd2103b Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Thu, 23 Jan 2025 02:53:46 +0900 Subject: [PATCH 44/76] =?UTF-8?q?[FEAT/#21]=20feed=20=EC=9E=91=EC=84=B1=20?= =?UTF-8?q?api=20=EA=B5=AC=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stackpot/converter/FeedConverter.java | 2 ++ .../stackpot/converter/FeedConverterImpl.java | 12 ++++++- .../java/stackpot/stackpot/domain/Feed.java | 4 +-- .../stackpot/service/FeedService.java | 3 ++ .../stackpot/service/FeedServiceImpl.java | 21 +++++++++++ .../web/controller/FeedController.java | 36 +++++++++++++++++-- .../stackpot/web/dto/FeedRequestDto.java | 17 +++++++++ 7 files changed, 88 insertions(+), 7 deletions(-) diff --git a/src/main/java/stackpot/stackpot/converter/FeedConverter.java b/src/main/java/stackpot/stackpot/converter/FeedConverter.java index 7d13bf98..4659cd61 100644 --- a/src/main/java/stackpot/stackpot/converter/FeedConverter.java +++ b/src/main/java/stackpot/stackpot/converter/FeedConverter.java @@ -2,11 +2,13 @@ import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.web.dto.FeedRequestDto; import stackpot.stackpot.web.dto.FeedResponseDto; import java.util.List; public interface FeedConverter { FeedResponseDto.FeedDto feedDto(Feed feed, int popularity, int likeCount); + Feed toFeed(FeedRequestDto.createDto request); } diff --git a/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java b/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java index 5b326766..de62cfe0 100644 --- a/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java @@ -3,6 +3,7 @@ import lombok.Builder; import org.springframework.stereotype.Component; import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.web.dto.FeedRequestDto; import stackpot.stackpot.web.dto.FeedResponseDto; import java.util.List; @@ -15,7 +16,7 @@ public FeedResponseDto.FeedDto feedDto(Feed feed, int popularity, int likeCount) return FeedResponseDto.FeedDto.builder() .id(feed.getFeedId()) .writer(feed.getUser().getNickname()) - .category(feed.getMainPart()) +// .category(feed.getMainPart()) .title(feed.getTitle()) .content(feed.getContent()) .popularity(popularity) @@ -23,4 +24,13 @@ public FeedResponseDto.FeedDto feedDto(Feed feed, int popularity, int likeCount) .createdAt(feed.getCreatedAt()) .build(); } + + @Override + public Feed toFeed(FeedRequestDto.createDto request) { + return Feed.builder() + .title(request.getTitle()) + .content(request.getContent()) + .visibility(request.getVisibility()) + .build(); + } } diff --git a/src/main/java/stackpot/stackpot/domain/Feed.java b/src/main/java/stackpot/stackpot/domain/Feed.java index 88f50643..14c15157 100644 --- a/src/main/java/stackpot/stackpot/domain/Feed.java +++ b/src/main/java/stackpot/stackpot/domain/Feed.java @@ -26,11 +26,9 @@ public class Feed extends BaseEntity{ @Column(nullable = false, columnDefinition = "TEXT") private String content; - @Column(nullable = false, length = 10) +// @Column(nullable = false, length = 10) private String mainPart; - @Column(nullable = false, length = 10) - private String interest; @Enumerated(EnumType.STRING) @Column(nullable = false) diff --git a/src/main/java/stackpot/stackpot/service/FeedService.java b/src/main/java/stackpot/stackpot/service/FeedService.java index 27ac2298..7087e37d 100644 --- a/src/main/java/stackpot/stackpot/service/FeedService.java +++ b/src/main/java/stackpot/stackpot/service/FeedService.java @@ -1,7 +1,10 @@ package stackpot.stackpot.service; +import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.web.dto.FeedRequestDto; import stackpot.stackpot.web.dto.FeedResponseDto; public interface FeedService { public FeedResponseDto.FeedResponse getPreViewFeeds(String category, String sort, String cursor, int limit); + public Feed createFeed(FeedRequestDto.createDto request); } diff --git a/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java b/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java index 95f7cf7e..c9e2b2cc 100644 --- a/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java @@ -3,11 +3,16 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import stackpot.stackpot.converter.FeedConverter; import stackpot.stackpot.converter.FeedConverterImpl; import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.domain.User; import stackpot.stackpot.repository.FeedRepository.FeedRepository; +import stackpot.stackpot.repository.UserRepository.UserRepository; +import stackpot.stackpot.web.dto.FeedRequestDto; import stackpot.stackpot.web.dto.FeedResponseDto; import java.time.LocalDateTime; @@ -19,6 +24,7 @@ public class FeedServiceImpl implements FeedService { private final FeedRepository feedRepository; private final FeedConverter feedConverter; + private final UserRepository userRepository; @Override public FeedResponseDto.FeedResponse getPreViewFeeds(String mainPart, String sort, String cursor, int limit) { @@ -51,4 +57,19 @@ public FeedResponseDto.FeedResponse getPreViewFeeds(String mainPart, String sort return new FeedResponseDto.FeedResponse(feedDtoList, nextCursor); } + + @Override + public Feed createFeed(FeedRequestDto.createDto request) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + Feed feed = feedConverter.toFeed(request); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + + feed.setUser(user); + return feedRepository.save(feed); + + } } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/controller/FeedController.java b/src/main/java/stackpot/stackpot/web/controller/FeedController.java index be85e253..29da456c 100644 --- a/src/main/java/stackpot/stackpot/web/controller/FeedController.java +++ b/src/main/java/stackpot/stackpot/web/controller/FeedController.java @@ -3,12 +3,21 @@ import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindingResult; +import org.springframework.validation.ObjectError; import org.springframework.web.bind.annotation.*; +import stackpot.stackpot.converter.FeedConverter; +import stackpot.stackpot.converter.FeedConverterImpl; +import stackpot.stackpot.converter.UserConverter; +import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.domain.User; import stackpot.stackpot.service.FeedService; -import stackpot.stackpot.web.dto.FeedResponseDto; -import stackpot.stackpot.web.dto.PotRequestDto; -import stackpot.stackpot.web.dto.PotResponseDto; +import stackpot.stackpot.web.dto.*; + +import java.util.List; +import java.util.stream.Collectors; @RestController @RequestMapping("/feeds") @@ -16,6 +25,8 @@ public class FeedController { private final FeedService feedService; + private final FeedConverter feedConverter; + @Operation(summary = "피드 미리보기 api") @GetMapping("") @@ -28,4 +39,23 @@ public ResponseEntity getPreViewFeeds( FeedResponseDto.FeedResponse response = feedService.getPreViewFeeds(category, sort, cursor, limit); return ResponseEntity.ok(response); } + + @Operation(summary = "feed 작성 api") + @PostMapping("") + public ResponseEntity signup(@Valid @RequestBody FeedRequestDto.createDto requset, + BindingResult bindingResult) { + // 유효성 검사 실패 처리 + if (bindingResult.hasErrors()) { + // 에러 메시지 수집 + List errors = bindingResult.getAllErrors() + .stream() + .map(ObjectError::getDefaultMessage) + .collect(Collectors.toList()); + return ResponseEntity.badRequest().body(errors); + } + // 정상 처리 + Feed feed = feedService.createFeed(requset); + FeedResponseDto.FeedDto response = feedConverter.feedDto(feed, 0,0); + return ResponseEntity.status(HttpStatus.CREATED).body(response); + } } diff --git a/src/main/java/stackpot/stackpot/web/dto/FeedRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/FeedRequestDto.java index 623c28ed..f68f2260 100644 --- a/src/main/java/stackpot/stackpot/web/dto/FeedRequestDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/FeedRequestDto.java @@ -1,4 +1,21 @@ package stackpot.stackpot.web.dto; +import jakarta.persistence.Column; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import stackpot.stackpot.domain.enums.Visibility; + public class FeedRequestDto { + @Getter + @Setter + @NoArgsConstructor + public static class createDto { + private String title; + private String content; + private Visibility visibility; + } } From 882bb52ba4028d4b88c294c4996ce693a1d0bc70 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Thu, 23 Jan 2025 03:16:29 +0900 Subject: [PATCH 45/76] =?UTF-8?q?[FEAT/#22]=20service=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stackpot/stackpot/service/FeedService.java | 1 + .../stackpot/service/FeedServiceImpl.java | 8 ++++++++ .../stackpot/web/controller/FeedController.java | 16 ++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/src/main/java/stackpot/stackpot/service/FeedService.java b/src/main/java/stackpot/stackpot/service/FeedService.java index 7087e37d..5eed6a0b 100644 --- a/src/main/java/stackpot/stackpot/service/FeedService.java +++ b/src/main/java/stackpot/stackpot/service/FeedService.java @@ -7,4 +7,5 @@ public interface FeedService { public FeedResponseDto.FeedResponse getPreViewFeeds(String category, String sort, String cursor, int limit); public Feed createFeed(FeedRequestDto.createDto request); + public boolean toggleLike(Long feedId); } diff --git a/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java b/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java index c9e2b2cc..65e3b16d 100644 --- a/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java @@ -72,4 +72,12 @@ public Feed createFeed(FeedRequestDto.createDto request) { return feedRepository.save(feed); } + + @Override + public boolean toggleLike(Long feedId) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + return false; + } } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/controller/FeedController.java b/src/main/java/stackpot/stackpot/web/controller/FeedController.java index 29da456c..d620b860 100644 --- a/src/main/java/stackpot/stackpot/web/controller/FeedController.java +++ b/src/main/java/stackpot/stackpot/web/controller/FeedController.java @@ -16,7 +16,9 @@ import stackpot.stackpot.service.FeedService; import stackpot.stackpot.web.dto.*; +import java.security.Principal; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @RestController @@ -58,4 +60,18 @@ public ResponseEntity signup(@Valid @RequestBody FeedRequestDto.createDto req FeedResponseDto.FeedDto response = feedConverter.feedDto(feed, 0,0); return ResponseEntity.status(HttpStatus.CREATED).body(response); } + @Operation(summary = "feed 좋아요 추가 api") + @PostMapping("/{feedId}/like") + public ResponseEntity toggleLike(@PathVariable Long feedId) { + + // 좋아요 토글 +// boolean isLiked = feedService + +// return ResponseEntity.ok(Map.of( +// "liked", isLiked, +// "message", isLiked ? "좋아요를 눌렀습니다." : "좋아요를 취소했습니다." +// )); + return null; + } + } From 1d9859836c45cebe3caed114efd1f564cc8c0ba7 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Thu, 23 Jan 2025 03:33:56 +0900 Subject: [PATCH 46/76] =?UTF-8?q?[FEAT/#22]=20feed=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=20=EC=B6=94=EA=B0=80=20api=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/stackpot/stackpot/repository/FeedLikeRepository.java | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/main/java/stackpot/stackpot/repository/FeedLikeRepository.java diff --git a/src/main/java/stackpot/stackpot/repository/FeedLikeRepository.java b/src/main/java/stackpot/stackpot/repository/FeedLikeRepository.java new file mode 100644 index 00000000..02a7befd --- /dev/null +++ b/src/main/java/stackpot/stackpot/repository/FeedLikeRepository.java @@ -0,0 +1,2 @@ +package stackpot.stackpot.repository;public interface FeedLikeRepository { +} From e53de2351c3e9895085c6f01eef43a376ce5c409 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Thu, 23 Jan 2025 03:34:08 +0900 Subject: [PATCH 47/76] =?UTF-8?q?[FEAT/#22]=20feed=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=20=EC=B6=94=EA=B0=80=20api=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stackpot/domain/mapping/FeedLike.java | 1 + .../repository/FeedLikeRepository.java | 22 +++++++++++++- .../FeedRepository/FeedRepository.java | 5 +++- .../stackpot/service/FeedServiceImpl.java | 29 +++++++++++++++++-- .../web/controller/FeedController.java | 16 ++++------ 5 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/main/java/stackpot/stackpot/domain/mapping/FeedLike.java b/src/main/java/stackpot/stackpot/domain/mapping/FeedLike.java index f1bab33a..79fc96f7 100644 --- a/src/main/java/stackpot/stackpot/domain/mapping/FeedLike.java +++ b/src/main/java/stackpot/stackpot/domain/mapping/FeedLike.java @@ -8,6 +8,7 @@ @Entity @Getter +@Setter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor diff --git a/src/main/java/stackpot/stackpot/repository/FeedLikeRepository.java b/src/main/java/stackpot/stackpot/repository/FeedLikeRepository.java index 02a7befd..cd198575 100644 --- a/src/main/java/stackpot/stackpot/repository/FeedLikeRepository.java +++ b/src/main/java/stackpot/stackpot/repository/FeedLikeRepository.java @@ -1,2 +1,22 @@ -package stackpot.stackpot.repository;public interface FeedLikeRepository { +package stackpot.stackpot.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.mapping.FeedLike; + +import java.util.Optional; + +@Repository +public interface FeedLikeRepository extends JpaRepository { + + // 특정 사용자가 특정 게시물에 좋아요를 눌렀는지 확인 + Optional findByFeedAndUser(Feed feed, User user); + + // 특정 게시물의 좋아요 개수 조회 + @Query("SELECT COUNT(fl) FROM FeedLike fl WHERE fl.feed = :feed") + Long countByFeed(@Param("feed") Feed feed); } diff --git a/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java b/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java index d1202f19..525159bf 100644 --- a/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java +++ b/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java @@ -6,10 +6,14 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.mapping.FeedLike; import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; + @Repository public interface FeedRepository extends JpaRepository { @@ -34,5 +38,4 @@ List findFeeds( @Param("lastCreatedAt") LocalDateTime lastCreatedAt, Pageable pageable); - } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java b/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java index 65e3b16d..0b22fbfa 100644 --- a/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java @@ -10,6 +10,8 @@ import stackpot.stackpot.converter.FeedConverterImpl; import stackpot.stackpot.domain.Feed; import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.mapping.FeedLike; +import stackpot.stackpot.repository.FeedLikeRepository; import stackpot.stackpot.repository.FeedRepository.FeedRepository; import stackpot.stackpot.repository.UserRepository.UserRepository; import stackpot.stackpot.web.dto.FeedRequestDto; @@ -17,6 +19,7 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -25,6 +28,7 @@ public class FeedServiceImpl implements FeedService { private final FeedRepository feedRepository; private final FeedConverter feedConverter; private final UserRepository userRepository; + private final FeedLikeRepository feedLikeRepository; @Override public FeedResponseDto.FeedResponse getPreViewFeeds(String mainPart, String sort, String cursor, int limit) { @@ -76,8 +80,29 @@ public Feed createFeed(FeedRequestDto.createDto request) { @Override public boolean toggleLike(Long feedId) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - String email = authentication.getName(); + String userEmail = authentication.getName(); + + Feed feed = feedRepository.findById(feedId) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + + User user = userRepository.findByEmail(userEmail) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); - return false; + Optional existingLike = feedLikeRepository.findByFeedAndUser(feed, user); + + if (existingLike.isPresent()) { + // 이미 좋아요가 있다면 삭제 (좋아요 취소) + feedLikeRepository.delete(existingLike.get()); + return false; // 좋아요 취소 + } else { + // 좋아요 추가 + FeedLike feedLike = FeedLike.builder() + .feed(feed) + .user(user) + .build(); + + feedLikeRepository.save(feedLike); + return true; // 좋아요 성공 + } } } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/controller/FeedController.java b/src/main/java/stackpot/stackpot/web/controller/FeedController.java index d620b860..f4ebc33c 100644 --- a/src/main/java/stackpot/stackpot/web/controller/FeedController.java +++ b/src/main/java/stackpot/stackpot/web/controller/FeedController.java @@ -9,14 +9,10 @@ import org.springframework.validation.ObjectError; import org.springframework.web.bind.annotation.*; import stackpot.stackpot.converter.FeedConverter; -import stackpot.stackpot.converter.FeedConverterImpl; -import stackpot.stackpot.converter.UserConverter; import stackpot.stackpot.domain.Feed; -import stackpot.stackpot.domain.User; import stackpot.stackpot.service.FeedService; import stackpot.stackpot.web.dto.*; -import java.security.Principal; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -65,13 +61,11 @@ public ResponseEntity signup(@Valid @RequestBody FeedRequestDto.createDto req public ResponseEntity toggleLike(@PathVariable Long feedId) { // 좋아요 토글 -// boolean isLiked = feedService - -// return ResponseEntity.ok(Map.of( -// "liked", isLiked, -// "message", isLiked ? "좋아요를 눌렀습니다." : "좋아요를 취소했습니다." -// )); - return null; + boolean isLiked = feedService.toggleLike(feedId); + return ResponseEntity.ok(Map.of( + "liked", isLiked, + "message", isLiked ? "좋아요를 눌렀습니다." : "좋아요를 취소했습니다." + )); } } From e50c7fe110badf2cb6bc622caa38b07ffbc74a8c Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Thu, 23 Jan 2025 18:12:25 +0900 Subject: [PATCH 48/76] =?UTF-8?q?[MOD/#22]=20feed=20domain=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stackpot/converter/FeedConverterImpl.java | 6 +-- .../java/stackpot/stackpot/domain/Feed.java | 8 ++-- .../stackpot/domain/enums/Category.java | 11 ++++++ .../stackpot/stackpot/domain/enums/Role.java | 5 ++- .../FeedRepository/FeedRepository.java | 6 ++- .../repository/FeedSaveRepository.java | 18 +++++++++ .../stackpot/service/FeedService.java | 5 ++- .../stackpot/service/FeedServiceImpl.java | 39 ++++++++++++++++++- .../web/controller/FeedController.java | 16 +++++++- .../stackpot/web/dto/FeedRequestDto.java | 3 ++ .../stackpot/web/dto/FeedResponseDto.java | 3 +- 11 files changed, 105 insertions(+), 15 deletions(-) create mode 100644 src/main/java/stackpot/stackpot/domain/enums/Category.java create mode 100644 src/main/java/stackpot/stackpot/repository/FeedSaveRepository.java diff --git a/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java b/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java index de62cfe0..461ff093 100644 --- a/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java @@ -1,13 +1,10 @@ package stackpot.stackpot.converter; -import lombok.Builder; import org.springframework.stereotype.Component; import stackpot.stackpot.domain.Feed; import stackpot.stackpot.web.dto.FeedRequestDto; import stackpot.stackpot.web.dto.FeedResponseDto; -import java.util.List; - @Component public class FeedConverterImpl implements FeedConverter{ @@ -16,7 +13,7 @@ public FeedResponseDto.FeedDto feedDto(Feed feed, int popularity, int likeCount) return FeedResponseDto.FeedDto.builder() .id(feed.getFeedId()) .writer(feed.getUser().getNickname()) -// .category(feed.getMainPart()) + .category(feed.getCategory()) .title(feed.getTitle()) .content(feed.getContent()) .popularity(popularity) @@ -30,6 +27,7 @@ public Feed toFeed(FeedRequestDto.createDto request) { return Feed.builder() .title(request.getTitle()) .content(request.getContent()) + .category(request.getCategor()) .visibility(request.getVisibility()) .build(); } diff --git a/src/main/java/stackpot/stackpot/domain/Feed.java b/src/main/java/stackpot/stackpot/domain/Feed.java index 14c15157..ca700da6 100644 --- a/src/main/java/stackpot/stackpot/domain/Feed.java +++ b/src/main/java/stackpot/stackpot/domain/Feed.java @@ -3,6 +3,8 @@ import jakarta.persistence.*; import lombok.*; import stackpot.stackpot.domain.common.BaseEntity; +import stackpot.stackpot.domain.enums.Category; +import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.domain.enums.Visibility; @Entity @@ -26,9 +28,9 @@ public class Feed extends BaseEntity{ @Column(nullable = false, columnDefinition = "TEXT") private String content; -// @Column(nullable = false, length = 10) - private String mainPart; - + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Category category; @Enumerated(EnumType.STRING) @Column(nullable = false) diff --git a/src/main/java/stackpot/stackpot/domain/enums/Category.java b/src/main/java/stackpot/stackpot/domain/enums/Category.java new file mode 100644 index 00000000..11160734 --- /dev/null +++ b/src/main/java/stackpot/stackpot/domain/enums/Category.java @@ -0,0 +1,11 @@ +package stackpot.stackpot.domain.enums; + +public enum Category { + + ALL, + BACKEND, + FRONTEND, + DESIGN, + PLANNING + +} diff --git a/src/main/java/stackpot/stackpot/domain/enums/Role.java b/src/main/java/stackpot/stackpot/domain/enums/Role.java index f4d36103..f3617965 100644 --- a/src/main/java/stackpot/stackpot/domain/enums/Role.java +++ b/src/main/java/stackpot/stackpot/domain/enums/Role.java @@ -1,5 +1,8 @@ package stackpot.stackpot.domain.enums; public enum Role { - Design, Backend, Frontend, PM; + BACKEND, + FRONTEND, + DESIGN, + PLANNING, } diff --git a/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java b/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java index 525159bf..1f10a7a0 100644 --- a/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java +++ b/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java @@ -7,6 +7,8 @@ import org.springframework.stereotype.Repository; import stackpot.stackpot.domain.Feed; import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.enums.Category; +import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.domain.mapping.FeedLike; @@ -25,7 +27,7 @@ public interface FeedRepository extends JpaRepository { "ON f.id = FL.feedId " + "LEFT JOIN (SELECT fs.feed.id AS feedId, COUNT(fs) AS saveCount FROM FeedSave fs GROUP BY fs.feed.id) FS " + "ON f.id = FS.feedId " + - "WHERE (:mainPart IS NULL OR :mainPart = '전체' OR f.mainPart = :mainPart) " + + "WHERE (:category IS NULL OR :category = '전체' OR f.category = :category) " + "AND ((:sort = 'new' AND f.createdAt < :lastCreatedAt) " + " OR (:sort = 'old' AND f.createdAt > :lastCreatedAt)) " + "ORDER BY " + @@ -33,7 +35,7 @@ public interface FeedRepository extends JpaRepository { "CASE WHEN :sort = 'new' THEN f.createdAt END DESC, " + "CASE WHEN :sort = 'old' THEN f.createdAt END ASC") List findFeeds( - @Param("mainPart") String mainPart, + @Param("category") Category category, @Param("sort") String sort, @Param("lastCreatedAt") LocalDateTime lastCreatedAt, Pageable pageable); diff --git a/src/main/java/stackpot/stackpot/repository/FeedSaveRepository.java b/src/main/java/stackpot/stackpot/repository/FeedSaveRepository.java new file mode 100644 index 00000000..909a6e8a --- /dev/null +++ b/src/main/java/stackpot/stackpot/repository/FeedSaveRepository.java @@ -0,0 +1,18 @@ +package stackpot.stackpot.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.mapping.FeedSave; + +import java.util.Optional; + +@Repository +public interface FeedSaveRepository extends JpaRepository { + // 특정 사용자가 특정 게시물에 좋아요를 눌렀는지 확인 + Optional findByFeedAndUser(Feed feed, User user); +} + diff --git a/src/main/java/stackpot/stackpot/service/FeedService.java b/src/main/java/stackpot/stackpot/service/FeedService.java index 5eed6a0b..cae2229a 100644 --- a/src/main/java/stackpot/stackpot/service/FeedService.java +++ b/src/main/java/stackpot/stackpot/service/FeedService.java @@ -1,11 +1,14 @@ package stackpot.stackpot.service; import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.domain.enums.Category; +import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.web.dto.FeedRequestDto; import stackpot.stackpot.web.dto.FeedResponseDto; public interface FeedService { - public FeedResponseDto.FeedResponse getPreViewFeeds(String category, String sort, String cursor, int limit); + public FeedResponseDto.FeedResponse getPreViewFeeds(Category category, String sort, String cursor, int limit); public Feed createFeed(FeedRequestDto.createDto request); public boolean toggleLike(Long feedId); + public boolean toggleSave(Long feedId); } diff --git a/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java b/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java index 0b22fbfa..9949271b 100644 --- a/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java @@ -10,14 +10,19 @@ import stackpot.stackpot.converter.FeedConverterImpl; import stackpot.stackpot.domain.Feed; import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.enums.Category; +import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.domain.mapping.FeedLike; +import stackpot.stackpot.domain.mapping.FeedSave; import stackpot.stackpot.repository.FeedLikeRepository; import stackpot.stackpot.repository.FeedRepository.FeedRepository; +import stackpot.stackpot.repository.FeedSaveRepository; import stackpot.stackpot.repository.UserRepository.UserRepository; import stackpot.stackpot.web.dto.FeedRequestDto; import stackpot.stackpot.web.dto.FeedResponseDto; import java.time.LocalDateTime; +import java.util.Calendar; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -29,9 +34,10 @@ public class FeedServiceImpl implements FeedService { private final FeedConverter feedConverter; private final UserRepository userRepository; private final FeedLikeRepository feedLikeRepository; + private final FeedSaveRepository feedSaveRepository; @Override - public FeedResponseDto.FeedResponse getPreViewFeeds(String mainPart, String sort, String cursor, int limit) { + public FeedResponseDto.FeedResponse getPreViewFeeds(Category categor, String sort, String cursor, int limit) { // 커서가 없으면 현재 시간 사용 LocalDateTime lastCreatedAt = cursor != null ? LocalDateTime.parse(cursor) @@ -41,7 +47,7 @@ public FeedResponseDto.FeedResponse getPreViewFeeds(String mainPart, String sort Pageable pageable = PageRequest.of(0, limit); // 데이터 조회 - List feedResults = feedRepository.findFeeds(mainPart, sort, lastCreatedAt, pageable); + List feedResults = feedRepository.findFeeds(categor, sort, lastCreatedAt, pageable); // Feed와 인기 점수를 DTO로 변환 List feedDtoList = feedResults.stream() @@ -105,4 +111,33 @@ public boolean toggleLike(Long feedId) { return true; // 좋아요 성공 } } + + @Override + public boolean toggleSave(Long feedId) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String userEmail = authentication.getName(); + + Feed feed = feedRepository.findById(feedId) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + + User user = userRepository.findByEmail(userEmail) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + + Optional existingSave = feedSaveRepository.findByFeedAndUser(feed, user); + + if (existingSave.isPresent()) { + // 이미 좋아요가 있다면 삭제 (좋아요 취소) + feedSaveRepository.delete(existingSave.get()); + return false; // 좋아요 취소 + } else { + // 좋아요 추가 + FeedSave feedSave = FeedSave.builder() + .feed(feed) + .user(user) + .build(); + + feedSaveRepository.save(feedSave); + return true; // 좋아요 성공 + } + } } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/controller/FeedController.java b/src/main/java/stackpot/stackpot/web/controller/FeedController.java index f4ebc33c..a62ded37 100644 --- a/src/main/java/stackpot/stackpot/web/controller/FeedController.java +++ b/src/main/java/stackpot/stackpot/web/controller/FeedController.java @@ -10,6 +10,8 @@ import org.springframework.web.bind.annotation.*; import stackpot.stackpot.converter.FeedConverter; import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.domain.enums.Category; +import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.service.FeedService; import stackpot.stackpot.web.dto.*; @@ -29,7 +31,7 @@ public class FeedController { @Operation(summary = "피드 미리보기 api") @GetMapping("") public ResponseEntity getPreViewFeeds( - @RequestParam(value = "category", required = false, defaultValue = "전체") String category, + @RequestParam(value = "category", required = false, defaultValue = "ALL") Category category, @RequestParam(value = "sort", required = false, defaultValue = "new") String sort, @RequestParam(value = "cursor", required = false) String cursor, @RequestParam(value = "limit", defaultValue = "10") int limit) { @@ -68,4 +70,16 @@ public ResponseEntity toggleLike(@PathVariable Long feedId) { )); } + @Operation(summary = "feed 저장하기 api") + @PostMapping("/{feedId}/save") + public ResponseEntity toggleSave(@PathVariable Long feedId) { + + // 좋아요 토글 + boolean isSaved = feedService.toggleSave(feedId); + return ResponseEntity.ok(Map.of( + "liked", isSaved, + "message", isSaved ? "좋아요를 눌렀습니다." : "좋아요를 취소했습니다." + )); + } + } diff --git a/src/main/java/stackpot/stackpot/web/dto/FeedRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/FeedRequestDto.java index f68f2260..58463c1f 100644 --- a/src/main/java/stackpot/stackpot/web/dto/FeedRequestDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/FeedRequestDto.java @@ -7,6 +7,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import stackpot.stackpot.domain.enums.Category; +import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.domain.enums.Visibility; public class FeedRequestDto { @@ -16,6 +18,7 @@ public class FeedRequestDto { public static class createDto { private String title; private String content; + private Category categor; private Visibility visibility; } } diff --git a/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java index 35476b63..37b682a9 100644 --- a/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java @@ -1,6 +1,7 @@ package stackpot.stackpot.web.dto; import lombok.*; +import stackpot.stackpot.domain.enums.Category; import java.time.LocalDateTime; import java.util.List; @@ -25,7 +26,7 @@ public static class FeedResponse { public static class FeedDto { private Long id; private String writer; - private String category; + private Category category; private String title; private String content; private int likeCount; From 4f86a1851e8e5068033875573e469d8bc31a921b Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Thu, 23 Jan 2025 18:15:29 +0900 Subject: [PATCH 49/76] =?UTF-8?q?[FEAT/#22]=20feed=20save=20=EB=A9=94?= =?UTF-8?q?=EC=84=B8=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/stackpot/stackpot/service/FeedServiceImpl.java | 4 +--- .../java/stackpot/stackpot/web/controller/FeedController.java | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java b/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java index 9949271b..1910990a 100644 --- a/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java @@ -126,11 +126,9 @@ public boolean toggleSave(Long feedId) { Optional existingSave = feedSaveRepository.findByFeedAndUser(feed, user); if (existingSave.isPresent()) { - // 이미 좋아요가 있다면 삭제 (좋아요 취소) feedSaveRepository.delete(existingSave.get()); - return false; // 좋아요 취소 + return false; } else { - // 좋아요 추가 FeedSave feedSave = FeedSave.builder() .feed(feed) .user(user) diff --git a/src/main/java/stackpot/stackpot/web/controller/FeedController.java b/src/main/java/stackpot/stackpot/web/controller/FeedController.java index a62ded37..6f26d071 100644 --- a/src/main/java/stackpot/stackpot/web/controller/FeedController.java +++ b/src/main/java/stackpot/stackpot/web/controller/FeedController.java @@ -77,8 +77,8 @@ public ResponseEntity toggleSave(@PathVariable Long feedId) { // 좋아요 토글 boolean isSaved = feedService.toggleSave(feedId); return ResponseEntity.ok(Map.of( - "liked", isSaved, - "message", isSaved ? "좋아요를 눌렀습니다." : "좋아요를 취소했습니다." + "saved", isSaved, + "message", isSaved ? "해당 피드를 저장했습니다." : "해당 피드 저장을 취소했습니다.." )); } From ac45fca09688401a8383980fa2eec1c0407dd468 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Thu, 23 Jan 2025 20:15:38 +0900 Subject: [PATCH 50/76] =?UTF-8?q?[FEAT/#24]=20feed=20detail=20=EB=B7=B0,?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stackpot/converter/FeedConverter.java | 2 +- .../stackpot/converter/FeedConverterImpl.java | 2 +- .../repository/FeedSaveRepository.java | 3 + .../stackpot/service/FeedService.java | 10 +++- .../stackpot/service/FeedServiceImpl.java | 55 +++++++++++++++-- .../web/controller/FeedController.java | 59 +++++++++++++++---- .../stackpot/web/dto/FeedResponseDto.java | 6 +- 7 files changed, 114 insertions(+), 23 deletions(-) diff --git a/src/main/java/stackpot/stackpot/converter/FeedConverter.java b/src/main/java/stackpot/stackpot/converter/FeedConverter.java index 4659cd61..39fd895c 100644 --- a/src/main/java/stackpot/stackpot/converter/FeedConverter.java +++ b/src/main/java/stackpot/stackpot/converter/FeedConverter.java @@ -8,7 +8,7 @@ import java.util.List; public interface FeedConverter { - FeedResponseDto.FeedDto feedDto(Feed feed, int popularity, int likeCount); + FeedResponseDto.FeedDto feedDto(Feed feed, long popularity, long likeCount); Feed toFeed(FeedRequestDto.createDto request); } diff --git a/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java b/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java index 461ff093..16158bad 100644 --- a/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java @@ -9,7 +9,7 @@ public class FeedConverterImpl implements FeedConverter{ @Override - public FeedResponseDto.FeedDto feedDto(Feed feed, int popularity, int likeCount) { + public FeedResponseDto.FeedDto feedDto(Feed feed, long popularity, long likeCount) { return FeedResponseDto.FeedDto.builder() .id(feed.getFeedId()) .writer(feed.getUser().getNickname()) diff --git a/src/main/java/stackpot/stackpot/repository/FeedSaveRepository.java b/src/main/java/stackpot/stackpot/repository/FeedSaveRepository.java index 909a6e8a..f03e2dac 100644 --- a/src/main/java/stackpot/stackpot/repository/FeedSaveRepository.java +++ b/src/main/java/stackpot/stackpot/repository/FeedSaveRepository.java @@ -14,5 +14,8 @@ public interface FeedSaveRepository extends JpaRepository { // 특정 사용자가 특정 게시물에 좋아요를 눌렀는지 확인 Optional findByFeedAndUser(Feed feed, User user); + + @Query("SELECT COUNT(fl) FROM FeedSave fl WHERE fl.feed = :feed") + Long countByFeed(@Param("feed") Feed feed); } diff --git a/src/main/java/stackpot/stackpot/service/FeedService.java b/src/main/java/stackpot/stackpot/service/FeedService.java index cae2229a..da1944e7 100644 --- a/src/main/java/stackpot/stackpot/service/FeedService.java +++ b/src/main/java/stackpot/stackpot/service/FeedService.java @@ -2,13 +2,19 @@ import stackpot.stackpot.domain.Feed; import stackpot.stackpot.domain.enums.Category; -import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.web.dto.FeedRequestDto; import stackpot.stackpot.web.dto.FeedResponseDto; public interface FeedService { - public FeedResponseDto.FeedResponse getPreViewFeeds(Category category, String sort, String cursor, int limit); + public FeedResponseDto.FeedPreviewList getPreViewFeeds(Category category, String sort, String cursor, int limit); public Feed createFeed(FeedRequestDto.createDto request); + + public Feed getFeed(Long feedId); + + public Feed modifyFeed(long feedId, FeedRequestDto.createDto request); public boolean toggleLike(Long feedId); public boolean toggleSave(Long feedId); + + public Long getSaveCount(Long feedId); + public Long getLikeCount(Long feedId); } diff --git a/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java b/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java index 1910990a..8a9e1896 100644 --- a/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/FeedServiceImpl.java @@ -7,11 +7,9 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import stackpot.stackpot.converter.FeedConverter; -import stackpot.stackpot.converter.FeedConverterImpl; import stackpot.stackpot.domain.Feed; import stackpot.stackpot.domain.User; import stackpot.stackpot.domain.enums.Category; -import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.domain.mapping.FeedLike; import stackpot.stackpot.domain.mapping.FeedSave; import stackpot.stackpot.repository.FeedLikeRepository; @@ -22,7 +20,6 @@ import stackpot.stackpot.web.dto.FeedResponseDto; import java.time.LocalDateTime; -import java.util.Calendar; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -37,7 +34,7 @@ public class FeedServiceImpl implements FeedService { private final FeedSaveRepository feedSaveRepository; @Override - public FeedResponseDto.FeedResponse getPreViewFeeds(Category categor, String sort, String cursor, int limit) { + public FeedResponseDto.FeedPreviewList getPreViewFeeds(Category categor, String sort, String cursor, int limit) { // 커서가 없으면 현재 시간 사용 LocalDateTime lastCreatedAt = cursor != null ? LocalDateTime.parse(cursor) @@ -65,7 +62,7 @@ public FeedResponseDto.FeedResponse getPreViewFeeds(Category categor, String sor ? null : ((Feed) feedResults.get(feedResults.size() - 1)[0]).getCreatedAt().toString(); - return new FeedResponseDto.FeedResponse(feedDtoList, nextCursor); + return new FeedResponseDto.FeedPreviewList(feedDtoList, nextCursor); } @Override @@ -83,6 +80,40 @@ public Feed createFeed(FeedRequestDto.createDto request) { } + @Override + public Feed getFeed(Long feedId) { + Feed feed = feedRepository.findById(feedId) + .orElseThrow(()->new IllegalArgumentException("해당 피드를 찾을 수 없습니다.")); + return feed; + } + + @Override + public Feed modifyFeed(long feedId, FeedRequestDto.createDto request) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + Feed feed = feedRepository.findById(feedId) + .orElseThrow(() -> new IllegalArgumentException("해당 피드를 찾을 수 없습니다.")); + + if(!feed.getUser().getEmail().equals(email)){ + throw new SecurityException("해당 피드를 수정할 권한이 없습니다."); + } + + if(request.getTitle() != null){ + feed.setTitle(request.getTitle()); + } + if(request.getContent() != null){ + feed.setContent(request.getContent()); + } + if(request.getVisibility() != null){ + feed.setVisibility(request.getVisibility()); + } + if(request.getCategor() != null){ + feed.setCategory(request.getCategor()); + } + return feedRepository.save(feed); + } + @Override public boolean toggleLike(Long feedId) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); @@ -138,4 +169,18 @@ public boolean toggleSave(Long feedId) { return true; // 좋아요 성공 } } + + @Override + public Long getSaveCount(Long feedId) { + Feed feed = feedRepository.findById(feedId) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + return feedSaveRepository.countByFeed(feed); + } + + @Override + public Long getLikeCount(Long feedId) { + Feed feed = feedRepository.findById(feedId) + .orElseThrow(() -> new IllegalArgumentException("게시물을 찾을 수 없습니다.")); + return feedLikeRepository.countByFeed(feed); + } } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/controller/FeedController.java b/src/main/java/stackpot/stackpot/web/controller/FeedController.java index 6f26d071..80723ebf 100644 --- a/src/main/java/stackpot/stackpot/web/controller/FeedController.java +++ b/src/main/java/stackpot/stackpot/web/controller/FeedController.java @@ -11,7 +11,6 @@ import stackpot.stackpot.converter.FeedConverter; import stackpot.stackpot.domain.Feed; import stackpot.stackpot.domain.enums.Category; -import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.service.FeedService; import stackpot.stackpot.web.dto.*; @@ -28,21 +27,55 @@ public class FeedController { private final FeedConverter feedConverter; - @Operation(summary = "피드 미리보기 api") + @Operation(summary = "feed 작성 api") + @PostMapping("") + public ResponseEntity createFeeds(@Valid @RequestBody FeedRequestDto.createDto requset, + BindingResult bindingResult) { + // 유효성 검사 실패 처리 + if (bindingResult.hasErrors()) { + // 에러 메시지 수집 + List errors = bindingResult.getAllErrors() + .stream() + .map(ObjectError::getDefaultMessage) + .collect(Collectors.toList()); + return ResponseEntity.badRequest().body(errors); + } + // 정상 처리 + Feed feed = feedService.createFeed(requset); + Long feedId = feed.getFeedId(); + Long saveCount = feedService.getSaveCount(feedId); + Long likeCount = feedService.getLikeCount(feedId); + + FeedResponseDto.FeedDto response = feedConverter.feedDto(feed, likeCount+saveCount,likeCount); + return ResponseEntity.status(HttpStatus.CREATED).body(response); + } + + @Operation(summary = "feed 미리보기 api") @GetMapping("") - public ResponseEntity getPreViewFeeds( + public ResponseEntity getPreViewFeeds( @RequestParam(value = "category", required = false, defaultValue = "ALL") Category category, @RequestParam(value = "sort", required = false, defaultValue = "new") String sort, @RequestParam(value = "cursor", required = false) String cursor, @RequestParam(value = "limit", defaultValue = "10") int limit) { - FeedResponseDto.FeedResponse response = feedService.getPreViewFeeds(category, sort, cursor, limit); + FeedResponseDto.FeedPreviewList response = feedService.getPreViewFeeds(category, sort, cursor, limit); return ResponseEntity.ok(response); } + @Operation(summary = "feed 상세보기 api") + @PostMapping("/{feedId}") + public ResponseEntity getDetailFeed(@PathVariable Long feedId) { - @Operation(summary = "feed 작성 api") - @PostMapping("") - public ResponseEntity signup(@Valid @RequestBody FeedRequestDto.createDto requset, + Feed feed = feedService.getFeed(feedId); + Long saveCount = feedService.getSaveCount(feedId); + Long likeCount = feedService.getLikeCount(feedId); + + FeedResponseDto.FeedDto response = feedConverter.feedDto(feed, likeCount+saveCount,likeCount); + return ResponseEntity.status(HttpStatus.CREATED).body(response); + } + + @Operation(summary = "feed 수정 api") + @PatchMapping("/{feedId}") + public ResponseEntity modifyFeed(@PathVariable Long feedId, @Valid @RequestBody FeedRequestDto.createDto requset, BindingResult bindingResult) { // 유효성 검사 실패 처리 if (bindingResult.hasErrors()) { @@ -54,9 +87,13 @@ public ResponseEntity signup(@Valid @RequestBody FeedRequestDto.createDto req return ResponseEntity.badRequest().body(errors); } // 정상 처리 - Feed feed = feedService.createFeed(requset); - FeedResponseDto.FeedDto response = feedConverter.feedDto(feed, 0,0); - return ResponseEntity.status(HttpStatus.CREATED).body(response); + Feed feed = feedService.modifyFeed(feedId, requset); + Long saveCount = feedService.getSaveCount(feedId); + Long likeCount = feedService.getLikeCount(feedId); + + FeedResponseDto.FeedDto response =feedConverter.feedDto(feed, likeCount+saveCount,likeCount); + + return ResponseEntity.ok(response); } @Operation(summary = "feed 좋아요 추가 api") @PostMapping("/{feedId}/like") @@ -78,7 +115,7 @@ public ResponseEntity toggleSave(@PathVariable Long feedId) { boolean isSaved = feedService.toggleSave(feedId); return ResponseEntity.ok(Map.of( "saved", isSaved, - "message", isSaved ? "해당 피드를 저장했습니다." : "해당 피드 저장을 취소했습니다.." + "message", isSaved ? "해당 피드를 저장했습니다." : "해당 피드 저장을 취소했습니다." )); } diff --git a/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java index 37b682a9..6cbcf03e 100644 --- a/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java @@ -14,7 +14,7 @@ public class FeedResponseDto { @Builder @AllArgsConstructor @NoArgsConstructor - public static class FeedResponse { + public static class FeedPreviewList { private List feeds; private String nextCursor; // 다음 커서 값 } @@ -29,9 +29,9 @@ public static class FeedDto { private Category category; private String title; private String content; - private int likeCount; + private Long popularity; + private Long likeCount; private LocalDateTime createdAt; - private int popularity; // } From 2f591d0c0f602b503c4eb9ee323765186bf21675 Mon Sep 17 00:00:00 2001 From: rudeore-098 <75055749+rudeore-098@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:41:02 +0900 Subject: [PATCH 51/76] =?UTF-8?q?[ADD]=20application.yml=20=EB=A6=AC?= =?UTF-8?q?=EB=8B=A4=EC=9D=B4=EB=A0=89=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 09e01ec5..66d25a4d 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -38,7 +38,7 @@ spring: client-authentication-method: client_secret_post client-id: ${KAKAO_CLIENT_ID} client-secret: ${KAKAO_CLIENT_SECRET} - redirect-uri: http://localhost:8080/login/oauth2/code/kakao + redirect-uri: https://stackpot.co.kr/login/oauth2/code/kakao authorization-grant-type: authorization_code scope: account_email client-name: Kakao @@ -49,4 +49,4 @@ spring: user-info-uri: https://kapi.kakao.com/v2/user/me user-name-attribute: id jwt: - secret: ${JWT_SECRET} \ No newline at end of file + secret: ${JWT_SECRET} From 9695760c09152180b9bce1d4fbc81ac309db9964 Mon Sep 17 00:00:00 2001 From: MiN <81948815+leesumin0526@users.noreply.github.com> Date: Fri, 24 Jan 2025 20:38:39 +0900 Subject: [PATCH 52/76] =?UTF-8?q?Feat/#12=20=EC=A0=84=EC=B2=B4=20=ED=8C=9F?= =?UTF-8?q?=20=ED=8C=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84=20(#26)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [feat/#12] 전체 팟 보기 api 구현 * [feat/#12] D-day와 모집분야별 필터링 구현 * [feat/#12] 팟 상세보기 API 구현 * [Feat/#12] Impl 구분, 필드명 통일 * [Feat/#12] Token 기준으로 수정 및 기능 개발 - 지원한 팟 목록 - 지원자 맘에 들어요 & 취소 - 지원자 맘에 들어요 목록 - 팟 전체 보기 (특정 모집 분야, 인기순) - 팟 상세 보기 * [Feat/#12] 내가 만든 팟 목록 * [Feat/#12] API 기능 개발중 * [Feat/#12] 전체 팟 기능 구현 완료 & 마이팟 리스트 반환 & todo 생성 구현 완료 * [feat/#12] API들 수정 * [feat/#12] API들 수정2 * [feat/#12] Todo 생성 API 수정 완료 * [feat/#12] api 설명 작성 중 * [feat/#12] 전체 팟 기능 구현 완료 * [feat/#12] redirect url 수정 --- .gitignore | 3 +- build.gradle | 5 + .../stackpot/config/OpenAIConfig.java | 20 + .../java/stackpot/stackpot/domain/Pot.java | 14 +- .../domain/PotRecruitmentDetails.java | 8 +- .../domain/mapping/PotApplication.java | 6 +- .../stackpot/domain/mapping/PotMember.java | 4 +- .../stackpot/domain/mapping/UserTodo.java | 1 + .../PotRepository/MyPotRepository.java | 16 + .../PotRepository/PotRepository.java | 14 + .../stackpot/service/MyPotService.java | 22 ++ .../stackpot/service/MyPotServiceImpl.java | 220 +++++++++++ .../stackpot/stackpot/service/PotService.java | 29 ++ .../stackpot/service/PotServiceImpl.java | 374 +++++++++++++++++- .../service/PotSummarizationService.java | 46 +++ .../web/controller/MyPotController.java | 58 +++ .../web/controller/PotController.java | 101 +++++ .../web/dto/ApplicantResponseDTO.java | 24 ++ .../stackpot/web/dto/LikeRequestDTO.java | 15 + .../web/dto/LikedApplicantResponseDTO.java | 13 + .../stackpot/web/dto/MyPotResponseDTO.java | 23 ++ .../stackpot/web/dto/MyPotTodoRequestDTO.java | 16 + .../web/dto/MyPotTodoResponseDTO.java | 30 ++ .../web/dto/MyPotTodoUpdateRequestDTO.java | 14 + .../stackpot/web/dto/PotAllResponseDTO.java | 27 ++ .../web/dto/PotMemberResponseDTO.java | 20 + .../stackpot/web/dto/PotResponseDto.java | 1 + .../web/dto/PotSummaryResponseDTO.java | 10 + .../dto/RecruitmentDetailsResponseDTO.java | 15 + 29 files changed, 1138 insertions(+), 11 deletions(-) create mode 100644 src/main/java/stackpot/stackpot/config/OpenAIConfig.java create mode 100644 src/main/java/stackpot/stackpot/repository/PotRepository/MyPotRepository.java create mode 100644 src/main/java/stackpot/stackpot/service/MyPotService.java create mode 100644 src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java create mode 100644 src/main/java/stackpot/stackpot/service/PotSummarizationService.java create mode 100644 src/main/java/stackpot/stackpot/web/controller/MyPotController.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/ApplicantResponseDTO.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/LikeRequestDTO.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/LikedApplicantResponseDTO.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/MyPotResponseDTO.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/MyPotTodoRequestDTO.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/MyPotTodoResponseDTO.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/MyPotTodoUpdateRequestDTO.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/PotAllResponseDTO.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/PotMemberResponseDTO.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/PotSummaryResponseDTO.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/RecruitmentDetailsResponseDTO.java diff --git a/.gitignore b/.gitignore index 91ef0b97..8f62fc45 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ out/ .vscode/ .env -application-secrets.yml \ No newline at end of file +application-secrets.yml.DS_Store +.DS_Store diff --git a/build.gradle b/build.gradle index 6744c379..8a6e95c7 100644 --- a/build.gradle +++ b/build.gradle @@ -59,6 +59,11 @@ dependencies { // Test Dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' + implementation 'mysql:mysql-connector-java:8.0.33' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.security:spring-security-crypto' } tasks.named('test') { diff --git a/src/main/java/stackpot/stackpot/config/OpenAIConfig.java b/src/main/java/stackpot/stackpot/config/OpenAIConfig.java new file mode 100644 index 00000000..657bd029 --- /dev/null +++ b/src/main/java/stackpot/stackpot/config/OpenAIConfig.java @@ -0,0 +1,20 @@ +package stackpot.stackpot.config; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Getter +@Configuration +public class OpenAIConfig { + + @Value("${OPEN_API_KEY}") + private String apiKey; + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/domain/Pot.java b/src/main/java/stackpot/stackpot/domain/Pot.java index ce89576c..1bb1ff8d 100644 --- a/src/main/java/stackpot/stackpot/domain/Pot.java +++ b/src/main/java/stackpot/stackpot/domain/Pot.java @@ -4,9 +4,12 @@ import lombok.*; import stackpot.stackpot.domain.common.BaseEntity; import stackpot.stackpot.domain.enums.PotModeOfOperation; +import stackpot.stackpot.domain.mapping.PotApplication; +import stackpot.stackpot.domain.mapping.PotMember; import java.time.LocalDate; import java.util.Map; +import java.util.List; @Entity @Getter @@ -21,10 +24,18 @@ public class Pot extends BaseEntity { private Long potId; @Setter - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "user_id", nullable = false) private User user; + @OneToMany(mappedBy = "pot") + private List recruitmentDetails; + + @OneToMany(mappedBy = "pot", cascade = CascadeType.ALL, orphanRemoval = true) + private List potApplication; + + @OneToMany(mappedBy = "pot", cascade = CascadeType.ALL, orphanRemoval = true) + private List potMembers; @Column(nullable = false, length = 255) private String potName; @@ -44,6 +55,7 @@ public class Pot extends BaseEntity { @Column(nullable = true, columnDefinition = "TEXT") private String potContent; + @Setter @Column(nullable = false, length = 255) private String potStatus; diff --git a/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java b/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java index 51414d09..4ad72557 100644 --- a/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java +++ b/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java @@ -4,8 +4,6 @@ import lombok.*; import stackpot.stackpot.domain.common.BaseEntity; -import java.time.LocalDate; - @Entity @Getter @Builder @@ -16,12 +14,18 @@ public class PotRecruitmentDetails extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(nullable = false) + @Getter + @Setter private Long recruitmentId; @Column(nullable = true, length = 255) + @Getter + @Setter private String recruitmentRole; @Column(nullable = true) + @Getter + @Setter private Integer recruitmentCount; @ManyToOne(fetch = FetchType.LAZY) diff --git a/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java b/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java index 55b7b3e5..1b2cad2e 100644 --- a/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java +++ b/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java @@ -28,8 +28,9 @@ public class PotApplication extends BaseEntity { @Column(nullable = true) private LocalDateTime appliedAt; + @Setter @Column(nullable = false, columnDefinition = "BOOLEAN DEFAULT false") - private Boolean liked; + private Boolean liked = false; @Column(nullable = false, length = 10) private String potRole; // 팟 역할 @@ -45,4 +46,7 @@ public void setApplicationStatus(ApplicationStatus status) { this.status = status; } + @OneToOne(mappedBy = "potApplication", cascade = CascadeType.ALL, orphanRemoval = true) + private PotMember potMember; + } diff --git a/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java b/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java index 4dd8c547..19919472 100644 --- a/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java +++ b/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java @@ -27,14 +27,14 @@ public class PotMember extends BaseEntity { private User user; @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "pot_application_id", nullable = false) + @JoinColumn(name = "application_id", nullable = false) private PotApplication potApplication; @Column(nullable = false, length = 10) private String roleName; @Column(nullable = false) - private String owner; + private boolean owner; @Column(nullable = false) private String appealContent; diff --git a/src/main/java/stackpot/stackpot/domain/mapping/UserTodo.java b/src/main/java/stackpot/stackpot/domain/mapping/UserTodo.java index 0304f639..08bec2ed 100644 --- a/src/main/java/stackpot/stackpot/domain/mapping/UserTodo.java +++ b/src/main/java/stackpot/stackpot/domain/mapping/UserTodo.java @@ -9,6 +9,7 @@ @Entity @Getter +@Setter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor diff --git a/src/main/java/stackpot/stackpot/repository/PotRepository/MyPotRepository.java b/src/main/java/stackpot/stackpot/repository/PotRepository/MyPotRepository.java new file mode 100644 index 00000000..2fd35dc9 --- /dev/null +++ b/src/main/java/stackpot/stackpot/repository/PotRepository/MyPotRepository.java @@ -0,0 +1,16 @@ +package stackpot.stackpot.repository.PotRepository; + +import org.springframework.data.jpa.repository.JpaRepository; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.mapping.UserTodo; +import stackpot.stackpot.web.dto.MyPotTodoResponseDTO; + +import java.util.List; +import java.util.Optional; + +public interface MyPotRepository extends JpaRepository { + List findByPot_PotId(Long potId); + List findByPotAndUser(Pot pot, User user); + +} diff --git a/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java b/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java index b90e83b5..ccd1f52b 100644 --- a/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java +++ b/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java @@ -2,8 +2,22 @@ +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.mapping.PotApplication; +import stackpot.stackpot.domain.mapping.UserTodo; + +import java.util.List; +import java.util.Optional; public interface PotRepository extends JpaRepository { + Page findByRecruitmentDetails_RecruitmentRole(String recruitmentRole, Pageable pageable); + Optional findPotWithRecruitmentDetailsByPotId(Long potId); + List findByPotApplication_User_Id(Long userId); + List findByUserId(Long userId); + Page findAll(Pageable pageable); } diff --git a/src/main/java/stackpot/stackpot/service/MyPotService.java b/src/main/java/stackpot/stackpot/service/MyPotService.java new file mode 100644 index 00000000..70aa5b3c --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/MyPotService.java @@ -0,0 +1,22 @@ +package stackpot.stackpot.service; + +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.mapping.UserTodo; +import stackpot.stackpot.web.dto.*; + +import java.util.List; + +public interface MyPotService { + + // 사용자의 진행 중인 팟 조회 + List getMyOnGoingPots(); + + // 사용자의 특정 팟에서의 생성 + List postTodo(Long potId, MyPotTodoRequestDTO requestDTO); + + + List getTodo(Long potId); + + List updateTodos(Long potId, List requestList); + +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java b/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java new file mode 100644 index 00000000..7313df36 --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java @@ -0,0 +1,220 @@ +package stackpot.stackpot.service; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.mapping.UserTodo; +import stackpot.stackpot.repository.PotRepository.MyPotRepository; +import stackpot.stackpot.repository.PotRepository.PotRepository; +import stackpot.stackpot.repository.UserRepository.UserRepository; +import stackpot.stackpot.web.dto.*; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class MyPotServiceImpl implements MyPotService { + + private final PotRepository potRepository; + private final MyPotRepository myPotRepository; + private final UserRepository userRepository; + + + @Override + public List getMyOnGoingPots() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + + // 사용자가 만든 팟 조회 + List myPots = potRepository.findByUserId(user.getId()); + + // 진행 중인 팟 리스트 변환 (멤버 정보 포함) + List ongoingPots = myPots.stream() + .filter(pot -> "recruiting".equals(pot.getPotStatus())) + .map(this::convertToOngoingPotDetail) + .collect(Collectors.toList()); + + // MyPotResponseDTO로 변환하여 반환 + return List.of(MyPotResponseDTO.builder() + .ongoingPots(ongoingPots) + .build()); + } + + + @Override + public List postTodo(Long potId, MyPotTodoRequestDTO requestDTO) { + // 현재 인증된 사용자 가져오기 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + + // 해당 Pot 존재 여부 확인 + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + + + // To-Do 생성 + UserTodo userTodo = UserTodo.builder() + .pot(pot) + .user(user) + .content(requestDTO.getContent()) + .status(requestDTO.getStatus()) + .build(); + + myPotRepository.save(userTodo); + + // 특정 팟의 모든 To-Do 조회 (업데이트된 리스트) + List potTodos = myPotRepository.findByPot_PotId(potId); + + // 사용자별로 그룹화하여 반환 + return potTodos.stream() + .collect(Collectors.groupingBy(UserTodo::getUser)) + .entrySet().stream() + .map(entry -> MyPotTodoResponseDTO.builder() + .userNickname(entry.getKey().getNickname()) + .userId(entry.getKey().getId()) + .todos(entry.getValue().stream() + .map(todo -> MyPotTodoResponseDTO.TodoDetailDTO.builder() + .todoId(todo.getTodoId()) + .content(todo.getContent()) + .status(todo.getStatus()) + .build()) + .collect(Collectors.toList())) + .build()) + .collect(Collectors.toList()); + } + + @Override + public List getTodo(Long potId) { + // 현재 인증된 사용자 가져오기 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + + // 해당 Pot 존재 여부 확인 + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + + // 특정 팟의 모든 To-Do 조회 + List potTodos = myPotRepository.findByPot_PotId(potId); + + // 사용자별로 그룹화하여 반환 + return potTodos.stream() + .collect(Collectors.groupingBy(UserTodo::getUser)) + .entrySet().stream() + .map(entry -> MyPotTodoResponseDTO.builder() + .userNickname(entry.getKey().getNickname()) + .userId(entry.getKey().getId()) + .todos(entry.getValue().stream() + .map(todo -> MyPotTodoResponseDTO.TodoDetailDTO.builder() + .todoId(todo.getTodoId()) + .content(todo.getContent()) + .status(todo.getStatus()) + .build()) + .collect(Collectors.toList())) + .build()) + .collect(Collectors.toList()); + } + + @Override + @Transactional + public List updateTodos(Long potId, List requestList) { + // 현재 인증된 사용자 가져오기 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + + // 해당 Pot 존재 여부 확인 + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + + // 특정 팟에 속한 모든 투두 리스트 조회 (사용자별) + List userTodos = myPotRepository.findByPotAndUser(pot, user); + + // 요청된 todoId와 일치하는 항목 업데이트 + Map todoMap = userTodos.stream() + .collect(Collectors.toMap(UserTodo::getTodoId, todo -> todo)); + + for (MyPotTodoUpdateRequestDTO updateRequest : requestList) { + UserTodo todo = todoMap.get(updateRequest.getTodoId()); + if (todo == null) { + throw new IllegalArgumentException("Todo with ID " + updateRequest.getTodoId() + " not found."); + } + + // 내용 업데이트 + todo.setContent(updateRequest.getContent()); + } + + // 변경된 상태 저장 + myPotRepository.saveAll(userTodos); + + // 사용자별로 그룹화하여 DTO로 변환 + Map> groupedByUser = userTodos.stream() + .collect(Collectors.groupingBy(UserTodo::getUser)); + + return groupedByUser.entrySet().stream() + .map(entry -> MyPotTodoResponseDTO.builder() + .userNickname(entry.getKey().getNickname()) + .userId(entry.getKey().getId()) + .todos(entry.getValue().stream() + .map(todo -> MyPotTodoResponseDTO.TodoDetailDTO.builder() + .todoId(todo.getTodoId()) + .content(todo.getContent()) + .status(todo.getStatus()) + .build()) + .collect(Collectors.toList())) + .build()) + .collect(Collectors.toList()); + } + + // 진행 중인 팟 변환 메서드 (멤버 포함) + private MyPotResponseDTO.OngoingPotsDetail convertToOngoingPotDetail(Pot pot) { + List recruitmentDetails = pot.getRecruitmentDetails().stream() + .map(details -> RecruitmentDetailsResponseDTO.builder() + .recruitmentId(details.getRecruitmentId()) + .recruitmentRole(details.getRecruitmentRole()) + .recruitmentCount(details.getRecruitmentCount()) + .build()) + .collect(Collectors.toList()); + + List potMembers = pot.getPotMembers().stream() + .map(member -> PotMemberResponseDTO.builder() + .potMemberId(member.getPotMemberId()) + .roleName(member.getRoleName()) + + .build()) + .collect(Collectors.toList()); + + return MyPotResponseDTO.OngoingPotsDetail.builder() + .user(UserResponseDto.builder() + .nickname(pot.getUser().getNickname()) + .role(pot.getUser().getRole()) + .build()) + .pot(PotResponseDto.builder() + .potName(pot.getPotName()) + .potStartDate(pot.getPotStartDate()) + .potEndDate(pot.getPotEndDate()) + .potStatus(pot.getPotStatus()) + .build()) + .potMembers(potMembers) + .build(); + } + + + +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/service/PotService.java b/src/main/java/stackpot/stackpot/service/PotService.java index 3367bcd7..cdf580d5 100644 --- a/src/main/java/stackpot/stackpot/service/PotService.java +++ b/src/main/java/stackpot/stackpot/service/PotService.java @@ -2,10 +2,39 @@ import stackpot.stackpot.web.dto.PotRequestDto; import stackpot.stackpot.web.dto.PotResponseDto; +import stackpot.stackpot.web.dto.*; + +import java.util.List; + public interface PotService { PotResponseDto createPotWithRecruitments(PotRequestDto requestDto); PotResponseDto updatePotWithRecruitments(Long potId, PotRequestDto requestDto); void deletePot(Long potId); + + //--------------- + + // 모집 역할에 따라 모든 팟 조회 + List getAllPots(String role, Integer page, Integer size); + + // 특정 팟의 세부 정보 조회 + ApplicantResponseDTO getPotDetails(Long potId); + + // 특정 지원자의 좋아요 상태 수정 + void patchLikes(Long potId, Long applicationId, Boolean liked); + + // 특정 팟의 좋아요한 지원자 목록 조회 + List getLikedApplicants(Long potId); + + // 사용자가 지원한 팟 목록 조회 + List getAppliedPots(); + + // 사용자가 참여 중인 팟 목록 조회 + List getMyPots(); + + // 팟 다 끓이기 + void patchPotStatus(Long potId); + + PotSummaryResponseDTO getPotSummary(Long potId); } diff --git a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java index 4bebfa9b..0c163388 100644 --- a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java @@ -3,20 +3,40 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.Authentication; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import stackpot.stackpot.config.security.JwtTokenProvider; import stackpot.stackpot.converter.PotConverter; import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.PotRecruitmentDetails; import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.mapping.PotApplication; +import stackpot.stackpot.domain.mapping.PotApplication; import stackpot.stackpot.repository.PotRepository.PotRecruitmentDetailsRepository; import stackpot.stackpot.repository.PotRepository.PotRepository; import stackpot.stackpot.repository.UserRepository.UserRepository; +import stackpot.stackpot.web.dto.*; import stackpot.stackpot.service.PotService; -import stackpot.stackpot.web.dto.PotRequestDto; -import stackpot.stackpot.web.dto.PotResponseDto; +import stackpot.stackpot.web.dto.*; import stackpot.stackpot.config.security.JwtTokenProvider; + +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; import org.springframework.security.core.context.SecurityContextHolder; +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -32,7 +52,6 @@ public class PotServiceImpl implements PotService { private final JwtTokenProvider jwtTokenProvider; private final UserRepository userRepository; - @Transactional public PotResponseDto createPotWithRecruitments(PotRequestDto requestDto) { // 인증 정보에서 사용자 이메일 가져오기 @@ -114,7 +133,6 @@ public PotResponseDto updatePotWithRecruitments(Long potId, PotRequestDto reques - @Transactional public void deletePot(Long potId) { // 인증 정보에서 사용자 이메일 가져오기 @@ -141,4 +159,352 @@ public void deletePot(Long potId) { potRepository.delete(pot); } + + //------------------- + + private final PotSummarizationService potSummarizationService; + + @Transactional + @Override + public List getAllPots(String role, Integer page, Integer size) { + Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending()); + Page potPage; + + if (role == null || role.isEmpty()) { + potPage = potRepository.findAll(pageable); + } else { + potPage = potRepository.findByRecruitmentDetails_RecruitmentRole(role, pageable); + } + + return potPage.getContent().stream() + .map(pot -> PotAllResponseDTO.PotDetail.builder() + .user(UserResponseDto.builder() + .nickname(pot.getUser().getNickname()) + .role(pot.getUser().getRole()) + .build()) + .pot(potConverter.toDto(pot, pot.getRecruitmentDetails())) // 변환기 사용 + .build()) + .collect(Collectors.toList()); + } + + + @Override + public ApplicantResponseDTO getPotDetails(Long potId) { + Pot pot = potRepository.findPotWithRecruitmentDetailsByPotId(potId) + .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + + // 지원자 정보를 DTO로 변환 + List applicantDto = pot.getPotApplication().stream() + .map(app -> ApplicantResponseDTO.ApplicantDto.builder() + .applicationId(app.getApplicationId()) + .potRole(app.getPotRole()) + .liked(app.getLiked()) + .build()) + .collect(Collectors.toList()); + + // 모집 정보를 DTO로 변환 + List recruitmentDetailsDto = pot.getRecruitmentDetails().stream() + .map(details -> PotRecruitmentResponseDto.builder() + .recruitmentId(details.getRecruitmentId()) + .recruitmentRole(details.getRecruitmentRole()) + .recruitmentCount(details.getRecruitmentCount()) + .build()) + .collect(Collectors.toList()); + + // Pot 정보를 DTO로 변환 + PotResponseDto potDto = PotResponseDto.builder() + .potId(pot.getPotId()) + .potName(pot.getPotName()) + .potStartDate(pot.getPotStartDate()) + .potEndDate(pot.getPotEndDate()) + .potDuration(pot.getPotDuration()) + .potLan(pot.getPotLan()) + .potContent(pot.getPotContent()) + .potStatus(pot.getPotStatus()) + .potSummary(pot.getPotSummary()) + .recruitmentDeadline(pot.getRecruitmentDeadline()) + .recruitmentDetails(recruitmentDetailsDto) + .potModeOfOperation(pot.getPotModeOfOperation().name()) + .dDay(Math.toIntExact(ChronoUnit.DAYS.between(LocalDate.now(), pot.getRecruitmentDeadline()))) + .build(); + + return ApplicantResponseDTO.builder() + .user(UserResponseDto.builder() + .nickname(pot.getUser().getNickname()) + .role(pot.getUser().getRole()) + .build()) + .pot(potDto) + .applicant(applicantDto) + .build(); + } + + // 특정 팟 지원자의 좋아요 상태 변경 + @Override + public void patchLikes(Long potId, Long applicationId, Boolean liked) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + + // 팟 생성자 확인 + if (!pot.getUser().getId().equals(user.getId())) { + throw new SecurityException("Only the pot owner can modify likes."); + } + + PotApplication application = pot.getPotApplication().stream() + .filter(app -> app.getApplicationId().equals(applicationId)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Application not found with id: " + applicationId)); + + application.setLiked(liked); + potRepository.save(pot); + } + + // 특정 팟의 좋아요한 지원자 목록 조회 + @Override + public List getLikedApplicants(Long potId) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + + // 팟 생성자 확인 + if (!pot.getUser().getId().equals(user.getId())) { + throw new SecurityException("Only the pot owner can view liked applicants."); + } + + return pot.getPotApplication().stream() + .filter(PotApplication::getLiked) + .map(app -> LikedApplicantResponseDTO.builder() + .applicationId(app.getApplicationId()) + .applicantRole(app.getPotRole()) + .potNickname(app.getUser().getNickname() + getVegetableNameByRole(app.getPotRole())) + .liked(app.getLiked()) + .build()) + .collect(Collectors.toList()); + /*pot role + 브로콜리 : 디자이너 + 당근 : 기획자 + 양파 : 백앤드 + 버섯 : 프론트앤드 + */ + } + + private String getVegetableNameByRole(String role) { + Map roleToVegetableMap = Map.of( + "디자이너", " 브로콜리", + "기획자", " 당근", + "백앤드", " 양파", + "프론트앤드", " 버섯" + ); + + return roleToVegetableMap.getOrDefault(role, "알 수 없음"); + } + + + @Override + public List getAppliedPots() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + + // 사용자가 지원한 팟 조회 + List appliedPots = potRepository.findByPotApplication_User_Id(user.getId()); + + // Pot 리스트를 PotAllResponseDTO.PotDetail로 변환 + return appliedPots.stream().map(pot -> { + // 모집 정보를 DTO로 변환 + List recruitmentDetailsDto = pot.getRecruitmentDetails().stream() + .map(details -> PotRecruitmentResponseDto.builder() + .recruitmentId(details.getRecruitmentId()) + .recruitmentRole(details.getRecruitmentRole()) + .recruitmentCount(details.getRecruitmentCount()) + .build()) + .collect(Collectors.toList()); + + // Pot 정보를 DTO로 변환 + PotResponseDto potDto = PotResponseDto.builder() + .potId(pot.getPotId()) + .potName(pot.getPotName()) + .potStartDate(pot.getPotStartDate()) + .potEndDate(pot.getPotEndDate()) + .potDuration(pot.getPotDuration()) + .potLan(pot.getPotLan()) + .potContent(pot.getPotContent()) + .potStatus(pot.getPotStatus()) + .potSummary(pot.getPotSummary()) + .recruitmentDeadline(pot.getRecruitmentDeadline()) + .recruitmentDetails(recruitmentDetailsDto) + .potModeOfOperation(pot.getPotModeOfOperation().name()) + .dDay(Math.toIntExact(ChronoUnit.DAYS.between(LocalDate.now(), pot.getRecruitmentDeadline()))) + .build(); + + // 유저 정보를 DTO로 변환 + UserResponseDto userDto = UserResponseDto.builder() + .nickname(pot.getUser().getNickname()) + .role(pot.getUser().getRole()) + .build(); + + return PotAllResponseDTO.PotDetail.builder() + .user(userDto) + .pot(potDto) + .build(); + }).collect(Collectors.toList()); + } + + // 사용자가 만든 팟 조회 + @Override + public List getMyPots() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + + // 사용자가 만든 팟 조회 + List myPots = potRepository.findByUserId(user.getId()); + + // 모집중인 팟 리스트 + List recruitingPots = myPots.stream() + .filter(pot -> "ongoing".equals(pot.getPotStatus())) + .map(this::convertToPotDetail) + .collect(Collectors.toList()); + + // 진행 중인 팟 리스트 변환 (멤버 정보 포함) + List ongoingPots = myPots.stream() + .filter(pot -> "recruiting".equals(pot.getPotStatus())) + .map(this::convertToOngoingPotDetail) + .collect(Collectors.toList()); + + // 끓인 팟 리스트 + List completedPots = myPots.stream() + .filter(pot -> "completed".equals(pot.getPotStatus())) + .map(this::convertToPotDetail) + .collect(Collectors.toList()); + + return List.of(PotAllResponseDTO.builder() + .recruitingPots(recruitingPots) + .ongoingPots(ongoingPots) + .completedPots(completedPots) + .build()); + + } + + @Override + public void patchPotStatus(Long potId) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + + // 팟 생성자 확인 + if (!pot.getUser().getId().equals(user.getId())) { + throw new SecurityException("Only the pot owner can modify pot status."); + } + + // 팟 상태를 "complete"으로 변경 + pot.setPotStatus("complete"); + + // 변경된 상태 저장 + potRepository.save(pot); + } + + @Override + public PotSummaryResponseDTO getPotSummary(Long potId) { + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + + String prompt = "구인글에 내용을 우리 프로젝트를 소개하는 400자로 정리해줘. " + + "기획 배경, 주요기능, 어떤 언어와 프레임워크 사용했는지 등등 구체적인게 들어있으면 더 좋아.\n" + + "내용: " + pot.getPotContent(); + + String summary = potSummarizationService.summarizeText(prompt, 400); + + return PotSummaryResponseDTO.builder() + .summary(summary) + .build(); + } + + // Pot을 PotAllResponseDTO.PotDetail로 변환하는 메서드 + private PotAllResponseDTO.PotDetail convertToPotDetail(Pot pot) { + List recruitmentDetailsDto = pot.getRecruitmentDetails().stream() + .map(details -> PotRecruitmentResponseDto.builder() + .recruitmentId(details.getRecruitmentId()) + .recruitmentRole(details.getRecruitmentRole()) + .recruitmentCount(details.getRecruitmentCount()) + .build()) + .collect(Collectors.toList()); + + PotResponseDto potDto = PotResponseDto.builder() + .potId(pot.getPotId()) + .potName(pot.getPotName()) + .potStartDate(pot.getPotStartDate()) + .potEndDate(pot.getPotEndDate()) + .potDuration(pot.getPotDuration()) + .potLan(pot.getPotLan()) + .potContent(pot.getPotContent()) + .potStatus(pot.getPotStatus()) + .potSummary(pot.getPotSummary()) + .recruitmentDeadline(pot.getRecruitmentDeadline()) + .recruitmentDetails(recruitmentDetailsDto) + .potModeOfOperation(pot.getPotModeOfOperation().name()) + .dDay(Math.toIntExact(ChronoUnit.DAYS.between(LocalDate.now(), pot.getRecruitmentDeadline()))) + .build(); + + return PotAllResponseDTO.PotDetail.builder() + .user(UserResponseDto.builder() + .nickname(pot.getUser().getNickname() + getVegetableNameByRole(pot.getUser().getRole())) + .role(pot.getUser().getRole()) + .build()) + .pot(potDto) + .build(); + } + + // 진행 중인 팟 변환 메서드 (멤버 포함) + private MyPotResponseDTO.OngoingPotsDetail convertToOngoingPotDetail(Pot pot) { + List recruitmentDetails = pot.getRecruitmentDetails().stream() + .map(details -> RecruitmentDetailsResponseDTO.builder() + .recruitmentId(details.getRecruitmentId()) + .recruitmentRole(details.getRecruitmentRole()) + .recruitmentCount(details.getRecruitmentCount()) + .build()) + .collect(Collectors.toList()); + + List potMembers = pot.getPotMembers().stream() + .map(member -> PotMemberResponseDTO.builder() + .potMemberId(member.getPotMemberId()) + .roleName(member.getRoleName()) + + .build()) + .collect(Collectors.toList()); + + return MyPotResponseDTO.OngoingPotsDetail.builder() + .user(UserResponseDto.builder() + .nickname(pot.getUser().getNickname() + getVegetableNameByRole(pot.getUser().getRole())) + .role(pot.getUser().getRole()) + .build()) + .pot(PotResponseDto.builder() + .potId(pot.getPotId()) + .potName(pot.getPotName()) + .potStartDate(pot.getPotStartDate()) + .potEndDate(pot.getPotEndDate()) + .potStatus(pot.getPotStatus()) + .build()) + .potMembers(potMembers) + .build(); + } } diff --git a/src/main/java/stackpot/stackpot/service/PotSummarizationService.java b/src/main/java/stackpot/stackpot/service/PotSummarizationService.java new file mode 100644 index 00000000..8516a49d --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/PotSummarizationService.java @@ -0,0 +1,46 @@ +package stackpot.stackpot.service; + +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import stackpot.stackpot.config.OpenAIConfig; + +import java.util.Collections; +import java.util.Map; + +@Service +public class PotSummarizationService { + private static final String OPENAI_API_URL = "https://api.openai.com/v1/chat/completions"; + private final RestTemplate restTemplate; + private final OpenAIConfig openAIConfig; + + public PotSummarizationService(RestTemplate restTemplate, OpenAIConfig openAIConfig) { + this.restTemplate = restTemplate; + this.openAIConfig = openAIConfig; + } + + public String summarizeText(String text, int maxTokens) { + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(openAIConfig.getApiKey()); + headers.setContentType(MediaType.APPLICATION_JSON); + + Map requestBody = Map.of( + "model", "gpt-4-turbo", + "messages", Collections.singletonList( + Map.of("role", "user", "content", text) + ), + "max_tokens", maxTokens + ); + + HttpEntity> entity = new HttpEntity<>(requestBody, headers); + ResponseEntity response = restTemplate.exchange(OPENAI_API_URL, HttpMethod.POST, entity, Map.class); + + Map responseBody = response.getBody(); + if (responseBody != null && responseBody.containsKey("choices")) { + Map choice = (Map) ((java.util.List) responseBody.get("choices")).get(0); + Map message = (Map) choice.get("message"); + return message.get("content"); + } + throw new RuntimeException("Failed to summarize text"); + } +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/controller/MyPotController.java b/src/main/java/stackpot/stackpot/web/controller/MyPotController.java new file mode 100644 index 00000000..59ef30cf --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/controller/MyPotController.java @@ -0,0 +1,58 @@ +package stackpot.stackpot.web.controller; + +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stackpot.stackpot.apiPayload.ApiResponse; +import stackpot.stackpot.service.MyPotService; +import stackpot.stackpot.service.PotService; +import stackpot.stackpot.web.dto.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +public class MyPotController { + + private final MyPotService myPotService; + + + // 사용자가 만든 진행 중인 팟 조회 + @Operation(summary = "사용자가 만든 진행 중인 팟 조회 API", description = "pot 상태는 다음과 같이 구분됩니다. recruiting / ongoing / completed\n") + @GetMapping("/my-pots") + public ResponseEntity>> getMyOnGoingPots() { + List myOngoingPots = myPotService.getMyOnGoingPots(); + return ResponseEntity.ok(ApiResponse.onSuccess(myOngoingPots)); + } + + // 팟에서의 투두 생성 + @Operation(summary = "Todo 생성 API", description = "status는 NOT_STARTED와 COMPLETED로 구분되며, 생성의 경우 NOT_STARTED로 전달해 주시면 됩니다.") + @PostMapping("/my-pots/{pot_id}/todos") + public ResponseEntity>> postMyTodo( + @PathVariable("pot_id") Long potId, + @RequestBody MyPotTodoRequestDTO request) { + + List response = myPotService.postTodo(potId, request); + return ResponseEntity.ok(ApiResponse.onSuccess(response)); + } + + // 팟에서의 투두 조회 + @Operation(summary = "Todo 조회 API") + @GetMapping("/my-pots/{pot_id}/todos") + public ResponseEntity>> getMyTodo(@PathVariable("pot_id") Long potId){ + List response = myPotService.getTodo(potId); // 수정된 부분 + return ResponseEntity.ok(ApiResponse.onSuccess(response)); + } + + @Operation(summary = "Todo 내용 일괄 수정 API", description = "사용자의 모든 투두의 내용을 한 번에 수정할 수 있습니다. 리스트 사이에 ,로 구분해서 전달해 주셔야 합니다!") + @PatchMapping("/my-pots/{pot_id}/todos") + public ResponseEntity>> updateMyTodos( + @PathVariable("pot_id") Long potId, + @RequestBody List requestList) { + + List response = myPotService.updateTodos(potId, requestList); + return ResponseEntity.ok(ApiResponse.onSuccess(response)); + } + +} diff --git a/src/main/java/stackpot/stackpot/web/controller/PotController.java b/src/main/java/stackpot/stackpot/web/controller/PotController.java index 1e828824..b03a7ea8 100644 --- a/src/main/java/stackpot/stackpot/web/controller/PotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/PotController.java @@ -1,20 +1,39 @@ package stackpot.stackpot.web.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.repository.PotRepository.PotRepository; import stackpot.stackpot.service.PotServiceImpl; import stackpot.stackpot.web.dto.PotRequestDto; import stackpot.stackpot.web.dto.PotResponseDto; +import stackpot.stackpot.apiPayload.ApiResponse; +import stackpot.stackpot.service.PotService; +import stackpot.stackpot.web.dto.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + @RestController @RequestMapping("/pots") @RequiredArgsConstructor public class PotController { + + private final PotService potService1; + private final PotServiceImpl potService; + private final PotRepository potRepository; + @PostMapping public ResponseEntity createPot( @@ -44,5 +63,87 @@ public ResponseEntity deletePot(@PathVariable("pot_id") Long potId) { return ResponseEntity.noContent().build(); } + //---------------------------- + + @Operation(summary = "팟 전체 보기 API", description = "Design, Backend, Frontend, PM으로 필터링 가능합니다. 만약 null인 경우 전체 카테고리에 대해서 조회합니다.") + @GetMapping + public ResponseEntity>> getPots( + @RequestParam(required = false) String recruitmentRole, + @RequestParam(defaultValue = "0") Integer page, + @RequestParam(defaultValue = "10") Integer size) { + + List pots = potService1.getAllPots(recruitmentRole, page, size); + + Page potPage = (recruitmentRole == null || recruitmentRole.isEmpty()) + ? potRepository.findAll(PageRequest.of(page, size)) + : potRepository.findByRecruitmentDetails_RecruitmentRole(recruitmentRole, PageRequest.of(page, size)); + + Map response = new HashMap<>(); + response.put("pots", pots); + response.put("totalPages", potPage.getTotalPages()); + response.put("currentPage", potPage.getNumber()); + response.put("totalElements", potPage.getTotalElements()); + + return ResponseEntity.ok(ApiResponse.onSuccess(response)); + } + + // 특정 팟의 상세정보 조회 + @Operation(summary = "특정 팟의 상세정보 조회 API", description = "potId를 통해 특정 팟에 대한 상세정보를 조회할 수 있습니다. ") + @GetMapping("/{pot_id}") + public ResponseEntity> getPotDetails(@PathVariable("pot_id") Long potId) { + ApplicantResponseDTO potDetails = potService1.getPotDetails(potId); + return ResponseEntity.ok(ApiResponse.onSuccess(potDetails)); + } + + // 특정 팟 지원자의 좋아요 상태 변경 + @Operation(summary = "특정 팟 지원자의 '마음에 들어요' 상태 변경 API", description = "지원자의 아이디와 liked 값을 true, false (Boolean)로 request 해 주시면 지원자의 liked 상태값이 해당 값에 맞춰 변경됩니다.") + @PatchMapping("/{pot_id}/applications/like") + public ResponseEntity> patchLikes( + @PathVariable("pot_id") Long potId, + @RequestBody LikeRequestDTO likeRequest) { + potService1.patchLikes(potId, likeRequest.getApplicationId(), likeRequest.getLiked()); + return ResponseEntity.ok(ApiResponse.onSuccess(null)); + } + + // 특정 팟의 좋아요한 지원자 목록 조회 + @Operation(summary = "특정 팟의 '마음에 들어요' 지원자들 목록 조회 API", description = "지원자의 id, pot 지원 역할, 지원 역할에 따른 팟에서의 nickname, like 상태 값을 반환합니다. ") + @GetMapping("/{pot_id}/applications/like") + public ResponseEntity>> getLikedApplicants( + @PathVariable("pot_id") Long potId) { + List likedApplicants = potService1.getLikedApplicants(potId); + return ResponseEntity.ok(ApiResponse.onSuccess(likedApplicants)); + } + + // 사용자가 지원한 팟 조회 + @Operation(summary = "사용자가 지원한 팟 조회 API") + @GetMapping("/apply") + public ResponseEntity>> getAppliedPots() { + List appliedPots = potService1.getAppliedPots(); + return ResponseEntity.ok(ApiResponse.onSuccess(appliedPots)); + } + + // 사용자가 만든 팟 조회 + @Operation(summary = "사용자가 만든 팟 조회 API", description = "모집 중인 나의 팟, 진행 중인 나의 팟, 끓인 나의 팟을 구분하여 리스트 형식으로 전달합니다. 진행 중인 팟의 경우 멤버들의 사진이 보여야 하기에 potMembers 정보를 함께 전달합니다.") + @GetMapping("/my-pots") + public ResponseEntity>> getMyPots() { + List myPots = potService1.getMyPots(); + return ResponseEntity.ok(ApiResponse.onSuccess(myPots)); + } + + /*// 사용자가 만든 팟 다 끓이기 + @PatchMapping("/{pot_id}/complete") + public ResponseEntity> patchPotStatus(@PathVariable("pot_id") Long potId) { + potService1.patchPotStatus(potId); + return ResponseEntity.ok(ApiResponse.onSuccess(null)); + }*/ + + + // Pot 내용 AI 요약 + @Operation(summary = "Pot 내용 AI 요약 API", description = "팟의 구인글 내용을 활용해 작성됩니다.") + @GetMapping("/{pot_id}/summary") + public ResponseEntity> getPotSummary(@PathVariable("pot_id") Long potId) { + PotSummaryResponseDTO summary = potService1.getPotSummary(potId); + return ResponseEntity.ok(ApiResponse.onSuccess(summary)); + } } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/dto/ApplicantResponseDTO.java b/src/main/java/stackpot/stackpot/web/dto/ApplicantResponseDTO.java new file mode 100644 index 00000000..9c71e704 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/ApplicantResponseDTO.java @@ -0,0 +1,24 @@ +package stackpot.stackpot.web.dto; + +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDate; +import java.util.List; + +@Builder +@Getter +public class ApplicantResponseDTO { + private UserResponseDto user; + private PotResponseDto pot; + private List recruitmentDetails; + private List applicant; + + @Getter + @Builder + public static class ApplicantDto { + private Long applicationId; + private String potRole; + private Boolean liked; + } +} diff --git a/src/main/java/stackpot/stackpot/web/dto/LikeRequestDTO.java b/src/main/java/stackpot/stackpot/web/dto/LikeRequestDTO.java new file mode 100644 index 00000000..e0172912 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/LikeRequestDTO.java @@ -0,0 +1,15 @@ +package stackpot.stackpot.web.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Builder +@Getter +@Setter +public class LikeRequestDTO { + private Long applicationId; + + @Builder.Default + private Boolean liked = false; +} diff --git a/src/main/java/stackpot/stackpot/web/dto/LikedApplicantResponseDTO.java b/src/main/java/stackpot/stackpot/web/dto/LikedApplicantResponseDTO.java new file mode 100644 index 00000000..e97bef6c --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/LikedApplicantResponseDTO.java @@ -0,0 +1,13 @@ +package stackpot.stackpot.web.dto; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class LikedApplicantResponseDTO { + private Long applicationId; + private String applicantRole; + private String potNickname; // user의 nickname + pot_role 조합 + private Boolean liked; +} diff --git a/src/main/java/stackpot/stackpot/web/dto/MyPotResponseDTO.java b/src/main/java/stackpot/stackpot/web/dto/MyPotResponseDTO.java new file mode 100644 index 00000000..0d80ec22 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/MyPotResponseDTO.java @@ -0,0 +1,23 @@ +package stackpot.stackpot.web.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Getter +@Builder +public class MyPotResponseDTO { + + @JsonProperty("MyPots") + private List ongoingPots; + + @Getter + @Builder + public static class OngoingPotsDetail { + private UserResponseDto user; + private PotResponseDto pot; + private List potMembers; + } +} diff --git a/src/main/java/stackpot/stackpot/web/dto/MyPotTodoRequestDTO.java b/src/main/java/stackpot/stackpot/web/dto/MyPotTodoRequestDTO.java new file mode 100644 index 00000000..9c031526 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/MyPotTodoRequestDTO.java @@ -0,0 +1,16 @@ +package stackpot.stackpot.web.dto; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.enums.TodoStatus; + + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class MyPotTodoRequestDTO { + private String content; + private TodoStatus status; +} diff --git a/src/main/java/stackpot/stackpot/web/dto/MyPotTodoResponseDTO.java b/src/main/java/stackpot/stackpot/web/dto/MyPotTodoResponseDTO.java new file mode 100644 index 00000000..0ba3d7b2 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/MyPotTodoResponseDTO.java @@ -0,0 +1,30 @@ +package stackpot.stackpot.web.dto; + +import lombok.*; +import stackpot.stackpot.domain.enums.TodoStatus; + +import java.util.List; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MyPotTodoResponseDTO { + + private String userNickname; + private Long userId; + private List todos; + + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class TodoDetailDTO { + private Long todoId; + private String content; + private TodoStatus status; + } +} + + diff --git a/src/main/java/stackpot/stackpot/web/dto/MyPotTodoUpdateRequestDTO.java b/src/main/java/stackpot/stackpot/web/dto/MyPotTodoUpdateRequestDTO.java new file mode 100644 index 00000000..b5da86f4 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/MyPotTodoUpdateRequestDTO.java @@ -0,0 +1,14 @@ +package stackpot.stackpot.web.dto; + +import lombok.*; + + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class MyPotTodoUpdateRequestDTO { + private Long todoId; + private String content; +} diff --git a/src/main/java/stackpot/stackpot/web/dto/PotAllResponseDTO.java b/src/main/java/stackpot/stackpot/web/dto/PotAllResponseDTO.java new file mode 100644 index 00000000..cb22f717 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/PotAllResponseDTO.java @@ -0,0 +1,27 @@ +package stackpot.stackpot.web.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Getter +@Builder +public class PotAllResponseDTO { + @JsonProperty("recruitingPots") + private List recruitingPots; + + @JsonProperty("ongoingPots") + private List ongoingPots; + + @JsonProperty("completedPots") + private List completedPots; + + @Getter + @Builder + public static class PotDetail { + private UserResponseDto user; + private PotResponseDto pot; + } +} diff --git a/src/main/java/stackpot/stackpot/web/dto/PotMemberResponseDTO.java b/src/main/java/stackpot/stackpot/web/dto/PotMemberResponseDTO.java new file mode 100644 index 00000000..2a35c336 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/PotMemberResponseDTO.java @@ -0,0 +1,20 @@ +package stackpot.stackpot.web.dto; + +import jakarta.persistence.*; +import lombok.Builder; +import lombok.Getter; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.mapping.PotApplication; + +import java.time.LocalDate; + +@Getter +@Builder +public class PotMemberResponseDTO { + + private Long potMemberId; + private String roleName; + private Boolean owner; + private String appealContent; +} diff --git a/src/main/java/stackpot/stackpot/web/dto/PotResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/PotResponseDto.java index 727bb86f..aafb8d6a 100644 --- a/src/main/java/stackpot/stackpot/web/dto/PotResponseDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/PotResponseDto.java @@ -24,4 +24,5 @@ public class PotResponseDto { private String potSummary; private LocalDate recruitmentDeadline; private List recruitmentDetails; + private Integer dDay; } diff --git a/src/main/java/stackpot/stackpot/web/dto/PotSummaryResponseDTO.java b/src/main/java/stackpot/stackpot/web/dto/PotSummaryResponseDTO.java new file mode 100644 index 00000000..77cb5f3b --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/PotSummaryResponseDTO.java @@ -0,0 +1,10 @@ +package stackpot.stackpot.web.dto; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class PotSummaryResponseDTO { + private String summary; +} diff --git a/src/main/java/stackpot/stackpot/web/dto/RecruitmentDetailsResponseDTO.java b/src/main/java/stackpot/stackpot/web/dto/RecruitmentDetailsResponseDTO.java new file mode 100644 index 00000000..692fa456 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/RecruitmentDetailsResponseDTO.java @@ -0,0 +1,15 @@ +package stackpot.stackpot.web.dto; + + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class RecruitmentDetailsResponseDTO { + + private Long recruitmentId; + private String recruitmentRole; + private Integer recruitmentCount; + +} From 4b3ff3f1b1d3f8be13c158c6eb816638d7c25529 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Fri, 24 Jan 2025 21:13:21 +0900 Subject: [PATCH 53/76] =?UTF-8?q?[ADD]=20feed=20api=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/controller/FeedController.java | 30 ++++--------------- .../web/controller/UserController.java | 2 +- 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/src/main/java/stackpot/stackpot/web/controller/FeedController.java b/src/main/java/stackpot/stackpot/web/controller/FeedController.java index 80723ebf..e0835b8d 100644 --- a/src/main/java/stackpot/stackpot/web/controller/FeedController.java +++ b/src/main/java/stackpot/stackpot/web/controller/FeedController.java @@ -29,17 +29,7 @@ public class FeedController { @Operation(summary = "feed 작성 api") @PostMapping("") - public ResponseEntity createFeeds(@Valid @RequestBody FeedRequestDto.createDto requset, - BindingResult bindingResult) { - // 유효성 검사 실패 처리 - if (bindingResult.hasErrors()) { - // 에러 메시지 수집 - List errors = bindingResult.getAllErrors() - .stream() - .map(ObjectError::getDefaultMessage) - .collect(Collectors.toList()); - return ResponseEntity.badRequest().body(errors); - } + public ResponseEntity createFeeds(@Valid @RequestBody FeedRequestDto.createDto requset) { // 정상 처리 Feed feed = feedService.createFeed(requset); Long feedId = feed.getFeedId(); @@ -63,7 +53,7 @@ public ResponseEntity getPreViewFeeds( } @Operation(summary = "feed 상세보기 api") @PostMapping("/{feedId}") - public ResponseEntity getDetailFeed(@PathVariable Long feedId) { + public ResponseEntity getDetailFeed(@PathVariable Long feedId) { Feed feed = feedService.getFeed(feedId); Long saveCount = feedService.getSaveCount(feedId); @@ -75,17 +65,7 @@ public ResponseEntity getDetailFeed(@PathVariable Long feedId) { @Operation(summary = "feed 수정 api") @PatchMapping("/{feedId}") - public ResponseEntity modifyFeed(@PathVariable Long feedId, @Valid @RequestBody FeedRequestDto.createDto requset, - BindingResult bindingResult) { - // 유효성 검사 실패 처리 - if (bindingResult.hasErrors()) { - // 에러 메시지 수집 - List errors = bindingResult.getAllErrors() - .stream() - .map(ObjectError::getDefaultMessage) - .collect(Collectors.toList()); - return ResponseEntity.badRequest().body(errors); - } + public ResponseEntity modifyFeed(@PathVariable Long feedId, @Valid @RequestBody FeedRequestDto.createDto requset) { // 정상 처리 Feed feed = feedService.modifyFeed(feedId, requset); Long saveCount = feedService.getSaveCount(feedId); @@ -97,7 +77,7 @@ public ResponseEntity modifyFeed(@PathVariable Long feedId, @Valid @RequestBo } @Operation(summary = "feed 좋아요 추가 api") @PostMapping("/{feedId}/like") - public ResponseEntity toggleLike(@PathVariable Long feedId) { + public ResponseEntity toggleLike(@PathVariable Long feedId) { // 좋아요 토글 boolean isLiked = feedService.toggleLike(feedId); @@ -109,7 +89,7 @@ public ResponseEntity toggleLike(@PathVariable Long feedId) { @Operation(summary = "feed 저장하기 api") @PostMapping("/{feedId}/save") - public ResponseEntity toggleSave(@PathVariable Long feedId) { + public ResponseEntity toggleSave(@PathVariable Long feedId) { // 좋아요 토글 boolean isSaved = feedService.toggleSave(feedId); diff --git a/src/main/java/stackpot/stackpot/web/controller/UserController.java b/src/main/java/stackpot/stackpot/web/controller/UserController.java index 960b0edf..ea8a7953 100644 --- a/src/main/java/stackpot/stackpot/web/controller/UserController.java +++ b/src/main/java/stackpot/stackpot/web/controller/UserController.java @@ -54,7 +54,7 @@ public ResponseEntity signup(@Valid @RequestBody UserRequestDto.JoinDto reque return ResponseEntity.status(HttpStatus.CREATED).body(UserConverter.toDto(user)); } - @Operation(summary = "닉넴임 생성") + @Operation(summary = "닉네임 생성") @GetMapping("/nickname") public ResponseEntity nickname(){ From 20c96f02076d2cc3ec2b5b58348ad63fee7d0190 Mon Sep 17 00:00:00 2001 From: starday119 Date: Fri, 24 Jan 2025 22:26:37 +0900 Subject: [PATCH 54/76] =?UTF-8?q?[FEAT]:=20=ED=8C=9F=20=EC=A7=80=EC=9B=90?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=EB=B0=8F=20=ED=8C=9F=20=EC=8B=9C=EC=9E=91?= =?UTF-8?q?=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 + .../PotMemberConverter.java | 13 +++ .../PotMemberConverterImpl.java | 53 ++++++++++++ .../java/stackpot/stackpot/domain/Pot.java | 1 + .../domain/mapping/PotApplication.java | 2 +- .../stackpot/domain/mapping/PotMember.java | 6 +- .../repository/PotMemberRepository.java | 21 +++++ .../service/EmailService/EmailService.java | 5 ++ .../EmailService/EmailServiceImpl.java | 29 +++++++ .../PotApplicationService.java | 2 +- .../PotApplicationServiceImpl.java | 30 ++++++- .../PotMemberService/PotMemberService.java | 12 +++ .../PotMemberServiceImpl.java | 80 +++++++++++++++++++ .../controller/PotApplicationController.java | 15 ++-- .../web/controller/PotController.java | 5 +- .../web/controller/PotMemberController.java | 41 ++++++++++ .../stackpot/web/dto/PotMemberRequestDto.java | 13 +++ .../web/dto/PotMemberResponseDto.java | 18 +++++ .../web/dto/UpdateAppealRequestDto.java | 13 +++ src/main/resources/application.yml | 16 +++- 20 files changed, 365 insertions(+), 14 deletions(-) create mode 100644 src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverter.java create mode 100644 src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java create mode 100644 src/main/java/stackpot/stackpot/repository/PotMemberRepository.java create mode 100644 src/main/java/stackpot/stackpot/service/EmailService/EmailService.java create mode 100644 src/main/java/stackpot/stackpot/service/EmailService/EmailServiceImpl.java create mode 100644 src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberService.java create mode 100644 src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberServiceImpl.java create mode 100644 src/main/java/stackpot/stackpot/web/controller/PotMemberController.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/PotMemberRequestDto.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/PotMemberResponseDto.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/UpdateAppealRequestDto.java diff --git a/build.gradle b/build.gradle index 6744c379..2344bd7d 100644 --- a/build.gradle +++ b/build.gradle @@ -59,6 +59,10 @@ dependencies { // Test Dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + //Email + implementation 'org.springframework.boot:spring-boot-starter-mail' + } tasks.named('test') { diff --git a/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverter.java b/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverter.java new file mode 100644 index 00000000..9866ee1d --- /dev/null +++ b/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverter.java @@ -0,0 +1,13 @@ +package stackpot.stackpot.converter.PotMemberConverter; + +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.mapping.PotApplication; +import stackpot.stackpot.domain.mapping.PotMember; +import stackpot.stackpot.web.dto.PotMemberRequestDto; +import stackpot.stackpot.web.dto.PotMemberResponseDto; + +public interface PotMemberConverter { + PotMember toEntity(User user, Pot pot, PotApplication application, Boolean isOwner); + PotMemberResponseDto toDto(PotMember entity); +} diff --git a/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java b/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java new file mode 100644 index 00000000..eb0976aa --- /dev/null +++ b/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java @@ -0,0 +1,53 @@ +package stackpot.stackpot.converter.PotMemberConverter; + +import org.springframework.stereotype.Component; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.mapping.PotApplication; +import stackpot.stackpot.domain.mapping.PotMember; +import stackpot.stackpot.web.dto.PotMemberRequestDto; +import stackpot.stackpot.web.dto.PotMemberResponseDto; + +@Component +public class PotMemberConverterImpl implements PotMemberConverter { + + @Override + public PotMember toEntity(User user, Pot pot, PotApplication application, Boolean isOwner) { + return PotMember.builder() + .user(user) + .pot(pot) + .potApplication(application) + .roleName(application != null ? mapRoleName(application.getPotRole()) : "Owner") + .isOwner(isOwner) + .appealContent(null) + .build(); + } + + @Override + public PotMemberResponseDto toDto(PotMember entity) { + return PotMemberResponseDto.builder() + .potMemberId(entity.getPotMemberId()) + .potId(entity.getPot().getPotId()) + .userId(entity.getUser().getId()) + .roleName(entity.getRoleName()) + .isOwner(entity.getIsOwner()) + .appealContent(entity.getAppealContent()) + .build(); + } + + private String mapRoleName(String potRole) { + switch (potRole) { + case "백엔드": + return "버섯"; + case "프론트엔드": + return "당근"; + case "디자인": + return "양파"; + case "기획": + return "브로콜리"; + default: + return "멤버"; + } + } + +} diff --git a/src/main/java/stackpot/stackpot/domain/Pot.java b/src/main/java/stackpot/stackpot/domain/Pot.java index ce89576c..f521d41a 100644 --- a/src/main/java/stackpot/stackpot/domain/Pot.java +++ b/src/main/java/stackpot/stackpot/domain/Pot.java @@ -44,6 +44,7 @@ public class Pot extends BaseEntity { @Column(nullable = true, columnDefinition = "TEXT") private String potContent; + @Setter @Column(nullable = false, length = 255) private String potStatus; diff --git a/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java b/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java index 5347d180..d5455f0e 100644 --- a/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java +++ b/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java @@ -18,7 +18,7 @@ public class PotApplication extends BaseEntity { @Id - @GeneratedValue(strategy = GenerationType.AUTO) // IDENTITY로 자동 증가 설정 + @GeneratedValue(strategy = GenerationType.IDENTITY) // IDENTITY로 자동 증가 설정 @Column(name = "application_id", nullable = false) private Long applicationId; // Primary Key diff --git a/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java b/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java index 4dd8c547..e078726b 100644 --- a/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java +++ b/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java @@ -34,8 +34,10 @@ public class PotMember extends BaseEntity { private String roleName; @Column(nullable = false) - private String owner; + private Boolean isOwner; - @Column(nullable = false) + @Setter + @Getter + @Column(nullable = true) private String appealContent; } diff --git a/src/main/java/stackpot/stackpot/repository/PotMemberRepository.java b/src/main/java/stackpot/stackpot/repository/PotMemberRepository.java new file mode 100644 index 00000000..35398fb8 --- /dev/null +++ b/src/main/java/stackpot/stackpot/repository/PotMemberRepository.java @@ -0,0 +1,21 @@ +package stackpot.stackpot.repository; + + + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.mapping.PotMember; + +import java.util.List; +import java.util.Optional; + +@Repository +// 8. PotMemberRepository +public interface PotMemberRepository extends JpaRepository { + @Query("SELECT pm.user.id FROM PotMember pm WHERE pm.pot.potId = :potId") + List findUserIdsByPotId(@Param("potId") Long potId); +} diff --git a/src/main/java/stackpot/stackpot/service/EmailService/EmailService.java b/src/main/java/stackpot/stackpot/service/EmailService/EmailService.java new file mode 100644 index 00000000..d8d83f4f --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/EmailService/EmailService.java @@ -0,0 +1,5 @@ +package stackpot.stackpot.service.EmailService; + +public interface EmailService { + void sendSupportNotification(String toEmail, String potName, String applicantName); +} diff --git a/src/main/java/stackpot/stackpot/service/EmailService/EmailServiceImpl.java b/src/main/java/stackpot/stackpot/service/EmailService/EmailServiceImpl.java new file mode 100644 index 00000000..a6f77ae4 --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/EmailService/EmailServiceImpl.java @@ -0,0 +1,29 @@ +package stackpot.stackpot.service.EmailService; + +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; + +@Service +public class EmailServiceImpl implements EmailService { + + private final JavaMailSender mailSender; + + public EmailServiceImpl(JavaMailSender mailSender) { + this.mailSender = mailSender; + } + + @Override + public void sendSupportNotification(String toEmail, String potName, String applicantName) { + try { + SimpleMailMessage message = new SimpleMailMessage(); + message.setTo(toEmail); + message.setSubject("새로운 팟 지원 알림"); + message.setText(applicantName + " 님이 '" + potName + "' 팟에 지원했습니다!"); + mailSender.send(message); + } catch (Exception e) { + // 예외 처리: 이메일 전송 실패 시 로그를 출력 + System.err.println("이메일 전송 실패: " + e.getMessage()); + } + } +} diff --git a/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationService.java b/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationService.java index 0e7a197e..79c9d48b 100644 --- a/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationService.java +++ b/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationService.java @@ -8,5 +8,5 @@ public interface PotApplicationService { PotApplicationResponseDto applyToPot(PotApplicationRequestDto dto,Long potId); - List getApplicationsByPot(Long potId); + List getApplicantsByPotId(Long potId); } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java index 59bae28d..64842670 100644 --- a/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java @@ -14,6 +14,7 @@ import stackpot.stackpot.repository.PotRepository.PotRepository; import stackpot.stackpot.repository.UserRepository.UserRepository; import stackpot.stackpot.config.security.JwtTokenProvider; +import stackpot.stackpot.service.EmailService.EmailService; import stackpot.stackpot.web.dto.PotApplicationRequestDto; import stackpot.stackpot.web.dto.PotApplicationResponseDto; @@ -30,7 +31,7 @@ public class PotApplicationServiceImpl implements PotApplicationService { private final UserRepository userRepository; private final PotApplicationConverter potApplicationConverter; private final JwtTokenProvider jwtTokenProvider; - + private final EmailService emailService; @Transactional public PotApplicationResponseDto applyToPot(PotApplicationRequestDto dto, Long potId) { // 인증된 사용자 이메일 가져오기 @@ -52,19 +53,44 @@ public PotApplicationResponseDto applyToPot(PotApplicationRequestDto dto, Long p // 지원 엔티티 생성 및 저장 PotApplication potApplication = potApplicationConverter.toEntity(dto, pot, user); + potApplication.setApplicationStatus(ApplicationStatus.PENDING); + potApplication.setAppliedAt(LocalDateTime.now()); + PotApplication savedApplication = potApplicationRepository.save(potApplication); + // 이메일 전송 + emailService.sendSupportNotification( + pot.getUser().getEmail(), + pot.getPotName(), + user.getNickname() + ); + // 저장된 지원 정보를 응답 DTO로 변환 return potApplicationConverter.toDto(savedApplication); } + @Override @Transactional(readOnly = true) - public List getApplicationsByPot(Long potId) { + public List getApplicantsByPotId(Long potId) { + // 현재 인증된 사용자 이메일 가져오기 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + // 팟 조회 + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new IllegalArgumentException("해당 팟을 찾을 수 없습니다.")); + + + + // 지원자 목록 조회 List applications = potApplicationRepository.findByPot_PotId(potId); + + // DTO 변환 후 반환 return applications.stream() .map(potApplicationConverter::toDto) .collect(Collectors.toList()); } + } diff --git a/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberService.java b/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberService.java new file mode 100644 index 00000000..fb9eb8c3 --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberService.java @@ -0,0 +1,12 @@ +package stackpot.stackpot.service.PotMemberService; + +import stackpot.stackpot.apiPayload.ApiResponse; +import stackpot.stackpot.web.dto.PotMemberRequestDto; +import stackpot.stackpot.web.dto.PotMemberResponseDto; + +import java.util.List; + +public interface PotMemberService { + List addMembersToPot(Long potId, PotMemberRequestDto requestDto); + void updateAppealContent(Long potId, Long memberId, String appealContent); +} diff --git a/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberServiceImpl.java new file mode 100644 index 00000000..a3611a79 --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberServiceImpl.java @@ -0,0 +1,80 @@ +package stackpot.stackpot.service.PotMemberService; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import stackpot.stackpot.apiPayload.ApiResponse; +import stackpot.stackpot.converter.PotMemberConverter.PotMemberConverter; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.mapping.PotApplication; +import stackpot.stackpot.domain.mapping.PotMember; +import stackpot.stackpot.repository.PotApplicationRepository.PotApplicationRepository; +import stackpot.stackpot.repository.PotMemberRepository; +import stackpot.stackpot.repository.PotRepository.PotRepository; +import stackpot.stackpot.repository.UserRepository.UserRepository; +import stackpot.stackpot.web.dto.PotMemberRequestDto; +import stackpot.stackpot.web.dto.PotMemberResponseDto; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class PotMemberServiceImpl implements PotMemberService { + + private final PotRepository potRepository; + private final UserRepository userRepository; + private final PotApplicationRepository potApplicationRepository; + private final PotMemberRepository potMemberRepository; + private final PotMemberConverter potMemberConverter; + + @Transactional + @Override + public List addMembersToPot (Long potId, PotMemberRequestDto requestDto) { + // 1. 팟 조회 + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new IllegalArgumentException("해당 팟을 찾을 수 없습니다.")); + + // 2. 팟 상태를 "ing"로 설정 + pot.setPotStatus("ing"); + potRepository.save(pot); // 변경 사항 저장 + + + // 4. 선택된 지원자들을 멤버로 추가 + List applicantIds = requestDto.getApplicantIds(); + List newMembers = new ArrayList<>(); + + for (Long applicantId : applicantIds) { + + PotApplication application = potApplicationRepository.findById(applicantId) + .orElseThrow(() -> new IllegalArgumentException("지원자를 찾을 수 없습니다.")); + User user = application.getUser(); + PotMember member = potMemberConverter.toEntity(user, pot, application, false); + newMembers.add(member); + } + + // 5. 저장 및 응답 반환 + List savedMembers = potMemberRepository.saveAll(newMembers); + return savedMembers.stream() + .map(potMemberConverter::toDto) + .collect(Collectors.toList()); + } + @Transactional + @Override + public void updateAppealContent(Long potId, Long memberId, String appealContent) { + // 1. 멤버 조회 + PotMember potMember = potMemberRepository.findById(memberId) + .orElseThrow(() -> new IllegalArgumentException("해당 멤버를 찾을 수 없습니다.")); + + // 2. 멤버가 해당 팟에 속해 있는지 확인 + if (!potMember.getPot().getPotId().equals(potId)) { + throw new IllegalArgumentException("해당 멤버는 지정된 팟에 속해 있지 않습니다."); + } + + // 3. 어필 내용 업데이트 + potMember.setAppealContent(appealContent); + potMemberRepository.save(potMember); // 변경 사항 저장 + } +} diff --git a/src/main/java/stackpot/stackpot/web/controller/PotApplicationController.java b/src/main/java/stackpot/stackpot/web/controller/PotApplicationController.java index f975f8d1..f7667e41 100644 --- a/src/main/java/stackpot/stackpot/web/controller/PotApplicationController.java +++ b/src/main/java/stackpot/stackpot/web/controller/PotApplicationController.java @@ -1,5 +1,6 @@ package stackpot.stackpot.web.controller; +import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -16,7 +17,7 @@ public class PotApplicationController { private final PotApplicationService potApplicationService; - + @Operation(summary = "팟 지원하기") @PostMapping public ResponseEntity applyToPot( @PathVariable("pot_id") Long potId, @@ -28,10 +29,14 @@ public ResponseEntity applyToPot( return ResponseEntity.ok(responseDto); // 성공 시 응답 반환 } + @Operation(summary = "팟 지원자 조회하기") + @GetMapping("") + public ResponseEntity> getApplicants( + @PathVariable("pot_id") Long potId) { + // 서비스 호출 + List applicants = potApplicationService.getApplicantsByPotId(potId); - @GetMapping - public ResponseEntity> getApplications(@PathVariable("pot_id") Long potId) { - List applications = potApplicationService.getApplicationsByPot(potId); - return ResponseEntity.ok(applications); + return ResponseEntity.ok(applicants); } + } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/controller/PotController.java b/src/main/java/stackpot/stackpot/web/controller/PotController.java index 1e828824..9d50c19c 100644 --- a/src/main/java/stackpot/stackpot/web/controller/PotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/PotController.java @@ -1,5 +1,6 @@ package stackpot.stackpot.web.controller; +import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -15,6 +16,7 @@ public class PotController { private final PotServiceImpl potService; + @Operation(summary = "팟 생성하기") @PostMapping public ResponseEntity createPot( @@ -25,6 +27,7 @@ public ResponseEntity createPot( return ResponseEntity.ok(responseDto); } + @Operation(summary = "팟 수정하기") @PatchMapping("/{pot_id}") public ResponseEntity updatePot( @PathVariable("pot_id") Long potId, @@ -35,7 +38,7 @@ public ResponseEntity updatePot( return ResponseEntity.ok(responseDto); // 수정된 팟 정보 반환 } - + @Operation(summary = "팟 삭제하기") @DeleteMapping("/{pot_id}") public ResponseEntity deletePot(@PathVariable("pot_id") Long potId) { // 팟 삭제 로직 호출 diff --git a/src/main/java/stackpot/stackpot/web/controller/PotMemberController.java b/src/main/java/stackpot/stackpot/web/controller/PotMemberController.java new file mode 100644 index 00000000..ff97655f --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/controller/PotMemberController.java @@ -0,0 +1,41 @@ +package stackpot.stackpot.web.controller; + +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stackpot.stackpot.apiPayload.ApiResponse; +import stackpot.stackpot.service.PotMemberService.PotMemberService; +import stackpot.stackpot.web.dto.PotMemberRequestDto; +import stackpot.stackpot.web.dto.PotMemberResponseDto; +import stackpot.stackpot.web.dto.UpdateAppealRequestDto; + +import java.util.List; + +@RestController +@RequestMapping("/pots/{pot_id}/members") +@RequiredArgsConstructor +public class PotMemberController { + + private final PotMemberService potMemberService; + + @Operation(summary = "팟 시작하기") + @PostMapping + public ResponseEntity>> addPotMembers( + @PathVariable("pot_id") Long potId, + @RequestBody PotMemberRequestDto requestDto) { + List response = potMemberService.addMembersToPot(potId, requestDto); + return ResponseEntity.ok(ApiResponse.onSuccess(response)); + } + @Operation(summary = "팟 어필하기") + @PatchMapping("/{member_id}/appeal") + public ResponseEntity> updateAppealContent( + @PathVariable("pot_id") Long potId, + @PathVariable("member_id") Long memberId, + @RequestBody @Valid UpdateAppealRequestDto requestDto) { + potMemberService.updateAppealContent(potId, memberId, requestDto.getAppealContent()); + return ResponseEntity.ok(ApiResponse.onSuccess("어필 내용이 성공적으로 업데이트되었습니다.")); + } +} diff --git a/src/main/java/stackpot/stackpot/web/dto/PotMemberRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/PotMemberRequestDto.java new file mode 100644 index 00000000..bf0a38df --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/PotMemberRequestDto.java @@ -0,0 +1,13 @@ +package stackpot.stackpot.web.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class PotMemberRequestDto { + + private List applicantIds; // 지원자 ID 리스트 +} diff --git a/src/main/java/stackpot/stackpot/web/dto/PotMemberResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/PotMemberResponseDto.java new file mode 100644 index 00000000..c42cd01e --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/PotMemberResponseDto.java @@ -0,0 +1,18 @@ +package stackpot.stackpot.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@AllArgsConstructor +public class PotMemberResponseDto { + private Long potMemberId; + private Long potId; + private Long userId; + private String roleName; + private Boolean isOwner; + private String appealContent; +} + diff --git a/src/main/java/stackpot/stackpot/web/dto/UpdateAppealRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/UpdateAppealRequestDto.java new file mode 100644 index 00000000..23967ff0 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/UpdateAppealRequestDto.java @@ -0,0 +1,13 @@ +package stackpot.stackpot.web.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class UpdateAppealRequestDto { + + private String appealContent; +} + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 5cdf0739..d9ad0fe8 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -15,7 +15,7 @@ spring: format_sql: true use_sql_comments: true hbm2ddl: - auto: create + auto: update default_batch_fetch_size: 1000 cloud: aws: @@ -49,4 +49,16 @@ spring: user-info-uri: https://kapi.kakao.com/v2/user/me user-name-attribute: id jwt: - secret: ${JWT_SECRET} \ No newline at end of file + secret: ${JWT_SECRET} + mail: + host: smtp.gmail.com + port: 587 + username: ${mail.username} + password: ${mail.password} + properties: + mail: + smtp: + auth: true + timeout: 5000 + starttls: + enable: true \ No newline at end of file From d80363981fe51f9eb502a669822860af9d706f38 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Sat, 25 Jan 2025 01:52:20 +0900 Subject: [PATCH 55/76] =?UTF-8?q?[Refactor]=20=EC=B9=B4=EC=B9=B4=EB=A1=9C?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 + .../security/CustomOAuth2UserService.java | 184 ++++++++--------- .../security/JwtAuthenticationFilter.java | 114 +++++------ .../config/security/SecurityConfig.java | 54 ++--- .../stackpot/service/KakaoService.java | 84 ++++++++ .../web/controller/KakaoLoginController.java | 34 ++++ .../web/controller/UserController.java | 15 ++ .../web/dto/KakaoTokenResponseDto.java | 28 +++ .../web/dto/KakaoUserInfoResponseDto.java | 190 ++++++++++++++++++ src/main/resources/application.yml | 19 +- 10 files changed, 532 insertions(+), 194 deletions(-) create mode 100644 src/main/java/stackpot/stackpot/service/KakaoService.java create mode 100644 src/main/java/stackpot/stackpot/web/controller/KakaoLoginController.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/KakaoTokenResponseDto.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/KakaoUserInfoResponseDto.java diff --git a/build.gradle b/build.gradle index 8a6e95c7..d37e085f 100644 --- a/build.gradle +++ b/build.gradle @@ -64,6 +64,10 @@ dependencies { implementation 'mysql:mysql-connector-java:8.0.33' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.security:spring-security-crypto' + + + implementation 'org.springframework.boot:spring-boot-starter-webflux' + implementation 'io.netty:netty-resolver-dns-native-macos:4.1.95.Final:osx-aarch_64' } tasks.named('test') { diff --git a/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java b/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java index 676203ff..3b99c9f7 100644 --- a/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java +++ b/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java @@ -1,100 +1,100 @@ -package stackpot.stackpot.config.security; - -import jakarta.transaction.Transactional; -import lombok.RequiredArgsConstructor; -import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.user.DefaultOAuth2User; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.stereotype.Service; -import stackpot.stackpot.domain.User; -import stackpot.stackpot.repository.UserRepository.UserRepository; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -@Service -@RequiredArgsConstructor -public class CustomOAuth2UserService extends DefaultOAuth2UserService { - - private final UserRepository userRepository; - -// @Transactional +//package stackpot.stackpot.config.security; +// +//import jakarta.transaction.Transactional; +//import lombok.RequiredArgsConstructor; +//import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +//import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +//import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +//import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +//import org.springframework.security.oauth2.core.user.OAuth2User; +//import org.springframework.stereotype.Service; +//import stackpot.stackpot.domain.User; +//import stackpot.stackpot.repository.UserRepository.UserRepository; +// +//import java.util.HashMap; +//import java.util.Map; +//import java.util.UUID; +// +//@Service +//@RequiredArgsConstructor +//public class CustomOAuth2UserService extends DefaultOAuth2UserService { +// +// private final UserRepository userRepository; +// +//// @Transactional +//// @Override +//// public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { +//// // 1. 유저 정보(attributes) 가져오기 +//// Map oAuth2UserAttributes = super.loadUser(userRequest).getAttributes(); +//// +//// // 2. resistrationId 가져오기 (third-party id) +//// String registrationId = userRequest.getClientRegistration().getRegistrationId(); +//// +//// // 3. userNameAttributeName 가져오기 +//// String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails() +//// .getUserInfoEndpoint().getUserNameAttributeName(); +//// +//// // 4. 유저 정보 dto 생성 +//// OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfo.of(registrationId, oAuth2UserAttributes); +//// +//// // 5. 회원가입 및 로그인 +//// User user = saveOrUpdateUser(oAuth2UserInfo); +//// +//// // 6. OAuth2User로 반환 +//// return new PrincipalDetails(user, oAuth2UserAttributes, userNameAttributeName); +//// } // @Override // public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { -// // 1. 유저 정보(attributes) 가져오기 -// Map oAuth2UserAttributes = super.loadUser(userRequest).getAttributes(); +// OAuth2User oAuth2User = super.loadUser(userRequest); +// System.out.println("loadUser"); +// +// // 사용자 정보 가져오기 +// Map attributes = oAuth2User.getAttributes(); +// Map kakaoAccount = (Map) attributes.get("kakao_account"); // -// // 2. resistrationId 가져오기 (third-party id) -// String registrationId = userRequest.getClientRegistration().getRegistrationId(); +// if (kakaoAccount == null) { +// throw new OAuth2AuthenticationException("Failed to retrieve kakao_account from attributes."); +// } // -// // 3. userNameAttributeName 가져오기 -// String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails() -// .getUserInfoEndpoint().getUserNameAttributeName(); +// // 이메일 가져오기 +// String email = (String) kakaoAccount.get("email"); +// if (email == null) { +// throw new OAuth2AuthenticationException("Email not found in kakao_account."); +// } // -// // 4. 유저 정보 dto 생성 -// OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfo.of(registrationId, oAuth2UserAttributes); +// // 사용자 정보 확인 +// userRepository.findByEmail(email).ifPresentOrElse( +// user -> System.out.println("Existing user found: " + email), +// () -> System.out.println("No user found. Redirecting to signup.") +// ); // -// // 5. 회원가입 및 로그인 -// User user = saveOrUpdateUser(oAuth2UserInfo); +// // attributes에 email 추가 +// Map modifiedAttributes = new HashMap<>(attributes); +// modifiedAttributes.put("email", email); +// +// // 디버깅: modifiedAttributes 확인 +// System.out.println("Final Modified Attributes: " + modifiedAttributes); +// +// saveOrUpdateUser(email); +// +// +// // DefaultOAuth2User 생성 +// return new DefaultOAuth2User( +// oAuth2User.getAuthorities(), +// modifiedAttributes, +// "email" // Principal로 사용할 필드 이름 +// ); +// } // -// // 6. OAuth2User로 반환 -// return new PrincipalDetails(user, oAuth2UserAttributes, userNameAttributeName); +// private void saveOrUpdateUser(String email) { +// System.out.println("saveOrUpdateUser 실행"); +// userRepository.findByEmail(email) +// .orElseGet(() -> { +// User user = User.builder() +// .email(email) +// .userTemperature(33) +// .build(); +// return userRepository.save(user); // 저장된 사용자 반환 +// }); // } - @Override - public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { - OAuth2User oAuth2User = super.loadUser(userRequest); - System.out.println("loadUser"); - - // 사용자 정보 가져오기 - Map attributes = oAuth2User.getAttributes(); - Map kakaoAccount = (Map) attributes.get("kakao_account"); - - if (kakaoAccount == null) { - throw new OAuth2AuthenticationException("Failed to retrieve kakao_account from attributes."); - } - - // 이메일 가져오기 - String email = (String) kakaoAccount.get("email"); - if (email == null) { - throw new OAuth2AuthenticationException("Email not found in kakao_account."); - } - - // 사용자 정보 확인 - userRepository.findByEmail(email).ifPresentOrElse( - user -> System.out.println("Existing user found: " + email), - () -> System.out.println("No user found. Redirecting to signup.") - ); - - // attributes에 email 추가 - Map modifiedAttributes = new HashMap<>(attributes); - modifiedAttributes.put("email", email); - - // 디버깅: modifiedAttributes 확인 - System.out.println("Final Modified Attributes: " + modifiedAttributes); - - saveOrUpdateUser(email); - - - // DefaultOAuth2User 생성 - return new DefaultOAuth2User( - oAuth2User.getAuthorities(), - modifiedAttributes, - "email" // Principal로 사용할 필드 이름 - ); - } - - private void saveOrUpdateUser(String email) { - System.out.println("saveOrUpdateUser 실행"); - userRepository.findByEmail(email) - .orElseGet(() -> { - User user = User.builder() - .email(email) - .userTemperature(33) - .build(); - return userRepository.save(user); // 저장된 사용자 반환 - }); - } -} \ No newline at end of file +//} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/config/security/JwtAuthenticationFilter.java b/src/main/java/stackpot/stackpot/config/security/JwtAuthenticationFilter.java index 2c74e7d6..79c0db6d 100644 --- a/src/main/java/stackpot/stackpot/config/security/JwtAuthenticationFilter.java +++ b/src/main/java/stackpot/stackpot/config/security/JwtAuthenticationFilter.java @@ -1,57 +1,57 @@ -package stackpot.stackpot.config.security; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.filter.OncePerRequestFilter; -import org.springframework.security.core.Authentication; - -import java.io.IOException; -public class JwtAuthenticationFilter extends OncePerRequestFilter { - - private final JwtTokenProvider jwtTokenProvider; - - public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { - this.jwtTokenProvider = jwtTokenProvider; - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - String token = resolveToken(request); - - try { - if (token != null) { - System.out.println("Token found: " + token); - if (jwtTokenProvider.validateToken(token)) { - Authentication authentication = jwtTokenProvider.getAuthentication(token); - SecurityContextHolder.getContext().setAuthentication(authentication); - System.out.println("Authentication set in SecurityContext: " + authentication.getName()); - } else { - System.out.println("Invalid or expired token."); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.getWriter().write("Invalid or expired token."); - return; - } - } else { - System.out.println("No token found in the request."); - } - filterChain.doFilter(request, response); - } catch (Exception ex) { - System.out.println("Exception in JwtAuthenticationFilter: " + ex.getMessage()); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - response.getWriter().write("Internal server error occurred."); - } - } - - private String resolveToken(HttpServletRequest request) { - String bearerToken = request.getHeader("Authorization"); - if (bearerToken != null && bearerToken.startsWith("Bearer ")) { - return bearerToken.substring(7); - } - System.out.println("Authorization header is missing or does not start with 'Bearer '."); - return null; - } -} \ No newline at end of file +//package stackpot.stackpot.config.security; +// +//import jakarta.servlet.FilterChain; +//import jakarta.servlet.ServletException; +//import jakarta.servlet.http.HttpServletRequest; +//import jakarta.servlet.http.HttpServletResponse; +//import org.springframework.security.core.context.SecurityContextHolder; +//import org.springframework.web.filter.OncePerRequestFilter; +//import org.springframework.security.core.Authentication; +// +//import java.io.IOException; +//public class JwtAuthenticationFilter extends OncePerRequestFilter { +// +// private final JwtTokenProvider jwtTokenProvider; +// +// public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { +// this.jwtTokenProvider = jwtTokenProvider; +// } +// +// @Override +// protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) +// throws ServletException, IOException { +// String token = resolveToken(request); +// +// try { +// if (token != null) { +// System.out.println("Token found: " + token); +// if (jwtTokenProvider.validateToken(token)) { +// Authentication authentication = jwtTokenProvider.getAuthentication(token); +// SecurityContextHolder.getContext().setAuthentication(authentication); +// System.out.println("Authentication set in SecurityContext: " + authentication.getName()); +// } else { +// System.out.println("Invalid or expired token."); +// response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); +// response.getWriter().write("Invalid or expired token."); +// return; +// } +// } else { +// System.out.println("No token found in the request."); +// } +// filterChain.doFilter(request, response); +// } catch (Exception ex) { +// System.out.println("Exception in JwtAuthenticationFilter: " + ex.getMessage()); +// response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); +// response.getWriter().write("Internal server error occurred."); +// } +// } +// +// private String resolveToken(HttpServletRequest request) { +// String bearerToken = request.getHeader("Authorization"); +// if (bearerToken != null && bearerToken.startsWith("Bearer ")) { +// return bearerToken.substring(7); +// } +// System.out.println("Authorization header is missing or does not start with 'Bearer '."); +// return null; +// } +//} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java index 3a0a1c1f..a055369d 100644 --- a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java +++ b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java @@ -22,7 +22,7 @@ @RequiredArgsConstructor public class SecurityConfig { - private final CustomOAuth2UserService customOAuth2UserService; +// private final CustomOAuth2UserService customOAuth2UserService; @Bean public WebSecurityCustomizer webSecurityCustomizer() { // security를 적용하지 않을 리소스 @@ -40,38 +40,38 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtTokenProvid .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)) .sessionManagement( session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .oauth2Login( - oauth -> oauth.userInfoEndpoint(config -> config.userService(customOAuth2UserService)) - .successHandler(successHandler(jwtTokenProvider))) +// .oauth2Login( +// oauth -> oauth.userInfoEndpoint(config -> config.userService(customOAuth2UserService)) +// .successHandler(successHandler(jwtTokenProvider))) .authorizeHttpRequests(request -> request - .requestMatchers("/", "/home", "/swagger-ui/**", "/v3/api-docs/**", "/v3/api-docs/**").permitAll() + .requestMatchers("/", "/home", "/swagger-ui/**", "/v3/api-docs/**", "/v3/api-docs/**","users/oauth/kakao").permitAll() .anyRequest().authenticated() - ) - .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); + ); +// .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); return http.build(); } // @Bean // public PasswordEncoder passwordEncoder() { // return new BCryptPasswordEncoder(); // } - @Bean - public AuthenticationSuccessHandler successHandler(JwtTokenProvider jwtTokenProvider) { - return (request, response, authentication) -> { - OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); - String email = (String) oAuth2User.getAttributes().get("email"); - - // JWT 토큰 생성 - TokenServiceResponse token = jwtTokenProvider.createToken(email); - System.out.println("STACKPOT TOKEN : " + token.getAccessToken()); - - // 응답에 JWT 추가 - response.setHeader("Authorization", "Bearer " + token.getAccessToken()); - response.setContentType("application/json"); - response.setCharacterEncoding("UTF-8"); - - // JSON 응답 반환 - response.getWriter().write("{\"accessTokendf\": \"" + token.getAccessToken() + "\"}"); - response.getWriter().flush(); - }; - } +// @Bean +// public AuthenticationSuccessHandler successHandler(JwtTokenProvider jwtTokenProvider) { +// return (request, response, authentication) -> { +// OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); +// String email = (String) oAuth2User.getAttributes().get("email"); +// +// // JWT 토큰 생성 +// TokenServiceResponse token = jwtTokenProvider.createToken(email); +// System.out.println("STACKPOT TOKEN : " + token.getAccessToken()); +// +// // 응답에 JWT 추가 +// response.setHeader("Authorization", "Bearer " + token.getAccessToken()); +// response.setContentType("application/json"); +// response.setCharacterEncoding("UTF-8"); +// +// // JSON 응답 반환 +// response.getWriter().write("{\"accessTokendf\": \"" + token.getAccessToken() + "\"}"); +// response.getWriter().flush(); +// }; +// } } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/service/KakaoService.java b/src/main/java/stackpot/stackpot/service/KakaoService.java new file mode 100644 index 00000000..1eb43805 --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/KakaoService.java @@ -0,0 +1,84 @@ +package stackpot.stackpot.service; + +import io.netty.handler.codec.http.HttpHeaderValues; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import stackpot.stackpot.web.dto.KakaoTokenResponseDto; +import stackpot.stackpot.web.dto.KakaoUserInfoResponseDto; +import org.springframework.http.HttpStatusCode; + +@Slf4j +@RequiredArgsConstructor +@Service +public class KakaoService { + + private String clientId; + private final String KAUTH_TOKEN_URL_HOST; + private final String KAUTH_USER_URL_HOST; + + @Autowired + public KakaoService(@Value("${KAKAO_CLIENT_ID}") String clientId) { + this.clientId = clientId; + this.KAUTH_TOKEN_URL_HOST = "https://kauth.kakao.com"; + this.KAUTH_USER_URL_HOST = "https://kapi.kakao.com"; + } + + public String getAccessTokenFromKakao(String code) { + + KakaoTokenResponseDto kakaoTokenResponseDto = WebClient.create(KAUTH_TOKEN_URL_HOST).post() + .uri(uriBuilder -> uriBuilder + .path("/oauth/token") + .queryParam("grant_type", "authorization_code") + .queryParam("client_id", clientId) + .queryParam("code", code) + .queryParam("scope", "account_email") + .build(true)) + .header(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString()) + .retrieve() + .onStatus(HttpStatusCode::is4xxClientError, clientResponse -> Mono.error(new RuntimeException("Invalid Parameter"))) + .onStatus(HttpStatusCode::is5xxServerError, clientResponse -> Mono.error(new RuntimeException("Internal Server Error"))) + .bodyToMono(KakaoTokenResponseDto.class) + .block(); + + log.info("[Kakao Service] Access Token ------> {}", kakaoTokenResponseDto.getAccessToken()); + log.info("[Kakao Service] Refresh Token ------> {}", kakaoTokenResponseDto.getRefreshToken()); + log.info("[Kakao Service] Id Token ------> {}", kakaoTokenResponseDto.getIdToken()); + log.info("[Kakao Service] Scope ------> {}", kakaoTokenResponseDto.getScope()); + + return kakaoTokenResponseDto.getAccessToken(); + } + + public KakaoUserInfoResponseDto getUserInfo(String accessToken) { + + KakaoUserInfoResponseDto userInfo = WebClient.create(KAUTH_USER_URL_HOST) + .get() + .uri(uriBuilder -> uriBuilder + .path("/v2/user/me") + .build(true)) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .header(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString()) + .retrieve() + .onStatus(HttpStatusCode::is4xxClientError, clientResponse -> Mono.error(new RuntimeException("Invalid Parameter"))) + .onStatus(HttpStatusCode::is5xxServerError, clientResponse -> Mono.error(new RuntimeException("Internal Server Error"))) + .bodyToMono(KakaoUserInfoResponseDto.class) + .block(); + + log.info("[Kakao Service] Raw User Info Response: {}", userInfo); + + if (userInfo == null || userInfo.getKakaoAccount() == null) { + log.error("[Kakao Service] Invalid user info response from Kakao API"); + throw new RuntimeException("Invalid user info response from Kakao API"); + } + + log.info("[Kakao Service] Auth ID ------> {}", userInfo.getId()); + log.info("[Kakao Service] email ------> {}", userInfo.getKakaoAccount().getEmail()); + + return userInfo; + } +} diff --git a/src/main/java/stackpot/stackpot/web/controller/KakaoLoginController.java b/src/main/java/stackpot/stackpot/web/controller/KakaoLoginController.java new file mode 100644 index 00000000..059f59b5 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/controller/KakaoLoginController.java @@ -0,0 +1,34 @@ +//package stackpot.stackpot.web.controller; +// +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.http.HttpStatus; +//import org.springframework.http.ResponseEntity; +//import org.springframework.web.bind.annotation.GetMapping; +//import org.springframework.web.bind.annotation.RequestMapping; +//import org.springframework.web.bind.annotation.RequestParam; +//import org.springframework.web.bind.annotation.RestController; +//import stackpot.stackpot.converter.UserConverter; +//import stackpot.stackpot.service.KakaoService; +//import stackpot.stackpot.web.dto.KakaoUserInfoResponseDto; +// +//import static com.mysql.cj.conf.PropertyKey.logger; +// +//@Slf4j +//@RestController +//@RequiredArgsConstructor +//@RequestMapping("") +//public class KakaoLoginController { +// private final KakaoService kakaoService; +// +// @GetMapping("/users/oauth/kakao") +// public ResponseEntity callback(@RequestParam("code") String code) { +// +// log.info("Authorization code: {}", code); // 인증 코드 확인 +// String accessToken = kakaoService.getAccessTokenFromKakao(code); +// KakaoUserInfoResponseDto userInfo = kakaoService.getUserInfo(accessToken); +// +// String email = userInfo.getKakaoAccount().getEmail();// 이메일 가져오기 +// return ResponseEntity.ok(userInfo); +// } +//} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/controller/UserController.java b/src/main/java/stackpot/stackpot/web/controller/UserController.java index ea8a7953..bb2f0057 100644 --- a/src/main/java/stackpot/stackpot/web/controller/UserController.java +++ b/src/main/java/stackpot/stackpot/web/controller/UserController.java @@ -14,7 +14,9 @@ import org.springframework.web.bind.annotation.*; import stackpot.stackpot.converter.UserConverter; import stackpot.stackpot.domain.User; +import stackpot.stackpot.service.KakaoService; import stackpot.stackpot.service.UserCommandService; +import stackpot.stackpot.web.dto.KakaoUserInfoResponseDto; import stackpot.stackpot.web.dto.UserRequestDto; import java.util.List; @@ -27,6 +29,7 @@ public class UserController { private final UserCommandService userCommandService; + private final KakaoService kakaoService; @Operation(summary = "토큰 test api") @GetMapping("/login/token") public ResponseEntity testEndpoint(Authentication authentication) { @@ -36,6 +39,18 @@ public ResponseEntity testEndpoint(Authentication authentication) { return ResponseEntity.ok("Authenticated user: " + authentication.getName()); } + @GetMapping("/oauth/kakao") + public ResponseEntity callback(@RequestParam("code") String code) { + + log.info("Authorization code: {}", code); // 인증 코드 확인 + String accessToken = kakaoService.getAccessTokenFromKakao(code); + KakaoUserInfoResponseDto userInfo = kakaoService.getUserInfo(accessToken); + + String email = userInfo.getKakaoAccount().getEmail();// 이메일 가져오기 + log.info("userInfo.getEmail -> ", email); + return ResponseEntity.ok(userInfo); + } + @Operation(summary = "회원가입 api") @PatchMapping("/profile") public ResponseEntity signup(@Valid @RequestBody UserRequestDto.JoinDto request, diff --git a/src/main/java/stackpot/stackpot/web/dto/KakaoTokenResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/KakaoTokenResponseDto.java new file mode 100644 index 00000000..2727d113 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/KakaoTokenResponseDto.java @@ -0,0 +1,28 @@ +package stackpot.stackpot.web.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor //역직렬화를 위한 기본 생성자 +@JsonIgnoreProperties(ignoreUnknown = true) +public class KakaoTokenResponseDto { + + @JsonProperty("token_type") + public String tokenType; + @JsonProperty("access_token") + public String accessToken; + @JsonProperty("id_token") + public String idToken; + @JsonProperty("expires_in") + public Integer expiresIn; + @JsonProperty("refresh_token") + public String refreshToken; + @JsonProperty("refresh_token_expires_in") + public Integer refreshTokenExpiresIn; + @JsonProperty("scope") + public String scope; +} + diff --git a/src/main/java/stackpot/stackpot/web/dto/KakaoUserInfoResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/KakaoUserInfoResponseDto.java new file mode 100644 index 00000000..a91fa3f6 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/KakaoUserInfoResponseDto.java @@ -0,0 +1,190 @@ +package stackpot.stackpot.web.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.Date; +import java.util.HashMap; + +@Getter +@NoArgsConstructor //역직렬화를 위한 기본 생성자 +@JsonIgnoreProperties(ignoreUnknown = true) +public class KakaoUserInfoResponseDto { + + //회원 번호 + @JsonProperty("id") + public Long id; + + //자동 연결 설정을 비활성화한 경우만 존재. + //true : 연결 상태, false : 연결 대기 상태 + @JsonProperty("has_signed_up") + public Boolean hasSignedUp; + + //서비스에 연결 완료된 시각. UTC + @JsonProperty("connected_at") + public Date connectedAt; + + //카카오싱크 간편가입을 통해 로그인한 시각. UTC + @JsonProperty("synched_at") + public Date synchedAt; + + //사용자 프로퍼티 + @JsonProperty("properties") + public HashMap properties; + + //카카오 계정 정보 + @JsonProperty("kakao_account") + public KakaoAccount kakaoAccount; + + //uuid 등 추가 정보 + @JsonProperty("for_partner") + public Partner partner; + + @Getter + @NoArgsConstructor + @JsonIgnoreProperties(ignoreUnknown = true) + public class KakaoAccount { + + //프로필 정보 제공 동의 여부 + @JsonProperty("profile_needs_agreement") + public Boolean isProfileAgree; + + //닉네임 제공 동의 여부 + @JsonProperty("profile_nickname_needs_agreement") + public Boolean isNickNameAgree; + + //프로필 사진 제공 동의 여부 + @JsonProperty("profile_image_needs_agreement") + public Boolean isProfileImageAgree; + + //사용자 프로필 정보 + @JsonProperty("profile") + public Profile profile; + + //이름 제공 동의 여부 + @JsonProperty("name_needs_agreement") + public Boolean isNameAgree; + + //카카오계정 이름 + @JsonProperty("name") + public String name; + + //이메일 제공 동의 여부 + @JsonProperty("email_needs_agreement") + public Boolean isEmailAgree; + + //이메일이 유효 여부 + // true : 유효한 이메일, false : 이메일이 다른 카카오 계정에 사용돼 만료 + @JsonProperty("is_email_valid") + public Boolean isEmailValid; + + //이메일이 인증 여부 + //true : 인증된 이메일, false : 인증되지 않은 이메일 + @JsonProperty("is_email_verified") + public Boolean isEmailVerified; + + //카카오계정 대표 이메일 + @JsonProperty("email") + public String email; + + //연령대 제공 동의 여부 + @JsonProperty("age_range_needs_agreement") + public Boolean isAgeAgree; + + //연령대 + //참고 https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info + @JsonProperty("age_range") + public String ageRange; + + //출생 연도 제공 동의 여부 + @JsonProperty("birthyear_needs_agreement") + public Boolean isBirthYearAgree; + + //출생 연도 (YYYY 형식) + @JsonProperty("birthyear") + public String birthYear; + + //생일 제공 동의 여부 + @JsonProperty("birthday_needs_agreement") + public Boolean isBirthDayAgree; + + //생일 (MMDD 형식) + @JsonProperty("birthday") + public String birthDay; + + //생일 타입 + // SOLAR(양력) 혹은 LUNAR(음력) + @JsonProperty("birthday_type") + public String birthDayType; + + //성별 제공 동의 여부 + @JsonProperty("gender_needs_agreement") + public Boolean isGenderAgree; + + //성별 + @JsonProperty("gender") + public String gender; + + //전화번호 제공 동의 여부 + @JsonProperty("phone_number_needs_agreement") + public Boolean isPhoneNumberAgree; + + //전화번호 + //국내 번호인 경우 +82 00-0000-0000 형식 + @JsonProperty("phone_number") + public String phoneNumber; + + //CI 동의 여부 + @JsonProperty("ci_needs_agreement") + public Boolean isCIAgree; + + //CI, 연계 정보 + @JsonProperty("ci") + public String ci; + + //CI 발급 시각, UTC + @JsonProperty("ci_authenticated_at") + public Date ciCreatedAt; + + @Getter + @NoArgsConstructor + @JsonIgnoreProperties(ignoreUnknown = true) + public class Profile { + + //닉네임 + @JsonProperty("nickname") + public String nickName; + + //프로필 미리보기 이미지 URL + @JsonProperty("thumbnail_image_url") + public String thumbnailImageUrl; + + //프로필 사진 URL + @JsonProperty("profile_image_url") + public String profileImageUrl; + + //프로필 사진 URL 기본 프로필인지 여부 + //true : 기본 프로필, false : 사용자 등록 + @JsonProperty("is_default_image") + public String isDefaultImage; + + //닉네임이 기본 닉네임인지 여부 + //true : 기본 닉네임, false : 사용자 등록 + @JsonProperty("is_default_nickname") + public Boolean isDefaultNickName; + + } + } + + @Getter + @NoArgsConstructor + @JsonIgnoreProperties(ignoreUnknown = true) + public class Partner { + //고유 ID + @JsonProperty("uuid") + public String uuid; + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 66d25a4d..8f4e3ce7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -30,23 +30,6 @@ spring: credentials: accessKey: ${AWS_ACCESS_KEY_ID} secretKey: ${AWS_SECRET_ACCESS_KEY} - security: - oauth2: - client: - registration: - kakao: - client-authentication-method: client_secret_post - client-id: ${KAKAO_CLIENT_ID} - client-secret: ${KAKAO_CLIENT_SECRET} - redirect-uri: https://stackpot.co.kr/login/oauth2/code/kakao - authorization-grant-type: authorization_code - scope: account_email - client-name: Kakao - provider: - kakao: - authorization-uri: https://kauth.kakao.com/oauth/authorize - token-uri: https://kauth.kakao.com/oauth/token - user-info-uri: https://kapi.kakao.com/v2/user/me - user-name-attribute: id + jwt: secret: ${JWT_SECRET} From 4026cbb2e75f9656d2f7fd063cca6e5221316475 Mon Sep 17 00:00:00 2001 From: starday119 Date: Sat, 25 Jan 2025 02:10:02 +0900 Subject: [PATCH 56/76] =?UTF-8?q?[MOD]:=20Role=20=ED=98=95=EC=8B=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PotApplicationConverterImpl.java | 5 +-- .../stackpot/converter/PotConverterImpl.java | 2 +- .../PotMemberConverterImpl.java | 21 +++-------- .../stackpot/converter/UserConverter.java | 5 +-- .../domain/PotRecruitmentDetails.java | 4 ++- .../java/stackpot/stackpot/domain/User.java | 3 +- .../domain/mapping/PotApplication.java | 4 ++- .../stackpot/domain/mapping/PotMember.java | 4 ++- .../EmailService/EmailServiceImpl.java | 20 +++++++++-- .../stackpot/service/MyPotServiceImpl.java | 6 ++-- .../stackpot/service/PotServiceImpl.java | 35 ++++++++++--------- .../service/UserCommandServiceImpl.java | 3 +- 12 files changed, 64 insertions(+), 48 deletions(-) diff --git a/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverterImpl.java b/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverterImpl.java index 322c56bc..3a2ac829 100644 --- a/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverterImpl.java @@ -2,6 +2,7 @@ import org.springframework.stereotype.Component; import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.domain.mapping.PotApplication; import stackpot.stackpot.domain.User; import stackpot.stackpot.domain.enums.ApplicationStatus; @@ -23,7 +24,7 @@ public PotApplication toEntity(PotApplicationRequestDto dto, Pot pot, User user) return PotApplication.builder() .pot(pot) .user(user) - .potRole(dto.getPotRole()) + .potRole(Role.valueOf(dto.getPotRole())) .liked(false) // 기본값 false .status(ApplicationStatus.PENDING) // 지원 상태 .appliedAt(LocalDateTime.now()) // 지원 시간 @@ -45,7 +46,7 @@ public PotApplicationResponseDto toDto(PotApplication entity) { return PotApplicationResponseDto.builder() .applicationId(entity.getApplicationId()) - .potRole(entity.getPotRole()) + .potRole(entity.getPotRole().name()) .liked(entity.getLiked()) .status(entity.getStatus() != null ? entity.getStatus().name() : "UNKNOWN") // 상태가 null이면 기본값 설정 .appliedAt(entity.getAppliedAt()) // null 가능성을 허용 diff --git a/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java b/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java index e9385566..0823ab8e 100644 --- a/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java @@ -48,7 +48,7 @@ public PotResponseDto toDto(Pot entity, List recruitmentD .recruitmentDeadline(entity.getRecruitmentDeadline()) .recruitmentDetails(recruitmentDetails.stream().map(r -> PotRecruitmentResponseDto.builder() .recruitmentId(r.getRecruitmentId()) - .recruitmentRole(r.getRecruitmentRole()) + .recruitmentRole(String.valueOf(r.getRecruitmentRole())) .recruitmentCount(r.getRecruitmentCount()) .build()).collect(Collectors.toList())) .build(); diff --git a/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java b/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java index 6d224bc1..8351e31e 100644 --- a/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java @@ -3,6 +3,7 @@ import org.springframework.stereotype.Component; import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.domain.mapping.PotApplication; import stackpot.stackpot.domain.mapping.PotMember; import stackpot.stackpot.web.dto.PotMemberAppealResponseDto; @@ -16,7 +17,7 @@ public PotMember toEntity(User user, Pot pot, PotApplication application, Boolea .user(user) .pot(pot) .potApplication(application) - .roleName(application != null ? mapRoleName(application.getPotRole()) : "Owner") + .roleName(application != null ? application.getPotRole() : null) // PotRole Enum 그대로 사용 .owner(isOwner) .appealContent(null) .build(); @@ -28,25 +29,13 @@ public PotMemberAppealResponseDto toDto(PotMember entity) { .potMemberId(entity.getPotMemberId()) .potId(entity.getPot().getPotId()) .userId(entity.getUser().getId()) - .roleName(entity.getRoleName()) + .roleName(String.valueOf(entity.getRoleName())) // PotRole 그대로 반환 .owner(entity.isOwner()) .appealContent(entity.getAppealContent()) .build(); } - private String mapRoleName(String potRole) { - switch (potRole) { - case "FRONTEND": - return "버섯"; - case "PLANNING": - return "당근"; - case "BACKEND": - return "양파"; - case "DESIGN": - return "브로콜리"; - default: - return "멤버"; - } - } + + } diff --git a/src/main/java/stackpot/stackpot/converter/UserConverter.java b/src/main/java/stackpot/stackpot/converter/UserConverter.java index 0b621aa7..c3874191 100644 --- a/src/main/java/stackpot/stackpot/converter/UserConverter.java +++ b/src/main/java/stackpot/stackpot/converter/UserConverter.java @@ -1,6 +1,7 @@ package stackpot.stackpot.converter; import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.web.dto.UserRequestDto; import stackpot.stackpot.web.dto.UserResponseDto; @@ -11,7 +12,7 @@ public static User toUser(UserRequestDto.JoinDto request) { .nickname(request.getNickname()) .kakaoId(request.getKakaoId()) .interest(request.getInterest()) - .role(request.getRole()) + .role(Role.valueOf(request.getRole())) .build(); } @@ -21,7 +22,7 @@ public static UserResponseDto toDto(User user) { .nickname(user.getNickname()) .email(user.getEmail()) // 추가된 코드 .kakaoId(user.getKakaoId()) - .role(user.getRole()) + .role(String.valueOf(user.getRole())) .interest(user.getInterest()) .userTemperature(user.getUserTemperature()) .build(); diff --git a/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java b/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java index 4ad72557..4e89a236 100644 --- a/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java +++ b/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java @@ -3,6 +3,7 @@ import jakarta.persistence.*; import lombok.*; import stackpot.stackpot.domain.common.BaseEntity; +import stackpot.stackpot.domain.enums.Role; @Entity @Getter @@ -21,7 +22,8 @@ public class PotRecruitmentDetails extends BaseEntity { @Column(nullable = true, length = 255) @Getter @Setter - private String recruitmentRole; + @Enumerated(EnumType.STRING) + private Role recruitmentRole; @Column(nullable = true) @Getter diff --git a/src/main/java/stackpot/stackpot/domain/User.java b/src/main/java/stackpot/stackpot/domain/User.java index 40c8d060..ea2aa5ec 100644 --- a/src/main/java/stackpot/stackpot/domain/User.java +++ b/src/main/java/stackpot/stackpot/domain/User.java @@ -5,6 +5,7 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import stackpot.stackpot.domain.common.BaseEntity; +import stackpot.stackpot.domain.enums.Role; import java.util.Collection; @@ -32,7 +33,7 @@ public class User extends BaseEntity implements UserDetails{ private String nickname; // 닉네임 @Column(nullable = true, length = 255) - private String role; // 역할 + private Role role; // 역할 @Column(nullable = true, length = 255) private String interest; // 관심사 diff --git a/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java b/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java index 943fddb6..a9726645 100644 --- a/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java +++ b/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java @@ -6,6 +6,7 @@ import stackpot.stackpot.domain.User; import stackpot.stackpot.domain.common.BaseEntity; import stackpot.stackpot.domain.enums.ApplicationStatus; +import stackpot.stackpot.domain.enums.Role; import java.time.LocalDateTime; @@ -33,8 +34,9 @@ public class PotApplication extends BaseEntity { @Column(nullable = false, columnDefinition = "BOOLEAN DEFAULT false") private Boolean liked = false; + @Enumerated(EnumType.STRING) @Column(nullable = false, length = 10) - private String potRole; // 팟 역할 + private Role potRole; // 팟 역할 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "pot_id", nullable = false) diff --git a/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java b/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java index 008f5252..3c00ef49 100644 --- a/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java +++ b/src/main/java/stackpot/stackpot/domain/mapping/PotMember.java @@ -5,6 +5,7 @@ import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.User; import stackpot.stackpot.domain.common.BaseEntity; +import stackpot.stackpot.domain.enums.Role; @Entity @Getter @@ -30,8 +31,9 @@ public class PotMember extends BaseEntity { @JoinColumn(name = "application_id", nullable = false) private PotApplication potApplication; + @Enumerated(EnumType.STRING) @Column(nullable = false, length = 10) - private String roleName; + private Role roleName; @Getter @Column(nullable = false) diff --git a/src/main/java/stackpot/stackpot/service/EmailService/EmailServiceImpl.java b/src/main/java/stackpot/stackpot/service/EmailService/EmailServiceImpl.java index a6f77ae4..4125f90f 100644 --- a/src/main/java/stackpot/stackpot/service/EmailService/EmailServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/EmailService/EmailServiceImpl.java @@ -18,12 +18,28 @@ public void sendSupportNotification(String toEmail, String potName, String appli try { SimpleMailMessage message = new SimpleMailMessage(); message.setTo(toEmail); - message.setSubject("새로운 팟 지원 알림"); - message.setText(applicantName + " 님이 '" + potName + "' 팟에 지원했습니다!"); + message.setSubject("[STACKPOT] 새로운 지원자가 있습니다 - '" + potName + "'"); + + // 이메일 본문 작성 + String emailBody = String.format( + "[%s]에 새로운 지원자가 있습니다!\n\n" + + "안녕하세요, STACKPOT에서 알려드립니다.\n\n" + + "회원님이 생성하신 [%s]에 새로운 지원자가 지원했습니다. 아래는 지원자 정보와 관련된 세부 사항입니다:\n\n" + + "- 지원자 이름: %s\n\n" + + "STACKPOT과 함께 성공적인 프로젝트를 만들어가세요!\n\n" + + "감사합니다.\n\n" + + "STACKPOT 드림\n\n" + + "고객센터: stackpot.notice@gmail.com\n" + + "홈페이지: https://www.stackpot.co.kr", + potName, potName, applicantName + ); + + message.setText(emailBody); mailSender.send(message); } catch (Exception e) { // 예외 처리: 이메일 전송 실패 시 로그를 출력 System.err.println("이메일 전송 실패: " + e.getMessage()); } } + } diff --git a/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java b/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java index 7313df36..a9ce1698 100644 --- a/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java @@ -187,7 +187,7 @@ private MyPotResponseDTO.OngoingPotsDetail convertToOngoingPotDetail(Pot pot) { List recruitmentDetails = pot.getRecruitmentDetails().stream() .map(details -> RecruitmentDetailsResponseDTO.builder() .recruitmentId(details.getRecruitmentId()) - .recruitmentRole(details.getRecruitmentRole()) + .recruitmentRole(String.valueOf(details.getRecruitmentRole())) .recruitmentCount(details.getRecruitmentCount()) .build()) .collect(Collectors.toList()); @@ -195,7 +195,7 @@ private MyPotResponseDTO.OngoingPotsDetail convertToOngoingPotDetail(Pot pot) { List potMembers = pot.getPotMembers().stream() .map(member -> PotMemberResponseDTO.builder() .potMemberId(member.getPotMemberId()) - .roleName(member.getRoleName()) + .roleName(String.valueOf(member.getRoleName())) .build()) .collect(Collectors.toList()); @@ -203,7 +203,7 @@ private MyPotResponseDTO.OngoingPotsDetail convertToOngoingPotDetail(Pot pot) { return MyPotResponseDTO.OngoingPotsDetail.builder() .user(UserResponseDto.builder() .nickname(pot.getUser().getNickname()) - .role(pot.getUser().getRole()) + .role(String.valueOf(pot.getUser().getRole())) .build()) .pot(PotResponseDto.builder() .potName(pot.getPotName()) diff --git a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java index c7440744..ad192f64 100644 --- a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java @@ -15,6 +15,7 @@ import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.PotRecruitmentDetails; import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.domain.mapping.PotApplication; import stackpot.stackpot.repository.PotRepository.PotRecruitmentDetailsRepository; import stackpot.stackpot.repository.PotRepository.PotRepository; @@ -54,7 +55,7 @@ public PotResponseDto createPotWithRecruitments(PotRequestDto requestDto) { // 모집 정보 저장 List recruitmentDetails = requestDto.getRecruitmentDetails().stream() .map(recruitmentDto -> PotRecruitmentDetails.builder() - .recruitmentRole(recruitmentDto.getRecruitmentRole()) + .recruitmentRole(Role.valueOf(recruitmentDto.getRecruitmentRole())) .recruitmentCount(recruitmentDto.getRecruitmentCount()) .pot(savedPot) .build()) @@ -105,7 +106,7 @@ public PotResponseDto updatePotWithRecruitments(Long potId, PotRequestDto reques // 새로운 모집 정보 저장 List recruitmentDetails = requestDto.getRecruitmentDetails().stream() .map(recruitmentDto -> PotRecruitmentDetails.builder() - .recruitmentRole(recruitmentDto.getRecruitmentRole()) + .recruitmentRole(Role.valueOf(recruitmentDto.getRecruitmentRole())) .recruitmentCount(recruitmentDto.getRecruitmentCount()) .pot(pot) .build()) @@ -165,7 +166,7 @@ public List getAllPots(String role, Integer page, I .map(pot -> PotAllResponseDTO.PotDetail.builder() .user(UserResponseDto.builder() .nickname(pot.getUser().getNickname()) - .role(pot.getUser().getRole()) + .role(String.valueOf(pot.getUser().getRole())) .build()) .pot(potConverter.toDto(pot, pot.getRecruitmentDetails())) // 변환기 사용 .build()) @@ -182,7 +183,7 @@ public ApplicantResponseDTO getPotDetails(Long potId) { List applicantDto = pot.getPotApplication().stream() .map(app -> ApplicantResponseDTO.ApplicantDto.builder() .applicationId(app.getApplicationId()) - .potRole(app.getPotRole()) + .potRole(String.valueOf(app.getPotRole())) .liked(app.getLiked()) .build()) .collect(Collectors.toList()); @@ -191,7 +192,7 @@ public ApplicantResponseDTO getPotDetails(Long potId) { List recruitmentDetailsDto = pot.getRecruitmentDetails().stream() .map(details -> PotRecruitmentResponseDto.builder() .recruitmentId(details.getRecruitmentId()) - .recruitmentRole(details.getRecruitmentRole()) + .recruitmentRole(String.valueOf(details.getRecruitmentRole())) .recruitmentCount(details.getRecruitmentCount()) .build()) .collect(Collectors.toList()); @@ -216,7 +217,7 @@ public ApplicantResponseDTO getPotDetails(Long potId) { return ApplicantResponseDTO.builder() .user(UserResponseDto.builder() .nickname(pot.getUser().getNickname()) - .role(pot.getUser().getRole()) + .role(String.valueOf(pot.getUser().getRole())) .build()) .pot(potDto) .applicant(applicantDto) @@ -270,8 +271,8 @@ public List getLikedApplicants(Long potId) { .filter(PotApplication::getLiked) .map(app -> LikedApplicantResponseDTO.builder() .applicationId(app.getApplicationId()) - .applicantRole(app.getPotRole()) - .potNickname(app.getUser().getNickname() + getVegetableNameByRole(app.getPotRole())) + .applicantRole(String.valueOf(app.getPotRole())) + .potNickname(app.getUser().getNickname() + getVegetableNameByRole(String.valueOf(app.getPotRole()))) .liked(app.getLiked()) .build()) .collect(Collectors.toList()); @@ -312,7 +313,7 @@ public List getAppliedPots() { List recruitmentDetailsDto = pot.getRecruitmentDetails().stream() .map(details -> PotRecruitmentResponseDto.builder() .recruitmentId(details.getRecruitmentId()) - .recruitmentRole(details.getRecruitmentRole()) + .recruitmentRole(String.valueOf(details.getRecruitmentRole())) .recruitmentCount(details.getRecruitmentCount()) .build()) .collect(Collectors.toList()); @@ -337,7 +338,7 @@ public List getAppliedPots() { // 유저 정보를 DTO로 변환 UserResponseDto userDto = UserResponseDto.builder() .nickname(pot.getUser().getNickname()) - .role(pot.getUser().getRole()) + .role(String.valueOf(pot.getUser().getRole())) .build(); return PotAllResponseDTO.PotDetail.builder() @@ -429,7 +430,7 @@ private PotAllResponseDTO.PotDetail convertToPotDetail(Pot pot) { List recruitmentDetailsDto = pot.getRecruitmentDetails().stream() .map(details -> PotRecruitmentResponseDto.builder() .recruitmentId(details.getRecruitmentId()) - .recruitmentRole(details.getRecruitmentRole()) + .recruitmentRole(String.valueOf(details.getRecruitmentRole())) .recruitmentCount(details.getRecruitmentCount()) .build()) .collect(Collectors.toList()); @@ -452,8 +453,8 @@ private PotAllResponseDTO.PotDetail convertToPotDetail(Pot pot) { return PotAllResponseDTO.PotDetail.builder() .user(UserResponseDto.builder() - .nickname(pot.getUser().getNickname() + getVegetableNameByRole(pot.getUser().getRole())) - .role(pot.getUser().getRole()) + .nickname(pot.getUser().getNickname() + getVegetableNameByRole(String.valueOf(pot.getUser().getRole()))) + .role(String.valueOf(pot.getUser().getRole())) .build()) .pot(potDto) .build(); @@ -464,7 +465,7 @@ private MyPotResponseDTO.OngoingPotsDetail convertToOngoingPotDetail(Pot pot) { List recruitmentDetails = pot.getRecruitmentDetails().stream() .map(details -> RecruitmentDetailsResponseDTO.builder() .recruitmentId(details.getRecruitmentId()) - .recruitmentRole(details.getRecruitmentRole()) + .recruitmentRole(String.valueOf(details.getRecruitmentRole())) .recruitmentCount(details.getRecruitmentCount()) .build()) .collect(Collectors.toList()); @@ -472,15 +473,15 @@ private MyPotResponseDTO.OngoingPotsDetail convertToOngoingPotDetail(Pot pot) { List potMembers = pot.getPotMembers().stream() .map(member -> PotMemberResponseDTO.builder() .potMemberId(member.getPotMemberId()) - .roleName(member.getRoleName()) + .roleName(String.valueOf(member.getRoleName())) .build()) .collect(Collectors.toList()); return MyPotResponseDTO.OngoingPotsDetail.builder() .user(UserResponseDto.builder() - .nickname(pot.getUser().getNickname() + getVegetableNameByRole(pot.getUser().getRole())) - .role(pot.getUser().getRole()) + .nickname(pot.getUser().getNickname() + getVegetableNameByRole(String.valueOf(pot.getUser().getRole()))) + .role(String.valueOf(pot.getUser().getRole())) .build()) .pot(PotResponseDto.builder() .potId(pot.getPotId()) diff --git a/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java b/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java index 013fa1b5..aa486ac9 100644 --- a/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java @@ -7,6 +7,7 @@ import org.springframework.stereotype.Service; import stackpot.stackpot.converter.UserConverter; import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.repository.UserRepository.UserRepository; import stackpot.stackpot.web.dto.UserRequestDto; @@ -37,7 +38,7 @@ private void updateUserData(User user, UserRequestDto.JoinDto request) { // 닉네임 user.setNickname(request.getNickname()); // 역할군 - user.setRole(request.getRole()); + user.setRole(Role.valueOf(request.getRole())); // 관심사 user.setInterest(request.getInterest()); } From 448a54f14412c1fa0bde756e32b9e9658aa32cb4 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Sat, 25 Jan 2025 03:04:49 +0900 Subject: [PATCH 57/76] =?UTF-8?q?[Refactor]=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EB=B0=9C=EA=B8=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/CustomOAuth2UserService.java | 100 --------------- .../security/JwtAuthenticationFilter.java | 114 +++++++++--------- .../config/security/JwtTokenProvider.java | 13 +- .../config/security/SecurityConfig.java | 55 +++------ .../stackpot/service/UserCommandService.java | 1 + .../service/UserCommandServiceImpl.java | 18 +++ .../web/controller/UserController.java | 16 ++- 7 files changed, 114 insertions(+), 203 deletions(-) delete mode 100644 src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java diff --git a/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java b/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java deleted file mode 100644 index 3b99c9f7..00000000 --- a/src/main/java/stackpot/stackpot/config/security/CustomOAuth2UserService.java +++ /dev/null @@ -1,100 +0,0 @@ -//package stackpot.stackpot.config.security; -// -//import jakarta.transaction.Transactional; -//import lombok.RequiredArgsConstructor; -//import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; -//import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -//import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -//import org.springframework.security.oauth2.core.user.DefaultOAuth2User; -//import org.springframework.security.oauth2.core.user.OAuth2User; -//import org.springframework.stereotype.Service; -//import stackpot.stackpot.domain.User; -//import stackpot.stackpot.repository.UserRepository.UserRepository; -// -//import java.util.HashMap; -//import java.util.Map; -//import java.util.UUID; -// -//@Service -//@RequiredArgsConstructor -//public class CustomOAuth2UserService extends DefaultOAuth2UserService { -// -// private final UserRepository userRepository; -// -//// @Transactional -//// @Override -//// public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { -//// // 1. 유저 정보(attributes) 가져오기 -//// Map oAuth2UserAttributes = super.loadUser(userRequest).getAttributes(); -//// -//// // 2. resistrationId 가져오기 (third-party id) -//// String registrationId = userRequest.getClientRegistration().getRegistrationId(); -//// -//// // 3. userNameAttributeName 가져오기 -//// String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails() -//// .getUserInfoEndpoint().getUserNameAttributeName(); -//// -//// // 4. 유저 정보 dto 생성 -//// OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfo.of(registrationId, oAuth2UserAttributes); -//// -//// // 5. 회원가입 및 로그인 -//// User user = saveOrUpdateUser(oAuth2UserInfo); -//// -//// // 6. OAuth2User로 반환 -//// return new PrincipalDetails(user, oAuth2UserAttributes, userNameAttributeName); -//// } -// @Override -// public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { -// OAuth2User oAuth2User = super.loadUser(userRequest); -// System.out.println("loadUser"); -// -// // 사용자 정보 가져오기 -// Map attributes = oAuth2User.getAttributes(); -// Map kakaoAccount = (Map) attributes.get("kakao_account"); -// -// if (kakaoAccount == null) { -// throw new OAuth2AuthenticationException("Failed to retrieve kakao_account from attributes."); -// } -// -// // 이메일 가져오기 -// String email = (String) kakaoAccount.get("email"); -// if (email == null) { -// throw new OAuth2AuthenticationException("Email not found in kakao_account."); -// } -// -// // 사용자 정보 확인 -// userRepository.findByEmail(email).ifPresentOrElse( -// user -> System.out.println("Existing user found: " + email), -// () -> System.out.println("No user found. Redirecting to signup.") -// ); -// -// // attributes에 email 추가 -// Map modifiedAttributes = new HashMap<>(attributes); -// modifiedAttributes.put("email", email); -// -// // 디버깅: modifiedAttributes 확인 -// System.out.println("Final Modified Attributes: " + modifiedAttributes); -// -// saveOrUpdateUser(email); -// -// -// // DefaultOAuth2User 생성 -// return new DefaultOAuth2User( -// oAuth2User.getAuthorities(), -// modifiedAttributes, -// "email" // Principal로 사용할 필드 이름 -// ); -// } -// -// private void saveOrUpdateUser(String email) { -// System.out.println("saveOrUpdateUser 실행"); -// userRepository.findByEmail(email) -// .orElseGet(() -> { -// User user = User.builder() -// .email(email) -// .userTemperature(33) -// .build(); -// return userRepository.save(user); // 저장된 사용자 반환 -// }); -// } -//} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/config/security/JwtAuthenticationFilter.java b/src/main/java/stackpot/stackpot/config/security/JwtAuthenticationFilter.java index 79c0db6d..2c74e7d6 100644 --- a/src/main/java/stackpot/stackpot/config/security/JwtAuthenticationFilter.java +++ b/src/main/java/stackpot/stackpot/config/security/JwtAuthenticationFilter.java @@ -1,57 +1,57 @@ -//package stackpot.stackpot.config.security; -// -//import jakarta.servlet.FilterChain; -//import jakarta.servlet.ServletException; -//import jakarta.servlet.http.HttpServletRequest; -//import jakarta.servlet.http.HttpServletResponse; -//import org.springframework.security.core.context.SecurityContextHolder; -//import org.springframework.web.filter.OncePerRequestFilter; -//import org.springframework.security.core.Authentication; -// -//import java.io.IOException; -//public class JwtAuthenticationFilter extends OncePerRequestFilter { -// -// private final JwtTokenProvider jwtTokenProvider; -// -// public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { -// this.jwtTokenProvider = jwtTokenProvider; -// } -// -// @Override -// protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) -// throws ServletException, IOException { -// String token = resolveToken(request); -// -// try { -// if (token != null) { -// System.out.println("Token found: " + token); -// if (jwtTokenProvider.validateToken(token)) { -// Authentication authentication = jwtTokenProvider.getAuthentication(token); -// SecurityContextHolder.getContext().setAuthentication(authentication); -// System.out.println("Authentication set in SecurityContext: " + authentication.getName()); -// } else { -// System.out.println("Invalid or expired token."); -// response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); -// response.getWriter().write("Invalid or expired token."); -// return; -// } -// } else { -// System.out.println("No token found in the request."); -// } -// filterChain.doFilter(request, response); -// } catch (Exception ex) { -// System.out.println("Exception in JwtAuthenticationFilter: " + ex.getMessage()); -// response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); -// response.getWriter().write("Internal server error occurred."); -// } -// } -// -// private String resolveToken(HttpServletRequest request) { -// String bearerToken = request.getHeader("Authorization"); -// if (bearerToken != null && bearerToken.startsWith("Bearer ")) { -// return bearerToken.substring(7); -// } -// System.out.println("Authorization header is missing or does not start with 'Bearer '."); -// return null; -// } -//} \ No newline at end of file +package stackpot.stackpot.config.security; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.security.core.Authentication; + +import java.io.IOException; +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtTokenProvider jwtTokenProvider; + + public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { + this.jwtTokenProvider = jwtTokenProvider; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + String token = resolveToken(request); + + try { + if (token != null) { + System.out.println("Token found: " + token); + if (jwtTokenProvider.validateToken(token)) { + Authentication authentication = jwtTokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + System.out.println("Authentication set in SecurityContext: " + authentication.getName()); + } else { + System.out.println("Invalid or expired token."); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("Invalid or expired token."); + return; + } + } else { + System.out.println("No token found in the request."); + } + filterChain.doFilter(request, response); + } catch (Exception ex) { + System.out.println("Exception in JwtAuthenticationFilter: " + ex.getMessage()); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + response.getWriter().write("Internal server error occurred."); + } + } + + private String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (bearerToken != null && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + System.out.println("Authorization header is missing or does not start with 'Bearer '."); + return null; + } +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java b/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java index 453e338e..c5c7a2d7 100644 --- a/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java +++ b/src/main/java/stackpot/stackpot/config/security/JwtTokenProvider.java @@ -9,6 +9,7 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Component; import org.springframework.security.core.Authentication; +import stackpot.stackpot.domain.User; import stackpot.stackpot.web.dto.TokenServiceResponse; import java.security.Key; @@ -20,27 +21,27 @@ public class JwtTokenProvider { @Value("${jwt.secret}") private String secretKey; - private final long validityInMilliseconds = 3600000; // 1시간 + private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 60; //1시간 + private static final long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 24; // 1일 private final UserDetailsService userDetailsService; // JWT 생성 (이메일 포함) - public TokenServiceResponse createToken(String email) { - Claims claims = Jwts.claims().setSubject(email); + public TokenServiceResponse createToken(User user) { + Claims claims = Jwts.claims().setSubject(user.getEmail()); Date now = new Date(); - Date validity = new Date(now.getTime() + validityInMilliseconds); String accessToken = Jwts.builder() .setClaims(claims) .setIssuedAt(now) - .setExpiration(validity) + .setExpiration(new Date(now.getTime() + ACCESS_TOKEN_EXPIRE_TIME)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); String refreshToken = Jwts.builder() .setClaims(claims) .setIssuedAt(now) - .setExpiration(validity) + .setExpiration(new Date(now.getTime() + REFRESH_TOKEN_EXPIRE_TIME)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); return TokenServiceResponse.of(accessToken, refreshToken); diff --git a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java index a055369d..71e68757 100644 --- a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java +++ b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java @@ -22,8 +22,6 @@ @RequiredArgsConstructor public class SecurityConfig { -// private final CustomOAuth2UserService customOAuth2UserService; - @Bean public WebSecurityCustomizer webSecurityCustomizer() { // security를 적용하지 않을 리소스 return web -> web.ignoring() @@ -31,47 +29,28 @@ public WebSecurityCustomizer webSecurityCustomizer() { // security를 적용하 .requestMatchers("/error", "/favicon.ico"); } -@Bean -public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtTokenProvider jwtTokenProvider) throws Exception { - http.formLogin(AbstractHttpConfigurer::disable) - .httpBasic(AbstractHttpConfigurer::disable) - .csrf(AbstractHttpConfigurer::disable) - .cors(withDefaults()) - .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)) - .sessionManagement( - session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtTokenProvider jwtTokenProvider) throws Exception { + http.formLogin(AbstractHttpConfigurer::disable) + .httpBasic(AbstractHttpConfigurer::disable) + .csrf(AbstractHttpConfigurer::disable) + .cors(withDefaults()) + .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)) + .sessionManagement( + session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // .oauth2Login( // oauth -> oauth.userInfoEndpoint(config -> config.userService(customOAuth2UserService)) // .successHandler(successHandler(jwtTokenProvider))) - .authorizeHttpRequests(request -> request - .requestMatchers("/", "/home", "/swagger-ui/**", "/v3/api-docs/**", "/v3/api-docs/**","users/oauth/kakao").permitAll() - .anyRequest().authenticated() - ); -// .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); - return http.build(); -} + .authorizeHttpRequests(request -> request + .requestMatchers("/", "/home", "/swagger-ui/**", "/v3/api-docs/**", "/v3/api-docs/**", "users/oauth/kakao").permitAll() + .anyRequest().authenticated() + ) + .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); + return http.build(); + } + // @Bean // public PasswordEncoder passwordEncoder() { // return new BCryptPasswordEncoder(); // } -// @Bean -// public AuthenticationSuccessHandler successHandler(JwtTokenProvider jwtTokenProvider) { -// return (request, response, authentication) -> { -// OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); -// String email = (String) oAuth2User.getAttributes().get("email"); -// -// // JWT 토큰 생성 -// TokenServiceResponse token = jwtTokenProvider.createToken(email); -// System.out.println("STACKPOT TOKEN : " + token.getAccessToken()); -// -// // 응답에 JWT 추가 -// response.setHeader("Authorization", "Bearer " + token.getAccessToken()); -// response.setContentType("application/json"); -// response.setCharacterEncoding("UTF-8"); -// -// // JSON 응답 반환 -// response.getWriter().write("{\"accessTokendf\": \"" + token.getAccessToken() + "\"}"); -// response.getWriter().flush(); -// }; -// } } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/service/UserCommandService.java b/src/main/java/stackpot/stackpot/service/UserCommandService.java index af7250e3..4139adc3 100644 --- a/src/main/java/stackpot/stackpot/service/UserCommandService.java +++ b/src/main/java/stackpot/stackpot/service/UserCommandService.java @@ -6,4 +6,5 @@ public interface UserCommandService { public User joinUser(UserRequestDto.JoinDto request); + public User saveNewUser(String email); } diff --git a/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java b/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java index 013fa1b5..0ab45c68 100644 --- a/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java @@ -10,6 +10,8 @@ import stackpot.stackpot.repository.UserRepository.UserRepository; import stackpot.stackpot.web.dto.UserRequestDto; +import java.util.Date; + @Service @RequiredArgsConstructor public class UserCommandServiceImpl implements UserCommandService{ @@ -31,6 +33,20 @@ public User joinUser(UserRequestDto.JoinDto request) { return userRepository.save(user); } + @Override + public User saveNewUser(String email) { + + return userRepository.findByEmail(email) + .orElseGet(() -> { + User newUser = User.builder() + .email(email) + .userTemperature(33) + .build(); + + return userRepository.save(newUser); + }); + } + private void updateUserData(User user, UserRequestDto.JoinDto request) { // 카카오 id user.setKakaoId(request.getKakaoId()); @@ -40,5 +56,7 @@ private void updateUserData(User user, UserRequestDto.JoinDto request) { user.setRole(request.getRole()); // 관심사 user.setInterest(request.getInterest()); + //한줄 소개 + user.setUserIntroduction(user.getRole()+"에 관심있는 "+user.getNickname()+"입니다."); } } diff --git a/src/main/java/stackpot/stackpot/web/controller/UserController.java b/src/main/java/stackpot/stackpot/web/controller/UserController.java index bb2f0057..1ece39e5 100644 --- a/src/main/java/stackpot/stackpot/web/controller/UserController.java +++ b/src/main/java/stackpot/stackpot/web/controller/UserController.java @@ -12,11 +12,14 @@ import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import org.springframework.web.bind.annotation.*; +import stackpot.stackpot.config.security.JwtTokenProvider; import stackpot.stackpot.converter.UserConverter; import stackpot.stackpot.domain.User; +import stackpot.stackpot.repository.UserRepository.UserRepository; import stackpot.stackpot.service.KakaoService; import stackpot.stackpot.service.UserCommandService; import stackpot.stackpot.web.dto.KakaoUserInfoResponseDto; +import stackpot.stackpot.web.dto.TokenServiceResponse; import stackpot.stackpot.web.dto.UserRequestDto; import java.util.List; @@ -29,7 +32,9 @@ public class UserController { private final UserCommandService userCommandService; + private final UserRepository userRepository; private final KakaoService kakaoService; + private final JwtTokenProvider jwtTokenProvider; @Operation(summary = "토큰 test api") @GetMapping("/login/token") public ResponseEntity testEndpoint(Authentication authentication) { @@ -40,7 +45,7 @@ public ResponseEntity testEndpoint(Authentication authentication) { } @GetMapping("/oauth/kakao") - public ResponseEntity callback(@RequestParam("code") String code) { + public ResponseEntity callback(@RequestParam("code") String code) { log.info("Authorization code: {}", code); // 인증 코드 확인 String accessToken = kakaoService.getAccessTokenFromKakao(code); @@ -48,7 +53,14 @@ public ResponseEntity callback(@RequestParam("code") S String email = userInfo.getKakaoAccount().getEmail();// 이메일 가져오기 log.info("userInfo.getEmail -> ", email); - return ResponseEntity.ok(userInfo); + + User user = userCommandService.saveNewUser(email); + + TokenServiceResponse token = jwtTokenProvider.createToken(user); + log.info("STACKPOT ACESSTOKEN : " + token.getAccessToken()); + + + return ResponseEntity.ok(token); } @Operation(summary = "회원가입 api") From 20b8a1683ed0e864058eeb5263d23c29f92e788f Mon Sep 17 00:00:00 2001 From: starday119 Date: Sat, 25 Jan 2025 05:46:39 +0900 Subject: [PATCH 58/76] =?UTF-8?q?[FEAT]:=20=ED=8C=9F=20=EC=A7=80=EC=9B=90?= =?UTF-8?q?=EC=9E=90=20=EB=AA=A9=EB=A1=9D=20=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stackpot/config/SwaggerConfig.java | 18 ++++++++----- .../PotMemberConverter.java | 1 + .../PotMemberConverterImpl.java | 25 ++++++++++++++++-- .../repository/PotMemberRepository.java | 3 +++ .../service/EmailService/EmailService.java | 2 +- .../EmailService/EmailServiceImpl.java | 8 +++--- .../PotApplicationServiceImpl.java | 6 ++++- .../PotMemberService/PotMemberService.java | 2 ++ .../PotMemberServiceImpl.java | 26 +++++++++++++++++++ .../web/controller/PotMemberController.java | 9 +++++++ .../stackpot/web/dto/PotAllMemRequestDto.java | 19 ++++++++++++++ .../web/dto/PotMemberAppealResponseDto.java | 3 ++- 12 files changed, 107 insertions(+), 15 deletions(-) create mode 100644 src/main/java/stackpot/stackpot/web/dto/PotAllMemRequestDto.java diff --git a/src/main/java/stackpot/stackpot/config/SwaggerConfig.java b/src/main/java/stackpot/stackpot/config/SwaggerConfig.java index a59267f2..2db0fab0 100644 --- a/src/main/java/stackpot/stackpot/config/SwaggerConfig.java +++ b/src/main/java/stackpot/stackpot/config/SwaggerConfig.java @@ -9,11 +9,13 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; + + @Configuration public class SwaggerConfig { @Bean - public OpenAPI StackPotAPI() { + public OpenAPI stackPotAPI() { Info info = new Info() .title("StackPot API") .description("StackPotAPI API 명세서") @@ -21,8 +23,9 @@ public OpenAPI StackPotAPI() { String jwtSchemeName = "JWT TOKEN"; - // API 요청헤더에 인증정보 포함 + // API 요청 헤더에 인증 정보 포함 SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName); + // SecuritySchemes 등록 Components components = new Components() .addSecuritySchemes(jwtSchemeName, new SecurityScheme() @@ -32,9 +35,10 @@ public OpenAPI StackPotAPI() { .bearerFormat("JWT")); return new OpenAPI() - .addServersItem(new Server().url("")) - .info(info) - .addSecurityItem(securityRequirement) - .components(components); + .info(info) // API 정보 설정 + .addServersItem(new Server().url("http://localhost:8080").description("Dev server")) // 서버 URL 설정 + .addServersItem(new Server().url("https://api.stackpot.co.kr").description("Production server")) // 서버 URL 설정 + .addSecurityItem(securityRequirement) // SecurityRequirement 추가 + .components(components); // SecuritySchemes 등록 } -} \ No newline at end of file +} diff --git a/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverter.java b/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverter.java index 3d49a6ac..d3efeb31 100644 --- a/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverter.java +++ b/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverter.java @@ -9,4 +9,5 @@ public interface PotMemberConverter { PotMember toEntity(User user, Pot pot, PotApplication application, Boolean isOwner); PotMemberAppealResponseDto toDto(PotMember entity); + } diff --git a/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java b/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java index 8351e31e..ef6f1abb 100644 --- a/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java @@ -6,6 +6,8 @@ import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.domain.mapping.PotApplication; import stackpot.stackpot.domain.mapping.PotMember; +import stackpot.stackpot.web.dto.PotAllMemRequestDto; +import stackpot.stackpot.web.dto.PotApplicationRequestDto; import stackpot.stackpot.web.dto.PotMemberAppealResponseDto; @Component @@ -24,17 +26,36 @@ public PotMember toEntity(User user, Pot pot, PotApplication application, Boolea } @Override + public PotMemberAppealResponseDto toDto(PotMember entity) { + String roleName = entity.getRoleName() != null ? entity.getRoleName().name() : "멤버"; + String nicknameWithRole = entity.getUser().getNickname() + roleName ; + return PotMemberAppealResponseDto.builder() .potMemberId(entity.getPotMemberId()) .potId(entity.getPot().getPotId()) .userId(entity.getUser().getId()) - .roleName(String.valueOf(entity.getRoleName())) // PotRole 그대로 반환 - .owner(entity.isOwner()) + .roleName(roleName) + .nickname(nicknameWithRole) + .isOwner(entity.isOwner()) .appealContent(entity.getAppealContent()) .build(); } + private String mapRoleName(String potRole) { + switch (potRole) { + case "BACKEND": + return "양파"; + case "FRONTEND": + return "버섯"; + case "DESIGN": + return "브로콜리"; + case "PLANNING": + return "당근"; + default: + return "멤버"; + } + } diff --git a/src/main/java/stackpot/stackpot/repository/PotMemberRepository.java b/src/main/java/stackpot/stackpot/repository/PotMemberRepository.java index 35398fb8..1dd9d0e9 100644 --- a/src/main/java/stackpot/stackpot/repository/PotMemberRepository.java +++ b/src/main/java/stackpot/stackpot/repository/PotMemberRepository.java @@ -18,4 +18,7 @@ public interface PotMemberRepository extends JpaRepository { @Query("SELECT pm.user.id FROM PotMember pm WHERE pm.pot.potId = :potId") List findUserIdsByPotId(@Param("potId") Long potId); + @Query("SELECT pm FROM PotMember pm WHERE pm.pot.potId = :potId") + List findByPotId(@Param("potId") Long potId); + } diff --git a/src/main/java/stackpot/stackpot/service/EmailService/EmailService.java b/src/main/java/stackpot/stackpot/service/EmailService/EmailService.java index d8d83f4f..dc431c7b 100644 --- a/src/main/java/stackpot/stackpot/service/EmailService/EmailService.java +++ b/src/main/java/stackpot/stackpot/service/EmailService/EmailService.java @@ -1,5 +1,5 @@ package stackpot.stackpot.service.EmailService; public interface EmailService { - void sendSupportNotification(String toEmail, String potName, String applicantName); + void sendSupportNotification(String toEmail, String potName, String applicantName, String applicantIntroduction); } diff --git a/src/main/java/stackpot/stackpot/service/EmailService/EmailServiceImpl.java b/src/main/java/stackpot/stackpot/service/EmailService/EmailServiceImpl.java index 4125f90f..fef658b0 100644 --- a/src/main/java/stackpot/stackpot/service/EmailService/EmailServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/EmailService/EmailServiceImpl.java @@ -14,24 +14,26 @@ public EmailServiceImpl(JavaMailSender mailSender) { } @Override - public void sendSupportNotification(String toEmail, String potName, String applicantName) { + public void sendSupportNotification(String toEmail, String potName, String applicantName, String applicantIntroduction) { try { SimpleMailMessage message = new SimpleMailMessage(); message.setTo(toEmail); message.setSubject("[STACKPOT] 새로운 지원자가 있습니다 - '" + potName + "'"); + // 이메일 본문 작성 // 이메일 본문 작성 String emailBody = String.format( "[%s]에 새로운 지원자가 있습니다!\n\n" + "안녕하세요, STACKPOT에서 알려드립니다.\n\n" + "회원님이 생성하신 [%s]에 새로운 지원자가 지원했습니다. 아래는 지원자 정보와 관련된 세부 사항입니다:\n\n" + - "- 지원자 이름: %s\n\n" + + "- 지원자 이름: %s\n" + + "- 한 줄 소개: %s\n\n" + "STACKPOT과 함께 성공적인 프로젝트를 만들어가세요!\n\n" + "감사합니다.\n\n" + "STACKPOT 드림\n\n" + "고객센터: stackpot.notice@gmail.com\n" + "홈페이지: https://www.stackpot.co.kr", - potName, potName, applicantName + potName, potName, applicantName, applicantIntroduction != null ? applicantIntroduction : "없음" ); message.setText(emailBody); diff --git a/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java index 64842670..f0d2ce99 100644 --- a/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java @@ -58,11 +58,13 @@ public PotApplicationResponseDto applyToPot(PotApplicationRequestDto dto, Long p PotApplication savedApplication = potApplicationRepository.save(potApplication); + // 이메일 전송 // 이메일 전송 emailService.sendSupportNotification( pot.getUser().getEmail(), pot.getPotName(), - user.getNickname() + user.getNickname(), + user.getUserIntroduction() // 한 줄 소개 추가 ); // 저장된 지원 정보를 응답 DTO로 변환 @@ -93,4 +95,6 @@ public List getApplicantsByPotId(Long potId) { .collect(Collectors.toList()); } + + } diff --git a/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberService.java b/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberService.java index 00786b9a..1db299a3 100644 --- a/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberService.java +++ b/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberService.java @@ -6,6 +6,8 @@ import java.util.List; public interface PotMemberService { + List getPotMembers(Long potId); List addMembersToPot(Long potId, PotMemberRequestDto requestDto); void updateAppealContent(Long potId, Long memberId, String appealContent); + void validateIsOwner(Long potId); // 팟 생성자 검증 } diff --git a/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberServiceImpl.java index ec1d10d2..3e4ac497 100644 --- a/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberServiceImpl.java @@ -2,6 +2,9 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import stackpot.stackpot.converter.PotMemberConverter.PotMemberConverter; import stackpot.stackpot.domain.Pot; @@ -29,6 +32,17 @@ public class PotMemberServiceImpl implements PotMemberService { private final PotMemberRepository potMemberRepository; private final PotMemberConverter potMemberConverter; + @Transactional + @Override + public List getPotMembers(Long potId) { + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new IllegalArgumentException("해당 팟을 찾을 수 없습니다.")); + + List potMembers = potMemberRepository.findByPotId(potId); + return potMembers.stream() + .map(potMemberConverter::toDto) + .collect(Collectors.toList()); + } @Transactional @Override public List addMembersToPot (Long potId, PotMemberRequestDto requestDto) { @@ -76,4 +90,16 @@ public void updateAppealContent(Long potId, Long memberId, String appealContent) potMember.setAppealContent(appealContent); potMemberRepository.save(potMember); // 변경 사항 저장 } + @Override + public void validateIsOwner(Long potId) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new IllegalArgumentException("해당 팟을 찾을 수 없습니다.")); + + if (!pot.getUser().getEmail().equals(email)) { + throw new AccessDeniedException("해당 작업은 팟 생성자만 수행할 수 있습니다."); + } + } } diff --git a/src/main/java/stackpot/stackpot/web/controller/PotMemberController.java b/src/main/java/stackpot/stackpot/web/controller/PotMemberController.java index 9de8be2f..2d33afc4 100644 --- a/src/main/java/stackpot/stackpot/web/controller/PotMemberController.java +++ b/src/main/java/stackpot/stackpot/web/controller/PotMemberController.java @@ -20,6 +20,15 @@ public class PotMemberController { private final PotMemberService potMemberService; + @Operation(summary = "팟 멤버 정보 가져오기( KAKAOID, 닉네임)") + @GetMapping + public ResponseEntity>> getPotMembers( + @PathVariable("pot_id") Long potId) { + potMemberService.validateIsOwner(potId); // 팟 생성자 검증 추가 + List response = potMemberService.getPotMembers(potId); + return ResponseEntity.ok(ApiResponse.onSuccess(response)); + } + @Operation(summary = "팟 시작하기") @PostMapping public ResponseEntity>> addPotMembers( diff --git a/src/main/java/stackpot/stackpot/web/dto/PotAllMemRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/PotAllMemRequestDto.java new file mode 100644 index 00000000..ca0020ef --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/PotAllMemRequestDto.java @@ -0,0 +1,19 @@ +package stackpot.stackpot.web.dto; + +import lombok.*; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class PotAllMemRequestDto { + private Long potMemberId; // 팟 멤버 ID + private Long potId; // 팟 ID + private Long userId; // 유저 ID + private String roleName; // 역할 이름 + private String nickname; // 닉네임 + 역할 + private Boolean isOwner; // 팟 생성자인지 여부 + private String appealContent; // 어필 내용 +} + diff --git a/src/main/java/stackpot/stackpot/web/dto/PotMemberAppealResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/PotMemberAppealResponseDto.java index c401e0d2..66acde73 100644 --- a/src/main/java/stackpot/stackpot/web/dto/PotMemberAppealResponseDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/PotMemberAppealResponseDto.java @@ -11,6 +11,7 @@ public class PotMemberAppealResponseDto { private Long potId; private Long userId; private String roleName; - private Boolean owner; + private Boolean isOwner; // 팟 생성자인지 여부 + private String nickname; // 닉네임 + 역할 private String appealContent; } From 471b6c72733ef1c4058ff4ffabf1d577823ad4a6 Mon Sep 17 00:00:00 2001 From: MiN <81948815+leesumin0526@users.noreply.github.com> Date: Sat, 25 Jan 2025 18:14:02 +0900 Subject: [PATCH 59/76] =?UTF-8?q?Feat/merge=EC=9A=A9=20(#33)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [feat/#28] 에러 수정 * [feat/#28] roll 변경 중 * [feat/#28] Role 처리 완료.. * [feat/#28] 전체 팟 보기 기능 수정 완료 * [feat/#28] 투두 새벽 3시에 자동 리셋 기능 구현 * [feat/#28] 팟 닉네임 처리 * [feat/#28] 팟 localdatetime 형식 통일 * [feat/#28] Dday 필드 삭제 * [feat/#28] todo 완료 api 구현 * [feat] 다른 사람 프로필 보기 API 구현 * [feat] 에러 수정 & 다른 사람 프로필 보기 API 구현 * [feat] 프로필 수정 API * [feat] 사용자의 팟 목록 조회 API * [feat] 사용자의 팟 목록 조회 API 날짜 형식 수정 * [feat] commit --- build.gradle | 14 ++ .../stackpot/StackpotApplication.java | 4 + .../stackpot/converter/FeedConverterImpl.java | 14 +- .../PotApplicationConverterImpl.java | 13 ++ .../stackpot/converter/PotConverterImpl.java | 15 +- .../PotMemberConverterImpl.java | 2 +- .../stackpot/converter/UserConverter.java | 4 +- .../converter/UserMypageConverter.java | 98 +++++++++ .../domain/PotRecruitmentDetails.java | 1 + .../FeedRepository/FeedRepository.java | 2 + .../PotRepository/MyPotRepository.java | 2 +- .../PotRepository/PotRepository.java | 5 +- .../stackpot/repository/TodoRepository.java | 11 + .../stackpot/service/MyPotService.java | 7 +- .../stackpot/service/MyPotServiceImpl.java | 194 +++++++++++++----- .../stackpot/stackpot/service/PotService.java | 3 +- .../stackpot/service/PotServiceImpl.java | 133 +++--------- .../stackpot/service/TodoCleanupService.java | 27 +++ .../stackpot/service/UserCommandService.java | 12 +- .../service/UserCommandServiceImpl.java | 95 ++++++++- .../web/controller/MyPotController.java | 29 ++- .../web/controller/PotController.java | 37 +++- .../web/controller/UserController.java | 29 ++- .../stackpot/web/dto/FeedResponseDto.java | 3 +- .../web/dto/LikedApplicantResponseDTO.java | 3 +- .../web/dto/PotApplicationResponseDto.java | 1 + .../web/dto/PotMemberAppealResponseDto.java | 1 + .../web/dto/PotMemberResponseDTO.java | 3 +- .../web/dto/PotRecruitmentResponseDto.java | 1 + .../stackpot/web/dto/PotResponseDto.java | 5 +- .../dto/RecruitmentDetailsResponseDTO.java | 3 +- .../web/dto/UserMypageResponseDto.java | 51 +++++ .../stackpot/web/dto/UserRequestDto.java | 3 +- .../stackpot/web/dto/UserResponseDto.java | 4 +- .../web/dto/UserUpdateRequestDto.java | 14 ++ src/main/resources/application.yml | 5 +- 36 files changed, 649 insertions(+), 199 deletions(-) create mode 100644 src/main/java/stackpot/stackpot/converter/UserMypageConverter.java create mode 100644 src/main/java/stackpot/stackpot/repository/TodoRepository.java create mode 100644 src/main/java/stackpot/stackpot/service/TodoCleanupService.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/UserMypageResponseDto.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/UserUpdateRequestDto.java diff --git a/build.gradle b/build.gradle index eadc9a39..6e9b38e5 100644 --- a/build.gradle +++ b/build.gradle @@ -73,6 +73,20 @@ dependencies { //Email implementation 'org.springframework.boot:spring-boot-starter-mail' + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' + implementation 'mysql:mysql-connector-java:8.0.33' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.security:spring-security-crypto' + + //Email + implementation 'org.springframework.boot:spring-boot-starter-mail' + + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' + implementation 'mysql:mysql-connector-java:8.0.33' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.security:spring-security-crypto' } tasks.named('test') { diff --git a/src/main/java/stackpot/stackpot/StackpotApplication.java b/src/main/java/stackpot/stackpot/StackpotApplication.java index a72aa2af..ca431423 100644 --- a/src/main/java/stackpot/stackpot/StackpotApplication.java +++ b/src/main/java/stackpot/stackpot/StackpotApplication.java @@ -2,8 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableScheduling +@EnableJpaAuditing public class StackpotApplication { public static void main(String[] args) { diff --git a/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java b/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java index 16158bad..07f1d13c 100644 --- a/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java @@ -5,6 +5,11 @@ import stackpot.stackpot.web.dto.FeedRequestDto; import stackpot.stackpot.web.dto.FeedResponseDto; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import static org.apache.tomcat.util.http.FastHttpDateFormat.formatDate; + @Component public class FeedConverterImpl implements FeedConverter{ @@ -18,7 +23,7 @@ public FeedResponseDto.FeedDto feedDto(Feed feed, long popularity, long likeCoun .content(feed.getContent()) .popularity(popularity) .likeCount(likeCount) - .createdAt(feed.getCreatedAt()) + .createdAt(formatLocalDateTime(feed.getCreatedAt())) .build(); } @@ -31,4 +36,11 @@ public Feed toFeed(FeedRequestDto.createDto request) { .visibility(request.getVisibility()) .build(); } + + // 날짜 포맷 적용 메서드 + private String formatLocalDateTime(LocalDateTime dateTime) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy년 M월 d일 H:mm"); + return (dateTime != null) ? dateTime.format(formatter) : "날짜 없음"; + } + } diff --git a/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverterImpl.java b/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverterImpl.java index 3a2ac829..dbdf1bf6 100644 --- a/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/PotApplicationConverter/PotApplicationConverterImpl.java @@ -10,6 +10,7 @@ import stackpot.stackpot.web.dto.PotApplicationResponseDto; import java.time.LocalDateTime; +import java.util.Map; @Component public class PotApplicationConverterImpl implements PotApplicationConverter { @@ -52,7 +53,19 @@ public PotApplicationResponseDto toDto(PotApplication entity) { .appliedAt(entity.getAppliedAt()) // null 가능성을 허용 .potId(entity.getPot().getPotId()) // Pot 엔티티에서 potId 가져오기 .userId(entity.getUser().getId()) // User 엔티티에서 id 가져오기 + .userNickname(entity.getUser().getNickname() + getVegetableNameByRole(String.valueOf(entity.getUser().getRole()))) .build(); } + private String getVegetableNameByRole(String role) { + Map roleToVegetableMap = Map.of( + "DESIGN", " 브로콜리", + "PLANNING", " 당근", + "BACKEND", " 양파", + "FRONTEND", " 버섯" + ); + + return roleToVegetableMap.getOrDefault(role, "알 수 없음"); + } + } diff --git a/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java b/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java index 0823ab8e..35c513d7 100644 --- a/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java @@ -12,9 +12,13 @@ import java.util.List; import java.util.stream.Collectors; +import java.time.format.DateTimeFormatter; + @Component public class PotConverterImpl implements PotConverter { + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy.MM.dd"); + @Override public Pot toEntity(PotRequestDto requestDto, User user) { return Pot.builder() @@ -37,8 +41,8 @@ public PotResponseDto toDto(Pot entity, List recruitmentD return PotResponseDto.builder() .potId(entity.getPotId()) .potName(entity.getPotName()) - .potStartDate(entity.getPotStartDate()) - .potEndDate(entity.getPotEndDate()) + .potStartDate(formatDate(entity.getPotStartDate())) + .potEndDate(formatDate(entity.getPotEndDate())) .potDuration(entity.getPotDuration()) .potLan(entity.getPotLan()) .potContent(entity.getPotContent()) @@ -48,9 +52,14 @@ public PotResponseDto toDto(Pot entity, List recruitmentD .recruitmentDeadline(entity.getRecruitmentDeadline()) .recruitmentDetails(recruitmentDetails.stream().map(r -> PotRecruitmentResponseDto.builder() .recruitmentId(r.getRecruitmentId()) - .recruitmentRole(String.valueOf(r.getRecruitmentRole())) + .recruitmentRole(r.getRecruitmentRole().name()) .recruitmentCount(r.getRecruitmentCount()) .build()).collect(Collectors.toList())) .build(); } + + private String formatDate(java.time.LocalDate date) { + return (date != null) ? date.format(DATE_FORMATTER) : "N/A"; + } + } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java b/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java index ef6f1abb..ce3b3758 100644 --- a/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java @@ -59,4 +59,4 @@ private String mapRoleName(String potRole) { -} +} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/converter/UserConverter.java b/src/main/java/stackpot/stackpot/converter/UserConverter.java index c3874191..820178e8 100644 --- a/src/main/java/stackpot/stackpot/converter/UserConverter.java +++ b/src/main/java/stackpot/stackpot/converter/UserConverter.java @@ -12,7 +12,7 @@ public static User toUser(UserRequestDto.JoinDto request) { .nickname(request.getNickname()) .kakaoId(request.getKakaoId()) .interest(request.getInterest()) - .role(Role.valueOf(request.getRole())) + .role(Role.valueOf(String.valueOf(request.getRole()))) .build(); } @@ -22,7 +22,7 @@ public static UserResponseDto toDto(User user) { .nickname(user.getNickname()) .email(user.getEmail()) // 추가된 코드 .kakaoId(user.getKakaoId()) - .role(String.valueOf(user.getRole())) + .role(user.getRole()) .interest(user.getInterest()) .userTemperature(user.getUserTemperature()) .build(); diff --git a/src/main/java/stackpot/stackpot/converter/UserMypageConverter.java b/src/main/java/stackpot/stackpot/converter/UserMypageConverter.java new file mode 100644 index 00000000..791a7087 --- /dev/null +++ b/src/main/java/stackpot/stackpot/converter/UserMypageConverter.java @@ -0,0 +1,98 @@ +package stackpot.stackpot.converter; + +import org.springframework.stereotype.Component; +import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.repository.FeedLikeRepository; +import stackpot.stackpot.web.dto.PotRecruitmentResponseDto; +import stackpot.stackpot.web.dto.UserMypageResponseDto; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.mysql.cj.util.TimeUtil.DATE_FORMATTER; + +@Component +public class UserMypageConverter { + + public UserMypageResponseDto toDto(User user, List completedPots, List feeds) { + return UserMypageResponseDto.builder() + .email(user.getEmail()) + .nickname(user.getNickname() + getVegetableNameByRole(user.getRole().name())) + .role(user.getRole()) + .interest(user.getInterest()) + .userTemperature(user.getUserTemperature()) + .kakaoId(user.getKakaoId()) + .userIntroduction(user.getUserIntroduction()) + .completedPots(completedPots.stream().map(this::convertToCompletedPotDto).collect(Collectors.toList())) + .feeds(feeds.stream().map(this::convertToFeedDto).collect(Collectors.toList())) + .build(); + } + + + private UserMypageResponseDto.CompletedPotDto convertToCompletedPotDto(Pot pot) { + // 역할별 인원 수 집계 + Map roleSummary = pot.getPotMembers().stream() + .collect(Collectors.groupingBy( + member -> member.getRoleName().name(), // Enum을 String으로 변환 + Collectors.counting() + )); + + + return UserMypageResponseDto.CompletedPotDto.builder() + .potId(pot.getPotId()) + .potName(pot.getPotName()) + .potStartDate(formatDate(pot.getPotStartDate())) + .potEndDate(formatDate(pot.getPotEndDate())) + .potSummary(pot.getPotSummary()) + .recruitmentDetails(pot.getRecruitmentDetails().stream() + .map(detail -> PotRecruitmentResponseDto.builder() + .recruitmentId(detail.getRecruitmentId()) + .recruitmentRole(detail.getRecruitmentRole().name()) // Enum → String 변환 + .recruitmentCount(detail.getRecruitmentCount()) + .build()) + .collect(Collectors.toList())) + .build(); + } + + private final FeedLikeRepository feedLikeRepository; + + public UserMypageConverter(FeedLikeRepository feedLikeRepository) { + this.feedLikeRepository = feedLikeRepository; + } + + private UserMypageResponseDto.FeedDto convertToFeedDto(Feed feed) { + return UserMypageResponseDto.FeedDto.builder() + .feedId(feed.getFeedId()) + .title(feed.getTitle()) + .content(feed.getContent()) + .category(feed.getCategory()) + .likeCount(feedLikeRepository.countByFeed(feed)) + .createdAt(formatLocalDateTime(feed.getCreatedAt())) + .build(); + } + + private String getVegetableNameByRole(String role) { + Map roleToVegetableMap = Map.of( + "BACKEND", " 양파", + "FRONTEND", " 버섯", + "DESIGN", " 브로콜리", + "PLANNING", " 당근" + ); + return roleToVegetableMap.getOrDefault(role, "알 수 없음"); + } + + private String formatDate(java.time.LocalDate date) { + return (date != null) ? date.format(DATE_FORMATTER) : "N/A"; + } + + // 날짜 포맷 적용 메서드 + private String formatLocalDateTime(LocalDateTime dateTime) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy년 M월 d일 H:mm"); + return (dateTime != null) ? dateTime.format(formatter) : "날짜 없음"; + } +} diff --git a/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java b/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java index 4e89a236..52496a42 100644 --- a/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java +++ b/src/main/java/stackpot/stackpot/domain/PotRecruitmentDetails.java @@ -5,6 +5,7 @@ import stackpot.stackpot.domain.common.BaseEntity; import stackpot.stackpot.domain.enums.Role; + @Entity @Getter @Builder diff --git a/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java b/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java index 1f10a7a0..4f69b9af 100644 --- a/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java +++ b/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java @@ -40,4 +40,6 @@ List findFeeds( @Param("lastCreatedAt") LocalDateTime lastCreatedAt, Pageable pageable); + List findByUser_Id(Long userId); + } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/repository/PotRepository/MyPotRepository.java b/src/main/java/stackpot/stackpot/repository/PotRepository/MyPotRepository.java index 2fd35dc9..862cdda4 100644 --- a/src/main/java/stackpot/stackpot/repository/PotRepository/MyPotRepository.java +++ b/src/main/java/stackpot/stackpot/repository/PotRepository/MyPotRepository.java @@ -12,5 +12,5 @@ public interface MyPotRepository extends JpaRepository { List findByPot_PotId(Long potId); List findByPotAndUser(Pot pot, User user); - + Optional findByTodoIdAndPot_PotId(Long todoId, Long potId); } diff --git a/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java b/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java index ccd1f52b..be8e1fd4 100644 --- a/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java +++ b/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java @@ -8,6 +8,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.domain.mapping.PotApplication; import stackpot.stackpot.domain.mapping.UserTodo; @@ -15,9 +16,11 @@ import java.util.Optional; public interface PotRepository extends JpaRepository { - Page findByRecruitmentDetails_RecruitmentRole(String recruitmentRole, Pageable pageable); + Page findByRecruitmentDetails_RecruitmentRole(Role recruitmentRole, Pageable pageable); Optional findPotWithRecruitmentDetailsByPotId(Long potId); List findByPotApplication_User_Id(Long userId); List findByUserId(Long userId); Page findAll(Pageable pageable); + List findByPotMembers_UserIdAndPotStatus(Long userId, String status); + List findByUserIdAndPotStatus(Long userId, String status); } diff --git a/src/main/java/stackpot/stackpot/repository/TodoRepository.java b/src/main/java/stackpot/stackpot/repository/TodoRepository.java new file mode 100644 index 00000000..9cf78b48 --- /dev/null +++ b/src/main/java/stackpot/stackpot/repository/TodoRepository.java @@ -0,0 +1,11 @@ +package stackpot.stackpot.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import stackpot.stackpot.domain.mapping.UserTodo; + +import java.time.LocalDateTime; + +public interface TodoRepository extends JpaRepository { + // 하루 전 createdAt 기준으로 데이터 삭제 + int deleteByCreatedAtBefore(LocalDateTime date); +} diff --git a/src/main/java/stackpot/stackpot/service/MyPotService.java b/src/main/java/stackpot/stackpot/service/MyPotService.java index 70aa5b3c..099b9566 100644 --- a/src/main/java/stackpot/stackpot/service/MyPotService.java +++ b/src/main/java/stackpot/stackpot/service/MyPotService.java @@ -5,18 +5,21 @@ import stackpot.stackpot.web.dto.*; import java.util.List; +import java.util.Map; public interface MyPotService { // 사용자의 진행 중인 팟 조회 - List getMyOnGoingPots(); + Map> getMyOnGoingPots(); + // 사용자의 특정 팟에서의 생성 List postTodo(Long potId, MyPotTodoRequestDTO requestDTO); - List getTodo(Long potId); List updateTodos(Long potId, List requestList); + List completeTodo(Long potId, Long todoId); + } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java b/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java index a9ce1698..ac27faee 100644 --- a/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java @@ -5,16 +5,18 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import stackpot.stackpot.converter.PotConverter; import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.enums.Role; +import stackpot.stackpot.domain.enums.TodoStatus; import stackpot.stackpot.domain.mapping.UserTodo; import stackpot.stackpot.repository.PotRepository.MyPotRepository; import stackpot.stackpot.repository.PotRepository.PotRepository; import stackpot.stackpot.repository.UserRepository.UserRepository; import stackpot.stackpot.web.dto.*; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; @Service @@ -24,29 +26,38 @@ public class MyPotServiceImpl implements MyPotService { private final PotRepository potRepository; private final MyPotRepository myPotRepository; private final UserRepository userRepository; + private final PotConverter potConverter; @Override - public List getMyOnGoingPots() { + public Map> getMyOnGoingPots() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String email = authentication.getName(); User user = userRepository.findByEmail(email) .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); - // 사용자가 만든 팟 조회 - List myPots = potRepository.findByUserId(user.getId()); + // 1. 내가 PotMember로 참여 중이고 상태가 'ONGOING'인 팟 조회 + List ongoingMemberPots = potRepository.findByPotMembers_UserIdAndPotStatus(user.getId(), "ONGOING"); - // 진행 중인 팟 리스트 변환 (멤버 정보 포함) - List ongoingPots = myPots.stream() - .filter(pot -> "recruiting".equals(pot.getPotStatus())) + // 2. 내가 만든 팟 중 상태가 'ONGOING'인 팟 조회 + List ongoingOwnedPots = potRepository.findByUserIdAndPotStatus(user.getId(), "ONGOING"); + + // Pot 리스트를 DTO로 변환 + List memberPotsDetails = ongoingMemberPots.stream() + .map(this::convertToOngoingPotDetail) + .collect(Collectors.toList()); + + List ownedPotsDetails = ongoingOwnedPots.stream() .map(this::convertToOngoingPotDetail) .collect(Collectors.toList()); - // MyPotResponseDTO로 변환하여 반환 - return List.of(MyPotResponseDTO.builder() - .ongoingPots(ongoingPots) - .build()); + // 결과를 분류하여 반환 + Map> result = new HashMap<>(); + result.put("joinedOngoingPots", memberPotsDetails); + result.put("ownedOngoingPots", ownedPotsDetails); + + return result; } @@ -81,17 +92,29 @@ public List postTodo(Long potId, MyPotTodoRequestDTO reque return potTodos.stream() .collect(Collectors.groupingBy(UserTodo::getUser)) .entrySet().stream() - .map(entry -> MyPotTodoResponseDTO.builder() - .userNickname(entry.getKey().getNickname()) - .userId(entry.getKey().getId()) - .todos(entry.getValue().stream() - .map(todo -> MyPotTodoResponseDTO.TodoDetailDTO.builder() - .todoId(todo.getTodoId()) - .content(todo.getContent()) - .status(todo.getStatus()) - .build()) - .collect(Collectors.toList())) - .build()) + .map(entry -> { + // 해당 유저의 pot에서 potMember 역할 찾기 + String roleName = entry.getValue().stream() + .findFirst() + .flatMap(todo -> todo.getPot().getPotMembers().stream() + .filter(member -> member.getUser().equals(entry.getKey())) + .map(member -> member.getRoleName().name()) // ENUM -> String 변환 + .findFirst() + ) + .orElse("UNKNOWN"); // 기본값 설정 + + return MyPotTodoResponseDTO.builder() + .userNickname(entry.getKey().getNickname() + getVegetableNameByRole(roleName)) + .userId(entry.getKey().getId()) + .todos(entry.getValue().stream() + .map(todo -> MyPotTodoResponseDTO.TodoDetailDTO.builder() + .todoId(todo.getTodoId()) + .content(todo.getContent()) + .status(todo.getStatus()) + .build()) + .collect(Collectors.toList())) + .build(); + }) .collect(Collectors.toList()); } @@ -111,21 +134,30 @@ public List getTodo(Long potId) { // 특정 팟의 모든 To-Do 조회 List potTodos = myPotRepository.findByPot_PotId(potId); - // 사용자별로 그룹화하여 반환 + // 특정 팟의 모든 To-Do 조회 return potTodos.stream() .collect(Collectors.groupingBy(UserTodo::getUser)) .entrySet().stream() - .map(entry -> MyPotTodoResponseDTO.builder() - .userNickname(entry.getKey().getNickname()) - .userId(entry.getKey().getId()) - .todos(entry.getValue().stream() - .map(todo -> MyPotTodoResponseDTO.TodoDetailDTO.builder() - .todoId(todo.getTodoId()) - .content(todo.getContent()) - .status(todo.getStatus()) - .build()) - .collect(Collectors.toList())) - .build()) + .map(entry -> { + // 해당 유저의 pot에서 potMember 역할 찾기 + String roleName = pot.getPotMembers().stream() + .filter(member -> member.getUser().equals(entry.getKey())) + .map(member -> member.getRoleName().name()) // Enum을 String으로 변환 + .findFirst() + .orElse("UNKNOWN"); // 기본값을 String으로 설정 + + return MyPotTodoResponseDTO.builder() + .userNickname(entry.getKey().getNickname() + getVegetableNameByRole(roleName)) + .userId(entry.getKey().getId()) + .todos(entry.getValue().stream() + .map(todo -> MyPotTodoResponseDTO.TodoDetailDTO.builder() + .todoId(todo.getTodoId()) + .content(todo.getContent()) + .status(todo.getStatus()) + .build()) + .collect(Collectors.toList())) + .build(); + }) .collect(Collectors.toList()); } @@ -168,6 +200,64 @@ public List updateTodos(Long potId, List { + // 해당 유저의 pot에서 potMember 역할 찾기 + String roleName = entry.getValue().stream() + .findFirst() + .flatMap(todo -> todo.getPot().getPotMembers().stream() + .filter(member -> member.getUser().equals(entry.getKey())) + .map(member -> member.getRoleName().name()) // ENUM -> String 변환 + .findFirst() + ) + .orElse("UNKNOWN"); // 기본값 설정 + + return MyPotTodoResponseDTO.builder() + .userNickname(entry.getKey().getNickname() + getVegetableNameByRole(roleName)) + .userId(entry.getKey().getId()) + .todos(entry.getValue().stream() + .map(todo -> MyPotTodoResponseDTO.TodoDetailDTO.builder() + .todoId(todo.getTodoId()) + .content(todo.getContent()) + .status(todo.getStatus()) + .build()) + .collect(Collectors.toList())) + .build(); + }) + .collect(Collectors.toList()); + } + + @Transactional + @Override + public List completeTodo(Long potId, Long todoId) { + // 현재 로그인한 사용자 확인 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + + // 해당 팟이 존재하는지 확인 + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + + // 해당 투두가 존재하는지 확인 및 소유자 검증 + UserTodo userTodo = myPotRepository.findByTodoIdAndPot_PotId(todoId, potId) + .orElseThrow(() -> new IllegalArgumentException("Todo not found for given potId and todoId")); + + if (!userTodo.getUser().equals(user)) { + throw new SecurityException("You are not authorized to update this todo"); + } + + // To-Do 상태 업데이트 + userTodo.setStatus(TodoStatus.COMPLETED); + myPotRepository.save(userTodo); + + // 특정 팟의 모든 To-Do 조회 후 반환 + List potTodos = myPotRepository.findByPot_PotId(potId); + + return potTodos.stream() + .collect(Collectors.groupingBy(UserTodo::getUser)) + .entrySet().stream() .map(entry -> MyPotTodoResponseDTO.builder() .userNickname(entry.getKey().getNickname()) .userId(entry.getKey().getId()) @@ -182,39 +272,35 @@ public List updateTodos(Long potId, List recruitmentDetails = pot.getRecruitmentDetails().stream() - .map(details -> RecruitmentDetailsResponseDTO.builder() - .recruitmentId(details.getRecruitmentId()) - .recruitmentRole(String.valueOf(details.getRecruitmentRole())) - .recruitmentCount(details.getRecruitmentCount()) - .build()) - .collect(Collectors.toList()); - List potMembers = pot.getPotMembers().stream() .map(member -> PotMemberResponseDTO.builder() .potMemberId(member.getPotMemberId()) - .roleName(String.valueOf(member.getRoleName())) - + .roleName(member.getRoleName()) + .appealContent(member.getAppealContent()) .build()) .collect(Collectors.toList()); return MyPotResponseDTO.OngoingPotsDetail.builder() .user(UserResponseDto.builder() - .nickname(pot.getUser().getNickname()) - .role(String.valueOf(pot.getUser().getRole())) - .build()) - .pot(PotResponseDto.builder() - .potName(pot.getPotName()) - .potStartDate(pot.getPotStartDate()) - .potEndDate(pot.getPotEndDate()) - .potStatus(pot.getPotStatus()) + .nickname(pot.getUser().getNickname() + getVegetableNameByRole(String.valueOf(pot.getUser().getRole()))) + .role(pot.getUser().getRole()) .build()) + .pot(potConverter.toDto(pot, pot.getRecruitmentDetails())) .potMembers(potMembers) .build(); } + // 역할에 따른 채소명을 반환하는 메서드 + private String getVegetableNameByRole(String role) { + Map roleToVegetableMap = Map.of( + "BACKEND", " 양파", + "FRONTEND", " 버섯", + "DESIGN", " 브로콜리", + "PLANNING", " 당근" + ); + return roleToVegetableMap.getOrDefault(role, "알 수 없음"); + } } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/service/PotService.java b/src/main/java/stackpot/stackpot/service/PotService.java index cdf580d5..e21061b4 100644 --- a/src/main/java/stackpot/stackpot/service/PotService.java +++ b/src/main/java/stackpot/stackpot/service/PotService.java @@ -1,5 +1,6 @@ package stackpot.stackpot.service; +import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.web.dto.PotRequestDto; import stackpot.stackpot.web.dto.PotResponseDto; import stackpot.stackpot.web.dto.*; @@ -16,7 +17,7 @@ public interface PotService { //--------------- // 모집 역할에 따라 모든 팟 조회 - List getAllPots(String role, Integer page, Integer size); + List getAllPots(Role role, Integer page, Integer size); // 특정 팟의 세부 정보 조회 ApplicantResponseDTO getPotDetails(Long potId); diff --git a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java index ad192f64..22730563 100644 --- a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java @@ -28,6 +28,7 @@ import java.util.Map; import java.util.stream.Collectors; + @Service @RequiredArgsConstructor public class PotServiceImpl implements PotService { @@ -146,17 +147,17 @@ public void deletePot(Long potId) { } - //------------------- +//------------------- private final PotSummarizationService potSummarizationService; @Transactional @Override - public List getAllPots(String role, Integer page, Integer size) { + public List getAllPots(Role role, Integer page, Integer size) { Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending()); Page potPage; - if (role == null || role.isEmpty()) { + if (role == null) { potPage = potRepository.findAll(pageable); } else { potPage = potRepository.findByRecruitmentDetails_RecruitmentRole(role, pageable); @@ -165,8 +166,8 @@ public List getAllPots(String role, Integer page, I return potPage.getContent().stream() .map(pot -> PotAllResponseDTO.PotDetail.builder() .user(UserResponseDto.builder() - .nickname(pot.getUser().getNickname()) - .role(String.valueOf(pot.getUser().getRole())) + .nickname(pot.getUser().getNickname() + getVegetableNameByRole(String.valueOf(pot.getUser().getRole()))) + .role(pot.getUser().getRole()) // ENUM → String 변환 .build()) .pot(potConverter.toDto(pot, pot.getRecruitmentDetails())) // 변환기 사용 .build()) @@ -197,29 +198,12 @@ public ApplicantResponseDTO getPotDetails(Long potId) { .build()) .collect(Collectors.toList()); - // Pot 정보를 DTO로 변환 - PotResponseDto potDto = PotResponseDto.builder() - .potId(pot.getPotId()) - .potName(pot.getPotName()) - .potStartDate(pot.getPotStartDate()) - .potEndDate(pot.getPotEndDate()) - .potDuration(pot.getPotDuration()) - .potLan(pot.getPotLan()) - .potContent(pot.getPotContent()) - .potStatus(pot.getPotStatus()) - .potSummary(pot.getPotSummary()) - .recruitmentDeadline(pot.getRecruitmentDeadline()) - .recruitmentDetails(recruitmentDetailsDto) - .potModeOfOperation(pot.getPotModeOfOperation().name()) - .dDay(Math.toIntExact(ChronoUnit.DAYS.between(LocalDate.now(), pot.getRecruitmentDeadline()))) - .build(); - return ApplicantResponseDTO.builder() .user(UserResponseDto.builder() .nickname(pot.getUser().getNickname()) - .role(String.valueOf(pot.getUser().getRole())) + .role(pot.getUser().getRole()) .build()) - .pot(potDto) + .pot(potConverter.toDto(pot, pot.getRecruitmentDetails())) // 변환기 사용 .applicant(applicantDto) .build(); } @@ -271,7 +255,7 @@ public List getLikedApplicants(Long potId) { .filter(PotApplication::getLiked) .map(app -> LikedApplicantResponseDTO.builder() .applicationId(app.getApplicationId()) - .applicantRole(String.valueOf(app.getPotRole())) + .applicantRole(app.getPotRole()) .potNickname(app.getUser().getNickname() + getVegetableNameByRole(String.valueOf(app.getPotRole()))) .liked(app.getLiked()) .build()) @@ -286,10 +270,10 @@ public List getLikedApplicants(Long potId) { private String getVegetableNameByRole(String role) { Map roleToVegetableMap = Map.of( - "디자이너", " 브로콜리", - "기획자", " 당근", - "백앤드", " 양파", - "프론트앤드", " 버섯" + "DESIGN", " 브로콜리", + "PLANNING", " 당근", + "BACKEND", " 양파", + "FRONTEND", " 버섯" ); return roleToVegetableMap.getOrDefault(role, "알 수 없음"); @@ -309,41 +293,16 @@ public List getAppliedPots() { // Pot 리스트를 PotAllResponseDTO.PotDetail로 변환 return appliedPots.stream().map(pot -> { - // 모집 정보를 DTO로 변환 - List recruitmentDetailsDto = pot.getRecruitmentDetails().stream() - .map(details -> PotRecruitmentResponseDto.builder() - .recruitmentId(details.getRecruitmentId()) - .recruitmentRole(String.valueOf(details.getRecruitmentRole())) - .recruitmentCount(details.getRecruitmentCount()) - .build()) - .collect(Collectors.toList()); - - // Pot 정보를 DTO로 변환 - PotResponseDto potDto = PotResponseDto.builder() - .potId(pot.getPotId()) - .potName(pot.getPotName()) - .potStartDate(pot.getPotStartDate()) - .potEndDate(pot.getPotEndDate()) - .potDuration(pot.getPotDuration()) - .potLan(pot.getPotLan()) - .potContent(pot.getPotContent()) - .potStatus(pot.getPotStatus()) - .potSummary(pot.getPotSummary()) - .recruitmentDeadline(pot.getRecruitmentDeadline()) - .recruitmentDetails(recruitmentDetailsDto) - .potModeOfOperation(pot.getPotModeOfOperation().name()) - .dDay(Math.toIntExact(ChronoUnit.DAYS.between(LocalDate.now(), pot.getRecruitmentDeadline()))) - .build(); - // 유저 정보를 DTO로 변환 UserResponseDto userDto = UserResponseDto.builder() .nickname(pot.getUser().getNickname()) - .role(String.valueOf(pot.getUser().getRole())) + .role(pot.getUser().getRole()) .build(); + return PotAllResponseDTO.PotDetail.builder() .user(userDto) - .pot(potDto) + .pot(potConverter.toDto(pot, pot.getRecruitmentDetails())) // 변환기 사용 .build(); }).collect(Collectors.toList()); } @@ -360,21 +319,21 @@ public List getMyPots() { // 사용자가 만든 팟 조회 List myPots = potRepository.findByUserId(user.getId()); - // 모집중인 팟 리스트 + // 모집중인 팟 리스트 (recruiting 상태 필터링) List recruitingPots = myPots.stream() - .filter(pot -> "ongoing".equals(pot.getPotStatus())) + .filter(pot -> "RECRUITING".equalsIgnoreCase(pot.getPotStatus())) // 소문자 비교 .map(this::convertToPotDetail) .collect(Collectors.toList()); - // 진행 중인 팟 리스트 변환 (멤버 정보 포함) + // 진행 중인 팟 리스트 (ongoing 상태 필터링) List ongoingPots = myPots.stream() - .filter(pot -> "recruiting".equals(pot.getPotStatus())) + .filter(pot -> "ONGOING".equalsIgnoreCase(pot.getPotStatus())) // 소문자 비교 .map(this::convertToOngoingPotDetail) .collect(Collectors.toList()); // 끓인 팟 리스트 List completedPots = myPots.stream() - .filter(pot -> "completed".equals(pot.getPotStatus())) + .filter(pot -> "COMPLETED".equals(pot.getPotStatus())) .map(this::convertToPotDetail) .collect(Collectors.toList()); @@ -427,69 +386,35 @@ public PotSummaryResponseDTO getPotSummary(Long potId) { // Pot을 PotAllResponseDTO.PotDetail로 변환하는 메서드 private PotAllResponseDTO.PotDetail convertToPotDetail(Pot pot) { - List recruitmentDetailsDto = pot.getRecruitmentDetails().stream() - .map(details -> PotRecruitmentResponseDto.builder() - .recruitmentId(details.getRecruitmentId()) - .recruitmentRole(String.valueOf(details.getRecruitmentRole())) - .recruitmentCount(details.getRecruitmentCount()) - .build()) - .collect(Collectors.toList()); - - PotResponseDto potDto = PotResponseDto.builder() - .potId(pot.getPotId()) - .potName(pot.getPotName()) - .potStartDate(pot.getPotStartDate()) - .potEndDate(pot.getPotEndDate()) - .potDuration(pot.getPotDuration()) - .potLan(pot.getPotLan()) - .potContent(pot.getPotContent()) - .potStatus(pot.getPotStatus()) - .potSummary(pot.getPotSummary()) - .recruitmentDeadline(pot.getRecruitmentDeadline()) - .recruitmentDetails(recruitmentDetailsDto) - .potModeOfOperation(pot.getPotModeOfOperation().name()) - .dDay(Math.toIntExact(ChronoUnit.DAYS.between(LocalDate.now(), pot.getRecruitmentDeadline()))) - .build(); return PotAllResponseDTO.PotDetail.builder() .user(UserResponseDto.builder() .nickname(pot.getUser().getNickname() + getVegetableNameByRole(String.valueOf(pot.getUser().getRole()))) - .role(String.valueOf(pot.getUser().getRole())) + .role(pot.getUser().getRole()) .build()) - .pot(potDto) + .pot(potConverter.toDto(pot, pot.getRecruitmentDetails())) // 변환기 사용 .build(); } // 진행 중인 팟 변환 메서드 (멤버 포함) private MyPotResponseDTO.OngoingPotsDetail convertToOngoingPotDetail(Pot pot) { - List recruitmentDetails = pot.getRecruitmentDetails().stream() - .map(details -> RecruitmentDetailsResponseDTO.builder() - .recruitmentId(details.getRecruitmentId()) - .recruitmentRole(String.valueOf(details.getRecruitmentRole())) - .recruitmentCount(details.getRecruitmentCount()) - .build()) - .collect(Collectors.toList()); List potMembers = pot.getPotMembers().stream() .map(member -> PotMemberResponseDTO.builder() .potMemberId(member.getPotMemberId()) - .roleName(String.valueOf(member.getRoleName())) - + .roleName(member.getRoleName()) .build()) .collect(Collectors.toList()); + System.out.println("Recruitment Details: " + pot.getRecruitmentDetails()); + System.out.println("Pot Members: " + pot.getPotMembers()); + return MyPotResponseDTO.OngoingPotsDetail.builder() .user(UserResponseDto.builder() .nickname(pot.getUser().getNickname() + getVegetableNameByRole(String.valueOf(pot.getUser().getRole()))) - .role(String.valueOf(pot.getUser().getRole())) - .build()) - .pot(PotResponseDto.builder() - .potId(pot.getPotId()) - .potName(pot.getPotName()) - .potStartDate(pot.getPotStartDate()) - .potEndDate(pot.getPotEndDate()) - .potStatus(pot.getPotStatus()) + .role(pot.getUser().getRole()) .build()) + .pot(potConverter.toDto(pot, pot.getRecruitmentDetails())) // 변환기 사용 .potMembers(potMembers) .build(); } diff --git a/src/main/java/stackpot/stackpot/service/TodoCleanupService.java b/src/main/java/stackpot/stackpot/service/TodoCleanupService.java new file mode 100644 index 00000000..ef9fc1d0 --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/TodoCleanupService.java @@ -0,0 +1,27 @@ +package stackpot.stackpot.service; + +import jakarta.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; +import lombok.RequiredArgsConstructor; +import stackpot.stackpot.repository.TodoRepository; + +import java.time.LocalDateTime; + +@Service +@RequiredArgsConstructor +public class TodoCleanupService { + + private final TodoRepository todoRepository; + + // 매일 오전 3시 (03:00:00)에 실행 + @Scheduled(cron = "0 0 3 * * ?") + @Transactional // 트랜잭션 추가 + public void deleteOldTodos() { + LocalDateTime yesterday = LocalDateTime.now().minusDays(1); + int deletedCount = todoRepository.deleteByCreatedAtBefore(yesterday); + System.out.println("[" + LocalDateTime.now() + "] " + deletedCount + "개의 오래된 TODO 항목이 삭제되었습니다."); + } +} diff --git a/src/main/java/stackpot/stackpot/service/UserCommandService.java b/src/main/java/stackpot/stackpot/service/UserCommandService.java index 4139adc3..8c291476 100644 --- a/src/main/java/stackpot/stackpot/service/UserCommandService.java +++ b/src/main/java/stackpot/stackpot/service/UserCommandService.java @@ -1,10 +1,18 @@ package stackpot.stackpot.service; import stackpot.stackpot.domain.User; +import stackpot.stackpot.web.dto.UserMypageResponseDto; import stackpot.stackpot.web.dto.UserRequestDto; +import stackpot.stackpot.web.dto.UserResponseDto; +import stackpot.stackpot.web.dto.UserUpdateRequestDto; public interface UserCommandService { + User joinUser(UserRequestDto.JoinDto request); + User saveNewUser(String email); - public User joinUser(UserRequestDto.JoinDto request); - public User saveNewUser(String email); + UserResponseDto getMypages(); + + UserMypageResponseDto getUserMypage(Long userId); + + UserResponseDto updateUserProfile(UserUpdateRequestDto requestDto); } diff --git a/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java b/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java index 1a794875..8af4c705 100644 --- a/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java @@ -6,18 +6,31 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import stackpot.stackpot.converter.UserConverter; +import stackpot.stackpot.converter.UserMypageConverter; +import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.User; import stackpot.stackpot.domain.enums.Role; +import stackpot.stackpot.repository.FeedRepository.FeedRepository; +import stackpot.stackpot.repository.PotRepository.PotRepository; import stackpot.stackpot.repository.UserRepository.UserRepository; +import stackpot.stackpot.web.dto.UserMypageResponseDto; import stackpot.stackpot.web.dto.UserRequestDto; +import stackpot.stackpot.web.dto.UserResponseDto; +import stackpot.stackpot.web.dto.UserUpdateRequestDto; import java.util.Date; +import java.util.List; +import java.util.Map; @Service @RequiredArgsConstructor public class UserCommandServiceImpl implements UserCommandService{ private final UserRepository userRepository; + private final PotRepository potRepository; + private final FeedRepository feedRepository; + private final UserMypageConverter userMypageConverter; @Override @Transactional @@ -54,10 +67,90 @@ private void updateUserData(User user, UserRequestDto.JoinDto request) { // 닉네임 user.setNickname(request.getNickname()); // 역할군 - user.setRole(Role.valueOf(request.getRole())); + user.setRole(Role.valueOf(String.valueOf(request.getRole()))); // 관심사 user.setInterest(request.getInterest()); //한줄 소개 user.setUserIntroduction(user.getRole()+"에 관심있는 "+user.getNickname()+"입니다."); } + + @Override + public UserResponseDto getMypages() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + + // User 정보를 UserResponseDto로 변환 + return UserResponseDto.builder() + .email(user.getEmail()) + .nickname(user.getNickname() + getVegetableNameByRole(user.getRole().name())) // 닉네임 + 역할 + .role(user.getRole()) + .interest(user.getInterest()) + .userTemperature(user.getUserTemperature()) + .kakaoId(user.getKakaoId()) + .userIntroduction(user.getUserIntroduction()) // 한 줄 소개 추가 + .build(); + } + + @Transactional + public UserMypageResponseDto getUserMypage(Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("User not found with id: " + userId)); + + // COMPLETED 상태의 팟 조회 + List completedPots = potRepository.findByUserIdAndPotStatus(userId, "COMPLETED"); + + // 사용자의 피드 조회 + List userFeeds = feedRepository.findByUser_Id(userId); + + // 컨버터를 사용하여 변환 (좋아요 개수 포함) + return userMypageConverter.toDto(user, completedPots, userFeeds); + } + + @Transactional + public UserResponseDto updateUserProfile(UserUpdateRequestDto requestDto) { + // 현재 로그인한 사용자 정보 가져오기 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + + // 업데이트할 필드 적용 + if (requestDto.getRole() != null) { + user.setRole(requestDto.getRole()); + } + if (requestDto.getInterest() != null && !requestDto.getInterest().isEmpty()) { + user.setInterest(requestDto.getInterest()); + } + if (requestDto.getUserIntroduction() != null && !requestDto.getUserIntroduction().isEmpty()) { + user.setUserIntroduction(requestDto.getUserIntroduction()); + } + + // 저장 후 DTO로 변환하여 반환 + userRepository.save(user); + + return UserResponseDto.builder() + .email(user.getEmail()) + .nickname(user.getNickname() + getVegetableNameByRole(user.getRole().name())) // 닉네임 + 역할 + .role(user.getRole()) + .interest(user.getInterest()) + .userTemperature(user.getUserTemperature()) + .kakaoId(user.getKakaoId()) + .userIntroduction(user.getUserIntroduction()) + .build(); + } + + // 역할에 따른 채소명을 반환하는 메서드 + private String getVegetableNameByRole(String role) { + Map roleToVegetableMap = Map.of( + "BACKEND", " 양파", + "FRONTEND", " 버섯", + "DESIGN", " 브로콜리", + "PLANNING", " 당근" + ); + return roleToVegetableMap.getOrDefault(role, "알 수 없음"); + } } diff --git a/src/main/java/stackpot/stackpot/web/controller/MyPotController.java b/src/main/java/stackpot/stackpot/web/controller/MyPotController.java index 59ef30cf..edc7d604 100644 --- a/src/main/java/stackpot/stackpot/web/controller/MyPotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/MyPotController.java @@ -10,6 +10,7 @@ import stackpot.stackpot.web.dto.*; import java.util.List; +import java.util.Map; @RestController @RequiredArgsConstructor @@ -19,15 +20,21 @@ public class MyPotController { // 사용자가 만든 진행 중인 팟 조회 - @Operation(summary = "사용자가 만든 진행 중인 팟 조회 API", description = "pot 상태는 다음과 같이 구분됩니다. recruiting / ongoing / completed\n") - @GetMapping("/my-pots") - public ResponseEntity>> getMyOnGoingPots() { - List myOngoingPots = myPotService.getMyOnGoingPots(); - return ResponseEntity.ok(ApiResponse.onSuccess(myOngoingPots)); + @Operation(summary = "사용자의 팟 목록 조회 API", description = "사용자가 생성했거나, 참여하고 있으며 진행 중(ONGOING)인 팟들 리스트를 조회합니다. \n") + @GetMapping("/mypots/ongoing") + public ResponseEntity>>> getMyOngoingPots() { + Map> response = myPotService.getMyOnGoingPots(); + return ResponseEntity.ok(ApiResponse.onSuccess(response)); } // 팟에서의 투두 생성 - @Operation(summary = "Todo 생성 API", description = "status는 NOT_STARTED와 COMPLETED로 구분되며, 생성의 경우 NOT_STARTED로 전달해 주시면 됩니다.") + @Operation( + summary = "Todo 생성 API", + description = """ + - Status: NOT_STARTED / COMPLETED + * 생성의 경우 NOT_STARTED로 전달해 주시면 됩니다. + """ + ) @PostMapping("/my-pots/{pot_id}/todos") public ResponseEntity>> postMyTodo( @PathVariable("pot_id") Long potId, @@ -55,4 +62,14 @@ public ResponseEntity>> updateMyTodos( return ResponseEntity.ok(ApiResponse.onSuccess(response)); } + @Operation(summary = "Todo 완료 API", description = "todo의 status를 COMPLETED로 변경합니다.") + @PatchMapping("/my-pots/{pot_id}/todos/{todo_id}") + public ResponseEntity>> completeTodo( + @PathVariable("pot_id") Long potId, + @PathVariable("todo_id") Long todoId) { + + List response = myPotService.completeTodo(potId, todoId); + return ResponseEntity.ok(ApiResponse.onSuccess(response)); + } + } diff --git a/src/main/java/stackpot/stackpot/web/controller/PotController.java b/src/main/java/stackpot/stackpot/web/controller/PotController.java index 60f87c7a..8c199b6b 100644 --- a/src/main/java/stackpot/stackpot/web/controller/PotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/PotController.java @@ -10,6 +10,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.repository.PotRepository.PotRepository; import stackpot.stackpot.service.PotServiceImpl; import stackpot.stackpot.web.dto.PotRequestDto; @@ -34,8 +35,14 @@ public class PotController { private final PotServiceImpl potService; private final PotRepository potRepository; - @Operation(summary = "팟 생성하기") - @PostMapping + @Operation( + summary = "팟 생성하기", + description = """ + - potStatus: RECRUITING / ONGOING / COMPLETED + - potModeOfOperation: ONLINE / OFFLINE / HYBRID + - Role: FRONTEND / BACKEND / DESIGN / PLANNING + """ + ) @PostMapping public ResponseEntity createPot( @RequestBody @Valid PotRequestDto requestDto) { @@ -65,20 +72,36 @@ public ResponseEntity deletePot(@PathVariable("pot_id") Long potId) { return ResponseEntity.noContent().build(); } - //---------------------------- +//---------------------------- + + @Operation( + summary = "팟 전체 보기 API", + description = """ + - Role: FRONTEND / BACKEND / DESIGN / PLANNING / (NULL) + 만약 null인 경우 모든 role에 대해서 조회합니다. + """ + ) - @Operation(summary = "팟 전체 보기 API", description = "Design, Backend, Frontend, PM으로 필터링 가능합니다. 만약 null인 경우 전체 카테고리에 대해서 조회합니다.") @GetMapping public ResponseEntity>> getPots( @RequestParam(required = false) String recruitmentRole, @RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "10") Integer size) { - List pots = potService1.getAllPots(recruitmentRole, page, size); + Role roleEnum = null; + if (recruitmentRole != null && !recruitmentRole.isEmpty()) { + try { + roleEnum = Role.valueOf(recruitmentRole.trim().toUpperCase()); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid recruitment role provided: " + recruitmentRole); + } + } + + List pots = potService1.getAllPots(roleEnum, page, size); - Page potPage = (recruitmentRole == null || recruitmentRole.isEmpty()) + Page potPage = (roleEnum == null) ? potRepository.findAll(PageRequest.of(page, size)) - : potRepository.findByRecruitmentDetails_RecruitmentRole(recruitmentRole, PageRequest.of(page, size)); + : potRepository.findByRecruitmentDetails_RecruitmentRole(roleEnum, PageRequest.of(page, size)); Map response = new HashMap<>(); response.put("pots", pots); diff --git a/src/main/java/stackpot/stackpot/web/controller/UserController.java b/src/main/java/stackpot/stackpot/web/controller/UserController.java index 1ece39e5..10a26ff3 100644 --- a/src/main/java/stackpot/stackpot/web/controller/UserController.java +++ b/src/main/java/stackpot/stackpot/web/controller/UserController.java @@ -12,15 +12,14 @@ import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import org.springframework.web.bind.annotation.*; +import stackpot.stackpot.apiPayload.ApiResponse; import stackpot.stackpot.config.security.JwtTokenProvider; import stackpot.stackpot.converter.UserConverter; import stackpot.stackpot.domain.User; import stackpot.stackpot.repository.UserRepository.UserRepository; import stackpot.stackpot.service.KakaoService; import stackpot.stackpot.service.UserCommandService; -import stackpot.stackpot.web.dto.KakaoUserInfoResponseDto; -import stackpot.stackpot.web.dto.TokenServiceResponse; -import stackpot.stackpot.web.dto.UserRequestDto; +import stackpot.stackpot.web.dto.*; import java.util.List; import java.util.stream.Collectors; @@ -89,4 +88,28 @@ public ResponseEntity nickname(){ // return ResponseEntity.ok(); } + @Operation(summary = "마이페이지 사용자 정보 조회 API") + @GetMapping("/mypages") + public ResponseEntity> usersMypages(){ + UserResponseDto userDetails = userCommandService.getMypages(); + return ResponseEntity.ok(ApiResponse.onSuccess(userDetails)); + } + + @Operation(summary = "다른 사람 마이페이지(프로필) 조회 API") + @GetMapping("/{userId}/mypages") + public ResponseEntity> getUserMypage(@PathVariable Long userId) { + UserMypageResponseDto response = userCommandService.getUserMypage(userId); + return ResponseEntity.ok(ApiResponse.onSuccess(response)); + } + + @PatchMapping("/profile/update") + @Operation(summary = "사용자 프로필 수정 API", description = "사용자의 역할, 관심사, 한 줄 소개를 수정합니다.") + public ResponseEntity> updateUserProfile( + @RequestBody @Valid UserUpdateRequestDto requestDto) { + + UserResponseDto updatedUser = userCommandService.updateUserProfile(requestDto); + return ResponseEntity.ok(ApiResponse.onSuccess(updatedUser)); + } + + } diff --git a/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java index 6cbcf03e..22a1a591 100644 --- a/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/FeedResponseDto.java @@ -1,5 +1,6 @@ package stackpot.stackpot.web.dto; +import com.fasterxml.jackson.annotation.JsonFormat; import lombok.*; import stackpot.stackpot.domain.enums.Category; @@ -31,7 +32,7 @@ public static class FeedDto { private String content; private Long popularity; private Long likeCount; - private LocalDateTime createdAt; + private String createdAt; } diff --git a/src/main/java/stackpot/stackpot/web/dto/LikedApplicantResponseDTO.java b/src/main/java/stackpot/stackpot/web/dto/LikedApplicantResponseDTO.java index e97bef6c..9ee52922 100644 --- a/src/main/java/stackpot/stackpot/web/dto/LikedApplicantResponseDTO.java +++ b/src/main/java/stackpot/stackpot/web/dto/LikedApplicantResponseDTO.java @@ -2,12 +2,13 @@ import lombok.Builder; import lombok.Getter; +import stackpot.stackpot.domain.enums.Role; @Getter @Builder public class LikedApplicantResponseDTO { private Long applicationId; - private String applicantRole; + private Role applicantRole; private String potNickname; // user의 nickname + pot_role 조합 private Boolean liked; } diff --git a/src/main/java/stackpot/stackpot/web/dto/PotApplicationResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/PotApplicationResponseDto.java index 090ee956..98281d98 100644 --- a/src/main/java/stackpot/stackpot/web/dto/PotApplicationResponseDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/PotApplicationResponseDto.java @@ -17,4 +17,5 @@ public class PotApplicationResponseDto { private LocalDateTime appliedAt; private Long potId; private Long userId; + private String userNickname; } diff --git a/src/main/java/stackpot/stackpot/web/dto/PotMemberAppealResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/PotMemberAppealResponseDto.java index 66acde73..0b53a56f 100644 --- a/src/main/java/stackpot/stackpot/web/dto/PotMemberAppealResponseDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/PotMemberAppealResponseDto.java @@ -2,6 +2,7 @@ import lombok.Builder; import lombok.Getter; +import stackpot.stackpot.domain.enums.Role; @Getter @Builder diff --git a/src/main/java/stackpot/stackpot/web/dto/PotMemberResponseDTO.java b/src/main/java/stackpot/stackpot/web/dto/PotMemberResponseDTO.java index 9db7fd3f..e359fab3 100644 --- a/src/main/java/stackpot/stackpot/web/dto/PotMemberResponseDTO.java +++ b/src/main/java/stackpot/stackpot/web/dto/PotMemberResponseDTO.java @@ -2,13 +2,14 @@ import lombok.Builder; import lombok.Getter; +import stackpot.stackpot.domain.enums.Role; @Getter @Builder public class PotMemberResponseDTO { private Long potMemberId; - private String roleName; + private Role roleName; private Boolean owner; private String appealContent; } diff --git a/src/main/java/stackpot/stackpot/web/dto/PotRecruitmentResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/PotRecruitmentResponseDto.java index a1d4de7d..88a2b4c7 100644 --- a/src/main/java/stackpot/stackpot/web/dto/PotRecruitmentResponseDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/PotRecruitmentResponseDto.java @@ -3,6 +3,7 @@ import lombok.Builder; import lombok.Getter; import lombok.Setter; +import stackpot.stackpot.domain.enums.Role; @Getter @Setter diff --git a/src/main/java/stackpot/stackpot/web/dto/PotResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/PotResponseDto.java index aafb8d6a..93607982 100644 --- a/src/main/java/stackpot/stackpot/web/dto/PotResponseDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/PotResponseDto.java @@ -14,8 +14,8 @@ public class PotResponseDto { private Long potId; private String potName; - private LocalDate potStartDate; - private LocalDate potEndDate; + private String potStartDate; + private String potEndDate; private String potDuration; private String potLan; private String potContent; @@ -24,5 +24,4 @@ public class PotResponseDto { private String potSummary; private LocalDate recruitmentDeadline; private List recruitmentDetails; - private Integer dDay; } diff --git a/src/main/java/stackpot/stackpot/web/dto/RecruitmentDetailsResponseDTO.java b/src/main/java/stackpot/stackpot/web/dto/RecruitmentDetailsResponseDTO.java index 692fa456..d9c930af 100644 --- a/src/main/java/stackpot/stackpot/web/dto/RecruitmentDetailsResponseDTO.java +++ b/src/main/java/stackpot/stackpot/web/dto/RecruitmentDetailsResponseDTO.java @@ -3,13 +3,14 @@ import lombok.Builder; import lombok.Getter; +import stackpot.stackpot.domain.enums.Role; @Getter @Builder public class RecruitmentDetailsResponseDTO { private Long recruitmentId; - private String recruitmentRole; + private Role recruitmentRole; private Integer recruitmentCount; } diff --git a/src/main/java/stackpot/stackpot/web/dto/UserMypageResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/UserMypageResponseDto.java new file mode 100644 index 00000000..2f05018c --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/UserMypageResponseDto.java @@ -0,0 +1,51 @@ +package stackpot.stackpot.web.dto; + +import lombok.*; +import stackpot.stackpot.domain.enums.Category; +import stackpot.stackpot.domain.enums.Role; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class UserMypageResponseDto { + private String email; + private String nickname; + private Role role; + private String interest; + private Integer userTemperature; + private String kakaoId; + private String userIntroduction; + private List completedPots; + private List feeds; + + @Getter + @Setter + @Builder + public static class CompletedPotDto { + private Long potId; + private String potName; + private String potStartDate; + private String potEndDate; + private String potSummary; + private List recruitmentDetails; + } + + @Getter + @Setter + @Builder + public static class FeedDto { + private Long feedId; + private String title; + private String content; + private Category category; + private Long likeCount; + private String createdAt; + } +} + diff --git a/src/main/java/stackpot/stackpot/web/dto/UserRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/UserRequestDto.java index 7ff9fab0..0ab6a6ca 100644 --- a/src/main/java/stackpot/stackpot/web/dto/UserRequestDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/UserRequestDto.java @@ -7,6 +7,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import stackpot.stackpot.domain.enums.Role; public class UserRequestDto { @@ -15,7 +16,7 @@ public class UserRequestDto { @NoArgsConstructor public static class JoinDto { @NotBlank(message = "Role은 공백일 수 없습니다.") - String role; + Role role; @NotBlank(message = "Interest는 공백일 수 없습니다.") String interest; @NotBlank(message = "Nickname은 공백일 수 없습니다.") diff --git a/src/main/java/stackpot/stackpot/web/dto/UserResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/UserResponseDto.java index 6b9d23bc..38e84137 100644 --- a/src/main/java/stackpot/stackpot/web/dto/UserResponseDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/UserResponseDto.java @@ -4,6 +4,7 @@ import lombok.Builder; import lombok.Getter; import lombok.Setter; +import stackpot.stackpot.domain.enums.Role; @Getter @Setter @@ -11,8 +12,9 @@ public class UserResponseDto { private String email; // 이메일 private String nickname; // 닉네임 - private String role; // 역할 + private Role role; // 역할 private String interest; // 관심사 private Integer userTemperature; // 유저 온도 private String kakaoId; + private String userIntroduction; } diff --git a/src/main/java/stackpot/stackpot/web/dto/UserUpdateRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/UserUpdateRequestDto.java new file mode 100644 index 00000000..ce182193 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/UserUpdateRequestDto.java @@ -0,0 +1,14 @@ +package stackpot.stackpot.web.dto; + +import lombok.Getter; +import lombok.Setter; +import stackpot.stackpot.domain.enums.Role; + + +@Getter +@Setter +public class UserUpdateRequestDto { + private Role role; + private String interest; + private String userIntroduction; +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 60b1762b..cc93fa1d 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -43,4 +43,7 @@ spring: auth: true timeout: 5000 starttls: - enable: true \ No newline at end of file + enable: true + task: + scheduling: + enabled: true \ No newline at end of file From ecac2153a959eb2daad975aa9cc8b0266906b6ec Mon Sep 17 00:00:00 2001 From: MiN <81948815+leesumin0526@users.noreply.github.com> Date: Sun, 26 Jan 2025 00:49:46 +0900 Subject: [PATCH 60/76] =?UTF-8?q?[Mod]=20api=20=EC=88=98=EC=A0=95=20(#34)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [MOD] 특정 팟 상세보기 API 응답형식 변경 & 팟 수정 API startDate 미반영 처리 --- src/main/java/stackpot/stackpot/service/PotServiceImpl.java | 1 - .../java/stackpot/stackpot/web/controller/PotController.java | 2 +- .../java/stackpot/stackpot/web/dto/ApplicantResponseDTO.java | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java index 22730563..f2a40976 100644 --- a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java @@ -90,7 +90,6 @@ public PotResponseDto updatePotWithRecruitments(Long potId, PotRequestDto reques // 업데이트 로직 pot.updateFields(Map.of( "potName", requestDto.getPotName(), - "potStartDate", requestDto.getPotStartDate(), "potEndDate", requestDto.getPotEndDate(), "potDuration", requestDto.getPotDuration(), "potLan", requestDto.getPotLan(), diff --git a/src/main/java/stackpot/stackpot/web/controller/PotController.java b/src/main/java/stackpot/stackpot/web/controller/PotController.java index 8c199b6b..8f996594 100644 --- a/src/main/java/stackpot/stackpot/web/controller/PotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/PotController.java @@ -72,7 +72,7 @@ public ResponseEntity deletePot(@PathVariable("pot_id") Long potId) { return ResponseEntity.noContent().build(); } -//---------------------------- + //---------------------------- @Operation( summary = "팟 전체 보기 API", diff --git a/src/main/java/stackpot/stackpot/web/dto/ApplicantResponseDTO.java b/src/main/java/stackpot/stackpot/web/dto/ApplicantResponseDTO.java index 9c71e704..f9568988 100644 --- a/src/main/java/stackpot/stackpot/web/dto/ApplicantResponseDTO.java +++ b/src/main/java/stackpot/stackpot/web/dto/ApplicantResponseDTO.java @@ -11,7 +11,6 @@ public class ApplicantResponseDTO { private UserResponseDto user; private PotResponseDto pot; - private List recruitmentDetails; private List applicant; @Getter From 71d2a3fb9d35ff53db557005fcbe9e36093bce0f Mon Sep 17 00:00:00 2001 From: isumin Date: Sun, 26 Jan 2025 00:52:14 +0900 Subject: [PATCH 61/76] =?UTF-8?q?[MOD]=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EB=B3=80=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/stackpot/stackpot/service/PotServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java index f2a40976..e4836948 100644 --- a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java @@ -199,7 +199,7 @@ public ApplicantResponseDTO getPotDetails(Long potId) { return ApplicantResponseDTO.builder() .user(UserResponseDto.builder() - .nickname(pot.getUser().getNickname()) + .nickname(pot.getUser().getNickname() + getVegetableNameByRole(String.valueOf(pot.getUser().getRole()))) .role(pot.getUser().getRole()) .build()) .pot(potConverter.toDto(pot, pot.getRecruitmentDetails())) // 변환기 사용 From c674dd66347bada32479c9b4b7e4d390a4dcd197 Mon Sep 17 00:00:00 2001 From: starday119 Date: Sun, 26 Jan 2025 02:01:10 +0900 Subject: [PATCH 62/76] =?UTF-8?q?[FEAT]=20:=20=EB=82=98=EC=9D=98=20?= =?UTF-8?q?=EB=81=93=EC=9D=B8=20=ED=8C=9F=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/security/SecurityConfig.java | 20 ++++++++++- .../stackpot/config/security/WebConfig.java | 2 +- .../stackpot/converter/PotConverter.java | 4 ++- .../stackpot/converter/PotConverterImpl.java | 29 ++++++++++++++-- .../PotMemberConverterImpl.java | 1 + .../java/stackpot/stackpot/domain/Pot.java | 1 + .../java/stackpot/stackpot/domain/User.java | 1 + .../repository/PotMemberRepository.java | 3 +- .../PotRepository/PotRepository.java | 4 +++ .../PotMemberServiceImpl.java | 6 +++- .../stackpot/stackpot/service/PotService.java | 2 +- .../stackpot/service/PotServiceImpl.java | 31 ++++++++++++++++- .../web/controller/FeedController.java | 2 ++ .../web/controller/MyPotController.java | 2 ++ .../controller/PotApplicationController.java | 2 ++ .../web/controller/PotController.java | 12 +++++-- .../web/controller/PotMemberController.java | 12 +++++-- .../web/controller/UserController.java | 3 +- .../web/dto/CompletedPotResponseDto.java | 34 +++++++++++++++++++ .../stackpot/web/dto/PotMemberRequestDto.java | 9 ++++- .../web/dto/RecruitmentDetailResponseDto.java | 14 ++++++++ 21 files changed, 177 insertions(+), 17 deletions(-) create mode 100644 src/main/java/stackpot/stackpot/web/dto/CompletedPotResponseDto.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/RecruitmentDetailResponseDto.java diff --git a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java index 71e68757..44debb6b 100644 --- a/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java +++ b/src/main/java/stackpot/stackpot/config/security/SecurityConfig.java @@ -14,7 +14,9 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import stackpot.stackpot.web.dto.TokenServiceResponse; - +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import static org.springframework.security.config.Customizer.withDefaults; @EnableWebSecurity @@ -48,6 +50,22 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtTokenProvid .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); return http.build(); } + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.addAllowedOrigin("http://localhost:3000"); + configuration.addAllowedOrigin("http://localhost:8080"); + configuration.addAllowedOrigin("https://stackpot.co.kr"); + configuration.addAllowedOrigin("https://api.stackpot.co.kr"); + configuration.addAllowedMethod("*"); + configuration.addAllowedHeader("*"); + configuration.setAllowCredentials(true); // 인증 정보 포함 허용 + configuration.setMaxAge(3600L); // 캐싱 시간 (1시간) + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } // @Bean // public PasswordEncoder passwordEncoder() { diff --git a/src/main/java/stackpot/stackpot/config/security/WebConfig.java b/src/main/java/stackpot/stackpot/config/security/WebConfig.java index 4bbc02d8..73e64604 100644 --- a/src/main/java/stackpot/stackpot/config/security/WebConfig.java +++ b/src/main/java/stackpot/stackpot/config/security/WebConfig.java @@ -14,7 +14,7 @@ public WebMvcConfigurer corsConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 모든 엔드포인트에 대해 CORS 허용 - .allowedOrigins("http://localhost:3000", "http://localhost:8080","https://stackpot.co.kr") // 허용할 도메인 + .allowedOrigins("http://localhost:3000", "http://localhost:8080","https://stackpot.co.kr","https://api.stackpot.co.kr") // 허용할 도메인 .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 허용할 HTTP 메서드 .allowedHeaders("Authorization", "Content-Type", "X-Requested-With") // 허용할 헤더 .allowCredentials(true) // 인증 정보 포함 (쿠키, 헤더 등) diff --git a/src/main/java/stackpot/stackpot/converter/PotConverter.java b/src/main/java/stackpot/stackpot/converter/PotConverter.java index d0fba6e0..903b1a4e 100644 --- a/src/main/java/stackpot/stackpot/converter/PotConverter.java +++ b/src/main/java/stackpot/stackpot/converter/PotConverter.java @@ -3,13 +3,15 @@ import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.PotRecruitmentDetails; import stackpot.stackpot.domain.User; +import stackpot.stackpot.web.dto.CompletedPotResponseDto; import stackpot.stackpot.web.dto.PotRequestDto; import stackpot.stackpot.web.dto.PotResponseDto; import java.util.List; +import java.util.Map; public interface PotConverter { Pot toEntity(PotRequestDto dto,User user); - + CompletedPotResponseDto toCompletedPotResponseDto(Pot pot, Map roleCounts); PotResponseDto toDto(Pot entity, List recruitmentDetails); } diff --git a/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java b/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java index 35c513d7..326edd80 100644 --- a/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java @@ -5,11 +5,10 @@ import stackpot.stackpot.domain.PotRecruitmentDetails; import stackpot.stackpot.domain.User; import stackpot.stackpot.domain.enums.PotModeOfOperation; -import stackpot.stackpot.web.dto.PotRequestDto; -import stackpot.stackpot.web.dto.PotResponseDto; -import stackpot.stackpot.web.dto.PotRecruitmentResponseDto; +import stackpot.stackpot.web.dto.*; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import java.time.format.DateTimeFormatter; @@ -62,4 +61,28 @@ private String formatDate(java.time.LocalDate date) { return (date != null) ? date.format(DATE_FORMATTER) : "N/A"; } + @Override + + public CompletedPotResponseDto toCompletedPotResponseDto(Pot pot, Map roleCounts) { + return CompletedPotResponseDto.builder() + .potId(pot.getPotId()) + .potName(pot.getPotName()) + .potStartDate(pot.getPotStartDate()) + .potEndDate(pot.getPotEndDate()) + .potDuration(pot.getPotDuration()) + .potLan(pot.getPotLan()) + .potContent(pot.getPotContent()) + .potStatus(pot.getPotStatus()) + .potModeOfOperation(pot.getPotModeOfOperation()) + .potSummary(pot.getPotSummary()) + .recruitmentDeadline(pot.getRecruitmentDeadline()) + .recruitmentDetails(pot.getRecruitmentDetails().stream() + .map(rd -> RecruitmentDetailResponseDto.builder() + .recruitmentRole(String.valueOf(rd.getRecruitmentRole())) + .recruitmentCount(rd.getRecruitmentCount()) + .build()) + .collect(Collectors.toList())) + .roleCounts(roleCounts) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java b/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java index ce3b3758..ccf6582c 100644 --- a/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java @@ -42,6 +42,7 @@ public PotMemberAppealResponseDto toDto(PotMember entity) { .build(); } + private String mapRoleName(String potRole) { switch (potRole) { case "BACKEND": diff --git a/src/main/java/stackpot/stackpot/domain/Pot.java b/src/main/java/stackpot/stackpot/domain/Pot.java index 1bb1ff8d..4da17c64 100644 --- a/src/main/java/stackpot/stackpot/domain/Pot.java +++ b/src/main/java/stackpot/stackpot/domain/Pot.java @@ -40,6 +40,7 @@ public class Pot extends BaseEntity { @Column(nullable = false, length = 255) private String potName; + @Setter @Column(nullable = false) private LocalDate potStartDate; diff --git a/src/main/java/stackpot/stackpot/domain/User.java b/src/main/java/stackpot/stackpot/domain/User.java index ea2aa5ec..e2fdc85a 100644 --- a/src/main/java/stackpot/stackpot/domain/User.java +++ b/src/main/java/stackpot/stackpot/domain/User.java @@ -32,6 +32,7 @@ public class User extends BaseEntity implements UserDetails{ @Column(nullable = true, length = 255) private String nickname; // 닉네임 + @Enumerated(EnumType.STRING) @Column(nullable = true, length = 255) private Role role; // 역할 diff --git a/src/main/java/stackpot/stackpot/repository/PotMemberRepository.java b/src/main/java/stackpot/stackpot/repository/PotMemberRepository.java index 1dd9d0e9..054cf36f 100644 --- a/src/main/java/stackpot/stackpot/repository/PotMemberRepository.java +++ b/src/main/java/stackpot/stackpot/repository/PotMemberRepository.java @@ -20,5 +20,6 @@ public interface PotMemberRepository extends JpaRepository { List findUserIdsByPotId(@Param("potId") Long potId); @Query("SELECT pm FROM PotMember pm WHERE pm.pot.potId = :potId") List findByPotId(@Param("potId") Long potId); - + @Query("SELECT pm.roleName, COUNT(pm) FROM PotMember pm WHERE pm.pot.potId = :potId GROUP BY pm.roleName") + List findRoleCountsByPotId(@Param("potId") Long potId); } diff --git a/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java b/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java index be8e1fd4..a708e685 100644 --- a/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java +++ b/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java @@ -23,4 +23,8 @@ public interface PotRepository extends JpaRepository { Page findAll(Pageable pageable); List findByPotMembers_UserIdAndPotStatus(Long userId, String status); List findByUserIdAndPotStatus(Long userId, String status); + + @Query("SELECT p FROM Pot p WHERE p.potStatus = 'COMPLETED' AND (p.user.id = :userId OR p.potId IN " + + "(SELECT pm.pot.potId FROM PotMember pm WHERE pm.user.id = :userId))") + List findCompletedPotsByCreatorOrMember(@Param("userId") Long userId); } diff --git a/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberServiceImpl.java index 3e4ac497..e6c1eede 100644 --- a/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberServiceImpl.java @@ -18,6 +18,7 @@ import stackpot.stackpot.web.dto.PotMemberRequestDto; import stackpot.stackpot.web.dto.PotMemberAppealResponseDto; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -51,7 +52,10 @@ public List addMembersToPot (Long potId, PotMemberRe .orElseThrow(() -> new IllegalArgumentException("해당 팟을 찾을 수 없습니다.")); // 2. 팟 상태를 "ing"로 설정 - pot.setPotStatus("ing"); + pot.setPotStatus("ONGOING"); + + // 3. 팟의 시작 날짜를 현재 날짜로 설정 + pot.setPotStartDate(LocalDate.now()); // 필드 이름에 따라 메서드 호출 potRepository.save(pot); // 변경 사항 저장 diff --git a/src/main/java/stackpot/stackpot/service/PotService.java b/src/main/java/stackpot/stackpot/service/PotService.java index e21061b4..b45963d8 100644 --- a/src/main/java/stackpot/stackpot/service/PotService.java +++ b/src/main/java/stackpot/stackpot/service/PotService.java @@ -11,7 +11,7 @@ public interface PotService { PotResponseDto createPotWithRecruitments(PotRequestDto requestDto); PotResponseDto updatePotWithRecruitments(Long potId, PotRequestDto requestDto); - + List getMyCompletedPots(); void deletePot(Long potId); //--------------- diff --git a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java index 22730563..f75edd2b 100644 --- a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java @@ -17,6 +17,7 @@ import stackpot.stackpot.domain.User; import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.domain.mapping.PotApplication; +import stackpot.stackpot.repository.PotMemberRepository; import stackpot.stackpot.repository.PotRepository.PotRecruitmentDetailsRepository; import stackpot.stackpot.repository.PotRepository.PotRepository; import stackpot.stackpot.repository.UserRepository.UserRepository; @@ -38,7 +39,7 @@ public class PotServiceImpl implements PotService { private final PotConverter potConverter; private final JwtTokenProvider jwtTokenProvider; private final UserRepository userRepository; - + private final PotMemberRepository potMemberRepository; @Transactional public PotResponseDto createPotWithRecruitments(PotRequestDto requestDto) { // 인증 정보에서 사용자 이메일 가져오기 @@ -51,6 +52,8 @@ public PotResponseDto createPotWithRecruitments(PotRequestDto requestDto) { // 팟 생성 Pot pot = potConverter.toEntity(requestDto, user); + // 2. 팟 상태를 "ing"로 설정 + pot.setPotStatus("RECRUITING"); Pot savedPot = potRepository.save(pot); // 모집 정보 저장 @@ -118,7 +121,33 @@ public PotResponseDto updatePotWithRecruitments(Long potId, PotRequestDto reques return potConverter.toDto(pot, recruitmentDetails); } + @Transactional + @Override + public List getMyCompletedPots() { + // 현재 인증된 사용자 가져오기 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + // 사용자 조회 + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + + // 사용자가 생성하거나 참여한 COMPLETED 상태의 팟 가져오기 + List completedPots = potRepository.findCompletedPotsByCreatorOrMember(user.getId()); + + // 역할별 인원 조회 및 DTO 변환 + return completedPots.stream() + .map(pot -> { + List roleCounts = potMemberRepository.findRoleCountsByPotId(pot.getPotId()); + Map roleCountsMap = roleCounts.stream() + .collect(Collectors.toMap( + roleCount -> ((Role) roleCount[0]).name(), + roleCount -> ((Long) roleCount[1]).intValue() + )); + return potConverter.toCompletedPotResponseDto(pot, roleCountsMap); + }) + .collect(Collectors.toList()); + } @Transactional public void deletePot(Long potId) { diff --git a/src/main/java/stackpot/stackpot/web/controller/FeedController.java b/src/main/java/stackpot/stackpot/web/controller/FeedController.java index e0835b8d..43feda7c 100644 --- a/src/main/java/stackpot/stackpot/web/controller/FeedController.java +++ b/src/main/java/stackpot/stackpot/web/controller/FeedController.java @@ -1,6 +1,7 @@ package stackpot.stackpot.web.controller; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -21,6 +22,7 @@ @RestController @RequestMapping("/feeds") @RequiredArgsConstructor +@Tag(name = "Feed Management", description = "피드 관리 API") public class FeedController { private final FeedService feedService; diff --git a/src/main/java/stackpot/stackpot/web/controller/MyPotController.java b/src/main/java/stackpot/stackpot/web/controller/MyPotController.java index edc7d604..0f2fa460 100644 --- a/src/main/java/stackpot/stackpot/web/controller/MyPotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/MyPotController.java @@ -1,6 +1,7 @@ package stackpot.stackpot.web.controller; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -14,6 +15,7 @@ @RestController @RequiredArgsConstructor +@Tag(name = "My Pot Management", description = "나의 팟 관리 API") public class MyPotController { private final MyPotService myPotService; diff --git a/src/main/java/stackpot/stackpot/web/controller/PotApplicationController.java b/src/main/java/stackpot/stackpot/web/controller/PotApplicationController.java index f7667e41..25a3503c 100644 --- a/src/main/java/stackpot/stackpot/web/controller/PotApplicationController.java +++ b/src/main/java/stackpot/stackpot/web/controller/PotApplicationController.java @@ -1,6 +1,7 @@ package stackpot.stackpot.web.controller; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -11,6 +12,7 @@ import java.util.List; +@Tag(name = "Pot Application Management", description = "팟 지원 관리 API") @RestController @RequestMapping("/pots/{pot_id}/applications") @RequiredArgsConstructor diff --git a/src/main/java/stackpot/stackpot/web/controller/PotController.java b/src/main/java/stackpot/stackpot/web/controller/PotController.java index 8c199b6b..6b2e7c40 100644 --- a/src/main/java/stackpot/stackpot/web/controller/PotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/PotController.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -23,7 +24,7 @@ import java.util.List; import java.util.Map; - +@Tag(name = "Pot Management", description = "팟 관리 API") @RestController @RequestMapping("/pots") @RequiredArgsConstructor @@ -71,8 +72,13 @@ public ResponseEntity deletePot(@PathVariable("pot_id") Long potId) { return ResponseEntity.noContent().build(); } - -//---------------------------- + @GetMapping("/completed") + @Operation(summary = "나의 끓인 팟 정보 가져오기", description = "potStatus가 COMPLETED인 팟의 목록을 가져옵니다.") + public ResponseEntity>> getMyCompletedPots() { + List response = potService.getMyCompletedPots(); + return ResponseEntity.ok(ApiResponse.onSuccess(response)); + } + //------------------- @Operation( summary = "팟 전체 보기 API", diff --git a/src/main/java/stackpot/stackpot/web/controller/PotMemberController.java b/src/main/java/stackpot/stackpot/web/controller/PotMemberController.java index 2d33afc4..cc4203bc 100644 --- a/src/main/java/stackpot/stackpot/web/controller/PotMemberController.java +++ b/src/main/java/stackpot/stackpot/web/controller/PotMemberController.java @@ -1,6 +1,9 @@ package stackpot.stackpot.web.controller; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -16,6 +19,7 @@ @RestController @RequestMapping("/pots/{pot_id}/members") @RequiredArgsConstructor +@Tag(name = "Pot Member Management", description = "팟 멤버 관리 API") public class PotMemberController { private final PotMemberService potMemberService; @@ -29,11 +33,15 @@ public ResponseEntity>> getPotMembe return ResponseEntity.ok(ApiResponse.onSuccess(response)); } - @Operation(summary = "팟 시작하기") + @Operation( + summary = "팟 시작하기", + description = "지원자 ID 리스트를 받아 팟 멤버를 추가합니다." + + ) @PostMapping public ResponseEntity>> addPotMembers( @PathVariable("pot_id") Long potId, - @RequestBody PotMemberRequestDto requestDto) { + @RequestBody @Valid PotMemberRequestDto requestDto) { List response = potMemberService.addMembersToPot(potId, requestDto); return ResponseEntity.ok(ApiResponse.onSuccess(response)); } diff --git a/src/main/java/stackpot/stackpot/web/controller/UserController.java b/src/main/java/stackpot/stackpot/web/controller/UserController.java index 10a26ff3..38108c80 100644 --- a/src/main/java/stackpot/stackpot/web/controller/UserController.java +++ b/src/main/java/stackpot/stackpot/web/controller/UserController.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.Operation; //import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -23,7 +24,7 @@ import java.util.List; import java.util.stream.Collectors; - +@Tag(name = "User Management", description = "유저 관리 API") @Slf4j @RestController @RequiredArgsConstructor diff --git a/src/main/java/stackpot/stackpot/web/dto/CompletedPotResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/CompletedPotResponseDto.java new file mode 100644 index 00000000..e8e58f45 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/CompletedPotResponseDto.java @@ -0,0 +1,34 @@ +package stackpot.stackpot.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Builder; +import lombok.Setter; +import stackpot.stackpot.domain.enums.PotModeOfOperation; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class CompletedPotResponseDto { + private Long potId; // 팟 ID + private String potName; // 팟 이름 + private LocalDate potStartDate; // 시작 날짜 + private LocalDate potEndDate; // 종료 날짜 + private String potDuration; // 팟 기간 설명 + private String potLan; // 사용 언어 + private String potContent; // 팟 설명 + private String potStatus; // 팟 상태 + private PotModeOfOperation potModeOfOperation; // 운영 방식 + private String potSummary; // 요약 설명 + private LocalDate recruitmentDeadline; // 모집 마감일 + private List recruitmentDetails; // 수정된 부분 // 모집 세부 정보 + private Map roleCounts; // 역할별 인원 +} + diff --git a/src/main/java/stackpot/stackpot/web/dto/PotMemberRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/PotMemberRequestDto.java index bf0a38df..02989216 100644 --- a/src/main/java/stackpot/stackpot/web/dto/PotMemberRequestDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/PotMemberRequestDto.java @@ -1,5 +1,7 @@ package stackpot.stackpot.web.dto; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @@ -8,6 +10,11 @@ @Getter @Setter public class PotMemberRequestDto { - + @ArraySchema( + arraySchema = @Schema( + description = "추가할 지원자 ID 리스트", + example = "[1, 2, 3]" + ) + ) private List applicantIds; // 지원자 ID 리스트 } diff --git a/src/main/java/stackpot/stackpot/web/dto/RecruitmentDetailResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/RecruitmentDetailResponseDto.java new file mode 100644 index 00000000..7b1c26d4 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/RecruitmentDetailResponseDto.java @@ -0,0 +1,14 @@ +package stackpot.stackpot.web.dto; + +import lombok.*; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class RecruitmentDetailResponseDto { + private String recruitmentRole; + private Integer recruitmentCount; +} + From bb1d1a6664646e68ebc891c92ea5a282bb5f8ef1 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Sun, 26 Jan 2025 12:51:28 +0900 Subject: [PATCH 63/76] =?UTF-8?q?[FEAT]=20task=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/controller/FeedController.java | 2 +- .../web/controller/KakaoLoginController.java | 34 ------------------ .../web/controller/MyPotController.java | 36 +++++++++++++++++++ 3 files changed, 37 insertions(+), 35 deletions(-) delete mode 100644 src/main/java/stackpot/stackpot/web/controller/KakaoLoginController.java diff --git a/src/main/java/stackpot/stackpot/web/controller/FeedController.java b/src/main/java/stackpot/stackpot/web/controller/FeedController.java index e0835b8d..5f838b11 100644 --- a/src/main/java/stackpot/stackpot/web/controller/FeedController.java +++ b/src/main/java/stackpot/stackpot/web/controller/FeedController.java @@ -40,7 +40,7 @@ public ResponseEntity createFeeds(@Valid @RequestBody F return ResponseEntity.status(HttpStatus.CREATED).body(response); } - @Operation(summary = "feed 미리보기 api") + @Operation(summary = "feed 미리보기 api ( 수정 필요 )") @GetMapping("") public ResponseEntity getPreViewFeeds( @RequestParam(value = "category", required = false, defaultValue = "ALL") Category category, diff --git a/src/main/java/stackpot/stackpot/web/controller/KakaoLoginController.java b/src/main/java/stackpot/stackpot/web/controller/KakaoLoginController.java deleted file mode 100644 index 059f59b5..00000000 --- a/src/main/java/stackpot/stackpot/web/controller/KakaoLoginController.java +++ /dev/null @@ -1,34 +0,0 @@ -//package stackpot.stackpot.web.controller; -// -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.http.HttpStatus; -//import org.springframework.http.ResponseEntity; -//import org.springframework.web.bind.annotation.GetMapping; -//import org.springframework.web.bind.annotation.RequestMapping; -//import org.springframework.web.bind.annotation.RequestParam; -//import org.springframework.web.bind.annotation.RestController; -//import stackpot.stackpot.converter.UserConverter; -//import stackpot.stackpot.service.KakaoService; -//import stackpot.stackpot.web.dto.KakaoUserInfoResponseDto; -// -//import static com.mysql.cj.conf.PropertyKey.logger; -// -//@Slf4j -//@RestController -//@RequiredArgsConstructor -//@RequestMapping("") -//public class KakaoLoginController { -// private final KakaoService kakaoService; -// -// @GetMapping("/users/oauth/kakao") -// public ResponseEntity callback(@RequestParam("code") String code) { -// -// log.info("Authorization code: {}", code); // 인증 코드 확인 -// String accessToken = kakaoService.getAccessTokenFromKakao(code); -// KakaoUserInfoResponseDto userInfo = kakaoService.getUserInfo(accessToken); -// -// String email = userInfo.getKakaoAccount().getEmail();// 이메일 가져오기 -// return ResponseEntity.ok(userInfo); -// } -//} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/controller/MyPotController.java b/src/main/java/stackpot/stackpot/web/controller/MyPotController.java index 59ef30cf..fd9c925d 100644 --- a/src/main/java/stackpot/stackpot/web/controller/MyPotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/MyPotController.java @@ -55,4 +55,40 @@ public ResponseEntity>> updateMyTodos( return ResponseEntity.ok(ApiResponse.onSuccess(response)); } + + @Operation(summary = "[미완성] mypotTask 불러오기 API") + @GetMapping("/my-pots/{pot_id}/tasks") + public ResponseEntity getPotTask(@PathVariable("pot_id") Long potId) { + + return null; + } + + @Operation(summary = "mypotTask 상세보기 API") + @GetMapping("/my-pots/{pot_id}/tasks/{task_id}") + public ResponseEntity getPotDetailTask(@PathVariable("pot_id") Long potId, @PathVariable("task_id") Long taskId) { + + return null; + } + + @Operation(summary = "mypotTask 수정하기 API") + @PostMapping("/my-pots/{pot_id}/tasks/{task_id}") + public ResponseEntity modifyPotTask(@PathVariable("pot_id") Long potId, @PathVariable("task_id") Long taskId) { // 바디 추가 + + return null; + } + + @Operation(summary = "mypotTask 삭제하기 API") + @PostMapping("/my-pots/{pot_id}/tasks/{task_id}") //deletet??? + public ResponseEntity deletetPotTask(@PathVariable("pot_id") Long potId, @PathVariable("task_id") Long taskId) { + + return null; + } + + @Operation(summary = "mypotTask 생성하기 API") + @PostMapping("/my-pots/{pot_id}/tasks") + public ResponseEntity createPotTask(@PathVariable("pot_id") Long potId) { // 바디 추가 + + return null; + } + } From 1faffa1783e475a3897cedbbe3214af809a2d012 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Sun, 26 Jan 2025 18:06:42 +0900 Subject: [PATCH 64/76] =?UTF-8?q?[FEAT]=20my=20pot=20task=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/TaskboardConverter.java | 18 +++++ .../converter/TaskboardConverterImpl.java | 52 +++++++++++++++ .../stackpot/repository/TaskRepository.java | 10 +++ .../repository/TaskboardRepository.java | 8 +++ .../stackpot/service/MyPotService.java | 2 + .../stackpot/service/MyPotServiceImpl.java | 48 +++++++++++++- .../web/controller/MyPotController.java | 66 ++++++++++--------- .../stackpot/web/dto/MyPotTaskRequestDto.java | 24 +++++++ .../web/dto/MyPotTaskResponseDto.java | 38 +++++++++++ 9 files changed, 234 insertions(+), 32 deletions(-) create mode 100644 src/main/java/stackpot/stackpot/converter/TaskboardConverter.java create mode 100644 src/main/java/stackpot/stackpot/converter/TaskboardConverterImpl.java create mode 100644 src/main/java/stackpot/stackpot/repository/TaskRepository.java create mode 100644 src/main/java/stackpot/stackpot/repository/TaskboardRepository.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/MyPotTaskRequestDto.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/MyPotTaskResponseDto.java diff --git a/src/main/java/stackpot/stackpot/converter/TaskboardConverter.java b/src/main/java/stackpot/stackpot/converter/TaskboardConverter.java new file mode 100644 index 00000000..647f472f --- /dev/null +++ b/src/main/java/stackpot/stackpot/converter/TaskboardConverter.java @@ -0,0 +1,18 @@ +package stackpot.stackpot.converter; + +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.Taskboard; +import stackpot.stackpot.domain.mapping.PotMember; +import stackpot.stackpot.domain.mapping.Task; +import stackpot.stackpot.repository.TaskRepository; +import stackpot.stackpot.web.dto.MyPotTaskRequestDto; +import stackpot.stackpot.web.dto.MyPotTaskResponseDto; + +import java.util.List; + +public interface TaskboardConverter { + Taskboard toTaskboard(Pot pot , MyPotTaskRequestDto.create requset); + MyPotTaskResponseDto toDTO(Taskboard taskboard); + List toParticipantDtoList(List participants); + MyPotTaskResponseDto.Participant toParticipantDto(PotMember participant); +} diff --git a/src/main/java/stackpot/stackpot/converter/TaskboardConverterImpl.java b/src/main/java/stackpot/stackpot/converter/TaskboardConverterImpl.java new file mode 100644 index 00000000..6c4c8575 --- /dev/null +++ b/src/main/java/stackpot/stackpot/converter/TaskboardConverterImpl.java @@ -0,0 +1,52 @@ +package stackpot.stackpot.converter; + +import org.springframework.stereotype.Component; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.Taskboard; +import stackpot.stackpot.domain.mapping.PotMember; +import stackpot.stackpot.web.dto.MyPotTaskRequestDto; +import stackpot.stackpot.web.dto.MyPotTaskResponseDto; + +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class TaskboardConverterImpl implements TaskboardConverter{ + @Override + public Taskboard toTaskboard(Pot pot, MyPotTaskRequestDto.create requset) { + return Taskboard.builder() + .title(requset.getTitle()) + .description(requset.getDescription()) + .endDate(requset.getDeadline()) + .startDate(requset.getDeadline()) + .status(requset.getTaskboardStatus()) + .pot(pot) + .build(); + } + @Override + public MyPotTaskResponseDto toDTO(Taskboard taskboard) { + return MyPotTaskResponseDto.builder() + .taskboardId(taskboard.getTaskboardId()) + .title(taskboard.getTitle()) + .description(taskboard.getDescription()) + .status(taskboard.getStatus()) + .build(); + } + @Override + + public List toParticipantDtoList(List participants) { + return participants.stream() + .map(this::toParticipantDto) + .collect(Collectors.toList()); + } + + @Override + public MyPotTaskResponseDto.Participant toParticipantDto(PotMember participant) { + return new MyPotTaskResponseDto.Participant( + participant.getUser().getId(), + participant.getPotMemberId(), + participant.getUser().getNickname(), + participant.getUser().getEmail() + ); + } +} diff --git a/src/main/java/stackpot/stackpot/repository/TaskRepository.java b/src/main/java/stackpot/stackpot/repository/TaskRepository.java new file mode 100644 index 00000000..25b3eb60 --- /dev/null +++ b/src/main/java/stackpot/stackpot/repository/TaskRepository.java @@ -0,0 +1,10 @@ +package stackpot.stackpot.repository; + +import org.aspectj.apache.bcel.generic.LOOKUPSWITCH; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import stackpot.stackpot.domain.mapping.Task; + +@Repository +public interface TaskRepository extends JpaRepository { +} diff --git a/src/main/java/stackpot/stackpot/repository/TaskboardRepository.java b/src/main/java/stackpot/stackpot/repository/TaskboardRepository.java new file mode 100644 index 00000000..a4a99702 --- /dev/null +++ b/src/main/java/stackpot/stackpot/repository/TaskboardRepository.java @@ -0,0 +1,8 @@ +package stackpot.stackpot.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import stackpot.stackpot.domain.Taskboard; +@Repository +public interface TaskboardRepository extends JpaRepository { +} diff --git a/src/main/java/stackpot/stackpot/service/MyPotService.java b/src/main/java/stackpot/stackpot/service/MyPotService.java index 099b9566..ba851899 100644 --- a/src/main/java/stackpot/stackpot/service/MyPotService.java +++ b/src/main/java/stackpot/stackpot/service/MyPotService.java @@ -1,6 +1,7 @@ package stackpot.stackpot.service; import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.Taskboard; import stackpot.stackpot.domain.mapping.UserTodo; import stackpot.stackpot.web.dto.*; @@ -22,4 +23,5 @@ public interface MyPotService { List completeTodo(Long potId, Long todoId); + MyPotTaskResponseDto creatTask(Long potId, MyPotTaskRequestDto.create request); } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java b/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java index ac27faee..3e1fc6ec 100644 --- a/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java @@ -6,13 +6,19 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import stackpot.stackpot.converter.PotConverter; +import stackpot.stackpot.converter.TaskboardConverter; import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.Taskboard; import stackpot.stackpot.domain.User; -import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.domain.enums.TodoStatus; +import stackpot.stackpot.domain.mapping.PotMember; +import stackpot.stackpot.domain.mapping.Task; import stackpot.stackpot.domain.mapping.UserTodo; +import stackpot.stackpot.repository.PotMemberRepository; import stackpot.stackpot.repository.PotRepository.MyPotRepository; import stackpot.stackpot.repository.PotRepository.PotRepository; +import stackpot.stackpot.repository.TaskRepository; +import stackpot.stackpot.repository.TaskboardRepository; import stackpot.stackpot.repository.UserRepository.UserRepository; import stackpot.stackpot.web.dto.*; @@ -27,6 +33,10 @@ public class MyPotServiceImpl implements MyPotService { private final MyPotRepository myPotRepository; private final UserRepository userRepository; private final PotConverter potConverter; + private final TaskboardConverter taskboardConverter; + private final TaskboardRepository taskboardRepository; + private final PotMemberRepository potMemberRepository; + private final TaskRepository taskRepository; @Override @@ -272,6 +282,40 @@ public List completeTodo(Long potId, Long todoId) { .collect(Collectors.toList()); } + + @Override + public MyPotTaskResponseDto creatTask(Long potId, MyPotTaskRequestDto.create request) { + + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + + Taskboard taskboard = taskboardConverter.toTaskboard(pot, request); + taskboardRepository.save(taskboard); + + List participants = potMemberRepository.findAllById(request.getParticipants()); + + if (participants.isEmpty()) { + throw new IllegalArgumentException("유효한 참가자를 찾을 수 없습니다. 요청된 ID를 확인해주세요."); + } + + List tasks = participants.stream() + .map(participant -> Task.builder() + .taskboard(taskboard) + .potMember(participant) + .build()) + .collect(Collectors.toList()); + + taskRepository.saveAll(tasks); + + List participantDtos = taskboardConverter.toParticipantDtoList(participants); + + MyPotTaskResponseDto response = taskboardConverter.toDTO(taskboard); + response.setParticipants(participantDtos); + + + return response; + } + private MyPotResponseDTO.OngoingPotsDetail convertToOngoingPotDetail(Pot pot) { List potMembers = pot.getPotMembers().stream() .map(member -> PotMemberResponseDTO.builder() @@ -303,4 +347,6 @@ private String getVegetableNameByRole(String role) { } + + } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/controller/MyPotController.java b/src/main/java/stackpot/stackpot/web/controller/MyPotController.java index 93d79e6f..2a6314e7 100644 --- a/src/main/java/stackpot/stackpot/web/controller/MyPotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/MyPotController.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -64,40 +65,43 @@ public ResponseEntity>> updateMyTodos( return ResponseEntity.ok(ApiResponse.onSuccess(response)); } - - @Operation(summary = "[미완성] mypotTask 불러오기 API") - @GetMapping("/my-pots/{pot_id}/tasks") - public ResponseEntity getPotTask(@PathVariable("pot_id") Long potId) { - - return null; - } - - @Operation(summary = "mypotTask 상세보기 API") - @GetMapping("/my-pots/{pot_id}/tasks/{task_id}") - public ResponseEntity getPotDetailTask(@PathVariable("pot_id") Long potId, @PathVariable("task_id") Long taskId) { - - return null; - } - - @Operation(summary = "mypotTask 수정하기 API") - @PostMapping("/my-pots/{pot_id}/tasks/{task_id}") - public ResponseEntity modifyPotTask(@PathVariable("pot_id") Long potId, @PathVariable("task_id") Long taskId) { // 바디 추가 - - return null; - } - - @Operation(summary = "mypotTask 삭제하기 API") - @PostMapping("/my-pots/{pot_id}/tasks/{task_id}") //deletet??? - public ResponseEntity deletetPotTask(@PathVariable("pot_id") Long potId, @PathVariable("task_id") Long taskId) { - - return null; - } - +// +// @Operation(summary = "[미완성] mypotTask 불러오기 API") +// @GetMapping("/my-pots/{pot_id}/tasks") +// public ResponseEntity getPotTask(@PathVariable("pot_id") Long potId) { +// +// return null; +// } +// +// @Operation(summary = "mypotTask 상세보기 API") +// @GetMapping("/my-pots/{pot_id}/tasks/{task_id}") +// public ResponseEntity getPotDetailTask(@PathVariable("pot_id") Long potId, @PathVariable("task_id") Long taskId) { +// +// return null; +// } +// +// @Operation(summary = "mypotTask 수정하기 API") +// @PostMapping("/my-pots/{pot_id}/tasks/{task_id}") +// public ResponseEntity modifyPotTask(@PathVariable("pot_id") Long potId, @PathVariable("task_id") Long taskId) { // 바디 추가 +// +// return null; +// } +// +// @Operation(summary = "mypotTask 삭제하기 API") +// @PostMapping("/my-pots/{pot_id}/tasks/{task_id}") //deletet??? +// public ResponseEntity deletetPotTask(@PathVariable("pot_id") Long potId, @PathVariable("task_id") Long taskId) { +// +// return null; +// } +// @Operation(summary = "mypotTask 생성하기 API") @PostMapping("/my-pots/{pot_id}/tasks") - public ResponseEntity createPotTask(@PathVariable("pot_id") Long potId) { // 바디 추가 + public ResponseEntity> createPotTask(@PathVariable("pot_id") Long potId, + @Valid @RequestBody MyPotTaskRequestDto.create request) { + MyPotTaskResponseDto reaponse = myPotService.creatTask(potId, request); + - return null; + return ResponseEntity.ok(ApiResponse.onSuccess(reaponse)); } @Operation(summary = "Todo 완료 API", description = "todo의 status를 COMPLETED로 변경합니다.") diff --git a/src/main/java/stackpot/stackpot/web/dto/MyPotTaskRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/MyPotTaskRequestDto.java new file mode 100644 index 00000000..96d29ca1 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/MyPotTaskRequestDto.java @@ -0,0 +1,24 @@ +package stackpot.stackpot.web.dto; + +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import stackpot.stackpot.domain.enums.TaskboardStatus; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +public class MyPotTaskRequestDto { + @Data + @Getter + @NoArgsConstructor + public static class create{ + private String title; + private LocalDateTime deadline; + private TaskboardStatus taskboardStatus; + private String description; + private List participants; + } + +} diff --git a/src/main/java/stackpot/stackpot/web/dto/MyPotTaskResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/MyPotTaskResponseDto.java new file mode 100644 index 00000000..af802a97 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/MyPotTaskResponseDto.java @@ -0,0 +1,38 @@ +package stackpot.stackpot.web.dto; + +import jakarta.persistence.*; +import lombok.*; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.enums.TaskboardStatus; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +@Builder +@Getter +@Setter +public class MyPotTaskResponseDto { + private Long taskboardId; + private String title; + private String description; + private TaskboardStatus status; + private LocalDateTime endDate; + private Pot pot; + private List participants; + + + @Getter + @Setter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Participant { + private Long userId; + private Long potMemberId; + private String nickName; + private String email; + } + +} From 8811ca55934cdb9c9448b860532f376196dda7ea Mon Sep 17 00:00:00 2001 From: starday119 Date: Sun, 26 Jan 2025 23:26:35 +0900 Subject: [PATCH 65/76] =?UTF-8?q?[FEAT]:=20=ED=8C=9F/=ED=94=BC=EB=93=9C=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stackpot/converter/FeedConverter.java | 5 +- .../stackpot/converter/FeedConverterImpl.java | 37 ++++++++- .../stackpot/converter/PotConverter.java | 2 + .../stackpot/converter/PotConverterImpl.java | 43 ++++++++++ .../PotMemberConverterImpl.java | 5 +- .../FeedRepository/FeedRepository.java | 7 +- .../PotRepository/PotRepository.java | 19 +++-- .../PotApplicationServiceImpl.java | 8 ++ .../stackpot/stackpot/service/PotService.java | 4 +- .../stackpot/service/PotServiceImpl.java | 20 +++-- .../stackpot/service/SearchService.java | 11 +++ .../stackpot/service/SearchServiceImpl.java | 43 ++++++++++ .../service/UserCommandServiceImpl.java | 5 +- .../web/controller/PotController.java | 23 +++--- .../web/controller/SearchController.java | 81 +++++++++++++++++++ .../stackpot/web/dto/CursorPageResponse.java | 19 +++++ .../web/dto/FeedSearchResponseDto.java | 18 +++++ .../web/dto/PotSearchResponseDto.java | 20 +++++ .../stackpot/web/dto/UserRequestDto.java | 2 +- 19 files changed, 328 insertions(+), 44 deletions(-) create mode 100644 src/main/java/stackpot/stackpot/service/SearchService.java create mode 100644 src/main/java/stackpot/stackpot/service/SearchServiceImpl.java create mode 100644 src/main/java/stackpot/stackpot/web/controller/SearchController.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/CursorPageResponse.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/FeedSearchResponseDto.java create mode 100644 src/main/java/stackpot/stackpot/web/dto/PotSearchResponseDto.java diff --git a/src/main/java/stackpot/stackpot/converter/FeedConverter.java b/src/main/java/stackpot/stackpot/converter/FeedConverter.java index 39fd895c..68932b4c 100644 --- a/src/main/java/stackpot/stackpot/converter/FeedConverter.java +++ b/src/main/java/stackpot/stackpot/converter/FeedConverter.java @@ -4,11 +4,10 @@ import stackpot.stackpot.domain.Feed; import stackpot.stackpot.web.dto.FeedRequestDto; import stackpot.stackpot.web.dto.FeedResponseDto; - -import java.util.List; +import stackpot.stackpot.web.dto.FeedSearchResponseDto; public interface FeedConverter { FeedResponseDto.FeedDto feedDto(Feed feed, long popularity, long likeCount); Feed toFeed(FeedRequestDto.createDto request); - + FeedSearchResponseDto toSearchDto(Feed feed); } diff --git a/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java b/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java index 07f1d13c..d220b753 100644 --- a/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/FeedConverterImpl.java @@ -1,18 +1,22 @@ package stackpot.stackpot.converter; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.repository.FeedLikeRepository; import stackpot.stackpot.web.dto.FeedRequestDto; import stackpot.stackpot.web.dto.FeedResponseDto; +import stackpot.stackpot.web.dto.FeedSearchResponseDto; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; - -import static org.apache.tomcat.util.http.FastHttpDateFormat.formatDate; - +@RequiredArgsConstructor @Component public class FeedConverterImpl implements FeedConverter{ + private final FeedLikeRepository feedLikeRepository; + + @Override public FeedResponseDto.FeedDto feedDto(Feed feed, long popularity, long likeCount) { return FeedResponseDto.FeedDto.builder() @@ -43,4 +47,31 @@ private String formatLocalDateTime(LocalDateTime dateTime) { return (dateTime != null) ? dateTime.format(formatter) : "날짜 없음"; } + + public FeedSearchResponseDto toSearchDto(Feed feed) { + Long likeCount = feedLikeRepository.countByFeed(feed); // 좋아요 개수 조회 + // 역할 이름 매핑 (유효한 역할만 처리) + String roleName = feed.getUser().getRole() != null ? feed.getUser().getRole().name() : "멤버"; + String nicknameWithRole = feed.getUser().getNickname() + " " + mapRoleName(roleName) ; + + return FeedSearchResponseDto.builder() + .feedId(feed.getFeedId()) + .title(feed.getTitle()) + .content(feed.getContent()) + .creatorNickname(nicknameWithRole) // 닉네임과 역할 포함 + .creatorRole(roleName) // 역할 이름 + .createdAt(formatLocalDateTime(feed.getCreatedAt())) // 시간 포맷 적용 + .likeCount(likeCount) // 좋아요 개수 포함 + .build(); + } + + private String mapRoleName(String roleName) { + return switch (roleName) { + case "BACKEND" -> "양파"; + case "FRONTEND" -> "버섯"; + case "DESIGN" -> "브로콜리"; + case "PLANNING" -> "당근"; + default -> "멤버"; + }; + } } diff --git a/src/main/java/stackpot/stackpot/converter/PotConverter.java b/src/main/java/stackpot/stackpot/converter/PotConverter.java index 903b1a4e..750157af 100644 --- a/src/main/java/stackpot/stackpot/converter/PotConverter.java +++ b/src/main/java/stackpot/stackpot/converter/PotConverter.java @@ -6,6 +6,7 @@ import stackpot.stackpot.web.dto.CompletedPotResponseDto; import stackpot.stackpot.web.dto.PotRequestDto; import stackpot.stackpot.web.dto.PotResponseDto; +import stackpot.stackpot.web.dto.PotSearchResponseDto; import java.util.List; import java.util.Map; @@ -14,4 +15,5 @@ public interface PotConverter { Pot toEntity(PotRequestDto dto,User user); CompletedPotResponseDto toCompletedPotResponseDto(Pot pot, Map roleCounts); PotResponseDto toDto(Pot entity, List recruitmentDetails); + PotSearchResponseDto toSearchDto(Pot pot); } diff --git a/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java b/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java index 326edd80..bb750dd1 100644 --- a/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/PotConverterImpl.java @@ -85,4 +85,47 @@ public CompletedPotResponseDto toCompletedPotResponseDto(Pot pot, Map rd.getRecruitmentRole() != null) + .map(rd -> rd.getRecruitmentRole().name()) + .collect(Collectors.joining(", ")) + : "없음" // 기본값 설정 + ) + .recruitmentDeadline(pot.getRecruitmentDeadline()) + .build(); + } + + + private String mapRoleName(String roleName) { + return switch (roleName) { + case "BACKEND" -> "양파"; + case "FRONTEND" -> "버섯"; + case "DESIGN" -> "브로콜리"; + case "PLANNING" -> "당근"; + default -> "멤버"; + }; + } } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java b/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java index ccf6582c..b8ae7f26 100644 --- a/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/PotMemberConverter/PotMemberConverterImpl.java @@ -3,11 +3,8 @@ import org.springframework.stereotype.Component; import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.User; -import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.domain.mapping.PotApplication; import stackpot.stackpot.domain.mapping.PotMember; -import stackpot.stackpot.web.dto.PotAllMemRequestDto; -import stackpot.stackpot.web.dto.PotApplicationRequestDto; import stackpot.stackpot.web.dto.PotMemberAppealResponseDto; @Component @@ -29,7 +26,7 @@ public PotMember toEntity(User user, Pot pot, PotApplication application, Boolea public PotMemberAppealResponseDto toDto(PotMember entity) { String roleName = entity.getRoleName() != null ? entity.getRoleName().name() : "멤버"; - String nicknameWithRole = entity.getUser().getNickname() + roleName ; + String nicknameWithRole = entity.getUser().getNickname() + " "+mapRoleName(roleName) ; return PotMemberAppealResponseDto.builder() .potMemberId(entity.getPotMemberId()) diff --git a/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java b/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java index 4f69b9af..f377ea79 100644 --- a/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java +++ b/src/main/java/stackpot/stackpot/repository/FeedRepository/FeedRepository.java @@ -1,20 +1,16 @@ package stackpot.stackpot.repository.FeedRepository; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import stackpot.stackpot.domain.Feed; -import stackpot.stackpot.domain.User; import stackpot.stackpot.domain.enums.Category; -import stackpot.stackpot.domain.enums.Role; -import stackpot.stackpot.domain.mapping.FeedLike; - import java.time.LocalDateTime; import java.util.List; -import java.util.Optional; @Repository public interface FeedRepository extends JpaRepository { @@ -41,5 +37,6 @@ List findFeeds( Pageable pageable); List findByUser_Id(Long userId); + Page findByTitleContainingOrContentContainingOrderByCreatedAtDesc(String titleKeyword, String contentKeyword, Pageable pageable); } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java b/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java index a708e685..c947d4ab 100644 --- a/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java +++ b/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java @@ -1,7 +1,6 @@ package stackpot.stackpot.repository.PotRepository; - import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -9,8 +8,6 @@ import org.springframework.data.repository.query.Param; import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.enums.Role; -import stackpot.stackpot.domain.mapping.PotApplication; -import stackpot.stackpot.domain.mapping.UserTodo; import java.util.List; import java.util.Optional; @@ -24,7 +21,17 @@ public interface PotRepository extends JpaRepository { List findByPotMembers_UserIdAndPotStatus(Long userId, String status); List findByUserIdAndPotStatus(Long userId, String status); - @Query("SELECT p FROM Pot p WHERE p.potStatus = 'COMPLETED' AND (p.user.id = :userId OR p.potId IN " + - "(SELECT pm.pot.potId FROM PotMember pm WHERE pm.user.id = :userId))") - List findCompletedPotsByCreatorOrMember(@Param("userId") Long userId); + @Query("SELECT p FROM Pot p " + + "WHERE LOWER(p.potName) LIKE LOWER(CONCAT('%', :keyword, '%')) " + + "OR LOWER(p.potContent) LIKE LOWER(CONCAT('%', :keyword, '%')) " + + "ORDER BY p.createdAt DESC") + Page searchByKeyword(@Param("keyword") String keyword, Pageable pageable); + + + @Query("SELECT p FROM Pot p WHERE p.potStatus = 'COMPLETED' " + + "AND (p.user.id = :userId OR p.potId IN " + + "(SELECT pm.pot.potId FROM PotMember pm WHERE pm.user.id = :userId)) " + + "AND (:cursor IS NULL OR p.potId < :cursor) " + + "ORDER BY p.potId DESC") + List findCompletedPotsByCursor(@Param("userId") Long userId, @Param("cursor") Long cursor, @Param("size") int size); } diff --git a/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java index f0d2ce99..5ab5e4f0 100644 --- a/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotApplicationService/PotApplicationServiceImpl.java @@ -80,12 +80,20 @@ public List getApplicantsByPotId(Long potId) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String email = authentication.getName(); + // 사용자 정보 조회 + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); // 팟 조회 Pot pot = potRepository.findById(potId) .orElseThrow(() -> new IllegalArgumentException("해당 팟을 찾을 수 없습니다.")); + // 소유자 확인 + if (!pot.getUser().equals(user)) { + throw new IllegalArgumentException("해당 팟 지원자 목록을 볼 수 있는 권한이 없습니다."); + } + // 지원자 목록 조회 List applications = potApplicationRepository.findByPot_PotId(potId); diff --git a/src/main/java/stackpot/stackpot/service/PotService.java b/src/main/java/stackpot/stackpot/service/PotService.java index b45963d8..8aba9dcf 100644 --- a/src/main/java/stackpot/stackpot/service/PotService.java +++ b/src/main/java/stackpot/stackpot/service/PotService.java @@ -1,5 +1,7 @@ package stackpot.stackpot.service; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.web.dto.PotRequestDto; import stackpot.stackpot.web.dto.PotResponseDto; @@ -11,7 +13,7 @@ public interface PotService { PotResponseDto createPotWithRecruitments(PotRequestDto requestDto); PotResponseDto updatePotWithRecruitments(Long potId, PotRequestDto requestDto); - List getMyCompletedPots(); + CursorPageResponse getMyCompletedPots(Long cursor, int size); void deletePot(Long potId); //--------------- diff --git a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java index 911ced7f..5d9500e3 100644 --- a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java @@ -23,8 +23,6 @@ import stackpot.stackpot.repository.UserRepository.UserRepository; import stackpot.stackpot.web.dto.*; -import java.time.LocalDate; -import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -93,6 +91,7 @@ public PotResponseDto updatePotWithRecruitments(Long potId, PotRequestDto reques // 업데이트 로직 pot.updateFields(Map.of( "potName", requestDto.getPotName(), +// "potStartDate", requestDto.getPotStartDate(), "potEndDate", requestDto.getPotEndDate(), "potDuration", requestDto.getPotDuration(), "potLan", requestDto.getPotLan(), @@ -122,7 +121,7 @@ public PotResponseDto updatePotWithRecruitments(Long potId, PotRequestDto reques @Transactional @Override - public List getMyCompletedPots() { + public CursorPageResponse getMyCompletedPots(Long cursor, int size) { // 현재 인증된 사용자 가져오기 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String email = authentication.getName(); @@ -131,11 +130,14 @@ public List getMyCompletedPots() { User user = userRepository.findByEmail(email) .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); - // 사용자가 생성하거나 참여한 COMPLETED 상태의 팟 가져오기 - List completedPots = potRepository.findCompletedPotsByCreatorOrMember(user.getId()); + // 사용자가 참여하거나 생성한 COMPLETED 상태의 팟 가져오기 + List pots = potRepository.findCompletedPotsByCursor(user.getId(), cursor, size + 1); - // 역할별 인원 조회 및 DTO 변환 - return completedPots.stream() + // 커서 및 데이터 반환 + List result = pots.size() > size ? pots.subList(0, size) : pots; + Long nextCursor = result.isEmpty() ? null : result.get(result.size() - 1).getPotId(); + + List content = result.stream() .map(pot -> { List roleCounts = potMemberRepository.findRoleCountsByPotId(pot.getPotId()); Map roleCountsMap = roleCounts.stream() @@ -146,6 +148,8 @@ public List getMyCompletedPots() { return potConverter.toCompletedPotResponseDto(pot, roleCountsMap); }) .collect(Collectors.toList()); + + return new CursorPageResponse<>(content, nextCursor, pots.size() > size); } @Transactional @@ -228,7 +232,7 @@ public ApplicantResponseDTO getPotDetails(Long potId) { return ApplicantResponseDTO.builder() .user(UserResponseDto.builder() - .nickname(pot.getUser().getNickname() + getVegetableNameByRole(String.valueOf(pot.getUser().getRole()))) + .nickname(pot.getUser().getNickname()) .role(pot.getUser().getRole()) .build()) .pot(potConverter.toDto(pot, pot.getRecruitmentDetails())) // 변환기 사용 diff --git a/src/main/java/stackpot/stackpot/service/SearchService.java b/src/main/java/stackpot/stackpot/service/SearchService.java new file mode 100644 index 00000000..b7473e44 --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/SearchService.java @@ -0,0 +1,11 @@ +package stackpot.stackpot.service; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import stackpot.stackpot.web.dto.FeedSearchResponseDto; +import stackpot.stackpot.web.dto.PotSearchResponseDto; + +public interface SearchService { + Page searchPots(String keyword, Pageable pageable); + Page searchFeeds(String keyword, Pageable pageable); +} diff --git a/src/main/java/stackpot/stackpot/service/SearchServiceImpl.java b/src/main/java/stackpot/stackpot/service/SearchServiceImpl.java new file mode 100644 index 00000000..101eacd9 --- /dev/null +++ b/src/main/java/stackpot/stackpot/service/SearchServiceImpl.java @@ -0,0 +1,43 @@ +package stackpot.stackpot.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import stackpot.stackpot.converter.FeedConverter; +import stackpot.stackpot.converter.PotConverter; +import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.domain.Pot; +import stackpot.stackpot.repository.FeedRepository.FeedRepository; +import stackpot.stackpot.repository.PotRepository.PotRepository; +import stackpot.stackpot.web.dto.FeedSearchResponseDto; +import stackpot.stackpot.web.dto.PotSearchResponseDto; + +@Service +@RequiredArgsConstructor +public class SearchServiceImpl implements SearchService { + + private final PotRepository potRepository; + private final FeedRepository feedRepository; + private final PotConverter potConverter; + private final FeedConverter feedConverter; + + @Override + @Transactional(readOnly = true) + public Page searchPots(String keyword, Pageable pageable) { + Page pots = potRepository.searchByKeyword(keyword, pageable); + return pots.map(potConverter::toSearchDto); + } + + @Override + @Transactional(readOnly = true) + public Page searchFeeds(String keyword, Pageable pageable) { + Page feeds = feedRepository.findByTitleContainingOrContentContainingOrderByCreatedAtDesc(keyword, keyword, pageable); + + // FeedConverter를 사용해 DTO 변환 및 좋아요 개수 포함 + return feeds.map(feedConverter::toSearchDto); + } + +} + diff --git a/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java b/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java index 8af4c705..60c4f0b4 100644 --- a/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java @@ -5,12 +5,10 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; -import stackpot.stackpot.converter.UserConverter; import stackpot.stackpot.converter.UserMypageConverter; import stackpot.stackpot.domain.Feed; import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.User; -import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.repository.FeedRepository.FeedRepository; import stackpot.stackpot.repository.PotRepository.PotRepository; import stackpot.stackpot.repository.UserRepository.UserRepository; @@ -19,7 +17,6 @@ import stackpot.stackpot.web.dto.UserResponseDto; import stackpot.stackpot.web.dto.UserUpdateRequestDto; -import java.util.Date; import java.util.List; import java.util.Map; @@ -67,7 +64,7 @@ private void updateUserData(User user, UserRequestDto.JoinDto request) { // 닉네임 user.setNickname(request.getNickname()); // 역할군 - user.setRole(Role.valueOf(String.valueOf(request.getRole()))); + user.setRole(request.getRole()); // 관심사 user.setInterest(request.getInterest()); //한줄 소개 diff --git a/src/main/java/stackpot/stackpot/web/controller/PotController.java b/src/main/java/stackpot/stackpot/web/controller/PotController.java index 6b2e7c40..0ae06a3f 100644 --- a/src/main/java/stackpot/stackpot/web/controller/PotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/PotController.java @@ -1,23 +1,20 @@ package stackpot.stackpot.web.controller; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; - import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import stackpot.stackpot.apiPayload.ApiResponse; import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.repository.PotRepository.PotRepository; -import stackpot.stackpot.service.PotServiceImpl; -import stackpot.stackpot.web.dto.PotRequestDto; -import stackpot.stackpot.web.dto.PotResponseDto; -import stackpot.stackpot.apiPayload.ApiResponse; import stackpot.stackpot.service.PotService; +import stackpot.stackpot.service.PotServiceImpl; import stackpot.stackpot.web.dto.*; import java.util.HashMap; @@ -73,11 +70,19 @@ public ResponseEntity deletePot(@PathVariable("pot_id") Long potId) { return ResponseEntity.noContent().build(); } @GetMapping("/completed") - @Operation(summary = "나의 끓인 팟 정보 가져오기", description = "potStatus가 COMPLETED인 팟의 목록을 가져옵니다.") - public ResponseEntity>> getMyCompletedPots() { - List response = potService.getMyCompletedPots(); + @Operation(summary = "나의 끓인 팟 정보 가져오기", description = "potStatus가 COMPLETED인 팟의 목록을 커서 기반 페이지네이션으로 가져옵니다.", + parameters = { + @Parameter(name = "cursor", description = "현재 페이지의 마지막 potId 값", example = "10"), + @Parameter(name = "size", description = "한 페이지에 가져올 데이터 개수", example = "3") + }) + public ResponseEntity>> getMyCompletedPots( + @RequestParam(value = "cursor", required = false) Long cursor, + @RequestParam(value = "size", defaultValue = "3") int size) { + CursorPageResponse response = potService.getMyCompletedPots(cursor, size); return ResponseEntity.ok(ApiResponse.onSuccess(response)); } + + //------------------- @Operation( diff --git a/src/main/java/stackpot/stackpot/web/controller/SearchController.java b/src/main/java/stackpot/stackpot/web/controller/SearchController.java new file mode 100644 index 00000000..854ff6c5 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/controller/SearchController.java @@ -0,0 +1,81 @@ +package stackpot.stackpot.web.controller; + + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import stackpot.stackpot.apiPayload.ApiResponse; +import stackpot.stackpot.service.SearchService; +import stackpot.stackpot.web.dto.FeedSearchResponseDto; +import stackpot.stackpot.web.dto.PotSearchResponseDto; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/search") +@RequiredArgsConstructor +@Tag(name = "Search Management", description = "검색 API") +public class SearchController { + + private final SearchService searchService; + + @GetMapping("/pots") + @Operation(summary = "팟 검색", description = "키워드로 팟 이름 및 내용을 검색합니다.", + parameters = { + @Parameter(name = "keyword", description = "검색 키워드", example = "JAVA"), + + }) + public ResponseEntity>> searchPots( + @RequestParam(required = false, defaultValue = "") String keyword, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + + // 기본값으로 Pageable 생성 + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + + // 키워드가 없는 경우 예외 처리 또는 전체 조회 처리 +// if (keyword.trim().isEmpty()) { +// return ResponseEntity.badRequest() +// .body(ApiResponse.onError("400", "검색 키워드를 입력해주세요.")); +// } + + // 서비스 호출 및 검색 수행 + Page response = searchService.searchPots(keyword, pageable); + return ResponseEntity.ok(ApiResponse.onSuccess(response)); + } + + + @Operation(summary = "피드 검색", description = "키워드로 피드 제목 및 내용을 검색합니다.", + parameters = { + @Parameter(name = "keyword", description = "검색 키워드", example = "Spring"), + @Parameter(name = "page", description = "요청 페이지 번호 (0부터 시작)", example = "0"), + @Parameter(name = "size", description = "한 페이지에 가져올 데이터 개수", example = "10") + }) + @GetMapping("/feeds") + public ResponseEntity>> searchFeeds( + @RequestParam("keyword") String keyword, + @RequestParam(defaultValue = "0") Integer page, + @RequestParam(defaultValue = "10") Integer size) { + + Page feedPage = searchService.searchFeeds(keyword, PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt"))); + + Map response = new HashMap<>(); + response.put("feeds", feedPage.getContent()); + response.put("totalPages", feedPage.getTotalPages()); + response.put("currentPage", feedPage.getNumber()); + response.put("totalElements", feedPage.getTotalElements()); + + return ResponseEntity.ok(ApiResponse.onSuccess(response)); + } +} diff --git a/src/main/java/stackpot/stackpot/web/dto/CursorPageResponse.java b/src/main/java/stackpot/stackpot/web/dto/CursorPageResponse.java new file mode 100644 index 00000000..b4aa3fad --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/CursorPageResponse.java @@ -0,0 +1,19 @@ +package stackpot.stackpot.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class CursorPageResponse { + + private List content; // 데이터 내용 + private Long nextCursor; // 다음 페이지를 가져올 커서 값 + private boolean hasMore; // 다음 데이터 존재 여부 +} diff --git a/src/main/java/stackpot/stackpot/web/dto/FeedSearchResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/FeedSearchResponseDto.java new file mode 100644 index 00000000..92787cb0 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/FeedSearchResponseDto.java @@ -0,0 +1,18 @@ +package stackpot.stackpot.web.dto; + +import lombok.*; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FeedSearchResponseDto { + private Long feedId; + private String title; + private String content; + private String creatorNickname; + private String creatorRole; + private String createdAt; + private Long likeCount; +} diff --git a/src/main/java/stackpot/stackpot/web/dto/PotSearchResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/PotSearchResponseDto.java new file mode 100644 index 00000000..9d2ae5b6 --- /dev/null +++ b/src/main/java/stackpot/stackpot/web/dto/PotSearchResponseDto.java @@ -0,0 +1,20 @@ +package stackpot.stackpot.web.dto; + +import lombok.*; + +import java.time.LocalDate; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PotSearchResponseDto { + private Long potId; + private String potName; + private String potContent; + private String creatorNickname; + private String creatorRole; + private String recruitmentPart; + private LocalDate recruitmentDeadline; +} diff --git a/src/main/java/stackpot/stackpot/web/dto/UserRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/UserRequestDto.java index 0ab6a6ca..2b7e085c 100644 --- a/src/main/java/stackpot/stackpot/web/dto/UserRequestDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/UserRequestDto.java @@ -15,7 +15,7 @@ public class UserRequestDto { @Setter @NoArgsConstructor public static class JoinDto { - @NotBlank(message = "Role은 공백일 수 없습니다.") + Role role; @NotBlank(message = "Interest는 공백일 수 없습니다.") String interest; From 551956e81e57bac55f260b313b1f74cc82f5cdef Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Mon, 27 Jan 2025 00:59:12 +0900 Subject: [PATCH 66/76] =?UTF-8?q?[FEAT]=20my=20pot=20task=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20/=20=EC=82=AD=EC=A0=9C=20/=20=EC=9E=90=EC=84=B8?= =?UTF-8?q?=ED=9E=88=20=EB=B3=B4=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/TaskboardConverterImpl.java | 1 + .../stackpot/stackpot/domain/Taskboard.java | 1 + .../stackpot/repository/TaskRepository.java | 5 ++ .../stackpot/service/MyPotService.java | 3 + .../stackpot/service/MyPotServiceImpl.java | 88 ++++++++++++++++--- .../web/controller/MyPotController.java | 73 ++++++++------- .../web/dto/MyPotTaskResponseDto.java | 2 +- 7 files changed, 129 insertions(+), 44 deletions(-) diff --git a/src/main/java/stackpot/stackpot/converter/TaskboardConverterImpl.java b/src/main/java/stackpot/stackpot/converter/TaskboardConverterImpl.java index 6c4c8575..99bbb07a 100644 --- a/src/main/java/stackpot/stackpot/converter/TaskboardConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/TaskboardConverterImpl.java @@ -30,6 +30,7 @@ public MyPotTaskResponseDto toDTO(Taskboard taskboard) { .title(taskboard.getTitle()) .description(taskboard.getDescription()) .status(taskboard.getStatus()) + .potId(taskboard.getPot().getPotId()) .build(); } @Override diff --git a/src/main/java/stackpot/stackpot/domain/Taskboard.java b/src/main/java/stackpot/stackpot/domain/Taskboard.java index 3364a538..4a12daaf 100644 --- a/src/main/java/stackpot/stackpot/domain/Taskboard.java +++ b/src/main/java/stackpot/stackpot/domain/Taskboard.java @@ -9,6 +9,7 @@ @Entity @Getter +@Setter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor diff --git a/src/main/java/stackpot/stackpot/repository/TaskRepository.java b/src/main/java/stackpot/stackpot/repository/TaskRepository.java index 25b3eb60..e35e468c 100644 --- a/src/main/java/stackpot/stackpot/repository/TaskRepository.java +++ b/src/main/java/stackpot/stackpot/repository/TaskRepository.java @@ -3,8 +3,13 @@ import org.aspectj.apache.bcel.generic.LOOKUPSWITCH; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import stackpot.stackpot.domain.Taskboard; import stackpot.stackpot.domain.mapping.Task; +import java.util.List; + @Repository public interface TaskRepository extends JpaRepository { + List findByTaskboard(Taskboard taskboard); + } diff --git a/src/main/java/stackpot/stackpot/service/MyPotService.java b/src/main/java/stackpot/stackpot/service/MyPotService.java index ba851899..2e6ce480 100644 --- a/src/main/java/stackpot/stackpot/service/MyPotService.java +++ b/src/main/java/stackpot/stackpot/service/MyPotService.java @@ -24,4 +24,7 @@ public interface MyPotService { List completeTodo(Long potId, Long todoId); MyPotTaskResponseDto creatTask(Long potId, MyPotTaskRequestDto.create request); + MyPotTaskResponseDto viewDetailTask(Long taskId); + MyPotTaskResponseDto modfiyTask(Long taskId, MyPotTaskRequestDto.create request); + void deleteTaskboard(Long potId, Long taskboardId); } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java b/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java index 3e1fc6ec..734df7a0 100644 --- a/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java @@ -10,6 +10,7 @@ import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.Taskboard; import stackpot.stackpot.domain.User; +import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.domain.enums.TodoStatus; import stackpot.stackpot.domain.mapping.PotMember; import stackpot.stackpot.domain.mapping.Task; @@ -297,21 +298,21 @@ public MyPotTaskResponseDto creatTask(Long potId, MyPotTaskRequestDto.create req if (participants.isEmpty()) { throw new IllegalArgumentException("유효한 참가자를 찾을 수 없습니다. 요청된 ID를 확인해주세요."); } - - List tasks = participants.stream() - .map(participant -> Task.builder() - .taskboard(taskboard) - .potMember(participant) - .build()) - .collect(Collectors.toList()); - - taskRepository.saveAll(tasks); - + createAndSaveTasks(taskboard, participants); List participantDtos = taskboardConverter.toParticipantDtoList(participants); MyPotTaskResponseDto response = taskboardConverter.toDTO(taskboard); response.setParticipants(participantDtos); + return response; + } + + @Override + public MyPotTaskResponseDto viewDetailTask(Long taskboardId) { + Taskboard taskboard = taskboardRepository.findById(taskboardId) + .orElseThrow(() -> new IllegalArgumentException("Taskboard not found with id: " + taskboardId)); + + MyPotTaskResponseDto response = taskboardConverter.toDTO(taskboard); return response; } @@ -335,6 +336,50 @@ private MyPotResponseDTO.OngoingPotsDetail convertToOngoingPotDetail(Pot pot) { .build(); } + + @Override + public MyPotTaskResponseDto modfiyTask(Long taskId, MyPotTaskRequestDto.create request) { + + Taskboard taskboard = taskboardRepository.findById(taskId) + .orElseThrow(() -> new IllegalArgumentException("Taskboard not found with id: " + taskId)); + + updateUserData(taskboard, request); + + List participants = new ArrayList<>(); + if (request.getParticipants() != null && !request.getParticipants().isEmpty()) { + participants = potMemberRepository.findAllById(request.getParticipants()); + if (participants.isEmpty()) { + throw new IllegalArgumentException("유효한 참가자를 찾을 수 없습니다. 요청된 ID를 확인해주세요."); + } + } + createAndSaveTasks(taskboard, participants); + List participantDtos = taskboardConverter.toParticipantDtoList(participants); + + MyPotTaskResponseDto response = taskboardConverter.toDTO(taskboard); + response.setParticipants(participantDtos); + + return response; + } + + @Transactional + @Override + public void deleteTaskboard(Long potId, Long taskboardId) { + Taskboard taskboard = taskboardRepository.findById(taskboardId) + .orElseThrow(() -> new IllegalArgumentException("Taskboard not found with id: " + taskboardId)); + +// // Taskboard가 해당 Pot에 속해 있는지 확인 +// if (!taskboard.getPot().getId().equals(potId)) { +// throw new IllegalArgumentException("The taskboard does not belong to the specified pot."); +// } + + // Taskboard에 연결된 Task 삭제 + List tasks = taskRepository.findByTaskboard(taskboard); + taskRepository.deleteAll(tasks); + + // Taskboard 삭제 + taskboardRepository.delete(taskboard); + } + // 역할에 따른 채소명을 반환하는 메서드 private String getVegetableNameByRole(String role) { Map roleToVegetableMap = Map.of( @@ -346,7 +391,30 @@ private String getVegetableNameByRole(String role) { return roleToVegetableMap.getOrDefault(role, "알 수 없음"); } + private void updateUserData(Taskboard taskboard, MyPotTaskRequestDto.create request) { + if(request.getTitle() !=null){ + taskboard.setTitle(request.getTitle()); + } + if(request.getDescription()!=null){ + taskboard.setDescription(request.getDescription()); + } + if(request.getDeadline()!=null){ + taskboard.setEndDate(request.getDeadline()); + } + if(request.getTaskboardStatus()!=null){ + taskboard.setStatus(request.getTaskboardStatus()); + } + } + private List createAndSaveTasks(Taskboard taskboard, List participants) { + List tasks = participants.stream() + .map(participant -> Task.builder() + .taskboard(taskboard) + .potMember(participant) + .build()) + .collect(Collectors.toList()); + return taskRepository.saveAll(tasks); + } } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/controller/MyPotController.java b/src/main/java/stackpot/stackpot/web/controller/MyPotController.java index 2a6314e7..9d8bb841 100644 --- a/src/main/java/stackpot/stackpot/web/controller/MyPotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/MyPotController.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import stackpot.stackpot.apiPayload.ApiResponse; @@ -65,45 +66,51 @@ public ResponseEntity>> updateMyTodos( return ResponseEntity.ok(ApiResponse.onSuccess(response)); } -// -// @Operation(summary = "[미완성] mypotTask 불러오기 API") -// @GetMapping("/my-pots/{pot_id}/tasks") -// public ResponseEntity getPotTask(@PathVariable("pot_id") Long potId) { -// -// return null; -// } -// -// @Operation(summary = "mypotTask 상세보기 API") -// @GetMapping("/my-pots/{pot_id}/tasks/{task_id}") -// public ResponseEntity getPotDetailTask(@PathVariable("pot_id") Long potId, @PathVariable("task_id") Long taskId) { -// -// return null; -// } -// -// @Operation(summary = "mypotTask 수정하기 API") -// @PostMapping("/my-pots/{pot_id}/tasks/{task_id}") -// public ResponseEntity modifyPotTask(@PathVariable("pot_id") Long potId, @PathVariable("task_id") Long taskId) { // 바디 추가 -// -// return null; -// } -// -// @Operation(summary = "mypotTask 삭제하기 API") -// @PostMapping("/my-pots/{pot_id}/tasks/{task_id}") //deletet??? -// public ResponseEntity deletetPotTask(@PathVariable("pot_id") Long potId, @PathVariable("task_id") Long taskId) { -// -// return null; -// } -// - @Operation(summary = "mypotTask 생성하기 API") + @Operation(summary = "mypotTask 생성 API") @PostMapping("/my-pots/{pot_id}/tasks") public ResponseEntity> createPotTask(@PathVariable("pot_id") Long potId, - @Valid @RequestBody MyPotTaskRequestDto.create request) { - MyPotTaskResponseDto reaponse = myPotService.creatTask(potId, request); + @RequestBody @Valid MyPotTaskRequestDto.create request) { + MyPotTaskResponseDto response = myPotService.creatTask(potId, request); + return ResponseEntity.ok(ApiResponse.onSuccess(response)); + } + @Operation(summary = "mypotTask 상세보기 API") + @GetMapping("/my-pots/{pot_id}/tasks/{task_id}") + public ResponseEntity> getPotDetailTask(@PathVariable("pot_id") Long potId, @PathVariable("task_id") Long taskId) { + + MyPotTaskResponseDto response = myPotService.viewDetailTask(taskId); - return ResponseEntity.ok(ApiResponse.onSuccess(reaponse)); + return ResponseEntity.ok(ApiResponse.onSuccess(response)); } + @Operation(summary = "[미완성] mypotTask 불러오기 API") + @GetMapping("/my-pots/{pot_id}/tasks") + public ResponseEntity getPotTask(@PathVariable("pot_id") Long potId) { + + return null; + } + + @Operation(summary = "mypotTask 수정 API") + @PatchMapping("/my-pots/{pot_id}/tasks/{task_id}") + public ResponseEntity> modifyPotTask(@PathVariable("task_id") Long taskId, @RequestBody @Valid MyPotTaskRequestDto.create request) { + MyPotTaskResponseDto response = myPotService.modfiyTask(taskId, request); + + return ResponseEntity.ok(ApiResponse.onSuccess(response)); + } + + @Operation(summary = "mypotTask 삭제 API") + @DeleteMapping("/my-pots/{pot_id}/tasks/{task_id}") + public ResponseEntity deletetPotTask(@PathVariable("pot_id") Long potId, @PathVariable("task_id") Long taskId) { + try { + myPotService.deleteTaskboard(potId, taskId); + return ResponseEntity.ok(ApiResponse.onSuccess("할일이 삭제되었습니다.")); + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage()); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body("An error occurred while deleting the taskboard and associated tasks."); + } + } @Operation(summary = "Todo 완료 API", description = "todo의 status를 COMPLETED로 변경합니다.") @PatchMapping("/my-pots/{pot_id}/todos/{todo_id}") public ResponseEntity>> completeTodo( diff --git a/src/main/java/stackpot/stackpot/web/dto/MyPotTaskResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/MyPotTaskResponseDto.java index af802a97..28b72b33 100644 --- a/src/main/java/stackpot/stackpot/web/dto/MyPotTaskResponseDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/MyPotTaskResponseDto.java @@ -19,7 +19,7 @@ public class MyPotTaskResponseDto { private String description; private TaskboardStatus status; private LocalDateTime endDate; - private Pot pot; + private Long potId; private List participants; From bd6ea47f308bbb69b094d61dd65cf7c09aabd489 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Mon, 27 Jan 2025 01:14:56 +0900 Subject: [PATCH 67/76] =?UTF-8?q?[FEAT]=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20api=20=EA=B5=AC=ED=98=84=20(=EC=A7=88?= =?UTF-8?q?=EB=AC=B8=20=EC=88=98=EC=A0=95=20=ED=95=84=EC=9A=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stackpot/service/UserCommandService.java | 2 ++ .../service/UserCommandServiceImpl.java | 17 +++++++++++++---- .../stackpot/web/controller/UserController.java | 9 +++++---- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/main/java/stackpot/stackpot/service/UserCommandService.java b/src/main/java/stackpot/stackpot/service/UserCommandService.java index 8c291476..0abf5efc 100644 --- a/src/main/java/stackpot/stackpot/service/UserCommandService.java +++ b/src/main/java/stackpot/stackpot/service/UserCommandService.java @@ -15,4 +15,6 @@ public interface UserCommandService { UserMypageResponseDto getUserMypage(Long userId); UserResponseDto updateUserProfile(UserUpdateRequestDto requestDto); + + String createNickname(); } diff --git a/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java b/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java index 8af4c705..2ac0d853 100644 --- a/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java @@ -2,6 +2,7 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; @@ -14,10 +15,7 @@ import stackpot.stackpot.repository.FeedRepository.FeedRepository; import stackpot.stackpot.repository.PotRepository.PotRepository; import stackpot.stackpot.repository.UserRepository.UserRepository; -import stackpot.stackpot.web.dto.UserMypageResponseDto; -import stackpot.stackpot.web.dto.UserRequestDto; -import stackpot.stackpot.web.dto.UserResponseDto; -import stackpot.stackpot.web.dto.UserUpdateRequestDto; +import stackpot.stackpot.web.dto.*; import java.util.Date; import java.util.List; @@ -31,6 +29,7 @@ public class UserCommandServiceImpl implements UserCommandService{ private final PotRepository potRepository; private final FeedRepository feedRepository; private final UserMypageConverter userMypageConverter; + private final PotSummarizationService potSummarizationService; @Override @Transactional @@ -143,6 +142,16 @@ public UserResponseDto updateUserProfile(UserUpdateRequestDto requestDto) { .build(); } + @Override + public String createNickname() { + String prompt = "“재미있고 긍정적인 형용사와 명사를 결합한 문구를 만들어 주세요. 형식은 ‘형용사 명사’입니다" + + "예를 들어, ‘잘 자라는 양파’, ‘힘이 넘치는 버섯’ 같은 느낌으로 작성해 주세요.”"; + + String nickname = potSummarizationService.summarizeText(prompt, 15); + + return nickname; + } + // 역할에 따른 채소명을 반환하는 메서드 private String getVegetableNameByRole(String role) { Map roleToVegetableMap = Map.of( diff --git a/src/main/java/stackpot/stackpot/web/controller/UserController.java b/src/main/java/stackpot/stackpot/web/controller/UserController.java index 38108c80..8138d90a 100644 --- a/src/main/java/stackpot/stackpot/web/controller/UserController.java +++ b/src/main/java/stackpot/stackpot/web/controller/UserController.java @@ -81,12 +81,13 @@ public ResponseEntity signup(@Valid @RequestBody UserRequestDto.JoinDto reque return ResponseEntity.status(HttpStatus.CREATED).body(UserConverter.toDto(user)); } - @Operation(summary = "닉네임 생성") + @Operation(summary = "닉네임 생성 [질문 수정 필요]") @GetMapping("/nickname") - public ResponseEntity nickname(){ + public ResponseEntity> nickname(){ + String nickName = userCommandService.createNickname(); - return null; -// return ResponseEntity.ok(); + + return ResponseEntity.ok(ApiResponse.onSuccess(nickName)); } @Operation(summary = "마이페이지 사용자 정보 조회 API") From a54eeb95b6f64f2a762b5655d6d5cdf273bdc2a4 Mon Sep 17 00:00:00 2001 From: starday119 Date: Mon, 27 Jan 2025 01:20:00 +0900 Subject: [PATCH 68/76] [MOD]: gradle modified and cursor error fix --- build.gradle | 14 +------- .../apiPayload/exception/ExceptionAdvice.java | 6 ++-- .../PotRepository/PotRepository.java | 2 +- .../stackpot/service/MyPotServiceImpl.java | 5 +-- .../PotMemberServiceImpl.java | 2 ++ .../stackpot/service/PotServiceImpl.java | 35 +++++++++++++++++-- .../web/controller/MyPotController.java | 16 +++++++-- .../web/controller/SearchController.java | 35 ++++++++++++++++--- 8 files changed, 88 insertions(+), 27 deletions(-) diff --git a/build.gradle b/build.gradle index 6e9b38e5..c9385619 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '3.4.1' + id 'org.springframework.boot' version '3.3.6' id 'io.spring.dependency-management' version '1.1.7' } @@ -59,8 +59,6 @@ dependencies { // Test Dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' implementation 'mysql:mysql-connector-java:8.0.33' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.security:spring-security-crypto' @@ -72,21 +70,11 @@ dependencies { //Email implementation 'org.springframework.boot:spring-boot-starter-mail' - - implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' implementation 'mysql:mysql-connector-java:8.0.33' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.security:spring-security-crypto' - //Email - implementation 'org.springframework.boot:spring-boot-starter-mail' - - implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' - implementation 'mysql:mysql-connector-java:8.0.33' - implementation 'org.springframework.boot:spring-boot-starter-security' - implementation 'org.springframework.security:spring-security-crypto' } tasks.named('test') { diff --git a/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java b/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java index 781afea5..c759d57b 100644 --- a/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java +++ b/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java @@ -2,6 +2,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; @@ -16,15 +17,14 @@ import stackpot.stackpot.apiPayload.ApiResponse; import stackpot.stackpot.apiPayload.code.ErrorReasonDTO; import stackpot.stackpot.apiPayload.code.status.ErrorStatus; +import stackpot.stackpot.web.controller.*; import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; -import lombok.extern.slf4j.Slf4j; - @Slf4j -//@RestControllerAdvice(annotations = {RestController.class}) +@RestControllerAdvice(annotations = {RestController.class}, basePackageClasses = {UserController.class, PotController.class, FeedController.class, MyPotController.class, PotApplicationController.class, PotMemberController.class}) public class ExceptionAdvice extends ResponseEntityExceptionHandler { diff --git a/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java b/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java index c947d4ab..694c696f 100644 --- a/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java +++ b/src/main/java/stackpot/stackpot/repository/PotRepository/PotRepository.java @@ -33,5 +33,5 @@ public interface PotRepository extends JpaRepository { "(SELECT pm.pot.potId FROM PotMember pm WHERE pm.user.id = :userId)) " + "AND (:cursor IS NULL OR p.potId < :cursor) " + "ORDER BY p.potId DESC") - List findCompletedPotsByCursor(@Param("userId") Long userId, @Param("cursor") Long cursor, @Param("size") int size); + List findCompletedPotsByCursor(@Param("userId") Long userId, @Param("cursor") Long cursor); } diff --git a/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java b/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java index ac27faee..58ba69f7 100644 --- a/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java @@ -8,7 +8,6 @@ import stackpot.stackpot.converter.PotConverter; import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.User; -import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.domain.enums.TodoStatus; import stackpot.stackpot.domain.mapping.UserTodo; import stackpot.stackpot.repository.PotRepository.MyPotRepository; @@ -16,7 +15,9 @@ import stackpot.stackpot.repository.UserRepository.UserRepository; import stackpot.stackpot.web.dto.*; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @Service diff --git a/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberServiceImpl.java index e6c1eede..a5985131 100644 --- a/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotMemberService/PotMemberServiceImpl.java @@ -106,4 +106,6 @@ public void validateIsOwner(Long potId) { throw new AccessDeniedException("해당 작업은 팟 생성자만 수행할 수 있습니다."); } } + + } diff --git a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java index 5d9500e3..c362efbd 100644 --- a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java @@ -131,7 +131,7 @@ public CursorPageResponse getMyCompletedPots(Long curso .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); // 사용자가 참여하거나 생성한 COMPLETED 상태의 팟 가져오기 - List pots = potRepository.findCompletedPotsByCursor(user.getId(), cursor, size + 1); + List pots = potRepository.findCompletedPotsByCursor(user.getId(), cursor); // 커서 및 데이터 반환 List result = pots.size() > size ? pots.subList(0, size) : pots; @@ -152,7 +152,7 @@ public CursorPageResponse getMyCompletedPots(Long curso return new CursorPageResponse<>(content, nextCursor, pots.size() > size); } - @Transactional + @Transactional public void deletePot(Long potId) { // 인증 정보에서 사용자 이메일 가져오기 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); @@ -450,4 +450,35 @@ private MyPotResponseDTO.OngoingPotsDetail convertToOngoingPotDetail(Pot pot) { .potMembers(potMembers) .build(); } +// @Transactional +// public void removeMemberFromPot(Long potId, Long userId) { +// // 현재 로그인한 사용자 확인 +// Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); +// String email = authentication.getName(); +// +// // 현재 로그인한 사용자 조회 +// User currentUser = userRepository.findByEmail(email) +// .orElseThrow(() -> new IllegalArgumentException("현재 사용자를 찾을 수 없습니다.")); +// +// // 팟 존재 여부 확인 +// Pot pot = potRepository.findById(potId) +// .orElseThrow(() -> new IllegalArgumentException("해당 팟을 찾을 수 없습니다.")); +// +// // 팟 생성자인지 확인 +// if (!pot.getUser().getId().equals(currentUser.getId())) { +// throw new IllegalStateException("해당 팟의 멤버를 삭제할 권한이 없습니다."); +// } +// +// // 사용자 존재 여부 확인 +// User user = userRepository.findById(userId) +// .orElseThrow(() -> new IllegalArgumentException("해당 사용자를 찾을 수 없습니다.")); +// +// // 팟 멤버 존재 여부 확인 +// PotMember member = potMemberRepository.findByPotAndUser(pot, user) +// .orElseThrow(() -> new IllegalArgumentException("해당 팟에 사용자가 존재하지 않습니다.")); +// +// // 팟 멤버 삭제 +// potMemberRepository.delete(member); +// } + } diff --git a/src/main/java/stackpot/stackpot/web/controller/MyPotController.java b/src/main/java/stackpot/stackpot/web/controller/MyPotController.java index 0f2fa460..40bd1e52 100644 --- a/src/main/java/stackpot/stackpot/web/controller/MyPotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/MyPotController.java @@ -8,7 +8,10 @@ import stackpot.stackpot.apiPayload.ApiResponse; import stackpot.stackpot.service.MyPotService; import stackpot.stackpot.service.PotService; -import stackpot.stackpot.web.dto.*; +import stackpot.stackpot.web.dto.MyPotResponseDTO; +import stackpot.stackpot.web.dto.MyPotTodoRequestDTO; +import stackpot.stackpot.web.dto.MyPotTodoResponseDTO; +import stackpot.stackpot.web.dto.MyPotTodoUpdateRequestDTO; import java.util.List; import java.util.Map; @@ -19,7 +22,7 @@ public class MyPotController { private final MyPotService myPotService; - + private final PotService potService; // 사용자가 만든 진행 중인 팟 조회 @Operation(summary = "사용자의 팟 목록 조회 API", description = "사용자가 생성했거나, 참여하고 있으며 진행 중(ONGOING)인 팟들 리스트를 조회합니다. \n") @@ -28,6 +31,15 @@ public ResponseEntity> response = myPotService.getMyOnGoingPots(); return ResponseEntity.ok(ApiResponse.onSuccess(response)); } +// @DeleteMapping("/{pot_id}/members/{user_id}") +// @Operation(summary = "팟에서 멤버 삭제", description = "팟 멤버가 본인의 팟을 삭제하면 팟 멤버에서 해당 사용자가 제거됩니다.") +// public ResponseEntity> removePotMember( +// @PathVariable("pot_id") Long potId, +// @PathVariable("user_id") Long userId) { +// +// potService.removeMemberFromPot(potId, userId); +// return ResponseEntity.ok(ApiResponse.onSuccess("팟 멤버가 성공적으로 삭제되었습니다.")); +// } // 팟에서의 투두 생성 @Operation( diff --git a/src/main/java/stackpot/stackpot/web/controller/SearchController.java b/src/main/java/stackpot/stackpot/web/controller/SearchController.java index 854ff6c5..c4e85c1c 100644 --- a/src/main/java/stackpot/stackpot/web/controller/SearchController.java +++ b/src/main/java/stackpot/stackpot/web/controller/SearchController.java @@ -9,6 +9,7 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -38,11 +39,9 @@ public class SearchController { }) public ResponseEntity>> searchPots( @RequestParam(required = false, defaultValue = "") String keyword, - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "10") int size) { + @PageableDefault(size = 3, sort = "createdAt") Pageable pageable){ + - // 기본값으로 Pageable 생성 - Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); // 키워드가 없는 경우 예외 처리 또는 전체 조회 처리 // if (keyword.trim().isEmpty()) { @@ -78,4 +77,32 @@ public ResponseEntity>> searchFeeds( return ResponseEntity.ok(ApiResponse.onSuccess(response)); } + + + @GetMapping + @Operation(summary = "통합 검색 API", description = "키워드를 기반으로 팟 또는 피드를 검색합니다.", + parameters = { + @Parameter(name = "type", description = "검색 타입 (pot: 팟 검색, feed: 피드 검색)", example = "pot"), + @Parameter(name = "keyword", description = "검색 키워드", example = "JAVA"), + @Parameter(name = "page", description = "페이지 번호", example = "0"), + @Parameter(name = "size", description = "페이지 크기", example = "10") + }) + public ResponseEntity>> search( + @RequestParam String type, + @RequestParam String keyword, + @PageableDefault(size = 10) Pageable pageable) { + + Page response; + if ("pot".equalsIgnoreCase(type)) { + response = searchService.searchPots(keyword, pageable); + } else if ("feed".equalsIgnoreCase(type)) { + response = searchService.searchFeeds(keyword, pageable); + } else { + throw new IllegalArgumentException("Invalid search type. Use 'pot' or 'feed'."); + } + + return ResponseEntity.ok(ApiResponse.onSuccess(response)); + } + + } From b4c8423e467a4e6add23cf39d45e351b7d52cb65 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Mon, 27 Jan 2025 01:29:02 +0900 Subject: [PATCH 69/76] [MERGE] dev --- build.gradle | 14 +------------- .../apiPayload/exception/ExceptionAdvice.java | 4 ++++ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/build.gradle b/build.gradle index 6e9b38e5..c9385619 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '3.4.1' + id 'org.springframework.boot' version '3.3.6' id 'io.spring.dependency-management' version '1.1.7' } @@ -59,8 +59,6 @@ dependencies { // Test Dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' implementation 'mysql:mysql-connector-java:8.0.33' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.security:spring-security-crypto' @@ -72,21 +70,11 @@ dependencies { //Email implementation 'org.springframework.boot:spring-boot-starter-mail' - - implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' implementation 'mysql:mysql-connector-java:8.0.33' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.security:spring-security-crypto' - //Email - implementation 'org.springframework.boot:spring-boot-starter-mail' - - implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' - implementation 'mysql:mysql-connector-java:8.0.33' - implementation 'org.springframework.boot:spring-boot-starter-security' - implementation 'org.springframework.security:spring-security-crypto' } tasks.named('test') { diff --git a/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java b/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java index 781afea5..d779b713 100644 --- a/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java +++ b/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java @@ -22,9 +22,13 @@ import java.util.Optional; import lombok.extern.slf4j.Slf4j; +import stackpot.stackpot.web.controller.*; + @Slf4j //@RestControllerAdvice(annotations = {RestController.class}) +@RestControllerAdvice(annotations = {RestController.class}, basePackageClasses = {UserController.class, PotController.class, FeedController.class, MyPotController.class, PotApplicationController.class, PotMemberController.class}) + public class ExceptionAdvice extends ResponseEntityExceptionHandler { From bed38a2c7d6f9c225bee8859718fc34bf0d4d1ee Mon Sep 17 00:00:00 2001 From: MiN <81948815+leesumin0526@users.noreply.github.com> Date: Mon, 27 Jan 2025 01:29:34 +0900 Subject: [PATCH 70/76] =?UTF-8?q?[Feat]=20=EC=97=90=EB=9F=AC=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20(#39)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FEAT/#3] Security 의존성 주입, SecurityConfig 파일 생성 * [FEAT/#3] SecurityConfig 파일 작성 * [MOD/#10] Domain들 연관 관계 및 필드명 수정 * [FIX] 팟 모집 기한 필드 PotRecruitmentDetails로 이동 & User 필드 수정 * [FIX] SecurityConfig 의존성 주입 * [feat/#12] 전체 팟 보기 api 구현 * [feat/#12] D-day와 모집분야별 필터링 구현 * [feat/#12] 팟 상세보기 API 구현 * [Merge] * [Feat/#12] Impl 구분, 필드명 통일 * [Feat/#12] Token 기준으로 수정 및 기능 개발 - 지원한 팟 목록 - 지원자 맘에 들어요 & 취소 - 지원자 맘에 들어요 목록 - 팟 전체 보기 (특정 모집 분야, 인기순) - 팟 상세 보기 * [Feat/#12] 내가 만든 팟 목록 * [Feat/#12] API 기능 개발중 * [Feat/#12] 전체 팟 기능 구현 완료 & 마이팟 리스트 반환 & todo 생성 구현 완료 * add git ignore * [feat/#12] API들 수정 * [feat/#12] API들 수정2 * [feat/#12] Todo 생성 API 수정 완료 * [feat/#12] api 설명 작성 중 * fix: resolve file tracking issue * [feat/#12] 전체 팟 기능 구현 완료 * [feat/#12] redirect url 수정 * [feat/#28] 에러 수정 * [feat/#28] roll 변경 중 * [feat/#28] Role 처리 완료.. * [feat/#28] 전체 팟 보기 기능 수정 완료 * [feat/#28] 투두 새벽 3시에 자동 리셋 기능 구현 * [feat/#28] 팟 닉네임 처리 * [feat/#28] 팟 localdatetime 형식 통일 * [feat/#28] Dday 필드 삭제 * [feat/#28] todo 완료 api 구현 * [feat] 다른 사람 프로필 보기 API 구현 * [feat] 에러 수정 & 다른 사람 프로필 보기 API 구현 * [feat] 프로필 수정 API * [feat] 사용자의 팟 목록 조회 API * [feat] 사용자의 팟 목록 조회 API 날짜 형식 수정 * [feat] 다른 사람 프로필 보기 feed/pot 파람으로 구분 * [feat] User Response Dto에 userID 필드 추가 * [feat] 에러 처리 * [feat] 에러 처리 * gradle --- .../Validation/annotation/ValidRole.java | 17 +++ .../Validation/validator/RoleValidator.java | 22 ++++ .../apiPayload/code/status/ErrorStatus.java | 24 +++- .../apiPayload/exception/ExceptionAdvice.java | 9 ++ .../exception/handler/ApplicationHandler.java | 10 ++ .../exception/handler/EnumHandler.java | 10 ++ .../exception/handler/MemberHandler.java | 10 ++ .../exception/handler/PotHandler.java | 10 ++ .../exception/handler/RecruitmentHandler.java | 10 ++ .../stackpot/converter/UserConverter.java | 25 +++- .../converter/UserMypageConverter.java | 1 + .../domain/mapping/PotApplication.java | 3 +- .../stackpot/service/MyPotServiceImpl.java | 117 +++++++++++------- .../stackpot/service/PotServiceImpl.java | 108 +++++++--------- .../stackpot/service/UserCommandService.java | 2 +- .../service/UserCommandServiceImpl.java | 38 ++++-- .../web/controller/KakaoLoginController.java | 34 ----- .../web/controller/PotController.java | 30 ++--- .../web/controller/UserController.java | 8 +- .../stackpot/web/dto/PotAllMemRequestDto.java | 3 + .../web/dto/PotApplicationRequestDto.java | 4 + .../web/dto/PotApplicationResponseDto.java | 1 + .../web/dto/PotRecruitmentRequestDto.java | 3 + .../web/dto/PotRecruitmentResponseDto.java | 1 + .../web/dto/UserMypageResponseDto.java | 1 + .../stackpot/web/dto/UserResponseDto.java | 1 + .../web/dto/UserUpdateRequestDto.java | 3 + src/main/resources/application.yml | 2 +- 28 files changed, 324 insertions(+), 183 deletions(-) create mode 100644 src/main/java/stackpot/stackpot/Validation/annotation/ValidRole.java create mode 100644 src/main/java/stackpot/stackpot/Validation/validator/RoleValidator.java create mode 100644 src/main/java/stackpot/stackpot/apiPayload/exception/handler/ApplicationHandler.java create mode 100644 src/main/java/stackpot/stackpot/apiPayload/exception/handler/EnumHandler.java create mode 100644 src/main/java/stackpot/stackpot/apiPayload/exception/handler/MemberHandler.java create mode 100644 src/main/java/stackpot/stackpot/apiPayload/exception/handler/PotHandler.java create mode 100644 src/main/java/stackpot/stackpot/apiPayload/exception/handler/RecruitmentHandler.java delete mode 100644 src/main/java/stackpot/stackpot/web/controller/KakaoLoginController.java diff --git a/src/main/java/stackpot/stackpot/Validation/annotation/ValidRole.java b/src/main/java/stackpot/stackpot/Validation/annotation/ValidRole.java new file mode 100644 index 00000000..90789bbb --- /dev/null +++ b/src/main/java/stackpot/stackpot/Validation/annotation/ValidRole.java @@ -0,0 +1,17 @@ +package stackpot.stackpot.Validation.annotation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import stackpot.stackpot.Validation.validator.RoleValidator; + +import java.lang.annotation.*; + +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = RoleValidator.class) +@Documented +public @interface ValidRole { + String message() default "유효하지 않은 모집 역할입니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} diff --git a/src/main/java/stackpot/stackpot/Validation/validator/RoleValidator.java b/src/main/java/stackpot/stackpot/Validation/validator/RoleValidator.java new file mode 100644 index 00000000..59d59f10 --- /dev/null +++ b/src/main/java/stackpot/stackpot/Validation/validator/RoleValidator.java @@ -0,0 +1,22 @@ +package stackpot.stackpot.Validation.validator; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import stackpot.stackpot.Validation.annotation.ValidRole; +import stackpot.stackpot.domain.enums.Role; + +public class RoleValidator implements ConstraintValidator { + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + if (value == null || value.isEmpty()) { + return false; + } + try { + Role.valueOf(value.toUpperCase()); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } +} diff --git a/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java b/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java index 1993f063..a25af02d 100644 --- a/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java @@ -15,7 +15,29 @@ public enum ErrorStatus implements BaseErrorCode { _UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"COMMON401","인증이 필요합니다."), _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."), - MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBER4001", "등록된 사용자가 없습니다."),; + MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBER4004", "등록된 사용자가 없습니다."), + + // Pot 관련 에러 + POT_NOT_FOUND(HttpStatus.NOT_FOUND, "POT4004", "팟이 존재하지 않습니다."), + POT_FORBIDDEN(HttpStatus.FORBIDDEN, "POT4003", "팟 생성자가 아닙니다."), + + // 모집 관련 에러 + RECRUITMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "RECRUITMENT4004", "모집 내역이 없습니다."), + + // 지원 관련 에러 + APPLICATION_NOT_FOUND(HttpStatus.NOT_FOUND, "APPLICATION4004", "지원 내역이 없습니다."), + + // 페이지 관련 에러 + INVALID_PAGE(HttpStatus.BAD_REQUEST, "PAGE4000", "Page는 1이상입니다."), + + // 투두 관련 에러 코드 + USER_TODO_NOT_FOUND(HttpStatus.BAD_REQUEST,"TODO4004", "해당 Pot ID 및 Todo ID에 대한 투두를 찾을 수 없습니다."), + USER_TODO_UNAUTHORIZED(HttpStatus.FORBIDDEN,"TODO4003", "해당 투두에 대한 수정 권한이 없습니다."), + + // Enum 관련 에러 + INVALID_POT_STATUS(HttpStatus.BAD_REQUEST, "POT_STATUS4000", "Pot Status 형식이 올바르지 않습니다 (RECRUITING / ONGOING / COMPLETED)"), + INVALID_POT_MODE_OF_OPERATION(HttpStatus.BAD_REQUEST, "MODE_OF_OPERATION4000", "Pot ModeOfOperation 형식이 올바르지 않습니다 (ONLINE / OFFLINE / HYBRID)"), + INVALID_ROLE(HttpStatus.BAD_REQUEST, "ROLE4000", "Role 형식이 올바르지 않습니다 (FRONTEND / DESIGN / BACKEND / PLANNING)"); private final HttpStatus httpStatus; diff --git a/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java b/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java index c759d57b..c400b724 100644 --- a/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java +++ b/src/main/java/stackpot/stackpot/apiPayload/exception/ExceptionAdvice.java @@ -23,6 +23,10 @@ import java.util.Map; import java.util.Optional; +import lombok.extern.slf4j.Slf4j; +import stackpot.stackpot.domain.Feed; +import stackpot.stackpot.web.controller.*; + @Slf4j @RestControllerAdvice(annotations = {RestController.class}, basePackageClasses = {UserController.class, PotController.class, FeedController.class, MyPotController.class, PotApplicationController.class, PotMemberController.class}) public class ExceptionAdvice extends ResponseEntityExceptionHandler { @@ -66,6 +70,11 @@ public ResponseEntity onThrowException(GeneralException generalException, HttpSe return handleExceptionInternal(generalException,errorReasonHttpStatus,null,request); } + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgumentException(IllegalArgumentException e, WebRequest request) { + return handleExceptionInternalFalse(e, ErrorStatus.INVALID_ROLE, HttpHeaders.EMPTY, HttpStatus.BAD_REQUEST, request, e.getMessage()); + } + private ResponseEntity handleExceptionInternal(Exception e, ErrorReasonDTO reason, HttpHeaders headers, HttpServletRequest request) { diff --git a/src/main/java/stackpot/stackpot/apiPayload/exception/handler/ApplicationHandler.java b/src/main/java/stackpot/stackpot/apiPayload/exception/handler/ApplicationHandler.java new file mode 100644 index 00000000..3d908501 --- /dev/null +++ b/src/main/java/stackpot/stackpot/apiPayload/exception/handler/ApplicationHandler.java @@ -0,0 +1,10 @@ +package stackpot.stackpot.apiPayload.exception.handler; + +import stackpot.stackpot.apiPayload.code.BaseErrorCode; +import stackpot.stackpot.apiPayload.exception.GeneralException; + +public class ApplicationHandler extends GeneralException { + public ApplicationHandler(BaseErrorCode code) { + super(code); + } +} diff --git a/src/main/java/stackpot/stackpot/apiPayload/exception/handler/EnumHandler.java b/src/main/java/stackpot/stackpot/apiPayload/exception/handler/EnumHandler.java new file mode 100644 index 00000000..509673be --- /dev/null +++ b/src/main/java/stackpot/stackpot/apiPayload/exception/handler/EnumHandler.java @@ -0,0 +1,10 @@ +package stackpot.stackpot.apiPayload.exception.handler; + +import stackpot.stackpot.apiPayload.code.BaseErrorCode; +import stackpot.stackpot.apiPayload.exception.GeneralException; + +public class EnumHandler extends GeneralException { + public EnumHandler(BaseErrorCode code) { + super(code); + } +} diff --git a/src/main/java/stackpot/stackpot/apiPayload/exception/handler/MemberHandler.java b/src/main/java/stackpot/stackpot/apiPayload/exception/handler/MemberHandler.java new file mode 100644 index 00000000..86924cd9 --- /dev/null +++ b/src/main/java/stackpot/stackpot/apiPayload/exception/handler/MemberHandler.java @@ -0,0 +1,10 @@ +package stackpot.stackpot.apiPayload.exception.handler; + +import stackpot.stackpot.apiPayload.code.BaseErrorCode; +import stackpot.stackpot.apiPayload.exception.GeneralException; + +public class MemberHandler extends GeneralException { + public MemberHandler(BaseErrorCode code) { + super(code); + } +} diff --git a/src/main/java/stackpot/stackpot/apiPayload/exception/handler/PotHandler.java b/src/main/java/stackpot/stackpot/apiPayload/exception/handler/PotHandler.java new file mode 100644 index 00000000..ead48947 --- /dev/null +++ b/src/main/java/stackpot/stackpot/apiPayload/exception/handler/PotHandler.java @@ -0,0 +1,10 @@ +package stackpot.stackpot.apiPayload.exception.handler; + +import stackpot.stackpot.apiPayload.code.BaseErrorCode; +import stackpot.stackpot.apiPayload.exception.GeneralException; + +public class PotHandler extends GeneralException { + public PotHandler(BaseErrorCode code) { + super(code); + } +} diff --git a/src/main/java/stackpot/stackpot/apiPayload/exception/handler/RecruitmentHandler.java b/src/main/java/stackpot/stackpot/apiPayload/exception/handler/RecruitmentHandler.java new file mode 100644 index 00000000..d0b28ffe --- /dev/null +++ b/src/main/java/stackpot/stackpot/apiPayload/exception/handler/RecruitmentHandler.java @@ -0,0 +1,10 @@ +package stackpot.stackpot.apiPayload.exception.handler; + +import stackpot.stackpot.apiPayload.code.BaseErrorCode; +import stackpot.stackpot.apiPayload.exception.GeneralException; + +public class RecruitmentHandler extends GeneralException { + public RecruitmentHandler(BaseErrorCode code) { + super(code); + } +} diff --git a/src/main/java/stackpot/stackpot/converter/UserConverter.java b/src/main/java/stackpot/stackpot/converter/UserConverter.java index 820178e8..e32e565f 100644 --- a/src/main/java/stackpot/stackpot/converter/UserConverter.java +++ b/src/main/java/stackpot/stackpot/converter/UserConverter.java @@ -5,6 +5,8 @@ import stackpot.stackpot.web.dto.UserRequestDto; import stackpot.stackpot.web.dto.UserResponseDto; +import java.util.Map; + public class UserConverter { public static User toUser(UserRequestDto.JoinDto request) { @@ -18,14 +20,33 @@ public static User toUser(UserRequestDto.JoinDto request) { public static UserResponseDto toDto(User user) { + if (user.getId() == null) { + throw new IllegalStateException("User ID is null"); + } + + // 역할명을 변환하여 닉네임에 추가 + String roleName = user.getRole() != null ? user.getRole().name() : "멤버"; + String nicknameWithRole = user.getNickname() + " " + toDtoRole(roleName); + return UserResponseDto.builder() - .nickname(user.getNickname()) - .email(user.getEmail()) // 추가된 코드 + .id(user.getId()) // id 값이 제대로 설정되었는지 로그 확인 + .nickname(nicknameWithRole) + .email(user.getEmail()) .kakaoId(user.getKakaoId()) .role(user.getRole()) .interest(user.getInterest()) .userTemperature(user.getUserTemperature()) .build(); } + + public static String toDtoRole(String roleName) { + return switch (roleName) { + case "BACKEND" -> "양파"; + case "FRONTEND" -> "버섯"; + case "DESIGN" -> "브로콜리"; + case "PLANNING" -> "당근"; + default -> "멤버"; + }; + } } diff --git a/src/main/java/stackpot/stackpot/converter/UserMypageConverter.java b/src/main/java/stackpot/stackpot/converter/UserMypageConverter.java index 791a7087..4d3627a8 100644 --- a/src/main/java/stackpot/stackpot/converter/UserMypageConverter.java +++ b/src/main/java/stackpot/stackpot/converter/UserMypageConverter.java @@ -21,6 +21,7 @@ public class UserMypageConverter { public UserMypageResponseDto toDto(User user, List completedPots, List feeds) { return UserMypageResponseDto.builder() + .id(user.getId()) .email(user.getEmail()) .nickname(user.getNickname() + getVegetableNameByRole(user.getRole().name())) .role(user.getRole()) diff --git a/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java b/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java index a9726645..b7ca450d 100644 --- a/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java +++ b/src/main/java/stackpot/stackpot/domain/mapping/PotApplication.java @@ -31,7 +31,8 @@ public class PotApplication extends BaseEntity { private LocalDateTime appliedAt; @Setter - @Column(nullable = false, columnDefinition = "BOOLEAN DEFAULT false") + @Column(nullable = false) + @Builder.Default private Boolean liked = false; @Enumerated(EnumType.STRING) diff --git a/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java b/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java index 58ba69f7..549eb094 100644 --- a/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java @@ -5,6 +5,9 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import stackpot.stackpot.apiPayload.code.status.ErrorStatus; +import stackpot.stackpot.apiPayload.exception.handler.MemberHandler; +import stackpot.stackpot.apiPayload.exception.handler.PotHandler; import stackpot.stackpot.converter.PotConverter; import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.User; @@ -68,13 +71,18 @@ public List postTodo(Long potId, MyPotTodoRequestDTO reque Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String email = authentication.getName(); + // 사용자 정보 조회 User user = userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); - // 해당 Pot 존재 여부 확인 + // 팟 조회 Pot pot = potRepository.findById(potId) - .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + .orElseThrow(() -> new PotHandler(ErrorStatus.POT_NOT_FOUND)); + // 소유자 확인 + if (!pot.getUser().equals(user)) { + throw new PotHandler(ErrorStatus.POT_FORBIDDEN); + } // To-Do 생성 UserTodo userTodo = UserTodo.builder() @@ -95,14 +103,7 @@ public List postTodo(Long potId, MyPotTodoRequestDTO reque .entrySet().stream() .map(entry -> { // 해당 유저의 pot에서 potMember 역할 찾기 - String roleName = entry.getValue().stream() - .findFirst() - .flatMap(todo -> todo.getPot().getPotMembers().stream() - .filter(member -> member.getUser().equals(entry.getKey())) - .map(member -> member.getRoleName().name()) // ENUM -> String 변환 - .findFirst() - ) - .orElse("UNKNOWN"); // 기본값 설정 + String roleName = getUserRoleInPot(entry.getKey(), pot); return MyPotTodoResponseDTO.builder() .userNickname(entry.getKey().getNickname() + getVegetableNameByRole(roleName)) @@ -125,12 +126,18 @@ public List getTodo(Long potId) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String email = authentication.getName(); + // 사용자 정보 조회 User user = userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); - // 해당 Pot 존재 여부 확인 + // 팟 조회 Pot pot = potRepository.findById(potId) - .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + .orElseThrow(() -> new PotHandler(ErrorStatus.POT_NOT_FOUND)); + + // 소유자 확인 + if (!pot.getUser().equals(user)) { + throw new PotHandler(ErrorStatus.POT_FORBIDDEN); + } // 특정 팟의 모든 To-Do 조회 List potTodos = myPotRepository.findByPot_PotId(potId); @@ -141,11 +148,7 @@ public List getTodo(Long potId) { .entrySet().stream() .map(entry -> { // 해당 유저의 pot에서 potMember 역할 찾기 - String roleName = pot.getPotMembers().stream() - .filter(member -> member.getUser().equals(entry.getKey())) - .map(member -> member.getRoleName().name()) // Enum을 String으로 변환 - .findFirst() - .orElse("UNKNOWN"); // 기본값을 String으로 설정 + String roleName = getUserRoleInPot(entry.getKey(), pot); // 기본값을 String으로 설정 return MyPotTodoResponseDTO.builder() .userNickname(entry.getKey().getNickname() + getVegetableNameByRole(roleName)) @@ -169,12 +172,18 @@ public List updateTodos(Long potId, List new IllegalArgumentException("User not found with email: " + email)); + .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); - // 해당 Pot 존재 여부 확인 + // 팟 조회 Pot pot = potRepository.findById(potId) - .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + .orElseThrow(() -> new PotHandler(ErrorStatus.POT_NOT_FOUND)); + + // 소유자 확인 + if (!pot.getUser().equals(user)) { + throw new PotHandler(ErrorStatus.POT_FORBIDDEN); + } // 특정 팟에 속한 모든 투두 리스트 조회 (사용자별) List userTodos = myPotRepository.findByPotAndUser(pot, user); @@ -203,14 +212,7 @@ public List updateTodos(Long potId, List { // 해당 유저의 pot에서 potMember 역할 찾기 - String roleName = entry.getValue().stream() - .findFirst() - .flatMap(todo -> todo.getPot().getPotMembers().stream() - .filter(member -> member.getUser().equals(entry.getKey())) - .map(member -> member.getRoleName().name()) // ENUM -> String 변환 - .findFirst() - ) - .orElse("UNKNOWN"); // 기본값 설정 + String roleName = getUserRoleInPot(entry.getKey(), pot); return MyPotTodoResponseDTO.builder() .userNickname(entry.getKey().getNickname() + getVegetableNameByRole(roleName)) @@ -234,19 +236,20 @@ public List completeTodo(Long potId, Long todoId) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String email = authentication.getName(); + // 사용자 정보 조회 User user = userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); - // 해당 팟이 존재하는지 확인 + // 팟 조회 Pot pot = potRepository.findById(potId) - .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + .orElseThrow(() -> new PotHandler(ErrorStatus.POT_NOT_FOUND)); // 해당 투두가 존재하는지 확인 및 소유자 검증 UserTodo userTodo = myPotRepository.findByTodoIdAndPot_PotId(todoId, potId) - .orElseThrow(() -> new IllegalArgumentException("Todo not found for given potId and todoId")); + .orElseThrow(() -> new PotHandler(ErrorStatus.USER_TODO_NOT_FOUND)); if (!userTodo.getUser().equals(user)) { - throw new SecurityException("You are not authorized to update this todo"); + throw new PotHandler(ErrorStatus.USER_TODO_UNAUTHORIZED); } // To-Do 상태 업데이트 @@ -259,17 +262,23 @@ public List completeTodo(Long potId, Long todoId) { return potTodos.stream() .collect(Collectors.groupingBy(UserTodo::getUser)) .entrySet().stream() - .map(entry -> MyPotTodoResponseDTO.builder() - .userNickname(entry.getKey().getNickname()) - .userId(entry.getKey().getId()) - .todos(entry.getValue().stream() - .map(todo -> MyPotTodoResponseDTO.TodoDetailDTO.builder() - .todoId(todo.getTodoId()) - .content(todo.getContent()) - .status(todo.getStatus()) - .build()) - .collect(Collectors.toList())) - .build()) + .map(entry -> { + // 소유자인지 확인하고 적절한 역할 적용 + String roleName = getUserRoleInPot(entry.getKey(), pot); + String userNicknameWithRole = entry.getKey().getNickname() + getVegetableNameByRole(roleName); + + return MyPotTodoResponseDTO.builder() + .userNickname(userNicknameWithRole) + .userId(entry.getKey().getId()) + .todos(entry.getValue().stream() + .map(todo -> MyPotTodoResponseDTO.TodoDetailDTO.builder() + .todoId(todo.getTodoId()) + .content(todo.getContent()) + .status(todo.getStatus()) + .build()) + .collect(Collectors.toList())) + .build(); + }) .collect(Collectors.toList()); } @@ -284,7 +293,7 @@ private MyPotResponseDTO.OngoingPotsDetail convertToOngoingPotDetail(Pot pot) { return MyPotResponseDTO.OngoingPotsDetail.builder() .user(UserResponseDto.builder() - .nickname(pot.getUser().getNickname() + getVegetableNameByRole(String.valueOf(pot.getUser().getRole()))) + .nickname(pot.getUser().getNickname()) .role(pot.getUser().getRole()) .build()) .pot(potConverter.toDto(pot, pot.getRecruitmentDetails())) @@ -303,5 +312,19 @@ private String getVegetableNameByRole(String role) { return roleToVegetableMap.getOrDefault(role, "알 수 없음"); } + private String getUserRoleInPot(User user, Pot pot) { + if (pot.getUser().equals(user)) { + // 소유자인 경우, 사용자의 역할을 직접 가져옴 + return pot.getUser().getRole().name(); + } else { + // 참여자인 경우, potMember에서 역할을 가져옴 + return pot.getPotMembers().stream() + .filter(member -> member.getUser().equals(user)) + .map(member -> member.getRoleName().name()) // ENUM -> String 변환 + .findFirst() + .orElse("UNKNOWN"); // 기본값 설정 + } + } + } \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java index c362efbd..d82ebb2d 100644 --- a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java @@ -10,8 +10,13 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import stackpot.stackpot.apiPayload.code.status.ErrorStatus; +import stackpot.stackpot.apiPayload.exception.handler.ApplicationHandler; +import stackpot.stackpot.apiPayload.exception.handler.MemberHandler; +import stackpot.stackpot.apiPayload.exception.handler.PotHandler; import stackpot.stackpot.config.security.JwtTokenProvider; import stackpot.stackpot.converter.PotConverter; +import stackpot.stackpot.converter.UserConverter; import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.PotRecruitmentDetails; import stackpot.stackpot.domain.User; @@ -23,6 +28,8 @@ import stackpot.stackpot.repository.UserRepository.UserRepository; import stackpot.stackpot.web.dto.*; +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -46,7 +53,7 @@ public PotResponseDto createPotWithRecruitments(PotRequestDto requestDto) { // 사용자 정보 조회 User user = userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); // 팟 생성 Pot pot = potConverter.toEntity(requestDto, user); @@ -77,15 +84,15 @@ public PotResponseDto updatePotWithRecruitments(Long potId, PotRequestDto reques // 사용자 정보 조회 User user = userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); // 팟 조회 Pot pot = potRepository.findById(potId) - .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + .orElseThrow(() -> new PotHandler(ErrorStatus.POT_NOT_FOUND)); // 소유자 확인 if (!pot.getUser().equals(user)) { - throw new IllegalArgumentException("You do not have permission to update this pot."); + throw new PotHandler(ErrorStatus.POT_FORBIDDEN); } // 업데이트 로직 @@ -160,15 +167,15 @@ public void deletePot(Long potId) { // 사용자 정보 조회 User user = userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); // 팟 조회 Pot pot = potRepository.findById(potId) - .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + .orElseThrow(() -> new PotHandler(ErrorStatus.POT_NOT_FOUND)); // 팟 소유자 확인 if (!pot.getUser().equals(user)) { - throw new IllegalArgumentException("You do not have permission to delete this pot."); + throw new PotHandler(ErrorStatus.POT_FORBIDDEN); } // 모집 정보 삭제 @@ -197,10 +204,7 @@ public List getAllPots(Role role, Integer page, Int return potPage.getContent().stream() .map(pot -> PotAllResponseDTO.PotDetail.builder() - .user(UserResponseDto.builder() - .nickname(pot.getUser().getNickname() + getVegetableNameByRole(String.valueOf(pot.getUser().getRole()))) - .role(pot.getUser().getRole()) // ENUM → String 변환 - .build()) + .user(UserConverter.toDto(pot.getUser())) .pot(potConverter.toDto(pot, pot.getRecruitmentDetails())) // 변환기 사용 .build()) .collect(Collectors.toList()); @@ -210,7 +214,7 @@ public List getAllPots(Role role, Integer page, Int @Override public ApplicantResponseDTO getPotDetails(Long potId) { Pot pot = potRepository.findPotWithRecruitmentDetailsByPotId(potId) - .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + .orElseThrow(() -> new PotHandler(ErrorStatus.POT_NOT_FOUND)); // 지원자 정보를 DTO로 변환 List applicantDto = pot.getPotApplication().stream() @@ -221,20 +225,9 @@ public ApplicantResponseDTO getPotDetails(Long potId) { .build()) .collect(Collectors.toList()); - // 모집 정보를 DTO로 변환 - List recruitmentDetailsDto = pot.getRecruitmentDetails().stream() - .map(details -> PotRecruitmentResponseDto.builder() - .recruitmentId(details.getRecruitmentId()) - .recruitmentRole(String.valueOf(details.getRecruitmentRole())) - .recruitmentCount(details.getRecruitmentCount()) - .build()) - .collect(Collectors.toList()); return ApplicantResponseDTO.builder() - .user(UserResponseDto.builder() - .nickname(pot.getUser().getNickname()) - .role(pot.getUser().getRole()) - .build()) + .user(UserConverter.toDto(pot.getUser())) .pot(potConverter.toDto(pot, pot.getRecruitmentDetails())) // 변환기 사용 .applicant(applicantDto) .build(); @@ -247,14 +240,14 @@ public void patchLikes(Long potId, Long applicationId, Boolean liked) { String email = authentication.getName(); User user = userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); Pot pot = potRepository.findById(potId) - .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + .orElseThrow(() -> new PotHandler(ErrorStatus.POT_NOT_FOUND)); // 팟 생성자 확인 if (!pot.getUser().getId().equals(user.getId())) { - throw new SecurityException("Only the pot owner can modify likes."); + throw new PotHandler(ErrorStatus.POT_FORBIDDEN); } PotApplication application = pot.getPotApplication().stream() @@ -273,14 +266,14 @@ public List getLikedApplicants(Long potId) { String email = authentication.getName(); User user = userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); Pot pot = potRepository.findById(potId) - .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + .orElseThrow(() -> new PotHandler(ErrorStatus.POT_NOT_FOUND)); // 팟 생성자 확인 if (!pot.getUser().getId().equals(user.getId())) { - throw new SecurityException("Only the pot owner can view liked applicants."); + throw new PotHandler(ErrorStatus.POT_FORBIDDEN); } return pot.getPotApplication().stream() @@ -292,12 +285,6 @@ public List getLikedApplicants(Long potId) { .liked(app.getLiked()) .build()) .collect(Collectors.toList()); - /*pot role - 브로콜리 : 디자이너 - 당근 : 기획자 - 양파 : 백앤드 - 버섯 : 프론트앤드 - */ } private String getVegetableNameByRole(String role) { @@ -318,25 +305,22 @@ public List getAppliedPots() { String email = authentication.getName(); User user = userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); // 사용자가 지원한 팟 조회 List appliedPots = potRepository.findByPotApplication_User_Id(user.getId()); - - // Pot 리스트를 PotAllResponseDTO.PotDetail로 변환 - return appliedPots.stream().map(pot -> { - // 유저 정보를 DTO로 변환 - UserResponseDto userDto = UserResponseDto.builder() - .nickname(pot.getUser().getNickname()) - .role(pot.getUser().getRole()) - .build(); + if (appliedPots.isEmpty()) { + throw new ApplicationHandler(ErrorStatus.APPLICATION_NOT_FOUND); + } - return PotAllResponseDTO.PotDetail.builder() - .user(userDto) - .pot(potConverter.toDto(pot, pot.getRecruitmentDetails())) // 변환기 사용 - .build(); - }).collect(Collectors.toList()); + return appliedPots.stream() + .map(pot -> PotAllResponseDTO.PotDetail.builder() + .user(UserConverter.toDto(pot.getUser())) + .pot(potConverter.toDto(pot, pot.getRecruitmentDetails())) + .build() + ) + .collect(Collectors.toList()); } // 사용자가 만든 팟 조회 @@ -346,7 +330,7 @@ public List getMyPots() { String email = authentication.getName(); User user = userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); // 사용자가 만든 팟 조회 List myPots = potRepository.findByUserId(user.getId()); @@ -383,18 +367,18 @@ public void patchPotStatus(Long potId) { String email = authentication.getName(); User user = userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); Pot pot = potRepository.findById(potId) - .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + .orElseThrow(() -> new PotHandler(ErrorStatus.POT_NOT_FOUND)); // 팟 생성자 확인 if (!pot.getUser().getId().equals(user.getId())) { - throw new SecurityException("Only the pot owner can modify pot status."); + throw new PotHandler(ErrorStatus.POT_FORBIDDEN); } // 팟 상태를 "complete"으로 변경 - pot.setPotStatus("complete"); + pot.setPotStatus("COMPLETED"); // 변경된 상태 저장 potRepository.save(pot); @@ -403,7 +387,7 @@ public void patchPotStatus(Long potId) { @Override public PotSummaryResponseDTO getPotSummary(Long potId) { Pot pot = potRepository.findById(potId) - .orElseThrow(() -> new IllegalArgumentException("Pot not found with id: " + potId)); + .orElseThrow(() -> new PotHandler(ErrorStatus.POT_NOT_FOUND)); String prompt = "구인글에 내용을 우리 프로젝트를 소개하는 400자로 정리해줘. " + "기획 배경, 주요기능, 어떤 언어와 프레임워크 사용했는지 등등 구체적인게 들어있으면 더 좋아.\n" + @@ -420,10 +404,7 @@ public PotSummaryResponseDTO getPotSummary(Long potId) { private PotAllResponseDTO.PotDetail convertToPotDetail(Pot pot) { return PotAllResponseDTO.PotDetail.builder() - .user(UserResponseDto.builder() - .nickname(pot.getUser().getNickname() + getVegetableNameByRole(String.valueOf(pot.getUser().getRole()))) - .role(pot.getUser().getRole()) - .build()) + .user(UserConverter.toDto(pot.getUser())) .pot(potConverter.toDto(pot, pot.getRecruitmentDetails())) // 변환기 사용 .build(); } @@ -438,14 +419,9 @@ private MyPotResponseDTO.OngoingPotsDetail convertToOngoingPotDetail(Pot pot) { .build()) .collect(Collectors.toList()); - System.out.println("Recruitment Details: " + pot.getRecruitmentDetails()); - System.out.println("Pot Members: " + pot.getPotMembers()); return MyPotResponseDTO.OngoingPotsDetail.builder() - .user(UserResponseDto.builder() - .nickname(pot.getUser().getNickname() + getVegetableNameByRole(String.valueOf(pot.getUser().getRole()))) - .role(pot.getUser().getRole()) - .build()) + .user(UserConverter.toDto(pot.getUser())) .pot(potConverter.toDto(pot, pot.getRecruitmentDetails())) // 변환기 사용 .potMembers(potMembers) .build(); diff --git a/src/main/java/stackpot/stackpot/service/UserCommandService.java b/src/main/java/stackpot/stackpot/service/UserCommandService.java index 8c291476..49c9b41d 100644 --- a/src/main/java/stackpot/stackpot/service/UserCommandService.java +++ b/src/main/java/stackpot/stackpot/service/UserCommandService.java @@ -12,7 +12,7 @@ public interface UserCommandService { UserResponseDto getMypages(); - UserMypageResponseDto getUserMypage(Long userId); + UserMypageResponseDto getUserMypage(Long userId, String dataType); UserResponseDto updateUserProfile(UserUpdateRequestDto requestDto); } diff --git a/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java b/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java index 60c4f0b4..e80010c9 100644 --- a/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/UserCommandServiceImpl.java @@ -5,6 +5,8 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import stackpot.stackpot.apiPayload.code.status.ErrorStatus; +import stackpot.stackpot.apiPayload.exception.handler.MemberHandler; import stackpot.stackpot.converter.UserMypageConverter; import stackpot.stackpot.domain.Feed; import stackpot.stackpot.domain.Pot; @@ -37,7 +39,7 @@ public User joinUser(UserRequestDto.JoinDto request) { String email = authentication.getName(); User user = userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); updateUserData(user, request); @@ -77,7 +79,7 @@ public UserResponseDto getMypages() { String email = authentication.getName(); User user = userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); // User 정보를 UserResponseDto로 변환 return UserResponseDto.builder() @@ -92,19 +94,31 @@ public UserResponseDto getMypages() { } @Transactional - public UserMypageResponseDto getUserMypage(Long userId) { + public UserMypageResponseDto getUserMypage(Long userId, String dataType) { User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found with id: " + userId)); + .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); + + List completedPots = List.of(); + List feeds = List.of(); + + if (dataType == null || dataType.isBlank()) { + // 모든 데이터 반환 (pot + feed) + completedPots = potRepository.findByUserIdAndPotStatus(userId, "COMPLETED"); + feeds = feedRepository.findByUser_Id(userId); + } else if ("pot".equalsIgnoreCase(dataType)) { + // 팟 정보만 반환 + completedPots = potRepository.findByUserIdAndPotStatus(userId, "COMPLETED"); + } else if ("feed".equalsIgnoreCase(dataType)) { + // 피드 정보만 반환 + feeds = feedRepository.findByUser_Id(userId); + } else { + throw new IllegalArgumentException("Invalid data type. Use 'pot', 'feed', or leave empty for all data."); + } - // COMPLETED 상태의 팟 조회 - List completedPots = potRepository.findByUserIdAndPotStatus(userId, "COMPLETED"); + return userMypageConverter.toDto(user, completedPots, feeds); + } - // 사용자의 피드 조회 - List userFeeds = feedRepository.findByUser_Id(userId); - // 컨버터를 사용하여 변환 (좋아요 개수 포함) - return userMypageConverter.toDto(user, completedPots, userFeeds); - } @Transactional public UserResponseDto updateUserProfile(UserUpdateRequestDto requestDto) { @@ -113,7 +127,7 @@ public UserResponseDto updateUserProfile(UserUpdateRequestDto requestDto) { String email = authentication.getName(); User user = userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); // 업데이트할 필드 적용 if (requestDto.getRole() != null) { diff --git a/src/main/java/stackpot/stackpot/web/controller/KakaoLoginController.java b/src/main/java/stackpot/stackpot/web/controller/KakaoLoginController.java deleted file mode 100644 index 059f59b5..00000000 --- a/src/main/java/stackpot/stackpot/web/controller/KakaoLoginController.java +++ /dev/null @@ -1,34 +0,0 @@ -//package stackpot.stackpot.web.controller; -// -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.http.HttpStatus; -//import org.springframework.http.ResponseEntity; -//import org.springframework.web.bind.annotation.GetMapping; -//import org.springframework.web.bind.annotation.RequestMapping; -//import org.springframework.web.bind.annotation.RequestParam; -//import org.springframework.web.bind.annotation.RestController; -//import stackpot.stackpot.converter.UserConverter; -//import stackpot.stackpot.service.KakaoService; -//import stackpot.stackpot.web.dto.KakaoUserInfoResponseDto; -// -//import static com.mysql.cj.conf.PropertyKey.logger; -// -//@Slf4j -//@RestController -//@RequiredArgsConstructor -//@RequestMapping("") -//public class KakaoLoginController { -// private final KakaoService kakaoService; -// -// @GetMapping("/users/oauth/kakao") -// public ResponseEntity callback(@RequestParam("code") String code) { -// -// log.info("Authorization code: {}", code); // 인증 코드 확인 -// String accessToken = kakaoService.getAccessTokenFromKakao(code); -// KakaoUserInfoResponseDto userInfo = kakaoService.getUserInfo(accessToken); -// -// String email = userInfo.getKakaoAccount().getEmail();// 이메일 가져오기 -// return ResponseEntity.ok(userInfo); -// } -//} \ No newline at end of file diff --git a/src/main/java/stackpot/stackpot/web/controller/PotController.java b/src/main/java/stackpot/stackpot/web/controller/PotController.java index 0ae06a3f..33264410 100644 --- a/src/main/java/stackpot/stackpot/web/controller/PotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/PotController.java @@ -9,6 +9,9 @@ import org.springframework.data.domain.PageRequest; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import stackpot.stackpot.apiPayload.code.status.ErrorStatus; +import stackpot.stackpot.apiPayload.exception.handler.EnumHandler; +import stackpot.stackpot.apiPayload.exception.handler.RecruitmentHandler; import stackpot.stackpot.apiPayload.ApiResponse; import stackpot.stackpot.domain.Pot; import stackpot.stackpot.domain.enums.Role; @@ -82,7 +85,6 @@ public ResponseEntity>> return ResponseEntity.ok(ApiResponse.onSuccess(response)); } - //------------------- @Operation( @@ -92,32 +94,37 @@ public ResponseEntity>> 만약 null인 경우 모든 role에 대해서 조회합니다. """ ) - @GetMapping public ResponseEntity>> getPots( @RequestParam(required = false) String recruitmentRole, - @RequestParam(defaultValue = "0") Integer page, + @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size) { + if (page < 1) { + throw new EnumHandler(ErrorStatus.INVALID_PAGE); + } + Role roleEnum = null; if (recruitmentRole != null && !recruitmentRole.isEmpty()) { try { roleEnum = Role.valueOf(recruitmentRole.trim().toUpperCase()); } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Invalid recruitment role provided: " + recruitmentRole); + throw new RecruitmentHandler(ErrorStatus.INVALID_ROLE); } } - List pots = potService1.getAllPots(roleEnum, page, size); + int adjustedPage = page - 1; + + List pots = potService1.getAllPots(roleEnum, adjustedPage, size); Page potPage = (roleEnum == null) - ? potRepository.findAll(PageRequest.of(page, size)) - : potRepository.findByRecruitmentDetails_RecruitmentRole(roleEnum, PageRequest.of(page, size)); + ? potRepository.findAll(PageRequest.of(adjustedPage, size)) + : potRepository.findByRecruitmentDetails_RecruitmentRole(roleEnum, PageRequest.of(adjustedPage, size)); Map response = new HashMap<>(); response.put("pots", pots); response.put("totalPages", potPage.getTotalPages()); - response.put("currentPage", potPage.getNumber()); + response.put("currentPage", potPage.getNumber() + 1); response.put("totalElements", potPage.getTotalElements()); return ResponseEntity.ok(ApiResponse.onSuccess(response)); @@ -166,13 +173,6 @@ public ResponseEntity>> getMyPots() { return ResponseEntity.ok(ApiResponse.onSuccess(myPots)); } - /*// 사용자가 만든 팟 다 끓이기 - @PatchMapping("/{pot_id}/complete") - public ResponseEntity> patchPotStatus(@PathVariable("pot_id") Long potId) { - potService1.patchPotStatus(potId); - return ResponseEntity.ok(ApiResponse.onSuccess(null)); - }*/ - // Pot 내용 AI 요약 @Operation(summary = "Pot 내용 AI 요약 API", description = "팟의 구인글 내용을 활용해 작성됩니다.") diff --git a/src/main/java/stackpot/stackpot/web/controller/UserController.java b/src/main/java/stackpot/stackpot/web/controller/UserController.java index 38108c80..f0cbc574 100644 --- a/src/main/java/stackpot/stackpot/web/controller/UserController.java +++ b/src/main/java/stackpot/stackpot/web/controller/UserController.java @@ -96,10 +96,12 @@ public ResponseEntity> usersMypages(){ return ResponseEntity.ok(ApiResponse.onSuccess(userDetails)); } - @Operation(summary = "다른 사람 마이페이지(프로필) 조회 API") + @Operation(summary = "다른 사람 마이페이지(프로필) 조회 API", description = "dataType = pot / feed / (null : pot + feed)") @GetMapping("/{userId}/mypages") - public ResponseEntity> getUserMypage(@PathVariable Long userId) { - UserMypageResponseDto response = userCommandService.getUserMypage(userId); + public ResponseEntity> getUserMypage( + @PathVariable Long userId, + @RequestParam(required = false) String dataType) { + UserMypageResponseDto response = userCommandService.getUserMypage(userId, dataType); return ResponseEntity.ok(ApiResponse.onSuccess(response)); } diff --git a/src/main/java/stackpot/stackpot/web/dto/PotAllMemRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/PotAllMemRequestDto.java index ca0020ef..83d5c8a6 100644 --- a/src/main/java/stackpot/stackpot/web/dto/PotAllMemRequestDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/PotAllMemRequestDto.java @@ -1,6 +1,8 @@ package stackpot.stackpot.web.dto; +import jakarta.validation.constraints.Pattern; import lombok.*; +import stackpot.stackpot.Validation.annotation.ValidRole; @Getter @Setter @@ -11,6 +13,7 @@ public class PotAllMemRequestDto { private Long potMemberId; // 팟 멤버 ID private Long potId; // 팟 ID private Long userId; // 유저 ID + @ValidRole private String roleName; // 역할 이름 private String nickname; // 닉네임 + 역할 private Boolean isOwner; // 팟 생성자인지 여부 diff --git a/src/main/java/stackpot/stackpot/web/dto/PotApplicationRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/PotApplicationRequestDto.java index 60aff1bd..9837cc99 100644 --- a/src/main/java/stackpot/stackpot/web/dto/PotApplicationRequestDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/PotApplicationRequestDto.java @@ -1,7 +1,10 @@ package stackpot.stackpot.web.dto; +import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; import lombok.*; +import stackpot.stackpot.Validation.annotation.ValidRole; @Getter @Setter @@ -10,6 +13,7 @@ @Builder public class PotApplicationRequestDto { @NotBlank(message = "팟 역할은 필수입니다.") + @ValidRole private String potRole; diff --git a/src/main/java/stackpot/stackpot/web/dto/PotApplicationResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/PotApplicationResponseDto.java index 98281d98..a4551714 100644 --- a/src/main/java/stackpot/stackpot/web/dto/PotApplicationResponseDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/PotApplicationResponseDto.java @@ -19,3 +19,4 @@ public class PotApplicationResponseDto { private Long userId; private String userNickname; } + diff --git a/src/main/java/stackpot/stackpot/web/dto/PotRecruitmentRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/PotRecruitmentRequestDto.java index a4ab9f65..5c5af7a6 100644 --- a/src/main/java/stackpot/stackpot/web/dto/PotRecruitmentRequestDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/PotRecruitmentRequestDto.java @@ -1,13 +1,16 @@ package stackpot.stackpot.web.dto; +import jakarta.validation.constraints.Pattern; import lombok.Builder; import lombok.Getter; import lombok.Setter; +import stackpot.stackpot.Validation.annotation.ValidRole; @Getter @Setter @Builder public class PotRecruitmentRequestDto { + @ValidRole private String recruitmentRole; private Integer recruitmentCount; } diff --git a/src/main/java/stackpot/stackpot/web/dto/PotRecruitmentResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/PotRecruitmentResponseDto.java index 88a2b4c7..1f463670 100644 --- a/src/main/java/stackpot/stackpot/web/dto/PotRecruitmentResponseDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/PotRecruitmentResponseDto.java @@ -1,5 +1,6 @@ package stackpot.stackpot.web.dto; +import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/stackpot/stackpot/web/dto/UserMypageResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/UserMypageResponseDto.java index 2f05018c..4ea8ba4d 100644 --- a/src/main/java/stackpot/stackpot/web/dto/UserMypageResponseDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/UserMypageResponseDto.java @@ -14,6 +14,7 @@ @AllArgsConstructor @NoArgsConstructor public class UserMypageResponseDto { + private Long id; private String email; private String nickname; private Role role; diff --git a/src/main/java/stackpot/stackpot/web/dto/UserResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/UserResponseDto.java index 38e84137..17d09440 100644 --- a/src/main/java/stackpot/stackpot/web/dto/UserResponseDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/UserResponseDto.java @@ -10,6 +10,7 @@ @Setter @Builder public class UserResponseDto { + private Long id; private String email; // 이메일 private String nickname; // 닉네임 private Role role; // 역할 diff --git a/src/main/java/stackpot/stackpot/web/dto/UserUpdateRequestDto.java b/src/main/java/stackpot/stackpot/web/dto/UserUpdateRequestDto.java index ce182193..17332a3b 100644 --- a/src/main/java/stackpot/stackpot/web/dto/UserUpdateRequestDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/UserUpdateRequestDto.java @@ -1,13 +1,16 @@ package stackpot.stackpot.web.dto; +import jakarta.validation.constraints.Pattern; import lombok.Getter; import lombok.Setter; +import stackpot.stackpot.Validation.annotation.ValidRole; import stackpot.stackpot.domain.enums.Role; @Getter @Setter public class UserUpdateRequestDto { + @ValidRole private Role role; private String interest; private String userIntroduction; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index cc93fa1d..dd577f4e 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -15,7 +15,7 @@ spring: format_sql: true use_sql_comments: true hbm2ddl: - auto: update + auto: create default_batch_fetch_size: 1000 cloud: aws: From 41f4ee81abfe778c7e54377fc61fda9429afac56 Mon Sep 17 00:00:00 2001 From: isumin Date: Mon, 27 Jan 2025 01:32:45 +0900 Subject: [PATCH 71/76] =?UTF-8?q?[MOD]=20yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index dd577f4e..cc93fa1d 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -15,7 +15,7 @@ spring: format_sql: true use_sql_comments: true hbm2ddl: - auto: create + auto: update default_batch_fetch_size: 1000 cloud: aws: From dd30a65c8ec8e159422f3a492818813c5986980c Mon Sep 17 00:00:00 2001 From: isumin Date: Mon, 27 Jan 2025 01:45:31 +0900 Subject: [PATCH 72/76] =?UTF-8?q?[MOD]=20API=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/stackpot/stackpot/web/controller/MyPotController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/stackpot/stackpot/web/controller/MyPotController.java b/src/main/java/stackpot/stackpot/web/controller/MyPotController.java index b5d288b5..33543e7b 100644 --- a/src/main/java/stackpot/stackpot/web/controller/MyPotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/MyPotController.java @@ -25,7 +25,7 @@ public class MyPotController { // 사용자가 만든 진행 중인 팟 조회 @Operation(summary = "사용자의 팟 목록 조회 API", description = "사용자가 생성했거나, 참여하고 있으며 진행 중(ONGOING)인 팟들 리스트를 조회합니다. \n") - @GetMapping("/mypots/ongoing") + @GetMapping("/my-pots") public ResponseEntity>>> getMyOngoingPots() { Map> response = myPotService.getMyOnGoingPots(); return ResponseEntity.ok(ApiResponse.onSuccess(response)); From d46b73b49739b11ab254d3bcea408b332d08a6c1 Mon Sep 17 00:00:00 2001 From: jjaeroong <133083872+jjaeroong@users.noreply.github.com> Date: Mon, 27 Jan 2025 22:21:42 +0900 Subject: [PATCH 73/76] Create gradle.yml --- .github/workflows/gradle.yml | 67 ++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/gradle.yml diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 00000000..8901d07a --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,67 @@ +name: Java CI with Gradle + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build-docker-image: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + # Spring Boot 어플리케이션 Build + - name: Build with Gradle Wrapper + run: ./gradlew build + + # Docker 이미지 Build + - name: docker image build + run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/spring-cicd-test . + + # DockerHub Login (push 하기 위해) + - name: docker login + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + # Docker hub 로 push + - name: Docker Hub push + run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/spring-cicd-test + + # 위 과정에서 푸시한 이미지를 ec2에서 풀받아서 실행 + run-docker-image-on-ec2: + needs: build-docker-image + runs-on: self-hosted + + steps: + - name: docker pull + run : sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/spring-cicd-test + + - name: docker stop container + run: | + if [ $(sudo docker ps -a -q -f name=spring-cicd-test) ]; then + sudo docker stop spring-cicd-test + fi + + - name: docker run new container + run: sudo docker run --rm -it -d -p 80:8080 --name spring-cicd-test ${{ secrets.DOCKERHUB_USERNAME }}/spring-cicd-test + + - name: delete old docker image + run: sudo docker system prune -f From f65ee9170f8c91bb566d5734609d04c679f88876 Mon Sep 17 00:00:00 2001 From: starday119 Date: Mon, 27 Jan 2025 23:20:56 +0900 Subject: [PATCH 74/76] =?UTF-8?q?[FEAT]:=20=EB=8F=84=EC=BB=A4=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 11 +++ build.gradle | 4 + .../repository/PotMemberRepository.java | 9 +- .../stackpot/stackpot/service/PotService.java | 6 +- .../stackpot/service/PotServiceImpl.java | 87 ++++++++++++------- .../web/controller/MyPotController.java | 17 ++-- .../web/controller/PotController.java | 3 + .../web/controller/SearchController.java | 2 +- src/main/resources/application.yml | 2 + 9 files changed, 98 insertions(+), 43 deletions(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..47ba90bb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +#FR open jdk 11 버전의 환경을 구성 + FROM eclipse-temurin:17 + + # build가 되는 시점에 JAR_FILE이라는 변수 명에 build/libs/*.jar 선언 + # build/libs - gradle로 빌드했을 "때 jar 파일이 생성되는 경로 + ARG JAR_FILE=build/libs/*.jar + + # JAR_FILE을 app.jar로 복사 + COPY ${JAR_FILE} app.jar + + ENTRYPOINT ["java", "-jar", "/app.jar"] \ No newline at end of file diff --git a/build.gradle b/build.gradle index c9385619..09d933e0 100644 --- a/build.gradle +++ b/build.gradle @@ -74,9 +74,13 @@ dependencies { implementation 'mysql:mysql-connector-java:8.0.33' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.security:spring-security-crypto' + //dotenv + implementation 'io.github.cdimascio:java-dotenv:5.2.2' + } + tasks.named('test') { useJUnitPlatform() } diff --git a/src/main/java/stackpot/stackpot/repository/PotMemberRepository.java b/src/main/java/stackpot/stackpot/repository/PotMemberRepository.java index 054cf36f..727673cd 100644 --- a/src/main/java/stackpot/stackpot/repository/PotMemberRepository.java +++ b/src/main/java/stackpot/stackpot/repository/PotMemberRepository.java @@ -1,8 +1,8 @@ package stackpot.stackpot.repository; - import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -22,4 +22,11 @@ public interface PotMemberRepository extends JpaRepository { List findByPotId(@Param("potId") Long potId); @Query("SELECT pm.roleName, COUNT(pm) FROM PotMember pm WHERE pm.pot.potId = :potId GROUP BY pm.roleName") List findRoleCountsByPotId(@Param("potId") Long potId); + + @Modifying + @Query("DELETE FROM PotMember pm WHERE pm.pot.potId = :potId AND pm.user.id = :userId") + void deleteByPotIdAndUserId(@Param("potId") Long potId, @Param("userId") Long userId); + + Optional findByPotAndUser(Pot pot, User user); + } diff --git a/src/main/java/stackpot/stackpot/service/PotService.java b/src/main/java/stackpot/stackpot/service/PotService.java index 8aba9dcf..bf369eee 100644 --- a/src/main/java/stackpot/stackpot/service/PotService.java +++ b/src/main/java/stackpot/stackpot/service/PotService.java @@ -1,10 +1,6 @@ package stackpot.stackpot.service; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import stackpot.stackpot.domain.enums.Role; -import stackpot.stackpot.web.dto.PotRequestDto; -import stackpot.stackpot.web.dto.PotResponseDto; import stackpot.stackpot.web.dto.*; import java.util.List; @@ -15,6 +11,8 @@ public interface PotService { PotResponseDto updatePotWithRecruitments(Long potId, PotRequestDto requestDto); CursorPageResponse getMyCompletedPots(Long cursor, int size); void deletePot(Long potId); + void removeMemberFromPot(Long potId); + String removePotOrMember(Long potId); //--------------- diff --git a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java index d82ebb2d..10b403b8 100644 --- a/src/main/java/stackpot/stackpot/service/PotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/PotServiceImpl.java @@ -22,14 +22,13 @@ import stackpot.stackpot.domain.User; import stackpot.stackpot.domain.enums.Role; import stackpot.stackpot.domain.mapping.PotApplication; +import stackpot.stackpot.domain.mapping.PotMember; import stackpot.stackpot.repository.PotMemberRepository; import stackpot.stackpot.repository.PotRepository.PotRecruitmentDetailsRepository; import stackpot.stackpot.repository.PotRepository.PotRepository; import stackpot.stackpot.repository.UserRepository.UserRepository; import stackpot.stackpot.web.dto.*; -import java.time.LocalDate; -import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -426,35 +425,59 @@ private MyPotResponseDTO.OngoingPotsDetail convertToOngoingPotDetail(Pot pot) { .potMembers(potMembers) .build(); } -// @Transactional -// public void removeMemberFromPot(Long potId, Long userId) { -// // 현재 로그인한 사용자 확인 -// Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); -// String email = authentication.getName(); -// -// // 현재 로그인한 사용자 조회 -// User currentUser = userRepository.findByEmail(email) -// .orElseThrow(() -> new IllegalArgumentException("현재 사용자를 찾을 수 없습니다.")); -// -// // 팟 존재 여부 확인 -// Pot pot = potRepository.findById(potId) -// .orElseThrow(() -> new IllegalArgumentException("해당 팟을 찾을 수 없습니다.")); -// -// // 팟 생성자인지 확인 -// if (!pot.getUser().getId().equals(currentUser.getId())) { -// throw new IllegalStateException("해당 팟의 멤버를 삭제할 권한이 없습니다."); -// } -// -// // 사용자 존재 여부 확인 -// User user = userRepository.findById(userId) -// .orElseThrow(() -> new IllegalArgumentException("해당 사용자를 찾을 수 없습니다.")); -// -// // 팟 멤버 존재 여부 확인 -// PotMember member = potMemberRepository.findByPotAndUser(pot, user) -// .orElseThrow(() -> new IllegalArgumentException("해당 팟에 사용자가 존재하지 않습니다.")); -// -// // 팟 멤버 삭제 -// potMemberRepository.delete(member); -// } + @Transactional + @Override + public void removeMemberFromPot(Long potId) { + // 현재 로그인한 사용자 확인 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + // 현재 로그인한 사용자 조회 + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("현재 사용자를 찾을 수 없습니다.")); + + // 팟 존재 여부 확인 + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new IllegalArgumentException("해당 팟을 찾을 수 없습니다.")); + + // 팟 멤버 존재 여부 확인 + PotMember member = potMemberRepository.findByPotAndUser(pot, user) + .orElseThrow(() -> new IllegalArgumentException("해당 팟에 사용자가 존재하지 않습니다.")); + + // 팟 멤버 삭제 + potMemberRepository.delete(member); + } + + + @Transactional + @Override + public String removePotOrMember(Long potId) { + // 현재 로그인한 사용자 확인 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + // 현재 로그인한 사용자 조회 + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("현재 사용자를 찾을 수 없습니다.")); + + // 팟 존재 여부 확인 + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new IllegalArgumentException("해당 팟을 찾을 수 없습니다.")); + + // 팟 생성자인지 확인 + if (pot.getUser().equals(user)) { + // 팟 생성자일 경우 팟과 관련된 모든 데이터 삭제 + potRepository.delete(pot); + return "팟이 성공적으로 삭제되었습니다."; + } else { + // 팟 멤버인지 확인 + PotMember member = potMemberRepository.findByPotAndUser(pot, user) + .orElseThrow(() -> new IllegalArgumentException("해당 팟에 사용자가 존재하지 않습니다.")); + + // 팟 멤버 삭제 + potMemberRepository.delete(member); + return "팟 멤버가 성공적으로 삭제되었습니다."; + } + } } diff --git a/src/main/java/stackpot/stackpot/web/controller/MyPotController.java b/src/main/java/stackpot/stackpot/web/controller/MyPotController.java index 33543e7b..0ff468ff 100644 --- a/src/main/java/stackpot/stackpot/web/controller/MyPotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/MyPotController.java @@ -30,15 +30,22 @@ public ResponseEntity> response = myPotService.getMyOnGoingPots(); return ResponseEntity.ok(ApiResponse.onSuccess(response)); } -// @DeleteMapping("/{pot_id}/members/{user_id}") -// @Operation(summary = "팟에서 멤버 삭제", description = "팟 멤버가 본인의 팟을 삭제하면 팟 멤버에서 해당 사용자가 제거됩니다.") +// @DeleteMapping("/{pot_id}/members") +// @Operation(summary = "팟에서 본인 삭제", description = "현재 로그인한 팟 멤버가 본인의 팟을 삭제하면 팟 멤버에서 자신이 제거됩니다.") // public ResponseEntity> removePotMember( -// @PathVariable("pot_id") Long potId, -// @PathVariable("user_id") Long userId) { +// @PathVariable("pot_id") Long potId) { // -// potService.removeMemberFromPot(potId, userId); +// potService.removeMemberFromPot(potId); // return ResponseEntity.ok(ApiResponse.onSuccess("팟 멤버가 성공적으로 삭제되었습니다.")); // } + @DeleteMapping("/{pot_id}/members") + @Operation(summary = "팟 멤버 삭제 또는 팟 삭제", description = "생성자는 팟을 삭제하며, 생성자가 아니면 팟 멤버에서 본인을 삭제합니다.") + public ResponseEntity> removePotOrMember( + @PathVariable("pot_id") Long potId) { + + String responseMessage = potService.removePotOrMember(potId); + return ResponseEntity.ok(ApiResponse.onSuccess(responseMessage)); + } // 팟에서의 투두 생성 @Operation( diff --git a/src/main/java/stackpot/stackpot/web/controller/PotController.java b/src/main/java/stackpot/stackpot/web/controller/PotController.java index 33264410..f300e099 100644 --- a/src/main/java/stackpot/stackpot/web/controller/PotController.java +++ b/src/main/java/stackpot/stackpot/web/controller/PotController.java @@ -72,6 +72,9 @@ public ResponseEntity deletePot(@PathVariable("pot_id") Long potId) { return ResponseEntity.noContent().build(); } + + + @GetMapping("/completed") @Operation(summary = "나의 끓인 팟 정보 가져오기", description = "potStatus가 COMPLETED인 팟의 목록을 커서 기반 페이지네이션으로 가져옵니다.", parameters = { diff --git a/src/main/java/stackpot/stackpot/web/controller/SearchController.java b/src/main/java/stackpot/stackpot/web/controller/SearchController.java index c4e85c1c..0dc261d7 100644 --- a/src/main/java/stackpot/stackpot/web/controller/SearchController.java +++ b/src/main/java/stackpot/stackpot/web/controller/SearchController.java @@ -80,7 +80,7 @@ public ResponseEntity>> searchFeeds( @GetMapping - @Operation(summary = "통합 검색 API", description = "키워드를 기반으로 팟 또는 피드를 검색합니다.", + @Operation(summary = "팟 or 피드 검색 API", description = "키워드를 기반으로 팟 또는 피드를 검색합니다.", parameters = { @Parameter(name = "type", description = "검색 타입 (pot: 팟 검색, feed: 피드 검색)", example = "pot"), @Parameter(name = "keyword", description = "검색 키워드", example = "JAVA"), diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index cc93fa1d..ae5a4a85 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,4 +1,6 @@ spring: + config: + import: optional:file:.env[.properties] datasource: url: ${URL} username: ${USERNAME} From f65df49938fab03bee5b165650860553ba1692d1 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Tue, 28 Jan 2025 03:06:27 +0900 Subject: [PATCH 75/76] =?UTF-8?q?[FEAT]=20task=20response=20dto=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/TaskboardConverterImpl.java | 21 ++++++++++++++----- .../stackpot/service/MyPotServiceImpl.java | 4 ++++ .../web/controller/FeedController.java | 4 ++-- .../web/dto/MyPotTaskResponseDto.java | 1 - 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/main/java/stackpot/stackpot/converter/TaskboardConverterImpl.java b/src/main/java/stackpot/stackpot/converter/TaskboardConverterImpl.java index 99bbb07a..4f5bcc78 100644 --- a/src/main/java/stackpot/stackpot/converter/TaskboardConverterImpl.java +++ b/src/main/java/stackpot/stackpot/converter/TaskboardConverterImpl.java @@ -8,6 +8,7 @@ import stackpot.stackpot.web.dto.MyPotTaskResponseDto; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @Component @@ -43,11 +44,21 @@ public List toParticipantDtoList(List roleToVegetableMap = Map.of( + "BACKEND", " 양파", + "FRONTEND", " 버섯", + "DESIGN", " 브로콜리", + "PLANNING", " 당근" ); + return roleToVegetableMap.getOrDefault(role, "알 수 없음"); } } diff --git a/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java b/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java index 83ebdda7..de776329 100644 --- a/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java +++ b/src/main/java/stackpot/stackpot/service/MyPotServiceImpl.java @@ -2,6 +2,7 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; @@ -31,6 +32,7 @@ import java.util.Map; import java.util.stream.Collectors; +@Slf4j @Service @RequiredArgsConstructor public class MyPotServiceImpl implements MyPotService { @@ -306,6 +308,8 @@ public MyPotTaskResponseDto creatTask(Long potId, MyPotTaskRequestDto.create req List participants = potMemberRepository.findAllById(request.getParticipants()); + log.info("dd", participants); + if (participants.isEmpty()) { throw new IllegalArgumentException("유효한 참가자를 찾을 수 없습니다. 요청된 ID를 확인해주세요."); } diff --git a/src/main/java/stackpot/stackpot/web/controller/FeedController.java b/src/main/java/stackpot/stackpot/web/controller/FeedController.java index 413f344c..17b2a962 100644 --- a/src/main/java/stackpot/stackpot/web/controller/FeedController.java +++ b/src/main/java/stackpot/stackpot/web/controller/FeedController.java @@ -29,7 +29,7 @@ public class FeedController { private final FeedConverter feedConverter; - @Operation(summary = "feed 작성 api") + @Operation(summary = "(수정 필요) feed 작성 api") @PostMapping("") public ResponseEntity createFeeds(@Valid @RequestBody FeedRequestDto.createDto requset) { // 정상 처리 @@ -65,7 +65,7 @@ public ResponseEntity getDetailFeed(@PathVariable Long return ResponseEntity.status(HttpStatus.CREATED).body(response); } - @Operation(summary = "feed 수정 api") + @Operation(summary = "feed 수정 api (수정 필요") @PatchMapping("/{feedId}") public ResponseEntity modifyFeed(@PathVariable Long feedId, @Valid @RequestBody FeedRequestDto.createDto requset) { // 정상 처리 diff --git a/src/main/java/stackpot/stackpot/web/dto/MyPotTaskResponseDto.java b/src/main/java/stackpot/stackpot/web/dto/MyPotTaskResponseDto.java index 28b72b33..2d22d6e1 100644 --- a/src/main/java/stackpot/stackpot/web/dto/MyPotTaskResponseDto.java +++ b/src/main/java/stackpot/stackpot/web/dto/MyPotTaskResponseDto.java @@ -32,7 +32,6 @@ public static class Participant { private Long userId; private Long potMemberId; private String nickName; - private String email; } } From 4265e2eaba0b8e1adbad4794055ed27cfc564880 Mon Sep 17 00:00:00 2001 From: rudeore-098 Date: Tue, 28 Jan 2025 03:24:40 +0900 Subject: [PATCH 76/76] =?UTF-8?q?[FEAT]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/controller/UserController.java | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/src/main/java/stackpot/stackpot/web/controller/UserController.java b/src/main/java/stackpot/stackpot/web/controller/UserController.java index 6f7d3cfd..6034733a 100644 --- a/src/main/java/stackpot/stackpot/web/controller/UserController.java +++ b/src/main/java/stackpot/stackpot/web/controller/UserController.java @@ -3,6 +3,7 @@ import io.swagger.v3.oas.annotations.Operation; //import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -22,6 +23,7 @@ import stackpot.stackpot.service.UserCommandService; import stackpot.stackpot.web.dto.*; +import java.io.IOException; import java.util.List; import java.util.stream.Collectors; @Tag(name = "User Management", description = "유저 관리 API") @@ -44,23 +46,49 @@ public ResponseEntity testEndpoint(Authentication authentication) { return ResponseEntity.ok("Authenticated user: " + authentication.getName()); } +// @GetMapping("/oauth/kakao") +// public ResponseEntity callback(@RequestParam("code") String code) { +// +// log.info("Authorization code: {}", code); // 인증 코드 확인 +// String accessToken = kakaoService.getAccessTokenFromKakao(code); +// KakaoUserInfoResponseDto userInfo = kakaoService.getUserInfo(accessToken); +// +// String email = userInfo.getKakaoAccount().getEmail();// 이메일 가져오기 +// log.info("userInfo.getEmail -> ", email); +// +// User user = userCommandService.saveNewUser(email); +// +// TokenServiceResponse token = jwtTokenProvider.createToken(user); +// log.info("STACKPOT ACESSTOKEN : " + token.getAccessToken()); +// +// +// return ResponseEntity.ok(token); +// } + @GetMapping("/oauth/kakao") - public ResponseEntity callback(@RequestParam("code") String code) { + public void callback(@RequestParam("code") String code, HttpServletResponse response) throws IOException { - log.info("Authorization code: {}", code); // 인증 코드 확인 + log.info("Authorization code: {}", code); String accessToken = kakaoService.getAccessTokenFromKakao(code); KakaoUserInfoResponseDto userInfo = kakaoService.getUserInfo(accessToken); - String email = userInfo.getKakaoAccount().getEmail();// 이메일 가져오기 - log.info("userInfo.getEmail -> ", email); + String email = userInfo.getKakaoAccount().getEmail(); + log.info("userInfo.getEmail -> {}", email); User user = userCommandService.saveNewUser(email); TokenServiceResponse token = jwtTokenProvider.createToken(user); - log.info("STACKPOT ACESSTOKEN : " + token.getAccessToken()); - - - return ResponseEntity.ok(token); + log.info("AccessToken: {}", token.getAccessToken()); + + if (user.getId() == null) { + // 미가입 유저: 회원가입 페이지로 리다이렉트 (토큰을 헤더로 추가) + response.setHeader("Authorization", "Bearer " + token.getAccessToken()); + response.sendRedirect("/sign-up"); + } else { + // 가입된 유저: 홈 페이지로 리다이렉트 (토큰을 헤더로 추가) + response.setHeader("Authorization", "Bearer " + token.getAccessToken()); + response.sendRedirect("/home"); + } } @Operation(summary = "회원가입 api") @@ -86,7 +114,6 @@ public ResponseEntity signup(@Valid @RequestBody UserRequestDto.JoinDto reque public ResponseEntity> nickname(){ String nickName = userCommandService.createNickname(); - return ResponseEntity.ok(ApiResponse.onSuccess(nickName)); }