Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 77 additions & 13 deletions .github/workflows/ci-docker.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# 워크플로우의 이름
name: IssueDive CI
name: IssueDive Docker CI

# 워크플로우가 언제 실행될지 정의
on:
push:
branches: [ "dev" ] # dev 브랜치에 push될 때 실행
pull_request:
pull_request_target: # Fork에서 보낸 PR도 공용 저장소의 Secrets에 접근할 수 있게 pull_request_target으로 변경함
branches: [ "dev" ] # dev 브랜치로 Pull Request가 생성될 때 실행

# 실행될 작업(Job)들을 정의
Expand All @@ -14,23 +14,87 @@ jobs:
name: Build and Test # 작업의 이름 (GitHub Actions UI에 표시됨)
runs-on: ubuntu-latest # 작업을 실행할 가상 머신 환경 (Ubuntu 최신 버전)

# 0. DB 서비스를 여기에 정의합니다.
# 이 작업(Job)이 실행되는 동안 MySQL 컨테이너를 함께 실행합니다.
services:
mysql:
image: mysql:8
# GitHub Secrets를 사용하여 DB를 설정합니다.
env:
MYSQL_ROOT_PASSWORD: ${{ secrets.DB_PASSWORD }}
MYSQL_DATABASE: issue_dive
ports:
- 3306:3306 # Runner의 3306 포트와 컨테이너의 3306 포트를 연결
options: >-
--health-cmd "mysqladmin ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5

# 작업의 단계(Step)들을 정의
steps:
# 1. 소스 코드 체크아웃
# GitHub Actions Runner가 우리 저장소의 코드에 접근할 수 있도록 내려받는 단계
- name: Checkout source code
uses: actions/checkout@v4

# 2. JDK 17 설치
- name: Set up JDK 17
uses: actions/setup-java@v4
# 2. Docker Buildx 설정
# Docker 빌드 성능을 향상시키고 다양한 빌드 기능을 활성화하는 단계
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
# 'pull_request_target'을 사용할 때는 어떤 코드를 체크아웃할지 명시해야 합니다.
with:
java-version: '17'
distribution: 'temurin'
ref: ${{ github.event.pull_request.head.sha }}

# 3. Docker 이미지 빌드
# Dockerfile을 기반으로 애플리케이션을 빌드하여 Docker 이미지를 생성.
# 이 단계에서 Gradle 빌드가 컨테이너 안에서 먼저 실행됨.
- 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 \
--build-arg DB_URL="$DB_URL" \
--build-arg DB_USER="$DB_USER" \
--build-arg DB_PASSWORD="$DB_PASSWORD" \
.
# 4. Docker 컨테이너 안에서 테스트 실행
- name: Run tests inside Docker container
# GitHub Secrets를 다시 한번 환경 변수로 가져옵니다.
env:
# Spring Boot는 이 환경 변수들을 자동으로 인식하여 DB 설정으로 사용합니다.
# URL - localhost: GitHub Actions가 services에 설정된 MySQL 컨테이너를 localhost로 연결
SPRING_DATASOURCE_URL: jdbc:mysql://localhost:3306/issue_dive?useSSL=false
SPRING_DATASOURCE_USERNAME: ${{ secrets.DB_USER }}
SPRING_DATASOURCE_PASSWORD: ${{ secrets.DB_PASSWORD }}
run: |
docker run --rm \
--network host \
-e SPRING_DATASOURCE_URL=$SPRING_DATASOURCE_URL \
-e SPRING_DATASOURCE_USERNAME=$SPRING_DATASOURCE_USERNAME \
-e SPRING_DATASOURCE_PASSWORD=$SPRING_DATASOURCE_PASSWORD \
issue-dive-app ./gradlew test

# --- 이전 방식 (더 이상 필요 없음) ---
# 이제 모든 빌드와 테스트가 Docker 컨테이너 안에서 이루어지므로,
# GitHub Actions Runner에 직접 Java를 설치하거나 gradlew에 실행 권한을 줄 필요가 없습니다.
# # 2. JDK 17 설치
# - name: Set up JDK 17
# uses: actions/setup-java@v4
# with:
# java-version: '17'
# distribution: 'temurin'

# # 3. gradlew 파일에 실행 권한 부여
# - name: Grant execute permission for gradlew
# run: chmod +x ./gradlew

# 3. gradlew 파일에 실행 권한 부여
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
# # 4. Gradle로 빌드 및 테스트 실행
# - name: Build with Gradle
# run: ./gradlew build --no-daemon

# 4. Gradle로 빌드 및 테스트 실행
- name: Build with Gradle
run: ./gradlew build --no-daemon
# re-run test용 주석 추가
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ application-dev.properties

