diff --git a/build.gradle b/build.gradle index 0e2ec78..9ffcf58 100644 --- a/build.gradle +++ b/build.gradle @@ -37,6 +37,9 @@ dependencies { implementation("software.amazon.awssdk:s3:2.21.0") implementation 'org.apache.httpcomponents:httpclient:4.5.13' + // Swagger UI - spring doc + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + } tasks.named('test') { diff --git a/src/main/java/Journey/Together/ServerApplication.java b/src/main/java/Journey/Together/ServerApplication.java index 4de1c8a..4eace54 100644 --- a/src/main/java/Journey/Together/ServerApplication.java +++ b/src/main/java/Journey/Together/ServerApplication.java @@ -8,6 +8,7 @@ public class ServerApplication { public static void main(String[] args) { SpringApplication.run(ServerApplication.class, args); + System.out.println("Server start"); } } diff --git a/src/main/java/Journey/Together/domain/member/controller/MemberController.java b/src/main/java/Journey/Together/domain/member/controller/MemberController.java new file mode 100644 index 0000000..75b2c71 --- /dev/null +++ b/src/main/java/Journey/Together/domain/member/controller/MemberController.java @@ -0,0 +1,23 @@ +package Journey.Together.domain.member.controller; + +import Journey.Together.domain.member.dto.MemberRes; +import Journey.Together.domain.member.service.MemberService; +import Journey.Together.global.common.ApiResponse; +import Journey.Together.global.exception.Success; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/v1/member") +@Tag(name = "Member", description = "사용자 관련 API") +public class MemberController { + + private final MemberService memberService; + + @GetMapping("") + public ApiResponse getMember() { + return ApiResponse.success(Success.LOGIN_SUCCESS); + } +} diff --git a/src/main/java/Journey/Together/domain/member/dto/MemberRes.java b/src/main/java/Journey/Together/domain/member/dto/MemberRes.java new file mode 100644 index 0000000..81b56e7 --- /dev/null +++ b/src/main/java/Journey/Together/domain/member/dto/MemberRes.java @@ -0,0 +1,4 @@ +package Journey.Together.domain.member.dto; + +public record MemberRes() { +} diff --git a/src/main/java/Journey/Together/domain/member/entity/Member.java b/src/main/java/Journey/Together/domain/member/entity/Member.java new file mode 100644 index 0000000..0c8e7f5 --- /dev/null +++ b/src/main/java/Journey/Together/domain/member/entity/Member.java @@ -0,0 +1,65 @@ +package Journey.Together.domain.member.entity; + +import Journey.Together.domain.member.enumerate.BloodType; +import Journey.Together.domain.member.enumerate.LoginType; +import Journey.Together.global.common.BaseTimeEntity; +import jakarta.persistence.*; +import lombok.*; + +@Getter +@Setter +@Entity +@Table(name = "member") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Member extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "member_id", nullable = false, columnDefinition = "bigint") + private Long memberId; + + // 이메일은 최대 255자 + 1자(@) + 69자해서 최대 320글자이므로, varchar(320) 사용 + @Column(name = "email", nullable = false, columnDefinition = "varchar(320)") + private String email; + + @Column(name = "name", nullable = false, columnDefinition = "varchar(50)") + private String name; + + @Column(name = "phone", columnDefinition = "varchar(15)") + private String phone; + + @Column(name = "profile_url", columnDefinition = "text") + private String profileUrl; + + @Column(name = "login_type", nullable = false, columnDefinition = "varchar(255)") + @Enumerated(EnumType.STRING) + private LoginType loginType; + + @Column(name = "blood_type", nullable = false, columnDefinition = "varchar(255)") + @Enumerated(EnumType.STRING) + private BloodType bloodType; + + @Column(name = "birth", columnDefinition = "varchar(255)") + private String birth; + + @Column(name = "disease", columnDefinition = "varchar(255)") + private String disease; + + @Column(name = "allergy", columnDefinition = "varchar(255)") + private String allergy; + + @Column(name = "medication", columnDefinition = "varchar(255)") + private String medication; + + @Builder + public Member(String email, String name, String phone, String profileUrl, String loginType,String bloodType, String birth, String allergy, String medication) { + this.email = email; + this.name = name; + this.phone = phone; + this.profileUrl = profileUrl; + this.loginType = LoginType.valueOf(loginType); + this.bloodType = BloodType.valueOf(bloodType); + this.birth = birth; + this.allergy = allergy; + this.medication=medication; + } +} diff --git a/src/main/java/Journey/Together/domain/member/enumerate/BloodType.java b/src/main/java/Journey/Together/domain/member/enumerate/BloodType.java new file mode 100644 index 0000000..27345f3 --- /dev/null +++ b/src/main/java/Journey/Together/domain/member/enumerate/BloodType.java @@ -0,0 +1,5 @@ +package Journey.Together.domain.member.enumerate; + +public enum BloodType { + A,B,AB,O +} diff --git a/src/main/java/Journey/Together/domain/member/enumerate/LoginType.java b/src/main/java/Journey/Together/domain/member/enumerate/LoginType.java new file mode 100644 index 0000000..45a5f80 --- /dev/null +++ b/src/main/java/Journey/Together/domain/member/enumerate/LoginType.java @@ -0,0 +1,5 @@ +package Journey.Together.domain.member.enumerate; + +public enum LoginType { + KAKAO,NAVER +} diff --git a/src/main/java/Journey/Together/domain/member/repository/MemberRepository.java b/src/main/java/Journey/Together/domain/member/repository/MemberRepository.java new file mode 100644 index 0000000..5923cfd --- /dev/null +++ b/src/main/java/Journey/Together/domain/member/repository/MemberRepository.java @@ -0,0 +1,9 @@ +package Journey.Together.domain.member.repository; + +import Journey.Together.domain.member.entity.Member; +import org.springframework.data.jpa.repository.JpaRepository; + + +public interface MemberRepository extends JpaRepository { + +} diff --git a/src/main/java/Journey/Together/domain/member/service/MemberService.java b/src/main/java/Journey/Together/domain/member/service/MemberService.java new file mode 100644 index 0000000..acf4bce --- /dev/null +++ b/src/main/java/Journey/Together/domain/member/service/MemberService.java @@ -0,0 +1,15 @@ +package Journey.Together.domain.member.service; + +import Journey.Together.domain.member.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class MemberService { + + private final MemberRepository memberRepository; + +} diff --git a/src/main/java/Journey/Together/global/common/ApiResponse.java b/src/main/java/Journey/Together/global/common/ApiResponse.java index efa1e63..244090a 100644 --- a/src/main/java/Journey/Together/global/common/ApiResponse.java +++ b/src/main/java/Journey/Together/global/common/ApiResponse.java @@ -1,8 +1,8 @@ package Journey.Together.global.common; +import Journey.Together.global.exception.ErrorCode; import Journey.Together.global.exception.Success; -import Journey.Together.global.exception.Error; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; @@ -22,15 +22,7 @@ public static ApiResponse success(Success success){ return new ApiResponse<>(success.getHttpStatusCode(), success.getMessage()); } - public static ApiResponse success(Success success, T data){ + public static ApiResponse success(Success success, T data) { return new ApiResponse(success.getHttpStatusCode(), success.getMessage(), data); } - - public static ApiResponse error(Error error){ - return new ApiResponse<>(error.getErrorCode(), error.getMessage()); - } - - public static ApiResponse error(Error error, String message){ - return new ApiResponse<>(error.getErrorCode(), message); - } } diff --git a/src/main/java/Journey/Together/global/config/SwaggerConfig.java b/src/main/java/Journey/Together/global/config/SwaggerConfig.java new file mode 100644 index 0000000..c1bcb94 --- /dev/null +++ b/src/main/java/Journey/Together/global/config/SwaggerConfig.java @@ -0,0 +1,35 @@ +package Journey.Together.global.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI openAPI() { + Info info = new Info().title("Capic Swagger UI") + .description("Capic API 테스트 페이지.") + .version("v0.0.1"); + + String jwtSchemeName = "JWT Authentication"; + SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName); + Components components = new Components() + .addSecuritySchemes(jwtSchemeName, new SecurityScheme() + .name(jwtSchemeName) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT")); + + // Swagger UI 접속 후, 딱 한 번만 accessToken을 입력해주면 모든 API에 토큰 인증 작업이 적용됩니다. + return new OpenAPI() + .info(info) + .addSecurityItem(securityRequirement) + .components(components); + } +} \ No newline at end of file diff --git a/src/main/java/Journey/Together/global/exception/ApplicationException.java b/src/main/java/Journey/Together/global/exception/ApplicationException.java new file mode 100644 index 0000000..d5a87d5 --- /dev/null +++ b/src/main/java/Journey/Together/global/exception/ApplicationException.java @@ -0,0 +1,10 @@ +package Journey.Together.global.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ApplicationException extends RuntimeException { + public ErrorCode errorCode; +} diff --git a/src/main/java/Journey/Together/global/exception/Error.java b/src/main/java/Journey/Together/global/exception/Error.java deleted file mode 100644 index a300817..0000000 --- a/src/main/java/Journey/Together/global/exception/Error.java +++ /dev/null @@ -1,54 +0,0 @@ -package Journey.Together.global.exception; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.springframework.http.HttpStatus; - -@Getter -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public enum Error { - - /** - * 404 NOT FOUND - */ - NOT_FOUND_USER_EXCEPTION(HttpStatus.NOT_FOUND, "찾을 수 없는 유저입니다."), - NOT_FOUND_IMAGE_EXCEPTION(HttpStatus.NOT_FOUND, "s3 서비스에서 이미지를 찾을 수 없습니다."), - - /** - * 400 BAD REQUEST EXCEPTION - */ - BAD_REQUEST_ID(HttpStatus.BAD_REQUEST, "잘못된 id값입니다."), - - - /** - * 401 UNAUTHORIZED EXCEPTION - */ - TOKEN_TIME_EXPIRED_EXCEPTION(HttpStatus.UNAUTHORIZED, "만료된 토큰입니다."), - EXPIRED_APPLE_IDENTITY_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 아이덴티티 토큰입니다."), - - /** - * 403 FORBIDDEN EXCEPTION - */ - UNAUTHORIZED_ACCESS(HttpStatus.FORBIDDEN, "리소스에 대한 권한이 없습니다."), - INVALID_USER_ACCESS(HttpStatus.FORBIDDEN, "접근 권한이 없는 유저입니다."), - - /** - * 422 UNPROCESSABLE_ENTITY - */ - UNPROCESSABLE_ENTITY_DELETE_EXCEPTION(HttpStatus.UNPROCESSABLE_ENTITY, "서버에서 요청을 이해해 삭제하려는 도중 문제가 생겼습니다."), - - - /** - * 500 INTERNAL_SERVER_ERROR - */ - INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "알 수 없는 서버 에러가 발생했습니다"), - ; - - private final HttpStatus httpStatus; - private final String message; - - public int getErrorCode() { - return httpStatus.value(); - } -} diff --git a/src/main/java/Journey/Together/global/exception/ErrorCode.java b/src/main/java/Journey/Together/global/exception/ErrorCode.java new file mode 100644 index 0000000..6ba6075 --- /dev/null +++ b/src/main/java/Journey/Together/global/exception/ErrorCode.java @@ -0,0 +1,60 @@ +package Journey.Together.global.exception; + +import lombok.Getter; +import lombok.ToString; +import org.springframework.http.HttpStatus; + +@Getter +@ToString +public enum ErrorCode { + + // 1000: Success Case + SUCCESS(HttpStatus.OK, 1000, "정상적인 요청입니다."), + CREATED(HttpStatus.CREATED, 1001, "정상적으로 생성되었습니다."), + + // 2000: Common Error + INTERNAL_SERVER_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, 2000, "예기치 못한 오류가 발생했습니다."), + NOT_FOUND_EXCEPTION(HttpStatus.NOT_FOUND, 2001, "존재하지 않는 리소스입니다."), + INVALID_VALUE_EXCEPTION(HttpStatus.BAD_REQUEST, 2002, "올바르지 않은 요청 값입니다."), + UNAUTHORIZED_EXCEPTION(HttpStatus.UNAUTHORIZED, 2003, "권한이 없는 요청입니다."), + ALREADY_DELETE_EXCEPTION(HttpStatus.BAD_REQUEST, 2004, "이미 삭제된 리소스입니다."), + FORBIDDEN_EXCEPTION(HttpStatus.FORBIDDEN, 2005, "인가되지 않는 요청입니다."), + ALREADY_EXIST_EXCEPTION(HttpStatus.BAD_REQUEST, 2006, "이미 존재하는 리소스입니다."), + + + // 3000: Auth Error + KAKAO_TOKEN_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, 3000, "토큰 발급에서 오류가 발생했습니다."), + KAKAO_USER_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, 3001, "카카오 프로필 정보를 가져오는 과정에서 오류가 발생했습니디."), + WRONG_TOKEN_EXCEPTION(HttpStatus.UNAUTHORIZED, 3002, "유효하지 않은 토큰입니다."), + LOGOUT_TOKEN_EXCEPTION(HttpStatus.UNAUTHORIZED, 3003, "로그아웃된 토큰입니다"), + WRONG_TOKEN(HttpStatus.UNAUTHORIZED, 3004, "유효하지 않은 토큰입니다."), + + + //4000: Apply Error + NOT_APPLY_EXCEPTION(HttpStatus.BAD_REQUEST,4000,"지원 기간 지났습니다"), + NOT_FOUND_POST_EXCEPTION(HttpStatus.NOT_FOUND,4001,"존재하지 않는 글입니다."), + NOT_FOUND_APPLY_EXCEPTION(HttpStatus.NOT_FOUND,4002,"존재하지 않는 지원서입니다."), + ALREADY_APPLY_EXCEPTION(HttpStatus.BAD_REQUEST,4003,"이미 지원했습니다."), + ALREADY_DECISION_EXCEPION(HttpStatus.BAD_REQUEST,4004,"이미 지원 결정했습니다."), + NOT_RECRUITING_EXCEPION(HttpStatus.BAD_REQUEST,4005,"이 공고는 모집 중이 아닙니다."), + NOT_FOUND_CATEGORY_EXCEPTION(HttpStatus.NOT_FOUND,4006,"카테고리가 없습니다"), + OVER_APPLY_EXCEPTION(HttpStatus.NOT_FOUND,4007,"지원 파트 정원이 찼습니다."), + INVALID_STACK_TYPE_EXCEPTION(HttpStatus.NOT_FOUND,4008,"기술 스택 없습니다"), + + //5000: Post Error + NOT_POST_EXCEPTION(HttpStatus.BAD_REQUEST,5000,"공고를 더 이상 생성할 수 없습니다"), + POST_VALUE_EXCEPTION(HttpStatus.BAD_REQUEST,5001,"올바르지 않은 요청 값입니다."), + NOT_FOUNT_SCRAP_EXCEPTION(HttpStatus.NOT_FOUND,5002,"스크랩 정보가 존재하지 않습니다."), + ALREADY_FINISH_EXCEPTION(HttpStatus.BAD_REQUEST, 5003, "이미 모집 기간이 마감된 공고입니다."), + ILLEGAL_POST_EXCEPTION(HttpStatus.BAD_REQUEST, 5004, "파트별 인원수가 전체 인원수와 일치하지 않습니다."); + + private final HttpStatus httpStatus; + private final Integer code; + private final String message; + + ErrorCode(HttpStatus httpStatus, Integer code, String message) { + this.httpStatus = httpStatus; + this.code = code; + this.message = message; + } +} diff --git a/src/main/java/Journey/Together/global/exception/ErrorResponse.java b/src/main/java/Journey/Together/global/exception/ErrorResponse.java new file mode 100644 index 0000000..73a0656 --- /dev/null +++ b/src/main/java/Journey/Together/global/exception/ErrorResponse.java @@ -0,0 +1,18 @@ +package Journey.Together.global.exception; + +import java.time.LocalDateTime; + + +public record ErrorResponse( + LocalDateTime timestamp, + Integer code, + String message) { + + public ErrorResponse(ErrorCode errorcode) { + this(LocalDateTime.now(), errorcode.getCode(), errorcode.getMessage()); + } + + public ErrorResponse(String message) { + this(LocalDateTime.now(), ErrorCode.INTERNAL_SERVER_EXCEPTION.getCode(), message); + } +} diff --git a/src/main/java/Journey/Together/global/exception/GlobalExceptionHandler.java b/src/main/java/Journey/Together/global/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..23d077a --- /dev/null +++ b/src/main/java/Journey/Together/global/exception/GlobalExceptionHandler.java @@ -0,0 +1,26 @@ +package Journey.Together.global.exception; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(ApplicationException.class) + protected ResponseEntity handleApplicationException(ApplicationException e){ + log.error(e + " " + e.getErrorCode().toString()); + return ResponseEntity.status(e.getErrorCode().getHttpStatus()) + .body(new ErrorResponse(e.getErrorCode())); + } + + @ExceptionHandler(RuntimeException.class) + protected ResponseEntity handleRuntimeException(RuntimeException e) { + log.error(e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new ErrorResponse(e.getMessage())); + } +} diff --git a/src/main/java/Journey/Together/global/exception/model/BadRequestException.java b/src/main/java/Journey/Together/global/exception/model/BadRequestException.java deleted file mode 100644 index bd35996..0000000 --- a/src/main/java/Journey/Together/global/exception/model/BadRequestException.java +++ /dev/null @@ -1,10 +0,0 @@ -package Journey.Together.global.exception.model; - -import Journey.Together.global.exception.Error; - -public class BadRequestException extends CustomException { - public BadRequestException(Error error, String message) { - super(error, message); - } - -} diff --git a/src/main/java/Journey/Together/global/exception/model/CustomException.java b/src/main/java/Journey/Together/global/exception/model/CustomException.java deleted file mode 100644 index a04d806..0000000 --- a/src/main/java/Journey/Together/global/exception/model/CustomException.java +++ /dev/null @@ -1,18 +0,0 @@ -package Journey.Together.global.exception.model; - -import Journey.Together.global.exception.Error; -import lombok.Getter; - -@Getter -public class CustomException extends RuntimeException{ - private final Error error; - - public CustomException(Error error, String message) { - super(message); - this.error = error; - } - - public int getHttpStatus() { - return error.getErrorCode(); - } -} diff --git a/src/main/java/Journey/Together/global/exception/model/ForbiddenException.java b/src/main/java/Journey/Together/global/exception/model/ForbiddenException.java deleted file mode 100644 index 9b93fdc..0000000 --- a/src/main/java/Journey/Together/global/exception/model/ForbiddenException.java +++ /dev/null @@ -1,10 +0,0 @@ -package Journey.Together.global.exception.model; - -import Journey.Together.global.exception.Error; - -public class ForbiddenException extends CustomException { - public ForbiddenException(Error error, String message) { - super(error, message); - } - -} \ No newline at end of file diff --git a/src/main/java/Journey/Together/global/exception/model/NotFoundException.java b/src/main/java/Journey/Together/global/exception/model/NotFoundException.java deleted file mode 100644 index f1cf639..0000000 --- a/src/main/java/Journey/Together/global/exception/model/NotFoundException.java +++ /dev/null @@ -1,9 +0,0 @@ -package Journey.Together.global.exception.model; - -import Journey.Together.global.exception.Error; - -public class NotFoundException extends CustomException { - public NotFoundException(Error error, String message) { - super(error, message); - } -} diff --git a/src/main/java/Journey/Together/global/exception/model/UnauthorizedException.java b/src/main/java/Journey/Together/global/exception/model/UnauthorizedException.java deleted file mode 100644 index 37587d2..0000000 --- a/src/main/java/Journey/Together/global/exception/model/UnauthorizedException.java +++ /dev/null @@ -1,10 +0,0 @@ -package Journey.Together.global.exception.model; - -import Journey.Together.global.exception.Error; - -public class UnauthorizedException extends CustomException { - public UnauthorizedException(Error error, String message) { - super(error, message); - } - -} diff --git a/src/main/java/Journey/Together/global/exception/model/UnprocessableEntityException.java b/src/main/java/Journey/Together/global/exception/model/UnprocessableEntityException.java deleted file mode 100644 index bc2ad58..0000000 --- a/src/main/java/Journey/Together/global/exception/model/UnprocessableEntityException.java +++ /dev/null @@ -1,9 +0,0 @@ -package Journey.Together.global.exception.model; - -import Journey.Together.global.exception.Error; - -public class UnprocessableEntityException extends CustomException { - public UnprocessableEntityException(Error error, String message) { - super(error, message); - } -} diff --git a/src/main/java/Journey/Together/global/exception/model/net/CustomJavaNetException.java b/src/main/java/Journey/Together/global/exception/model/net/CustomJavaNetException.java deleted file mode 100644 index 764cce5..0000000 --- a/src/main/java/Journey/Together/global/exception/model/net/CustomJavaNetException.java +++ /dev/null @@ -1,21 +0,0 @@ -package Journey.Together.global.exception.model.net; - -import Journey.Together.global.exception.Error; -import lombok.Getter; - -import java.io.IOException; - -@Getter -public class CustomJavaNetException extends IOException { - private final Error error; - - public CustomJavaNetException(Error error, String message) { - super(message); - this.error = error; - } - - public int getHttpStatus() { - return error.getErrorCode(); - } - -} diff --git a/src/main/java/Journey/Together/global/exception/model/net/UnknownHostException.java b/src/main/java/Journey/Together/global/exception/model/net/UnknownHostException.java deleted file mode 100644 index e30f0c5..0000000 --- a/src/main/java/Journey/Together/global/exception/model/net/UnknownHostException.java +++ /dev/null @@ -1,10 +0,0 @@ -package Journey.Together.global.exception.model.net; - -import Journey.Together.global.exception.Error; - -public class UnknownHostException extends CustomJavaNetException { - public UnknownHostException(Error error, String message) { - super(error, message); - } - -}