Skip to content

Commit 6d9464d

Browse files
committed
[Feat] veo로 비디오 생성
1 parent 58a4f3b commit 6d9464d

32 files changed

Lines changed: 1588 additions & 380 deletions

.github/workflows/main-server.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ jobs:
2525
echo "${{ secrets.APPLICATION_YML }}" | base64 --decode > src/main/resources/application.yml
2626
cat src/main/resources/application.yml
2727
28+
- name: 구글 키 정보 넣기
29+
run: |
30+
echo "${{ secrets.GOOGLE_KEY_JSON }}" | base64 --decode > src/main/resources/service-account-key.json
31+
cat src/main/resources/service-account-key.json
32+
33+
2834
- name: Gradle Caching
2935
uses: actions/cache@v3
3036
with:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ build/
1010

1111
**/firebase-account.json
1212
**/application.yml
13+
**/service-account-key.json
1314

1415
### STS ###
1516
.apt_generated

build.gradle

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,58 +22,69 @@ repositories {
2222
}
2323

2424
dependencies {
25-
implementation 'org.springframework.boot:spring-boot-starter-web'
26-
annotationProcessor 'org.projectlombok:lombok'
27-
testCompileOnly 'org.projectlombok:lombok'
28-
testAnnotationProcessor 'org.projectlombok:lombok'
25+
// Spring Boot Starters
26+
implementation 'org.springframework.boot:spring-boot-starter-web'
2927
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
30-
implementation 'org.springframework.boot:spring-boot-starter-security'
3128
implementation 'org.springframework.boot:spring-boot-starter-validation'
29+
implementation 'org.springframework.boot:spring-boot-starter-security'
30+
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
3231
implementation 'org.springframework.boot:spring-boot-starter-mail'
32+
33+
// 데이터베이스
3334
runtimeOnly 'com.mysql:mysql-connector-j'
34-
testImplementation 'org.springframework.security:spring-security-test'
35-
35+
3636
// JWT
3737
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
3838
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
3939
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
40-
41-
42-
//Oauth2
40+
41+
// OAuth2
4342
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
44-
45-
//swagger
46-
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
47-
48-
//QueryDSL 추가
43+
44+
// WebClient (HTTP 클라이언트)
45+
implementation 'org.springframework.boot:spring-boot-starter-webflux' // ✅ WebClient용으로만 유지
46+
47+
// Apple 로그인
48+
implementation 'org.bouncycastle:bcpkix-jdk15on:1.69'
49+
50+
// Querydsl
4951
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
5052
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
5153
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
5254
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
53-
54-
//AWS
55+
56+
// Lombok
57+
compileOnly 'org.projectlombok:lombok'
58+
annotationProcessor 'org.projectlombok:lombok'
59+
testCompileOnly 'org.projectlombok:lombok'
60+
testAnnotationProcessor 'org.projectlombok:lombok'
61+
62+
// Swagger (Spring Web용)
63+
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' // ✅ WebFlux -> WebMVC로 롤백
64+
65+
// AWS
5566
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
56-
57-
//webclient
58-
implementation 'org.springframework.boot:spring-boot-starter-webflux'
59-
60-
// apple로그인을 위한
61-
implementation 'org.bouncycastle:bcpkix-jdk15on:1.69'
62-
63-
// 레디스
64-
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
65-
67+
6668
// 모니터링
6769
implementation 'org.springframework.boot:spring-boot-starter-actuator'
6870
implementation 'io.micrometer:micrometer-registry-prometheus'
69-
71+
7072
// Ably
7173
implementation 'io.ably:ably-java:1.2.16'
72-
73-
//fcm
74+
75+
// FCM
7476
implementation 'com.google.firebase:firebase-admin:9.2.0'
7577
implementation 'com.google.api-client:google-api-client:2.2.0'
76-
78+
79+
// Google Gen AI SDK (Veo 비디오 생성용)
80+
implementation 'com.google.genai:google-genai:1.10.0'
81+
82+
// Google Cloud Storage (Veo 이미지 업로드용)
83+
implementation 'com.google.cloud:google-cloud-storage:2.36.1'
84+
85+
// ✅ Servlet API 추가 (호환성)
86+
implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0'
87+
7788
// 테스트 의존성
7889
testImplementation 'org.springframework.boot:spring-boot-starter-test'
7990
testImplementation 'org.springframework.security:spring-security-test'
@@ -95,6 +106,8 @@ def generated = 'src/main/generated'
95106
// querydsl QClass 파일 생성 위치를 지정
96107
tasks.withType(JavaCompile) {
97108
options.getGeneratedSourceOutputDirectory().set(file(generated))
109+
// Gen AI Function Calling을 위한 매개변수 이름 보존
110+
options.compilerArgs += ["-parameters"]
98111
}
99112

100113
// java source set 에 querydsl QClass 위치 추가

src/main/java/server/common/ErrorCode.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,19 @@ public enum ErrorCode {
130130
YOUTUBE_API_FORBIDDEN("B1705", "YouTube API에 대한 접근 권한이 없습니다.", HttpStatus.INTERNAL_SERVER_ERROR),
131131

132132
// 외부 API
133-
DISCORD_NOTIFICATION_FAILED("B1800", "디스코드 알림 전송에 실패했습니다.", HttpStatus.INTERNAL_SERVER_ERROR);
133+
DISCORD_NOTIFICATION_FAILED("B1800", "디스코드 알림 전송에 실패했습니다.", HttpStatus.INTERNAL_SERVER_ERROR),
134+
135+
// B19xx: Veo 비디오 생성 예외
136+
VEO_CLIENT_INITIALIZATION_FAILED("B1900", "Veo 클라이언트 초기화에 실패했습니다.", HttpStatus.INTERNAL_SERVER_ERROR),
137+
VEO_VIDEO_GENERATION_FAILED("B1901", "비디오 생성에 실패했습니다.", HttpStatus.INTERNAL_SERVER_ERROR),
138+
VEO_OPERATION_NOT_FOUND("B1902", "비디오 생성 작업을 찾을 수 없습니다.", HttpStatus.NOT_FOUND),
139+
VEO_OPERATION_TIMEOUT("B1903", "비디오 생성 작업이 시간 초과되었습니다.", HttpStatus.REQUEST_TIMEOUT),
140+
VEO_INVALID_PROMPT("B1904", "프롬프트가 유효하지 않습니다.", HttpStatus.BAD_REQUEST),
141+
VEO_INVALID_IMAGE_URL("B1905", "이미지 URL이 유효하지 않습니다.", HttpStatus.BAD_REQUEST),
142+
VEO_API_QUOTA_EXCEEDED("B1906", "Veo API 할당량을 초과했습니다.", HttpStatus.TOO_MANY_REQUESTS),
143+
VEO_API_UNAUTHORIZED("B1907", "Veo API 인증에 실패했습니다.", HttpStatus.UNAUTHORIZED),
144+
VEO_OPERATION_INTERRUPTED("B1908", "비디오 생성 작업이 중단되었습니다.", HttpStatus.INTERNAL_SERVER_ERROR),
145+
VEO_INSUFFICIENT_CREDITS("B1909", "비디오 생성을 위한 크레딧이 부족합니다.", HttpStatus.PAYMENT_REQUIRED);
134146

135147
private final String code;
136148
private final String message;

src/main/java/server/common/error_notification/ErrorNotificationDto.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,28 @@ public static ErrorNotificationDto create(ErrorCode errorCode, Exception excepti
7171
.build();
7272
}
7373

74+
/**
75+
* ✅ WebFlux 호환용 - HttpServletRequest 없이 ErrorNotificationDto 생성
76+
*/
77+
public static ErrorNotificationDto createWithoutRequest(ErrorCode errorCode, Exception exception) {
78+
return ErrorNotificationDto.builder()
79+
.timestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
80+
.errorCode(errorCode.getCode())
81+
.errorMessage(errorCode.getMessage())
82+
.exceptionType(exception.getClass().getSimpleName())
83+
.exceptionMessage(exception.getMessage())
84+
.requestUri("WebFlux Request")
85+
.requestMethod("UNKNOWN")
86+
.userAgent("N/A")
87+
.clientIp("UNKNOWN")
88+
.requestParams("N/A")
89+
.requestBody("")
90+
.requestHeaders("N/A")
91+
.stackTrace(getStackTraceAsString(exception))
92+
.exceptionChain(buildExceptionChain(exception))
93+
.build();
94+
}
95+
7496
/**
7597
* 예외 체인을 분석하여 상위 예외부터 하위 원인 예외까지 순서대로 구성
7698
*/

src/main/java/server/common/error_notification/SenderToDiscord.java

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
/**
1818
* 디스코드 웹훅을 통한 에러 알림 서비스
19+
* ✅ WebFlux 호환을 위해 HttpServletRequest를 nullable로 처리합니다.
1920
*/
2021
@Slf4j
2122
@Service
@@ -26,15 +27,18 @@ public class SenderToDiscord implements ErrorNotificationService {
2627
private final ErrorNotificationConfig errorNotificationConfig;
2728
private final WebClient webClient;
2829

30+
/**
31+
* ✅ WebFlux 호환 - request가 null일 수 있습니다
32+
*/
2933
@Override
3034
public void sendErrorNotification(ErrorCode errorCode, Exception exception, HttpServletRequest request) {
3135
// 알림이 비활성화되어 있으면 전송하지 않음
3236
if (!errorNotificationConfig.isEnabled()) {
3337
return;
3438
}
3539

36-
// 경로가 제외 목록에 있으면 전송하지 않음
37-
if (errorNotificationConfig.isPathExcluded(request.getRequestURI())) {
40+
// ✅ request가 null이 아닌 경우에만 경로 체크
41+
if (request != null && errorNotificationConfig.isPathExcluded(request.getRequestURI())) {
3842
return;
3943
}
4044

@@ -45,7 +49,10 @@ public void sendErrorNotification(ErrorCode errorCode, Exception exception, Http
4549
}
4650

4751
try {
48-
ErrorNotificationDto notificationDto = ErrorNotificationDto.create(errorCode, exception, request);
52+
// ✅ request가 null인 경우를 고려하여 ErrorNotificationDto 생성
53+
ErrorNotificationDto notificationDto = request != null
54+
? ErrorNotificationDto.create(errorCode, exception, request)
55+
: ErrorNotificationDto.createWithoutRequest(errorCode, exception);
4956

5057
sendDiscordNotification(notificationDto);
5158
} catch (Exception e) {
@@ -137,35 +144,14 @@ public void sendLog(String title, String message, int color) {
137144
}
138145

139146
/**
140-
* 간단한 embed 객체 생성
147+
* 간단한 embed 생성
141148
*/
142149
private Map<String, Object> createSimpleEmbed(String title, String message, int color) {
143150
Map<String, Object> embed = new HashMap<>();
144151
embed.put("title", title);
145-
embed.put("description", truncateMessage(message, 1000)); // 디스코드 description 제한
152+
embed.put("description", message);
146153
embed.put("color", color);
147-
embed.put("timestamp", getIsoTimestamp());
154+
embed.put("timestamp", ZonedDateTime.now().format(DateTimeFormatter.ISO_INSTANT));
148155
return embed;
149156
}
150-
151-
/**
152-
* 메시지 길이 제한
153-
*/
154-
private String truncateMessage(String message, int maxLength) {
155-
if (message == null) {
156-
return "";
157-
}
158-
if (message.length() <= maxLength) {
159-
return message;
160-
}
161-
return message.substring(0, maxLength - 3) + "...";
162-
}
163-
164-
/**
165-
* ISO 타임스탬프 생성
166-
*/
167-
private String getIsoTimestamp() {
168-
return ZonedDateTime.now(java.time.ZoneOffset.UTC)
169-
.format(DateTimeFormatter.ISO_INSTANT);
170-
}
171157
}

src/main/java/server/config/AblyConfig.java

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/main/java/server/config/AwsS3Config.java

Lines changed: 0 additions & 33 deletions
This file was deleted.

src/main/java/server/config/CorsConfig.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,19 @@
55
import org.springframework.web.servlet.config.annotation.CorsRegistry;
66
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
77

8+
/**
9+
* Spring Web CORS 설정 (해커톤용 단순화)
10+
*/
811
@Configuration
9-
public class CorsConfig implements WebMvcConfigurer {
12+
public class CorsConfig {
1013

1114
@Bean
1215
public WebMvcConfigurer corsConfigurer() {
1316
return new WebMvcConfigurer() {
1417
@Override
1518
public void addCorsMappings(CorsRegistry registry) {
1619
registry.addMapping("/**")
17-
.allowedOriginPatterns(
18-
"http://localhost:5000",
19-
"http://localhost:3000",
20-
"http://172.31.35.182:8080"
21-
)
20+
.allowedOriginPatterns("*") // ✅ 해커톤용 모든 Origin 허용
2221
.allowedHeaders("*")
2322
.exposedHeaders("ACCESS_KEY", "Authorization", "RefreshToken")
2423
.allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH")

src/main/java/server/config/FCMConfig.java

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)