.env
.java-version
logs/
logs/
gradle.properties
13 changes: 12 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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


Expand All @@ -26,3 +36,4 @@ COPY --from=builder /app/build/libs/*.jar app.jar

# 컨테이너 실행 시
ENTRYPOINT ["java", "-jar", "app.jar"]

26 changes: 26 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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'
Expand All @@ -62,4 +80,12 @@ clean{
tasks.named('test') {
useJUnitPlatform()
jvmArgs '-Xshare:off'
}

// Flyway 설정
flyway {
url = dbUrl
user = dbUser
password = dbPassword
cleanDisabled = false
}
13 changes: 6 additions & 7 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -34,8 +35,6 @@ services:
ports:
- "8080:8080"
command: ./gradlew bootRun
# volumes:
# - ./:/app
tty: true

volumes:
Expand Down
7 changes: 7 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}

rootProject.name = 'issueDive'
11 changes: 9 additions & 2 deletions src/main/java/com/issueDive/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -34,8 +35,12 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource())) // CORS 설정
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorize -> authorize
// 모든 요청(/**)을 허용 (임시로)
.anyRequest().permitAll() // 개발 단계에서는 전체 허용
// .requestMatchers(PUBLIC_URLS).permitAll() // 공개 URL은 모두 허용
// .anyRequest().authenticated() // 나머지는 인증 필요
)
.formLogin(formLogin -> formLogin.disable()) // 폼 로그인 비활성화 (필요 시)
.logout(logout -> logout.disable()); // 로그아웃 비활성화 (필요 시)
Expand All @@ -47,11 +52,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);

Expand Down
5 changes: 4 additions & 1 deletion src/main/java/com/issueDive/dto/CreateIssueRequest.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.issueDive.dto;

import java.util.List;

public record CreateIssueRequest(
String title,
String description,
Long assigneeId
Long assigneeId,
List<Long> labels
) {
}
17 changes: 15 additions & 2 deletions src/main/java/com/issueDive/entity/Issue.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ public class Issue {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
@Column(nullable = false, length = 255)
private String title;

@Column(columnDefinition = "TEXT")
private String description;

@Builder.Default
@Enumerated(EnumType.STRING)
@Column(nullable = false)
@Column(name = "_status", nullable = false)
private IssueStatus status = IssueStatus.OPEN;

@ManyToOne(fetch = FetchType.LAZY)
Expand All @@ -57,4 +57,17 @@ public class Issue {
inverseJoinColumns = @JoinColumn(name = "label_id")
)
private Set<Label> labels = new HashSet<>();

@PrePersist
public void prePersist() {
LocalDateTime now = LocalDateTime.now();
this.createdAt = now;
this.updatedAt = now;
}

@PreUpdate
public void preUpdate() {
this.updatedAt = LocalDateTime.now();
}

}
51 changes: 51 additions & 0 deletions src/main/resources/db/migration/V1__init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

CREATE TABLE label (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE,
color VARCHAR(7) NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

CREATE TABLE issue (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
_status ENUM('OPEN', 'CLOSED') DEFAULT 'OPEN',
author_id BIGINT NOT NULL,
assignee_id BIGINT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (author_id) REFERENCES users(id),
FOREIGN KEY (assignee_id) REFERENCES users(id)
);

CREATE TABLE issue_label (
issue_id BIGINT,
label_id BIGINT,
PRIMARY KEY (issue_id, label_id),
FOREIGN KEY (issue_id) REFERENCES issue(id),
FOREIGN KEY (label_id) REFERENCES label(id)
);

CREATE TABLE comment (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
issue_id BIGINT NOT NULL,
author_id BIGINT NOT NULL,
parent_id BIGINT NULL,
description TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (issue_id) REFERENCES issue(id),
FOREIGN KEY (author_id) REFERENCES users(id),
FOREIGN KEY (parent_id) REFERENCES comment(id)
);
11 changes: 11 additions & 0 deletions src/main/resources/db/migration/V2__insert_initial_data.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- V2__insert_initial_data.sql
-- 테스트 목적

-- 테스트용 사용자 추가 (비밀번호는 'password'를 암호화한 값 예시)
INSERT INTO users (id, username, email, password) VALUES
(1, 'testuser', 'test@example.com', '$2a$10$N.ps3jC1V2doSB.qG0aL/e.UP1s1b4yVw2yWvnsd2z5K/kY2.Y0Cu');

-- (선택) 테스트용 라벨 추가
INSERT INTO label (id, name, color, description) VALUES
(1, 'bug', '#d73a4a', 'Something isn''t working'),
(2, 'feature', '#007bff', 'New feature or request');
Loading