Skip to content

Commit 9d6bb06

Browse files
authored
Merge pull request #14 from TurtleTongtong/develop
feat: 배포 자동화
2 parents 9c55dda + 38b2bf4 commit 9d6bb06

76 files changed

Lines changed: 2794 additions & 4 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/deploy.yml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: CI/CD to Lightsail
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
7+
jobs:
8+
build-and-push:
9+
runs-on: ubuntu-latest
10+
11+
permissions:
12+
contents: read
13+
packages: write
14+
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v4
18+
19+
- name: Set up JDK 17
20+
uses: actions/setup-java@v4
21+
with:
22+
distribution: temurin
23+
java-version: "17"
24+
25+
- name: Build jar
26+
run: |
27+
chmod +x ./gradlew
28+
./gradlew bootJar -x test --no-daemon
29+
30+
- name: Login to GHCR
31+
uses: docker/login-action@v3
32+
with:
33+
registry: ghcr.io
34+
username: ${{ github.actor }}
35+
password: ${{ secrets.GITHUB_TOKEN }}
36+
37+
- name: Build and push Docker image
38+
run: |
39+
IMAGE_NAME=ghcr.io/turtletongtong/backend:latest
40+
docker build -t $IMAGE_NAME .
41+
docker push $IMAGE_NAME
42+
43+
deploy:
44+
runs-on: ubuntu-latest
45+
needs: build-and-push
46+
47+
env:
48+
DB_NAME: ${{ secrets.DB_NAME }}
49+
DB_USERNAME: ${{ secrets.DB_USERNAME }}
50+
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
51+
MYSQL_ROOT_PASSWORD: ${{ secrets.MYSQL_ROOT_PASSWORD }}
52+
JWT_SECRET: ${{ secrets.JWT_SECRET }}
53+
54+
steps:
55+
- name: Checkout
56+
uses: actions/checkout@v4
57+
58+
- name: Copy docker-compose to server
59+
uses: appleboy/scp-action@v0.1.7
60+
with:
61+
host: ${{ secrets.SSH_HOST }}
62+
username: ${{ secrets.SSH_USER }}
63+
key: ${{ secrets.SSH_KEY }}
64+
source: "docker-compose.prod.yml"
65+
target: "/srv/turtleconnect/docker-compose.yml"
66+
67+
- name: Deploy over SSH
68+
uses: appleboy/ssh-action@v1.2.0
69+
with:
70+
host: ${{ secrets.SSH_HOST }}
71+
username: ${{ secrets.SSH_USER }}
72+
key: ${{ secrets.SSH_KEY }}
73+
script: |
74+
cd /srv/turtleconnect
75+
docker compose -f docker-compose.yml pull
76+
docker compose -f docker-compose.yml up -d
77+
docker image prune -f

.gitignore

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# === Java ===
2+
*.class
3+
*.jar
4+
*.war
5+
*.ear
6+
*.iml
7+
*.log
8+
9+
# === Maven / Gradle ===
10+
target/
11+
build/
12+
.gradle/
13+
out/
14+
15+
# === IntelliJ IDEA ===
16+
.idea/
17+
*.ipr
18+
*.iws
19+
*.iml
20+
.idea/**/workspace.xml
21+
.idea/**/tasks.xml
22+
.idea/**/usage.statistics.xml
23+
.idea/**/dictionaries
24+
.idea/**/shelf
25+
26+
# === Logs ===
27+
log/
28+
logs/
29+
*.log
30+
31+
# === Operating System ===
32+
.DS_Store
33+
Thumbs.db
34+
35+
# === Spring Boot ===
36+
application-dev.yaml
37+
application-prod.yaml
38+
39+
# === ENV files ===
40+
.env
41+
.env.*
42+
!env.example
43+
44+
# === Lombok generated ===
45+
.lombok/
46+
47+
# === Redis or DB dump ===
48+
dump.rdb
49+
*.sql
50+
51+
# === Others ===
52+
*.class
53+
*.swp
54+
*.swo

