diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml new file mode 100644 index 0000000..c0f2097 --- /dev/null +++ b/.github/workflows/build_and_deploy.yml @@ -0,0 +1,58 @@ +name: Deploy to DockerHub & EC2 + +on: + push: + branches: [ main, develop ] + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 + with: + cache-read-only: false + + - name: Grant Execute Permission For Gradlew + run: chmod +x gradlew + + - name: Build with Gradle + run: | + set -e + ./gradlew clean build -Dspring.profiles.active=local + + - name: Build Docker image + run: | + docker build \ + --build-arg SPRING_PROFILES_ACTIVE=prob \ + -f Dockerfile \ + -t ${{ vars.DOCKERHUB_USERNAME}}/erica-favicon:latest . + + - name: DockerHub login + uses: docker/login-action@v2 + with: + username: ${{ vars.DOCKERHUB_USERNAME}} + password: ${{ vars.DOCKERHUB_PASSWORD}} + + - name: Push Docker image + run: | + docker push ${{ vars.DOCKERHUB_USERNAME}}/erica-favicon:latest + + - name: Deploy to EC2 + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.EC2_HOST }} + username: ubuntu + key: ${{ secrets.EC2_SSH_KEY }} + script: | + docker pull ${{ vars.DOCKERHUB_USERNAME }}/erica-favicon:latest + docker rm -f back 2>/dev/null || true + docker compose -f docker-compose.yml --env-file .env up --build -d \ No newline at end of file diff --git a/.github/workflows/pr-test.yml b/.github/workflows/build_test.yml similarity index 84% rename from .github/workflows/pr-test.yml rename to .github/workflows/build_test.yml index cec07ce..38ccd1e 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/build_test.yml @@ -1,11 +1,11 @@ -name: PR Build Test +name: PR Build TEST on: pull_request: branches: [ main, develop ] jobs: - test: + build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -25,6 +25,6 @@ jobs: run: chmod +x gradlew - name: Build with Gradle - run: | + run: | set -e - ./gradlew clean build \ No newline at end of file + ./gradlew clean build -Dspring.profiles.active=local diff --git a/.gitignore b/.gitignore index 27965e3..1415df9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Ignore security and cors configuration files -/src/main/java/com/capstone/favicon/config/SecurityConfig.java -/src/main/java/com/capstone/favicon/config/CorsConfig.java +/org +.env +*.pem HELP.md .gradle @@ -49,8 +50,6 @@ out/ ### ec2 ### favicon-key.pem -application.properties -SecurityConfig.java requirement.txt 기상청_월별_processed.csv 기후_감기_건강보험심사평가원.csv diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b2bbc88 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM amazoncorretto:17 +RUN yum update -y && \ + yum install -y python3 && \ + yum clean all +COPY build/libs/app.jar app.jar +ENTRYPOINT ["java", "-jar", "/app.jar"] \ No newline at end of file diff --git a/build.gradle b/build.gradle index c9dffe7..853b09b 100644 --- a/build.gradle +++ b/build.gradle @@ -17,6 +17,18 @@ repositories { mavenCentral() } +springBoot { + mainClass = 'com.capstone.favicon.FaviconApplication' +} + +bootJar { + archiveFileName = 'app.jar' +} + +jar { + enabled = false +} + dependencies { implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' @@ -25,7 +37,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - runtimeOnly 'org.postgresql:postgresql:42.6.0' + implementation 'org.postgresql:postgresql:42.6.0' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // lombok compileOnly 'org.projectlombok:lombok:1.18.28' diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a5a7b62 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +services: + redis: + image: redis:latest + container_name: redis + ports: + - "6379:6379" + command: + - redis-server + networks: + - backend-network + + backend: + image: 211i2/erica-backend:latest + container_name: backend + ports: + - "8080:8080" + networks: + - backend-network + depends_on: + - redis + +networks: + backend-network: + driver: bridge diff --git a/src/main/java/com/capstone/favicon/config/CorsConfig.java b/src/main/java/com/capstone/favicon/config/CorsConfig.java new file mode 100644 index 0000000..76357ce --- /dev/null +++ b/src/main/java/com/capstone/favicon/config/CorsConfig.java @@ -0,0 +1,25 @@ +package com.capstone.favicon.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +@Configuration +public class CorsConfig { + + @Bean + public CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowCredentials(true); + configuration.addAllowedOrigin("http://localhost:3000"); + configuration.addAllowedOrigin("http://127.0.0.1:3000"); + configuration.addAllowedHeader("*"); + configuration.addAllowedMethod("*"); + source.registerCorsConfiguration("/**", configuration); + return new CorsFilter(source); + } + +} \ No newline at end of file diff --git a/src/main/java/com/capstone/favicon/config/SecurityConfig.java b/src/main/java/com/capstone/favicon/config/SecurityConfig.java new file mode 100644 index 0000000..f68f294 --- /dev/null +++ b/src/main/java/com/capstone/favicon/config/SecurityConfig.java @@ -0,0 +1,42 @@ +package com.capstone.favicon.config; + +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; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf((csrfConfig) -> + csrfConfig.disable() + ) + .authorizeHttpRequests(authorize -> authorize + .requestMatchers("/users/email-check", "/users/code-check", "users/register", + "/notice/create", "/notice/list", "/notice/{noticeId}", "/notice/view/{noticeId}", "/faq/create", "faq/{faqId}", + "/data-set/filter", "/data-set/count","/data-set/ratio", "/data-set/incrementDownload/{datasetId}", "/data-set/top10", + "/data-set/theme", "/data-set/{datasetId}", "/data-set/category/{themeId}", "/data-set/filter", "/faq/list", "faq/{faqId}", + "/users/login", "/users/logout", "/users/admin-check", "/s3/upload", "/s3/delete/{resourceId}", + "/users/delete-account", "/users/session-check", "/data-set", "/request/list","/request/list/{requestId}/review", "/request/{requestId}","/request/question", + "/request/question/{questionId}", "/request/answer", "/request/answer/{answerId}","/data-set/search-sorted", "/data-set/search-sorted/{category}", + "/trend/**", "data-set/group-by-theme", "/region", "/analysis", + "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html", "/data-set/download/{datasetId}").permitAll() + .anyRequest().authenticated() + ) + .httpBasic(httpBasic -> httpBasic.disable()) + .formLogin(formLogin -> formLogin.disable()) + .sessionManagement(session -> session + .sessionFixation().migrateSession() + .maximumSessions(1) + ) + .addFilterBefore(new Utf8Filter(), UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..5d8ed83 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,46 @@ +spring.application.name=Favicon + +spring.mail.host=smtp.gmail.com +spring.mail.port=587 +spring.mail.username=${SPRING_MAIL_USERNAME} +spring.mail.password=${SPRING_MAIL_PASSWORD} +spring.mail.properties.mail.smtp.auth=ture +spring.mail.properties.mail.smtp.starttls.required=true + +spring.data.redis.host=redis +spring.data.redis.port=6379 + +# AWS +aws.s3.bucket-name=${AWS_S3_BUCKET} +aws.s3.region=${AWS_S3_REGION} +aws.s3.access-key=${AWS_S3_ACCESS_KEY_ID} +aws.s3.secret-key=${AWS_S3_SECRET_ACCESS_KEY} + + +# DB +spring.datasource.url=${SPRING_RDS_URL} +spring.datasource.username=${SPRING_RDS_USERNAME} +spring.datasource.password=${SPRING_RDS_PASSWORD} +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true +spring.datasource.driver-class-name=org.postgresql.Driver + +# tomcat UTF-8 +server.tomcat.uri-encoding=UTF-8 +server.servlet.encoding.charset=UTF-8 +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true + +# api-docs +spring.api-docs.enabled=true +spring.api-docs.version=openapi_3_0 +spring.api-docs.packagesToScan=mokindang.jubging +spring.api-docs.path=/v3/api-docs + +# swagger +springdoc.default-consumes-media-type=application/json +springdoc.auto-tag-classes=true +springdoc.api-docs.groups.enabled=false +springdoc.swagger-ui.operations-sorter=method +springdoc.swagger-ui.path=/swagger-ui.html \ No newline at end of file