From 2161cc092c1e9cdf444363a7e0d44eb28eae7d11 Mon Sep 17 00:00:00 2001 From: meraki6512 Date: Fri, 29 Aug 2025 13:37:27 +0900 Subject: [PATCH 1/7] =?UTF-8?q?[#31]=20chore:=20=ED=94=84=EB=A1=A0?= =?UTF-8?q?=ED=8A=B8=EC=97=94=EB=93=9C=20=EC=97=B0=EB=8F=99=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=ED=95=B4=EA=B2=B0=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EB=B0=B1=EC=97=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 프론트엔드 연동 과정에서 발생한 빌드 및 런타임 오류를 해결하고, 전반적인 개발 환경을 안정화했습니다. - **빌드 시스템 수정 (build.gradle, settings.gradle, gradle.properties):** - Flyway 플러그인 관련 의존성 문제를 해결하여 로컬 및 CI 환경에서 빌드가 정상적으로 동작하도록 수정. - `gradle.properties`를 도입하여 민감한 DB 정보를 Git 추적에서 제외. - **Docker 환경 수정 (Dockerfile, docker-compose.yml, ci-docker.yml):** - Docker 빌드 시점에 환경 변수를 안전하게 전달하도록 수정하여 빌드 안정성 확보. - **DB 스키마 수정 (V1__init.sql, V2__insert_initial_data.sql):** - JPA 엔티티와 DB 테이블 간의 스키마 불일치(테이블명, 컬럼명) 문제 해결. - 초기 데이터(seed data)를 추가하여 API 테스트가 즉시 가능하도록 개선. --- .github/workflows/ci-docker.yml | 14 ++++- .gitignore | 3 +- Dockerfile | 13 ++++- build.gradle | 26 ++++++++++ docker-compose.yml | 13 +++-- settings.gradle | 7 +++ .../com/issueDive/config/SecurityConfig.java | 14 +++-- .../com/issueDive/dto/CreateIssueRequest.java | 5 +- src/main/java/com/issueDive/entity/Issue.java | 17 ++++++- src/main/resources/db/migration/V1__init.sql | 51 +++++++++++++++++++ .../db/migration/V2__insert_initial_data.sql | 11 ++++ .../issueDive/service/IssueServiceTest.java | 5 +- 12 files changed, 159 insertions(+), 20 deletions(-) create mode 100644 src/main/resources/db/migration/V1__init.sql create mode 100644 src/main/resources/db/migration/V2__insert_initial_data.sql diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 028c965..baec0c6 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -15,11 +15,21 @@ jobs: uses: actions/checkout@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-build-action@v2 - name: Build Docker image + # GitHub Secrets를 환경 변수로 설정하고, + # docker build 명령어에 --build-arg로 전달합니다. + env: + DB_URL: ${{ secrets.DB_URL }} + DB_USER: ${{ secrets.DB_USER }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} run: | - docker build -t issue-dive-app . + docker build -t issue-dive-app \ + --build-arg DB_URL="$DB_URL" \ + --build-arg DB_USER="$DB_USER" \ + --build-arg DB_PASSWORD="$DB_PASSWORD" \ + . - name: Run tests inside Docker container run: | diff --git a/.gitignore b/.gitignore index 1f30306..6ab6eb1 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,5 @@ application-test.properties .env .java-version -logs/ \ No newline at end of file +logs/ +gradle.properties \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index f4069d5..e2626bc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,17 @@ FROM gradle:8.14.3-jdk17 AS builder WORKDIR /app +# build-arg를 받기 위한 ARG 선언을 추가합니다. +ARG DB_URL +ARG DB_USER +ARG DB_PASSWORD + +# gradle.properties 파일을 동적으로 생성합니다. +RUN echo "dbUrl=${DB_URL}" >> gradle.properties +RUN echo "dbUser=${DB_USER}" >> gradle.properties +RUN echo "dbPassword=${DB_PASSWORD}" >> gradle.properties + + # Gradle 캐시 최적화를 위해 build.gradle과 settings.gradle 먼저 복사 COPY build.gradle settings.gradle gradlew ./ COPY gradle ./gradle @@ -12,7 +23,6 @@ RUN ./gradlew --no-daemon dependencies # 나머지 소스 복사 및 빌드 COPY src ./src -# RUN ./gradlew build --no-daemon -Dspring.profiles.active=test RUN ./gradlew assemble --no-daemon -Dspring.profiles.active=test @@ -26,3 +36,4 @@ COPY --from=builder /app/build/libs/*.jar app.jar # 컨테이너 실행 시 ENTRYPOINT ["java", "-jar", "app.jar"] + diff --git a/build.gradle b/build.gradle index 68478d2..23656d9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,26 @@ +// buildscript 블록 +// 플러그인 자체와, 플러그인이 사용할 MySQL 라이브러리를 함께 정의. +buildscript { + repositories { + gradlePluginPortal() + } + dependencies { + def flywayVersion = "11.11.2" + classpath "org.flywaydb:flyway-gradle-plugin:${flywayVersion}" + classpath "org.flywaydb:flyway-mysql:${flywayVersion}" + } +} + plugins { id 'java' id 'org.springframework.boot' version '3.5.5' id 'io.spring.dependency-management' version '1.1.7' +// id 'org.flywaydb.flyway' version "11.11.2" } +// buildscript에 정의한 플러그인 적용. +apply plugin: 'org.flywaydb.flyway' + group = 'com.issueDive' version = '0.0.1-SNAPSHOT' description = 'issueDive' @@ -41,6 +58,7 @@ dependencies { testImplementation 'org.testcontainers:junit-jupiter' testImplementation 'org.testcontainers:mysql' testImplementation 'com.h2database:h2' +// flywayPlugin 'org.flywaydb:flyway-mysql' // flywayClean 같은 명령어 직접 사용 시 필요. 라이브러리 지정. // QueryDsl implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' @@ -60,4 +78,12 @@ clean{ tasks.named('test') { useJUnitPlatform() jvmArgs '-Xshare:off' +} + +// Flyway 설정 +flyway { + url = dbUrl + user = dbUser + password = dbPassword + cleanDisabled = false } \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 1b44657..85f78df 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,10 +7,6 @@ services: environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: issue_dive - # 일반 사용자 추가하지 않거나 별도의 사용자명으로 변경 - # 예를 들어 USER는 issueuser 등으로 변경 가능 - # MYSQL_USER: issueuser - # MYSQL_PASSWORD: user_password ports: - "3306:3306" volumes: @@ -22,7 +18,12 @@ services: retries: 5 app: - build: . + build: + context: . + args: + - DB_URL=${SPRING_DATASOURCE_URL} + - DB_USER=${SPRING_DATASOURCE_USERNAME} + - DB_PASSWORD=${SPRING_DATASOURCE_PASSWORD} container_name: issueDive-app depends_on: mysql: @@ -34,8 +35,6 @@ services: ports: - "8080:8080" command: ./gradlew bootRun -# volumes: -# - ./:/app tty: true volumes: diff --git a/settings.gradle b/settings.gradle index 1f19b78..128f36c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,8 @@ +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } +} + rootProject.name = 'issueDive' diff --git a/src/main/java/com/issueDive/config/SecurityConfig.java b/src/main/java/com/issueDive/config/SecurityConfig.java index 246a091..847df46 100644 --- a/src/main/java/com/issueDive/config/SecurityConfig.java +++ b/src/main/java/com/issueDive/config/SecurityConfig.java @@ -4,6 +4,7 @@ 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.configurers.AbstractHttpConfigurer; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -33,9 +34,12 @@ public class SecurityConfig { public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .cors(cors -> cors.configurationSource(corsConfigurationSource())) // CORS 설정 + .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(authorize -> authorize - .requestMatchers(PUBLIC_URLS).permitAll() // 공개 URL은 모두 허용 - .anyRequest().authenticated() // 나머지는 인증 필요 + // 모든 요청(/**)을 허용 (임시로) + .requestMatchers("/**").permitAll() +// .requestMatchers(PUBLIC_URLS).permitAll() // 공개 URL은 모두 허용 +// .anyRequest().authenticated() // 나머지는 인증 필요 ) .formLogin(formLogin -> formLogin // 폼 로그인 사용 .defaultSuccessUrl("/") @@ -53,11 +57,13 @@ public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); // 프론트엔드 서버 주소 허용 - configuration.setAllowedOrigins(Arrays.asList("http://localhost:5173")); + configuration.setAllowedOrigins(Arrays.asList("http://localhost:5173", "http://localhost:5174")); +// configuration.setAllowedOrigins(Arrays.asList("http://localhost:5174")); // 모든 HTTP 메서드 허용 configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); // 모든 헤더 허용 - configuration.setAllowedHeaders(Arrays.asList("*")); +// configuration.setAllowedHeaders(Arrays.asList("Origin", "Content-Type", "Accept", "Authorization")); + configuration.addAllowedHeader("*"); // 자격 증명(쿠키 등) 허용 configuration.setAllowCredentials(true); diff --git a/src/main/java/com/issueDive/dto/CreateIssueRequest.java b/src/main/java/com/issueDive/dto/CreateIssueRequest.java index 6338f83..dae02d0 100644 --- a/src/main/java/com/issueDive/dto/CreateIssueRequest.java +++ b/src/main/java/com/issueDive/dto/CreateIssueRequest.java @@ -1,8 +1,11 @@ package com.issueDive.dto; +import java.util.List; + public record CreateIssueRequest( String title, String description, - Long assigneeId + Long assigneeId, + List labels ) { } diff --git a/src/main/java/com/issueDive/entity/Issue.java b/src/main/java/com/issueDive/entity/Issue.java index 97e658e..f61aa9a 100644 --- a/src/main/java/com/issueDive/entity/Issue.java +++ b/src/main/java/com/issueDive/entity/Issue.java @@ -22,7 +22,7 @@ public class Issue { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false) + @Column(nullable = false, length = 255) private String title; @Column(columnDefinition = "TEXT") @@ -30,7 +30,7 @@ public class Issue { @Builder.Default @Enumerated(EnumType.STRING) - @Column(nullable = false) + @Column(name = "_status", nullable = false) private IssueStatus status = IssueStatus.OPEN; @ManyToOne(fetch = FetchType.LAZY) @@ -57,4 +57,17 @@ public class Issue { inverseJoinColumns = @JoinColumn(name = "label_id") ) private Set