diff --git a/.github/workflows/dev_deploy_docker_beanstalk.yml b/.github/workflows/dev_deploy_docker_beanstalk.yml index 900dcb3..4752a7e 100644 --- a/.github/workflows/dev_deploy_docker_beanstalk.yml +++ b/.github/workflows/dev_deploy_docker_beanstalk.yml @@ -1,92 +1,84 @@ -#name: suppin dev Docker Beanstalk CI/CD # Workflow 이름 -# -#on: -# push: -# branches: -# - dev -# pull_request: -# branches: -# - dev -# types: [closed] -# workflow_dispatch: # (2).수동 실행도 가능하도록 -# -#jobs: -# build: -# runs-on: ubuntu-latest # (3).OS환경 -# if: github.ref == 'refs/heads/main' || (github.event_name == 'pull_request' && github.event.pull_request.merged == true) -# -# -# steps: -# - name: Checkout current repository -# uses: actions/checkout@v2 -# -# - name: Set up JDK 17 -# uses: actions/setup-java@v1 -# with: -# java-version: 17 -# -# - name: Grant execute permission for gradlew -# run: chmod +x ./gradlew -# shell: bash -# -# - name: Build with Gradle -# run: ./gradlew clean build -# shell: bash -# -# - name: Configure AWS credentials -# uses: aws-actions/configure-aws-credentials@v1 -# with: -# aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} -# aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} -# aws-region: ap-northeast-2 -# -# - name: Login to Amazon ECR -# id: login-ecr -# uses: aws-actions/amazon-ecr-login@v1 -# -# - name: Build, tag, and push image to Amazon ECR -# id: build-image -# env: -# ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} -# ECR_REPOSITORY: tree-dev -# IMAGE_TAG: latest -# run: | -# # Docker 이미지 빌드 -# docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . -# -# # 빌드한 이미지를 Amazon ECR로 푸시 -# docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -# -# # 빌드된 이미지의 정보 출력 -# echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" -# -# - name: Get current time -# uses: 1466587594/get-current-time@v2 -# id: current-time -# with: -# format: YYYYMMDD_HH-mm-ss -# utcOffset: "+09:00" -# -# - name: Generate deployment package -# run: | -# mkdir -p deploy -# cp -r .ebextensions deploy/.ebextensions -# cp Dockerrun.aws.json deploy/Dockerrun.aws.json -# cp -r .platform deploy/.platform -# cd deploy && zip -r deploy.zip . -# -# - name: Beanstalk Deploy -# uses: einaregilsson/beanstalk-deploy@v14 -# with: -# aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }} -# aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} -# application_name: suppin-dev -# environment_name: suppin-dev-env -# version_label: github-action-${{ steps.current-time.outputs.formattedTime }} -# region: ap-northeast-2 -# deployment_package: deploy/deploy.zip -# wait_for_deployment: false -# -# -# -# +name: suppin dev Docker Beanstalk CI/CD # Workflow 이름 + +on: + +jobs: + build: + runs-on: ubuntu-latest # (3).OS환경 + if: github.ref == 'refs/heads/main' || (github.event_name == 'pull_request' && github.event.pull_request.merged == true) + + + steps: + - name: Checkout current repository + uses: actions/checkout@v2 + + - name: Set up JDK 17 + uses: actions/setup-java@v1 + with: + java-version: 17 + + - name: Grant execute permission for gradlew + run: chmod +x ./gradlew + shell: bash + + - name: Build with Gradle + run: ./gradlew clean build + shell: bash + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ap-northeast-2 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + id: build-image + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: tree-dev + IMAGE_TAG: latest + run: | + # Docker 이미지 빌드 + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . + + # 빌드한 이미지를 Amazon ECR로 푸시 + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + + # 빌드된 이미지의 정보 출력 + echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" + + - name: Get current time + uses: 1466587594/get-current-time@v2 + id: current-time + with: + format: YYYYMMDD_HH-mm-ss + utcOffset: "+09:00" + + - name: Generate deployment package + run: | + mkdir -p deploy + cp -r .ebextensions deploy/.ebextensions + cp Dockerrun.aws.json deploy/Dockerrun.aws.json + cp -r .platform deploy/.platform + cd deploy && zip -r deploy.zip . + + - name: Beanstalk Deploy + uses: einaregilsson/beanstalk-deploy@v14 + with: + aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + application_name: suppin-dev + environment_name: suppin-dev-env + version_label: github-action-${{ steps.current-time.outputs.formattedTime }} + region: ap-northeast-2 + deployment_package: deploy/deploy.zip + wait_for_deployment: false + + + + diff --git a/build.gradle b/build.gradle index 03057f5..8e3b645 100644 --- a/build.gradle +++ b/build.gradle @@ -1,49 +1,54 @@ plugins { - id 'java' - id 'org.springframework.boot' version '2.7.7' - id 'io.spring.dependency-management' version '1.1.4' + id 'java' + id 'org.springframework.boot' version '3.2.8' + id 'io.spring.dependency-management' version '1.1.6' } group = 'com.cmc' version = '0.0.1-SNAPSHOT' java { - sourceCompatibility = '17' - targetCompatibility = '17' - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } + sourceCompatibility = '17' + targetCompatibility = '17' + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } } configurations { - compileOnly { - extendsFrom annotationProcessor - } + compileOnly { + extendsFrom annotationProcessor + } } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'javax.servlet:javax.servlet-api:4.0.1' - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springdoc:springdoc-openapi-ui:1.6.11' - implementation 'org.springframework.boot:spring-boot-starter-validation' - - compileOnly 'org.projectlombok:lombok' - developmentOnly 'org.springframework.boot:spring-boot-devtools' - 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.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' + + // JWT dependencies + implementation 'io.jsonwebtoken:jjwt-api:0.12.2' + implementation 'io.jsonwebtoken:jjwt-impl:0.12.2' + implementation 'io.jsonwebtoken:jjwt-jackson:0.12.2' + + compileOnly 'org.projectlombok:lombok' + developmentOnly 'org.springframework.boot:spring-boot-devtools' + 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' } tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } jar { - enabled = false + enabled = false } diff --git a/src/main/java/com/cmc/suppin/answer/domain/AnonymousParticipant.java b/src/main/java/com/cmc/suppin/answer/domain/AnonymousParticipant.java index 589b8ac..23e80d7 100644 --- a/src/main/java/com/cmc/suppin/answer/domain/AnonymousParticipant.java +++ b/src/main/java/com/cmc/suppin/answer/domain/AnonymousParticipant.java @@ -2,8 +2,8 @@ import com.cmc.suppin.global.domain.BaseDateTimeEntity; import com.cmc.suppin.survey.domain.Survey; +import jakarta.persistence.*; -import javax.persistence.*; import java.util.ArrayList; import java.util.List; @@ -18,8 +18,11 @@ public class AnonymousParticipant extends BaseDateTimeEntity { @JoinColumn(name = "survey_id") private Survey survey; - @Column(columnDefinition = "VARCHAR(255)", nullable = false) - private String sessionId; + @Column(columnDefinition = "VARCHAR(13)", nullable = false) + private String phoneNumber; + + @Column(nullable = false) + private Boolean isAgreed; @OneToMany(mappedBy = "anonymousParticipant") private List answerList = new ArrayList<>(); diff --git a/src/main/java/com/cmc/suppin/answer/domain/Answer.java b/src/main/java/com/cmc/suppin/answer/domain/Answer.java index 3fcdffa..cefb10d 100644 --- a/src/main/java/com/cmc/suppin/answer/domain/Answer.java +++ b/src/main/java/com/cmc/suppin/answer/domain/Answer.java @@ -2,8 +2,8 @@ import com.cmc.suppin.global.domain.BaseDateTimeEntity; import com.cmc.suppin.survey.domain.Question; +import jakarta.persistence.*; -import javax.persistence.*; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/cmc/suppin/answer/domain/AnswerOption.java b/src/main/java/com/cmc/suppin/answer/domain/AnswerOption.java index 2f06669..4240668 100644 --- a/src/main/java/com/cmc/suppin/answer/domain/AnswerOption.java +++ b/src/main/java/com/cmc/suppin/answer/domain/AnswerOption.java @@ -1,8 +1,8 @@ package com.cmc.suppin.answer.domain; import com.cmc.suppin.survey.domain.QuestionOption; +import jakarta.persistence.*; -import javax.persistence.*; @Entity public class AnswerOption { diff --git a/src/main/java/com/cmc/suppin/comment/domain/Comment.java b/src/main/java/com/cmc/suppin/comment/domain/Comment.java index f6f2734..855e686 100644 --- a/src/main/java/com/cmc/suppin/comment/domain/Comment.java +++ b/src/main/java/com/cmc/suppin/comment/domain/Comment.java @@ -2,8 +2,8 @@ import com.cmc.suppin.event.domain.Event; import com.cmc.suppin.global.domain.BaseDateTimeEntity; +import jakarta.persistence.*; -import javax.persistence.*; @Entity public class Comment extends BaseDateTimeEntity { diff --git a/src/main/java/com/cmc/suppin/event/domain/Event.java b/src/main/java/com/cmc/suppin/event/domain/Event.java index 8e150f8..3affeb8 100644 --- a/src/main/java/com/cmc/suppin/event/domain/Event.java +++ b/src/main/java/com/cmc/suppin/event/domain/Event.java @@ -5,8 +5,8 @@ import com.cmc.suppin.global.enums.EventType; import com.cmc.suppin.member.domain.Member; import com.cmc.suppin.survey.domain.Survey; +import jakarta.persistence.*; -import javax.persistence.*; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/cmc/suppin/global/domain/BaseDateTimeEntity.java b/src/main/java/com/cmc/suppin/global/domain/BaseDateTimeEntity.java index 196f352..b3eeb7a 100644 --- a/src/main/java/com/cmc/suppin/global/domain/BaseDateTimeEntity.java +++ b/src/main/java/com/cmc/suppin/global/domain/BaseDateTimeEntity.java @@ -1,13 +1,13 @@ package com.cmc.suppin.global.domain; +import jakarta.persistence.Column; +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 javax.persistence.Column; -import javax.persistence.EntityListeners; -import javax.persistence.MappedSuperclass; import java.time.LocalDateTime; @EntityListeners(AuditingEntityListener.class) diff --git a/src/main/java/com/cmc/suppin/global/exception/ExceptionAdvice.java b/src/main/java/com/cmc/suppin/global/exception/ExceptionAdvice.java index 8a76b7b..99a7a54 100644 --- a/src/main/java/com/cmc/suppin/global/exception/ExceptionAdvice.java +++ b/src/main/java/com/cmc/suppin/global/exception/ExceptionAdvice.java @@ -1,24 +1,21 @@ package com.cmc.suppin.global.exception; +import com.cmc.suppin.global.exception.status.ErrorStatus; +import com.cmc.suppin.global.presentation.ApiResponse; +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.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 com.cmc.suppin.global.exception.status.ErrorStatus; -import com.cmc.suppin.global.presentation.ApiResponse; -import javax.servlet.http.HttpServletRequest; -import javax.validation.ConstraintViolationException; -import java.util.LinkedHashMap; import java.util.Map; -import java.util.Optional; @Slf4j @RestControllerAdvice(annotations = {RestController.class}) @@ -34,6 +31,7 @@ public ResponseEntity validation(ConstraintViolationException e, WebRequ return handleExceptionInternalConstraint(e, ErrorStatus.valueOf(errorMessage), HttpHeaders.EMPTY, request); } + /* @Override public ResponseEntity handleMethodArgumentNotValid( MethodArgumentNotValidException e, HttpHeaders headers, HttpStatus status, WebRequest request) { @@ -49,6 +47,8 @@ public ResponseEntity handleMethodArgumentNotValid( return handleExceptionInternalArgs(e, HttpHeaders.EMPTY, ErrorStatus.valueOf("_BAD_REQUEST"), request, errors); } + */ + @org.springframework.web.bind.annotation.ExceptionHandler public ResponseEntity exception(Exception e, WebRequest request) { diff --git a/src/main/java/com/cmc/suppin/global/exception/status/ErrorStatus.java b/src/main/java/com/cmc/suppin/global/exception/status/ErrorStatus.java index 2188d1e..daa338a 100644 --- a/src/main/java/com/cmc/suppin/global/exception/status/ErrorStatus.java +++ b/src/main/java/com/cmc/suppin/global/exception/status/ErrorStatus.java @@ -21,7 +21,7 @@ public enum ErrorStatus implements BaseErrorCode { TEMP_EXCEPTION(HttpStatus.BAD_REQUEST, "TEMP4001", "테스트"), // Member - MEMBER_NICKNAME_DUPLICATED(HttpStatus.BAD_REQUEST, "MEMBER4001", "중복된 닉네임 입니다."), + MEMBER_USERID_DUPLICATED(HttpStatus.BAD_REQUEST, "MEMBER4001", "중복된 아이디 입니다."), MEMBER_PASSWORD_ERROR(HttpStatus.BAD_REQUEST, "MEMBER4002", "비밀번호가 잘못되었습니다."), //JWT diff --git a/src/main/java/com/cmc/suppin/global/exception/status/SuccessStatus.java b/src/main/java/com/cmc/suppin/global/exception/status/SuccessStatus.java index 9b9f06b..4826f9d 100644 --- a/src/main/java/com/cmc/suppin/global/exception/status/SuccessStatus.java +++ b/src/main/java/com/cmc/suppin/global/exception/status/SuccessStatus.java @@ -12,7 +12,10 @@ public enum SuccessStatus implements BaseCode { _OK(HttpStatus.OK, "COMMON200", "성공입니다."), //Member - MEMBER_DELETE_SUCCESS(HttpStatus.OK, "MEMBER2001", "회원 탈퇴 성공입니다."), + MEMBER_JOIN_SUCCESS(HttpStatus.OK, "MEMBER2000", "회원 가입 성공입니다."), + MEMBER_ID_CONFIRM_SUCCESS(HttpStatus.OK, "MEMBER2001", "아이디가 중복되지 않습니다."), + MEMBER_DELETE_SUCCESS(HttpStatus.OK, "MEMBER2002", "회원 탈퇴 성공입니다."), + MEMBER_LOGIN_SUCCESS(HttpStatus.OK, "MEMBER2003", "로그인 성공입니다."), ; private final HttpStatus httpStatus; diff --git a/src/main/java/com/cmc/suppin/global/presentation/ApiResponse.java b/src/main/java/com/cmc/suppin/global/presentation/ApiResponse.java index d239b00..d4827a2 100644 --- a/src/main/java/com/cmc/suppin/global/presentation/ApiResponse.java +++ b/src/main/java/com/cmc/suppin/global/presentation/ApiResponse.java @@ -1,11 +1,11 @@ package com.cmc.suppin.global.presentation; +import com.cmc.suppin.global.exception.BaseCode; +import com.cmc.suppin.global.exception.status.SuccessStatus; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import lombok.AllArgsConstructor; import lombok.Getter; -import com.cmc.suppin.global.exception.BaseCode; -import com.cmc.suppin.global.exception.status.SuccessStatus; @Getter @AllArgsConstructor @@ -23,6 +23,10 @@ public static ApiResponse onSuccess(T result) { return new ApiResponse<>(true, SuccessStatus._OK.getCode(), SuccessStatus._OK.getMessage(), result); } + public static ApiResponse onSuccess(T result, SuccessStatus successStatus) { + return new ApiResponse<>(true, successStatus.getCode(), successStatus.getMessage(), result); + } + public static ApiResponse of(BaseCode code, T result) { return new ApiResponse<>(true, code.getReasonHttpStatus().getCode(), code.getReasonHttpStatus().getMessage(), result); } diff --git a/src/main/java/com/cmc/suppin/global/security/SecurityConfig.java b/src/main/java/com/cmc/suppin/global/security/SecurityConfig.java new file mode 100644 index 0000000..3b52087 --- /dev/null +++ b/src/main/java/com/cmc/suppin/global/security/SecurityConfig.java @@ -0,0 +1,108 @@ +package com.cmc.suppin.global.security; + +import com.cmc.suppin.global.security.jwt.JWTFilter; +import com.cmc.suppin.global.security.jwt.JWTUtil; +import com.cmc.suppin.global.security.jwt.LoginFilter; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; + +import java.util.Collections; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + //AuthenticationManager가 인자로 받을 AuthenticationConfiguraion 객체 생성자 주입 + private final AuthenticationConfiguration authenticationConfiguration; + + //JWTUtil 주입 + private final JWTUtil jwtUtil; + + public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JWTUtil jwtUtil) { + + this.authenticationConfiguration = authenticationConfiguration; + this.jwtUtil = jwtUtil; + } + + //AuthenticationManager Bean 등록 + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { + + return configuration.getAuthenticationManager(); + } + + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + + http + .cors((corsCustomizer -> corsCustomizer.configurationSource(new CorsConfigurationSource() { + + @Override + public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { + + CorsConfiguration configuration = new CorsConfiguration(); + + configuration.setAllowedOrigins(Collections.singletonList("http://localhost:3000")); + configuration.setAllowedMethods(Collections.singletonList("*")); + configuration.setAllowCredentials(true); + configuration.setAllowedHeaders(Collections.singletonList("*")); + configuration.setMaxAge(3600L); + + configuration.setExposedHeaders(Collections.singletonList("Authorization")); + + return configuration; + } + }))); + + //csrf disable + http + .csrf((auth) -> auth.disable()); + + //From 로그인 방식 disable + http + .formLogin((auth) -> auth.disable()); + + //http basic 인증 방식 disable + http + .httpBasic((auth) -> auth.disable()); + + //경로별 인가 작업 + http + .authorizeHttpRequests((auth) -> auth + .anyRequest().permitAll()); + + //JWTFilter 추가 + http + .addFilterBefore(new JWTFilter(jwtUtil), LoginFilter.class); + + //LoginFilter 추가 LoginFilter()는 인자를 받음 (AuthenticationManager() 메소드에 authenticationConfiguration 객체를 넣어야 함) 따라서 등록 필요 + http + .addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil), UsernamePasswordAuthenticationFilter.class); + + //세션 설정 + http + .sessionManagement((session) -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + + + return http.build(); + } +} + diff --git a/src/main/java/com/cmc/suppin/global/security/jwt/JWTFilter.java b/src/main/java/com/cmc/suppin/global/security/jwt/JWTFilter.java new file mode 100644 index 0000000..e57b279 --- /dev/null +++ b/src/main/java/com/cmc/suppin/global/security/jwt/JWTFilter.java @@ -0,0 +1,72 @@ +package com.cmc.suppin.global.security.jwt; + +import com.cmc.suppin.member.controller.dto.MemberDetails; +import com.cmc.suppin.member.domain.Member; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +public class JWTFilter extends OncePerRequestFilter { + + private final JWTUtil jwtUtil; + + public JWTFilter(JWTUtil jwtUtil) { + this.jwtUtil = jwtUtil; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + + //request에서 Authorization 헤더를 찾음 + String authorization = request.getHeader("Authorization"); + + //Authorization 헤더 검증(토큰이 없는 경우 처리해주는 부분) + if (authorization == null || !authorization.startsWith("Bearer ")) { + + System.out.println("token null"); + filterChain.doFilter(request, response); + + //조건이 해당되면 메소드 종료 (필수) + return; + } + + //Bearer 부분 제거 후 순수 토큰만 획득 + String token = authorization.split(" ")[1]; + + //토큰 소멸 시간 검증(토큰 만료시 처리해주는 부분) + if (jwtUtil.isExpired(token)) { + + System.out.println("token expired"); + filterChain.doFilter(request, response); + + //조건이 해당되면 메소드 종료 (필수) + return; + } + + //토큰에서 username과 role 획득 + String username = jwtUtil.getUsername(token); + String role = jwtUtil.getRole(token); + + // Member Entity를 생성하여 값 set + Member member = new Member(username, "tempPassword", "tempEmail", "tempPhoneNumber", role); + + + //MemberDetails에 회원 정보 객체 담기 + MemberDetails customUserDetails = new MemberDetails(member); + + //스프링 시큐리티 인증 토큰 생성 + Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities()); + //세션에 사용자 등록 + SecurityContextHolder.getContext().setAuthentication(authToken); + + filterChain.doFilter(request, response); + } +} + diff --git a/src/main/java/com/cmc/suppin/global/security/jwt/JWTUtil.java b/src/main/java/com/cmc/suppin/global/security/jwt/JWTUtil.java new file mode 100644 index 0000000..358c6a1 --- /dev/null +++ b/src/main/java/com/cmc/suppin/global/security/jwt/JWTUtil.java @@ -0,0 +1,48 @@ +package com.cmc.suppin.global.security.jwt; + +import io.jsonwebtoken.Jwts; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +@Component +public class JWTUtil { + + private SecretKey secretKey; + + public JWTUtil(@Value("${JWT_TOKEN_SECRET}") String secret) { + + this.secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm()); + } + + public String getUsername(String token) { + + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("username", String.class); + } + + public String getRole(String token) { + + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("role", String.class); + } + + public Boolean isExpired(String token) { + + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date()); + } + + public String createJwt(String userId, String role, Long expiredMs) { + + return Jwts.builder() + .claim("userId", userId) + .claim("role", role) + .issuedAt(new Date(System.currentTimeMillis())) + .expiration(new Date(System.currentTimeMillis() + expiredMs)) + .signWith(secretKey) + .compact(); + } +} + diff --git a/src/main/java/com/cmc/suppin/global/security/jwt/LoginFilter.java b/src/main/java/com/cmc/suppin/global/security/jwt/LoginFilter.java new file mode 100644 index 0000000..fd9a419 --- /dev/null +++ b/src/main/java/com/cmc/suppin/global/security/jwt/LoginFilter.java @@ -0,0 +1,77 @@ +package com.cmc.suppin.global.security.jwt; + +import com.cmc.suppin.member.controller.dto.MemberDetails; +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +import java.util.Collection; +import java.util.Iterator; + +public class LoginFilter extends UsernamePasswordAuthenticationFilter { + + private final AuthenticationManager authenticationManager; + + private final JWTUtil jwtUtil; + + public LoginFilter(AuthenticationManager authenticationManager, JWTUtil jwtUtil) { + + this.authenticationManager = authenticationManager; + this.jwtUtil = jwtUtil; + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { + + //클라이언트 요청에서 username, password 추출 + String username = obtainUsername(request); + String password = obtainPassword(request); + + //스프링 시큐리티에서 username과 password를 검증하기 위해서는 token에 담아야 함 + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, null); + + //token에 담은 검증을 위한 AuthenticationManager로 전달 + return authenticationManager.authenticate(authToken); + } + + //로그인 성공시 실행하는 메소드 (여기서 JWT를 발급됨) + @Override + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) { + + MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal(); + + String username = memberDetails.getUsername(); + + Collection authorities = authentication.getAuthorities(); + if (authorities.isEmpty()) { + throw new IllegalStateException("권한이 없습니다."); + } + + Iterator iterator = authorities.iterator(); + GrantedAuthority auth = iterator.next(); + + String role = auth.getAuthority(); + + long expirationTime = 1000 * 60 * 60 * 24 * 7; // 7일 + + String token = jwtUtil.createJwt(username, role, expirationTime); + + response.addHeader("Authorization", "Bearer " + token); + } + + + //로그인 실패시 실행하는 메소드 + @Override + protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) { + + //로그인 실패시 401 응답코드 반환 + response.setStatus(401); + } +} + diff --git a/src/main/java/com/cmc/suppin/member/controller/.gitkeep b/src/main/java/com/cmc/suppin/member/controller/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/cmc/suppin/member/controller/MemberApi.java b/src/main/java/com/cmc/suppin/member/controller/MemberApi.java new file mode 100644 index 0000000..851b686 --- /dev/null +++ b/src/main/java/com/cmc/suppin/member/controller/MemberApi.java @@ -0,0 +1,66 @@ +package com.cmc.suppin.member.controller; + +import com.cmc.suppin.global.exception.status.SuccessStatus; +import com.cmc.suppin.global.presentation.ApiResponse; +import com.cmc.suppin.member.controller.dto.MemberDetails; +import com.cmc.suppin.member.controller.dto.MemberRequestDTO; +import com.cmc.suppin.member.controller.dto.MemberResponseDTO; +import com.cmc.suppin.member.converter.MemberConverter; +import com.cmc.suppin.member.domain.Member; +import com.cmc.suppin.member.service.command.MemberCommandService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@RestController +@Slf4j +@RequiredArgsConstructor +@Validated +@Tag(name = "Member", description = "Member 관련 API") +@RequestMapping("/api/members") +public class MemberApi { + + private final MemberCommandService memberCommandService; + + // 회원가입 + @PostMapping("/join") + @Operation(summary = "회원가입 API", description = "request 파라미터 : id, password, name, phone, email") + public ApiResponse join(@RequestBody @Valid MemberRequestDTO.JoinDTO request) { + Member member = memberCommandService.join(request); + + return ApiResponse.onSuccess(MemberConverter.toJoinResultDTO(member), SuccessStatus.MEMBER_JOIN_SUCCESS); + + } + + @PostMapping("/checkUserId") + @Operation(summary = "아이디 중복 체크 API", description = "request : userId, response: 중복이면 false, 중복 아니면 true") + public ApiResponse checkUserId(@RequestBody MemberRequestDTO.IdConfirmDTO request) { + boolean checkUserId = memberCommandService.confirmUserId(request); + + return ApiResponse.onSuccess(MemberConverter.toIdConfirmResultDTO(checkUserId), SuccessStatus.MEMBER_ID_CONFIRM_SUCCESS); + } + + @DeleteMapping("/delete") + @Operation(summary = "회원탈퇴 API", description = "JWT 토큰을 헤더에 포함시켜 보내주시면 됩니다.") + public ApiResponse deleteMember(@AuthenticationPrincipal MemberDetails memberDetails) { + if (memberDetails == null) { + return ApiResponse.onFailure("403", "인증된 사용자만 삭제할 수 있습니다.", null); + } + memberCommandService.deleteMember(memberDetails.getUserId()); + return ApiResponse.onSuccess(null, SuccessStatus.MEMBER_DELETE_SUCCESS); + } + + // 로그인 + @PostMapping("/login") + @Operation(summary = "로그인 API", description = "request : userId, password") + public ApiResponse login(@RequestBody @Valid MemberRequestDTO.LoginRequestDTO request) { + MemberResponseDTO.LoginResponseDTO response = memberCommandService.login(request); + return ApiResponse.onSuccess(response, SuccessStatus.MEMBER_LOGIN_SUCCESS); + } + +} diff --git a/src/main/java/com/cmc/suppin/member/controller/dto/MemberDetails.java b/src/main/java/com/cmc/suppin/member/controller/dto/MemberDetails.java new file mode 100644 index 0000000..db9b49b --- /dev/null +++ b/src/main/java/com/cmc/suppin/member/controller/dto/MemberDetails.java @@ -0,0 +1,67 @@ +package com.cmc.suppin.member.controller.dto; + +import com.cmc.suppin.member.domain.Member; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.ArrayList; +import java.util.Collection; + +public class MemberDetails implements UserDetails { + + private final Member member; + + public MemberDetails(Member member) { + this.member = member; + } + + @Override + public Collection getAuthorities() { + + Collection collection = new ArrayList<>(); + + collection.add(new GrantedAuthority() { + @Override + public String getAuthority() { + return member.getRole(); + } + }); + + return collection; + } + + @Override + public String getPassword() { + return member.getPassword(); + } + + @Override + public String getUsername() { + return member.getUserId(); + } + + public String getUserId() { + return member.getUserId(); + } + + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/com/cmc/suppin/member/controller/dto/MemberRequestDTO.java b/src/main/java/com/cmc/suppin/member/controller/dto/MemberRequestDTO.java new file mode 100644 index 0000000..91b4604 --- /dev/null +++ b/src/main/java/com/cmc/suppin/member/controller/dto/MemberRequestDTO.java @@ -0,0 +1,50 @@ +package com.cmc.suppin.member.controller.dto; + +import jakarta.persistence.Id; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class MemberRequestDTO { + + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class JoinDTO { + @NotBlank(message = "아이디를 입력해주세요") + @Id + private String userId; + + @NotBlank(message = "이름을 입력해주세요") + private String name; + + @NotBlank(message = "비밀번호를 입력해주세요") + @Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}", message = "비밀번호는 8~20자 영문, 숫자, 특수문자를 사용하세요.") + private String password; + + @NotBlank(message = "이메일을 입력해주세요") + @Email(message = "이메일 형식이 올바르지 않습니다.") + private String email; + + @NotBlank(message = "휴대폰 번호를 입력해주세요") + private String phone; + } + + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class IdConfirmDTO { + private String userId; + } + + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class LoginRequestDTO { + private String userId; + private String password; + } +} diff --git a/src/main/java/com/cmc/suppin/member/controller/dto/MemberResponseDTO.java b/src/main/java/com/cmc/suppin/member/controller/dto/MemberResponseDTO.java new file mode 100644 index 0000000..86bbad1 --- /dev/null +++ b/src/main/java/com/cmc/suppin/member/controller/dto/MemberResponseDTO.java @@ -0,0 +1,40 @@ +package com.cmc.suppin.member.controller.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +public class MemberResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class JoinResultDTO { + Long memberId; + String userId; + String name; + String email; + LocalDateTime createdAt; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class IdConfirmResultDTO { + Boolean checkUserId; + } + + @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class LoginResponseDTO { + private String token; + private String userId; + } +} diff --git a/src/main/java/com/cmc/suppin/member/converter/.gitkeep b/src/main/java/com/cmc/suppin/member/converter/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java b/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java new file mode 100644 index 0000000..e7f6681 --- /dev/null +++ b/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java @@ -0,0 +1,46 @@ +package com.cmc.suppin.member.converter; + +import com.cmc.suppin.member.controller.dto.MemberRequestDTO; +import com.cmc.suppin.member.controller.dto.MemberResponseDTO; +import com.cmc.suppin.member.domain.Member; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +@Component +public class MemberConverter { + + public Member toEntity(MemberRequestDTO.JoinDTO request, BCryptPasswordEncoder encoder) { + return Member.builder() + .userId(request.getUserId()) + .name(request.getName()) + .password(encoder.encode(request.getPassword())) + .email(request.getEmail()) + .phoneNumber(request.getPhone()) + .build(); + } + + public static MemberResponseDTO.JoinResultDTO toJoinResultDTO(Member member) { + return MemberResponseDTO.JoinResultDTO.builder() + .memberId(member.getId()) + .userId(member.getUserId()) + .name(member.getName()) + .email(member.getEmail()) + .createdAt(LocalDateTime.now()) + .build(); + } + + public static MemberResponseDTO.IdConfirmResultDTO toIdConfirmResultDTO(boolean checkUserId) { + return MemberResponseDTO.IdConfirmResultDTO.builder() + .checkUserId(checkUserId) + .build(); + } + + public static MemberResponseDTO.LoginResponseDTO toLoginResponseDTO(String token, Member member) { + return MemberResponseDTO.LoginResponseDTO.builder() + .token(token) + .userId(member.getUserId()) + .build(); + } +} diff --git a/src/main/java/com/cmc/suppin/member/domain/Member.java b/src/main/java/com/cmc/suppin/member/domain/Member.java index fff34e7..f1cf504 100644 --- a/src/main/java/com/cmc/suppin/member/domain/Member.java +++ b/src/main/java/com/cmc/suppin/member/domain/Member.java @@ -2,12 +2,19 @@ import com.cmc.suppin.event.domain.Event; import com.cmc.suppin.global.domain.BaseDateTimeEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.DynamicInsert; -import javax.persistence.*; import java.util.ArrayList; import java.util.List; @Entity +@Getter +@Builder +@DynamicInsert +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor public class Member extends BaseDateTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -17,12 +24,12 @@ public class Member extends BaseDateTimeEntity { @OneToMany(mappedBy = "member") private List eventList = new ArrayList<>(); + @Column(columnDefinition = "VARCHAR(30)", nullable = false) + private String userId; + @Column(columnDefinition = "VARCHAR(20)", nullable = false) private String name; - @Column(columnDefinition = "VARCHAR(30)", nullable = false) - private String nickname; - @Column(columnDefinition = "VARCHAR(30)", nullable = false) private String email; @@ -32,5 +39,15 @@ public class Member extends BaseDateTimeEntity { @Column(columnDefinition = "VARCHAR(13)", nullable = false) private String phoneNumber; + private String role; + + // 추가된 생성자 + public Member(String name, String password, String email, String phoneNumber, String role) { + this.name = name; + this.password = password; + this.email = email; + this.phoneNumber = phoneNumber; + this.role = role; + } } diff --git a/src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java b/src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java new file mode 100644 index 0000000..fb6eedd --- /dev/null +++ b/src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java @@ -0,0 +1,15 @@ +package com.cmc.suppin.member.domain.repository; + +import com.cmc.suppin.member.domain.Member; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface MemberRepository extends JpaRepository { + + Boolean existsByUserId(String userId); + + Optional findByUserId(String userId); + + void deleteByUserId(String userId); +} diff --git a/src/main/java/com/cmc/suppin/member/service/.gitkeep b/src/main/java/com/cmc/suppin/member/service/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/cmc/suppin/member/service/command/MemberCommandService.java b/src/main/java/com/cmc/suppin/member/service/command/MemberCommandService.java new file mode 100644 index 0000000..051b4c1 --- /dev/null +++ b/src/main/java/com/cmc/suppin/member/service/command/MemberCommandService.java @@ -0,0 +1,16 @@ +package com.cmc.suppin.member.service.command; + +import com.cmc.suppin.member.controller.dto.MemberRequestDTO; +import com.cmc.suppin.member.controller.dto.MemberResponseDTO; +import com.cmc.suppin.member.domain.Member; + +public interface MemberCommandService { + + Member join(MemberRequestDTO.JoinDTO request); + + Boolean confirmUserId(MemberRequestDTO.IdConfirmDTO request); + + void deleteMember(String memberId); + + MemberResponseDTO.LoginResponseDTO login(MemberRequestDTO.LoginRequestDTO request); +} diff --git a/src/main/java/com/cmc/suppin/member/service/command/MemberCommandServiceImpl.java b/src/main/java/com/cmc/suppin/member/service/command/MemberCommandServiceImpl.java new file mode 100644 index 0000000..e5223fb --- /dev/null +++ b/src/main/java/com/cmc/suppin/member/service/command/MemberCommandServiceImpl.java @@ -0,0 +1,90 @@ +package com.cmc.suppin.member.service.command; + +import com.cmc.suppin.global.security.jwt.JWTUtil; +import com.cmc.suppin.member.controller.dto.MemberRequestDTO; +import com.cmc.suppin.member.controller.dto.MemberResponseDTO; +import com.cmc.suppin.member.converter.MemberConverter; +import com.cmc.suppin.member.domain.Member; +import com.cmc.suppin.member.domain.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Slf4j +@RequiredArgsConstructor +@Transactional +public class MemberCommandServiceImpl implements MemberCommandService { + + private final MemberRepository memberRepository; + private final BCryptPasswordEncoder bCryptPasswordEncoder; + private final MemberConverter memberConverter; + private final JWTUtil jwtUtil; + + /** + * 회원가입 + */ + @Override + public Member join(MemberRequestDTO.JoinDTO request) { + // 중복된 아이디 체크 + if (memberRepository.existsByUserId(request.getUserId())) { + throw new IllegalArgumentException("이미 존재하는 유저입니다."); + } + + // 비밀번호 조건 검증 + String password = request.getPassword(); + if (!isValidPassword(password)) { + throw new IllegalArgumentException("비밀번호는 8~20자 영문, 숫자, 특수문자를 사용해야 합니다."); + } + + // DTO를 Entity로 변환 + Member member = memberConverter.toEntity(request, bCryptPasswordEncoder); + + // 회원 정보 저장 + memberRepository.save(member); + + return member; + } + + // 비밀번호 조건 검증 메서드 + private boolean isValidPassword(String password) { + return password.matches("(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}"); + } + + /** + * ID 중복 확인 + */ + @Override + public Boolean confirmUserId(MemberRequestDTO.IdConfirmDTO request) { + // 아이디 중복 체크 + return !memberRepository.existsByUserId(request.getUserId()); + } + + /** + * 회원 탈퇴 + */ + @Override + public void deleteMember(String memberId) { + memberRepository.deleteByUserId(memberId); + } + + /** + * 로그인 + */ + @Override + public MemberResponseDTO.LoginResponseDTO login(MemberRequestDTO.LoginRequestDTO request) { + Member member = memberRepository.findByUserId(request.getUserId()) + .orElseThrow(() -> new IllegalArgumentException("Invalid user ID or password")); + + if (!bCryptPasswordEncoder.matches(request.getPassword(), member.getPassword())) { + throw new IllegalArgumentException("Invalid user ID or password"); + } + + String token = jwtUtil.createJwt(member.getUserId(), member.getRole(), 604800000L); // 1주일 유효 토큰 + return MemberConverter.toLoginResponseDTO(token, member); + } + + +} diff --git a/src/main/java/com/cmc/suppin/survey/domain/Question.java b/src/main/java/com/cmc/suppin/survey/domain/Question.java index 173e01d..42b34c9 100644 --- a/src/main/java/com/cmc/suppin/survey/domain/Question.java +++ b/src/main/java/com/cmc/suppin/survey/domain/Question.java @@ -2,8 +2,8 @@ import com.cmc.suppin.answer.domain.Answer; import com.cmc.suppin.global.enums.QuestionType; +import jakarta.persistence.*; -import javax.persistence.*; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/cmc/suppin/survey/domain/QuestionOption.java b/src/main/java/com/cmc/suppin/survey/domain/QuestionOption.java index 1fc91e6..f940722 100644 --- a/src/main/java/com/cmc/suppin/survey/domain/QuestionOption.java +++ b/src/main/java/com/cmc/suppin/survey/domain/QuestionOption.java @@ -1,8 +1,8 @@ package com.cmc.suppin.survey.domain; import com.cmc.suppin.answer.domain.AnswerOption; +import jakarta.persistence.*; -import javax.persistence.*; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/cmc/suppin/survey/domain/Survey.java b/src/main/java/com/cmc/suppin/survey/domain/Survey.java index b5948ea..7fa4fb6 100644 --- a/src/main/java/com/cmc/suppin/survey/domain/Survey.java +++ b/src/main/java/com/cmc/suppin/survey/domain/Survey.java @@ -3,8 +3,8 @@ import com.cmc.suppin.answer.domain.AnonymousParticipant; import com.cmc.suppin.event.domain.Event; import com.cmc.suppin.global.domain.BaseDateTimeEntity; +import jakarta.persistence.*; -import javax.persistence.*; import java.util.ArrayList; import java.util.List; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 82665d1..9d59683 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,4 +1,6 @@ spring: + server: + port: 8080 datasource: url: ${DB_URL} username: ${DB_USER}