Dockerfile

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# 1) Build stage
2+
FROM eclipse-temurin:17-jdk AS build
3+
4+
WORKDIR /app
5+
6+
# Copy build scripts
7+
COPY gradlew .
8+
COPY gradle gradle
9+
COPY build.gradle settings.gradle ./
10+
11+
# Download dependencies
12+
RUN chmod +x ./gradlew
13+
RUN ./gradlew dependencies --no-daemon
14+
15+
# Copy source and build
16+
COPY src src
17+
RUN ./gradlew bootJar -x test --no-daemon
18+
19+
# 2) Run stage
20+
FROM eclipse-temurin:17-jre
21+
22+
WORKDIR /app
23+
24+
# Copy jar
25+
COPY --from=build /app/build/libs/*.jar app.jar
26+
27+
# 환경은 docker-compose에서 주입 (SPRING_PROFILES_ACTIVE 등)
28+
EXPOSE 8080
29+
30+
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

build.gradle

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,40 @@ repositories {
2626

2727
dependencies {
2828
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
29+
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
30+
31+
// Spring Boot Web
2932
implementation 'org.springframework.boot:spring-boot-starter-web'
33+
34+
// Spring Boot Validation
35+
implementation 'org.springframework.boot:spring-boot-starter-validation'
36+
37+
// JPA
38+
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
39+
40+
// MySQL Driver
41+
runtimeOnly 'com.mysql:mysql-connector-j'
42+
43+
// Lombok
3044
compileOnly 'org.projectlombok:lombok'
3145
annotationProcessor 'org.projectlombok:lombok'
46+
47+
// Redis
48+
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
49+
50+
// Security + JWT
51+
implementation 'org.springframework.boot:spring-boot-starter-security'
52+
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
53+
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
54+
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
55+
implementation 'io.github.cdimascio:java-dotenv:5.1.1'
56+
57+
// Swagger
58+
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9'
59+
60+
// 테스트
3261
testImplementation 'org.springframework.boot:spring-boot-starter-test'
33-
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
62+
testImplementation 'org.springframework.security:spring-security-test'
3463
}
3564

3665
tasks.named('test') {

docker-compose.prod.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
version: "3.8"
2+
3+
services:
4+
app:
5+
image: ghcr.io/turtletongtong/backend:latest
6+
container_name: turtleconnect-app
7+
environment:
8+
SPRING_PROFILES_ACTIVE: prod
9+
SERVER_PORT: 8080
10+
11+
DB_HOST: mysql
12+
DB_PORT: 3306
13+
DB_NAME: ${DB_NAME}
14+
DB_USERNAME: ${DB_USERNAME}
15+
DB_PASSWORD: ${DB_PASSWORD}
16+
17+
REDIS_HOST: redis
18+
REDIS_PORT: 6379
19+
20+
JWT_SECRET: ${JWT_SECRET}
21+
depends_on:
22+
- mysql
23+
- redis
24+
ports:
25+
- "8080:8080"
26+
restart: always
27+
28+
mysql:
29+
image: mysql:8.0
30+
container_name: turtleconnect-mysql
31+
environment:
32+
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
33+
MYSQL_DATABASE: ${DB_NAME}
34+
MYSQL_USER: ${DB_USERNAME}
35+
MYSQL_PASSWORD: ${DB_PASSWORD}
36+
volumes:
37+
- ./mysql_data:/var/lib/mysql
38+
command:
39+
- --character-set-server=utf8mb4
40+
- --collation-server=utf8mb4_unicode_ci
41+
restart: always
42+
43+
redis:
44+
image: redis:7-alpine
45+
container_name: turtleconnect-redis
46+
command: ["redis-server", "--appendonly", "yes"]
47+
volumes:
48+
- ./redis_data:/data
49+
restart: always

gradlew

100644100755
File mode changed.

src/main/java/com/turtletongtong/turtleconnect/TurtleconnectApplication.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
package com.turtletongtong.turtleconnect;
22

3+
import io.github.cdimascio.dotenv.Dotenv;
34
import org.springframework.boot.SpringApplication;
45
import org.springframework.boot.autoconfigure.SpringBootApplication;
6+
import org.springframework.context.annotation.Bean;
57

68
@SpringBootApplication
79
public class TurtleconnectApplication {
8-
10+
@Bean
11+
public Dotenv dotenv() {
12+
// .env 파일을 읽어서 환경변수로 사용
13+
return Dotenv.configure().directory("./")
14+
.ignoreIfMissing() // .env 파일이 없어도 에러 발생 안함
15+
.load();
16+
}
917
public static void main(String[] args) {
1018
SpringApplication.run(TurtleconnectApplication.class, args);
1119
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package com.turtletongtong.turtleconnect.festival.controller;
2+
3+
import com.turtletongtong.turtleconnect.festival.dto.request.FestivalCreateRequest;
4+
import com.turtletongtong.turtleconnect.festival.dto.request.FestivalUpdateRequest;
5+
import com.turtletongtong.turtleconnect.festival.dto.response.FestivalResponse;
6+
import com.turtletongtong.turtleconnect.festival.service.FestivalService;
7+
import io.swagger.v3.oas.annotations.Operation;
8+
import io.swagger.v3.oas.annotations.tags.Tag;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.web.bind.annotation.*;
11+
12+
import java.util.List;
13+
14+
@RestController
15+
@RequiredArgsConstructor
16+
@RequestMapping("/api/admin/festivals")
17+
@Tag(name = "Admin - Festival", description = "관리자용 축제 관리 API")
18+
public class AdminFestivalController {
19+
20+
private final FestivalService festivalService;
21+
22+
@Operation(summary = "축제 생성")
23+
@PostMapping
24+
public FestivalResponse create(@RequestBody FestivalCreateRequest request) {
25+
return festivalService.createFestival(request);
26+
}
27+
28+
@Operation(summary = "축제 수정")
29+
@PutMapping("/{id}")
30+
public FestivalResponse update(
31+
@PathVariable Long id,
32+
@RequestBody FestivalUpdateRequest request
33+
) {
34+
return festivalService.updateFestival(id, request);
35+
}
36+
37+
@Operation(summary = "축제 삭제")
38+
@DeleteMapping("/{id}")
39+
public void delete(@PathVariable Long id) {
40+
festivalService.deleteFestival(id);
41+
}
42+
43+
@Operation(summary = "축제 목록 조회 (관리자용)")
44+
@GetMapping
45+
public List<FestivalResponse> getAll() {
46+
return festivalService.getAllFestivalsForAdmin();
47+
}
48+
49+
@Operation(summary = "축제 상세 조회 (관리자용)")
50+
@GetMapping("/{id}")
51+
public FestivalResponse get(@PathVariable Long id) {
52+
return festivalService.getFestival(id);
53+
}
54+
55+
@Operation(summary = "축제 상태 토글 (ACTIVE <-> INACTIVE)")
56+
@PatchMapping("/{id}/toggle-status")
57+
public FestivalResponse toggleStatus(@PathVariable Long id) {
58+
return festivalService.toggleFestivalStatus(id);
59+
}
60+
61+
@Operation(summary = "축제 패스권 할인 토글 (ON/OFF)")
62+
@PatchMapping("/{id}/toggle-discount")
63+
public FestivalResponse toggleDiscount(@PathVariable Long id) {
64+
return festivalService.toggleFestivalDiscount(id);
65+
}
66+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.turtletongtong.turtleconnect.festival.controller;
2+
3+
import com.turtletongtong.turtleconnect.festival.dto.response.FestivalResponse;
4+
import com.turtletongtong.turtleconnect.festival.service.FestivalService;
5+
import io.swagger.v3.oas.annotations.Operation;
6+
import io.swagger.v3.oas.annotations.tags.Tag;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.web.bind.annotation.*;
9+
10+
import java.util.List;
11+
12+
@RestController
13+
@RequiredArgsConstructor
14+
@RequestMapping("/api/public/festivals")
15+
@Tag(name = "Public - Festival", description = "사용자용 축제 조회 API")
16+
public class PublicFestivalController {
17+
18+
private final FestivalService festivalService;
19+
20+
@Operation(summary = "활성화된 축제 목록 조회")
21+
@GetMapping
22+
public List<FestivalResponse> getActiveFestivals() {
23+
return festivalService.getActiveFestivals();
24+
}
25+
26+
@Operation(summary = "축제 상세 조회")
27+
@GetMapping("/{id}")
28+
public FestivalResponse getFestival(@PathVariable Long id) {
29+
return festivalService.getFestival(id);
30+
}
31+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.turtletongtong.turtleconnect.festival.dto.request;
2+
3+
import com.turtletongtong.turtleconnect.festival.entity.FestivalStatus;
4+
5+
import java.time.LocalDate;
6+
7+
public record FestivalCreateRequest(
8+
String name,
9+
String description,
10+
String location,
11+
LocalDate startDate,
12+
LocalDate endDate,
13+
String specialEvent,
14+
String mainPrograms,
15+
String imageUrl,
16+
FestivalStatus status,
17+
boolean hasDiscount,
18+
String discountDescription
19+
) {}

0 commit comments

Comments
 (0)