Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
d77a4be
chore: AI Swagger 접근 보호 설정
deli-minju May 19, 2026
e046282
Merge pull request #58 from GACHI-Project/chore/#56-protect-ai-swagger
deli-minju May 19, 2026
d461503
refactor: 가정통신문 분석을 AI 서버 client로 전환
deli-minju May 19, 2026
8038afd
refactor: AI 서버 client 안정성 보강
deli-minju May 19, 2026
e4958a2
Merge branch 'develop' into refac/#57-ai-server-client
deli-minju May 20, 2026
5c94632
Merge pull request #59 from GACHI-Project/refac/#57-ai-server-client
deli-minju May 20, 2026
b444225
refactor: AI 서버 장애 시 분석 실패 정책 반영
deli-minju May 20, 2026
f7f37b2
fix: 가정통신문 분석 재시도 중복 실행 방지
deli-minju May 20, 2026
1d0f578
feat: migration v11 추가
Hminkyung May 21, 2026
13d7dbf
feat: user에 update 코드 추가
Hminkyung May 21, 2026
c6b6c4f
Merge pull request #61 from GACHI-Project/refac/#60-newsletter-ai-fai…
deli-minju May 23, 2026
225f575
feat: AI 가정통신문 전체 분석 응답 저장 연동
deli-minju May 24, 2026
f0447a8
refactor: AI 분석 응답 저장 검증 보강
deli-minju May 24, 2026
aefbcde
chore: AI 컨테이너 OpenAI 환경변수 전달 설정 추가
deli-minju May 24, 2026
79d16a0
Merge pull request #64 from GACHI-Project/feat/#63-newsletter-analyze…
deli-minju May 24, 2026
cdc7f61
feat: repository 코드 추가
Hminkyung May 24, 2026
24aabae
feat: request dto 추가
Hminkyung May 24, 2026
0b486e9
feat: controller 코드 추가 및 successcode 추가
Hminkyung May 24, 2026
9273a8d
feat: errorcode 추가
Hminkyung May 24, 2026
410b5a8
feat: auth dto에 회원가입 언어코드 추가
Hminkyung May 24, 2026
91260a8
feat: auth service 로직에 자녀 색상코드 추가
Hminkyung May 24, 2026
ea40cb0
Merge branch 'develop' of https://github.com/GACHI-Project/GACHI-BE i…
Hminkyung May 24, 2026
5c3cb36
feat: service 로직 추가- 사용자언어설정으로 받아와서 번역처리
Hminkyung May 25, 2026
bc93db6
feat: controller 코드 수정
Hminkyung May 25, 2026
85ab4ae
feat: controller 코드 수정-parameter 받기 대신에 받아오기
Hminkyung May 25, 2026
19d9f11
feat: description 추가
Hminkyung May 25, 2026
00f0644
feat: migration 알림 여부 추가
Hminkyung May 25, 2026
306a0ac
feat: user entity에 알림 설정 여부 추가
Hminkyung May 25, 2026
678ca2f
feat: 알림 설정 변경 dto 추가
Hminkyung May 25, 2026
8be2ce8
feat: 내 정보 조회 controller 추가
Hminkyung May 25, 2026
3d0ebd3
feat: migration 파일 추가-사용자 알림설정
Hminkyung May 25, 2026
cb243b3
feat: spotlessapply 반영 및 description 추가
Hminkyung May 25, 2026
5316f98
feat: AI 분석 결과 기반 캘린더 미리보기 자동 생성
deli-minju May 25, 2026
812a4ba
fix: 캘린더 미리보기 저장 실패 격리
deli-minju May 25, 2026
b193aba
Merge pull request #67 from GACHI-Project/feat/#65-calendar-preview-f…
deli-minju May 25, 2026
807188e
feat: 코드리뷰 반영
Hminkyung May 25, 2026
23572b5
Merge branch 'develop' of https://github.com/GACHI-Project/GACHI-BE i…
Hminkyung May 25, 2026
ca1b3cc
fix: HttpClient 변경
Hminkyung May 25, 2026
922291b
feat: AI 번역 추가로 진행
Hminkyung May 25, 2026
84f5e12
feat: spotlessApply 반영
Hminkyung May 25, 2026
8abed56
feat: 테스트 코드에 언어설정 추가
Hminkyung May 25, 2026
ecd502f
Merge pull request #66 from GACHI-Project/feat/#62-language-change
Hminkyung May 25, 2026
d1d889a
Merge branch 'main' into develop
deli-minju May 25, 2026
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
1 change: 0 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ dependencies {
implementation 'software.amazon.awssdk:s3'
implementation 'com.drewnoakes:metadata-extractor:2.19.0'
implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'org.apache.pdfbox:pdfbox:3.0.3'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
compileOnly 'org.projectlombok:lombok'
Expand Down
5 changes: 5 additions & 0 deletions deploy/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH=true
SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE=true
SPRING_MAIL_USERNAME=

AI_SERVER_BASE_URL=http://ai:8000
OPENAI_ENABLED=false
OPENAI_API_KEY=
OPENAI_MODEL=gpt-4.1-mini
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_TIMEOUT_SECONDS=60

AUTH_EMAIL_FROM_ADDRESS=
AUTH_EMAIL_NOOP_ALLOWED=false
Expand Down
12 changes: 8 additions & 4 deletions deploy/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,7 @@ services:
CLOVA_OCR_SECRET_KEY: ${CLOVA_OCR_SECRET_KEY}
PAPAGO_CLIENT_ID: ${PAPAGO_CLIENT_ID}
PAPAGO_CLIENT_SECRET: ${PAPAGO_CLIENT_SECRET}
OPENAI_API_KEY: ${OPENAI_API_KEY}
OPENAI_MODEL: ${OPENAI_MODEL:-gpt-4.1-mini}
OPENAI_MAX_TOKENS: ${OPENAI_MAX_TOKENS:-2000}
AI_SERVER_BASE_URL: ${AI_SERVER_BASE_URL:-http://ai:8000}
secrets:
- source: spring_mail_password
target: spring_mail_password
Expand All @@ -108,6 +106,8 @@ services:
condition: service_healthy
redis:
condition: service_healthy
ai:
condition: service_healthy
Comment on lines +109 to +110
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

백엔드 기동이 AI 헬스체크에 하드 의존합니다.

Line 109-110에서 backendaiservice_healthy를 필수로 기다리면, AI 장애 시 백엔드 재기동이 막혀 비-AI API까지 함께 중단될 수 있습니다. AI 실패는 앱 레이어에서 처리하고 컨테이너 기동 의존성은 분리하는 게 안전합니다.

수정 제안
     depends_on:
       db:
         condition: service_healthy
       redis:
         condition: service_healthy
-      ai:
-        condition: service_healthy
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ai:
condition: service_healthy
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/docker-compose.yml` around lines 109 - 110, 현재 docker-compose에
backend가 ai의 service_healthy에 하드 의존하고 있어 AI 헬스체크 실패 시 backend 재기동이 차단됩니다;
docker-compose의 backend 서비스 설정에서 ai에 대한 "condition: service_healthy"를 제거하거나 대체하여
컨테이너 시작 의존성을 분리하세요 (예: 제거하거나 "condition: service_started"로 바꿔 AI 헬스 상태에 따른 재시작
제어를 애플리케이션 레이어로 위임). 변경 대상 식별자: 서비스 이름 "backend", "ai"와 조건 키 "condition:
service_healthy".

healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:8080/actuator/health || exit 1"]
interval: 30s
Expand All @@ -120,7 +120,11 @@ services:
image: ${AI_IMAGE:-gachi-ai:latest}
container_name: gachi-ai
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
OPENAI_ENABLED: ${OPENAI_ENABLED:-false}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
OPENAI_MODEL: ${OPENAI_MODEL:-gpt-4.1-mini}
OPENAI_BASE_URL: ${OPENAI_BASE_URL:-https://api.openai.com/v1}
OPENAI_TIMEOUT_SECONDS: ${OPENAI_TIMEOUT_SECONDS:-60}
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:8000/ai/health || exit 1"]
interval: 30s
Expand Down
30 changes: 30 additions & 0 deletions deploy/nginx/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,36 @@ http {
proxy_set_header X-Forwarded-Proto $scheme;
}

location /ai/docs {
auth_basic "GACHI Swagger";
auth_basic_user_file /etc/nginx/secrets/swagger_htpasswd.txt;
proxy_pass http://ai_upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

location = /ai/openapi.json {
auth_basic "GACHI Swagger";
auth_basic_user_file /etc/nginx/secrets/swagger_htpasswd.txt;
proxy_pass http://ai_upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

location /ai/redoc {
auth_basic "GACHI Swagger";
auth_basic_user_file /etc/nginx/secrets/swagger_htpasswd.txt;
proxy_pass http://ai_upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

location /ai/ {
proxy_pass http://ai_upstream;
proxy_set_header Host $host;
Expand Down
32 changes: 31 additions & 1 deletion deploy/nginx/nginx.https.template.conf
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,36 @@ http {
proxy_set_header X-Forwarded-Proto $scheme;
}

location /ai/docs {
auth_basic "GACHI Swagger";
auth_basic_user_file /etc/nginx/secrets/swagger_htpasswd.txt;
proxy_pass http://ai_upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

location = /ai/openapi.json {
auth_basic "GACHI Swagger";
auth_basic_user_file /etc/nginx/secrets/swagger_htpasswd.txt;
proxy_pass http://ai_upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

location /ai/redoc {
auth_basic "GACHI Swagger";
auth_basic_user_file /etc/nginx/secrets/swagger_htpasswd.txt;
proxy_pass http://ai_upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

location /ai/ {
proxy_pass http://ai_upstream;
proxy_set_header Host $host;
Expand All @@ -93,4 +123,4 @@ http {
add_header Content-Type text/plain;
}
}
}
}
2 changes: 1 addition & 1 deletion docs/error-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

상세 에러 코드는 아래 Google Sheets를 단일 원본으로 관리합니다.

- Google Sheets: [Error Code Single Source](https://docs.google.com/spreadsheets/d/1sUG_JJsVl6a8nR6k8jO_b7UrIEZzuvQdPB6S2S6fqgo/edit?gid=2097363290#gid=2097363290)
- Google Sheets: [Error Code Single Source](https://docs.google.com/spreadsheets/d/1sUG_JJsVl6a8nR6k8jO_b7UrIEZzuvQdPB6S2S6fqgo/edit?gid=1519705696#gid=1519705696)

## 운영 원칙

Expand Down
28 changes: 28 additions & 0 deletions docs/newsletter-ai-analyze-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 가정통신문 AI 전체 분석 응답 저장 연동

이 문서는 BE가 AI 서버의 가정통신문 전체 분석 응답을 저장 모델에 반영하는 구현 결정을 정리한다. 상세 API 명세는 노션을 기준으로 관리한다.

## 결정 사항

- BE는 가정통신문 파이프라인의 AI 분석 단계에서 `POST /ai/newsletters/analyze`를 호출한다.
- AI 서버 응답의 top-level `title`, `summary`를 `newsletter.title`, `newsletter.summary`에 저장한다.
- AI 서버 응답의 `items`는 기존 checklist 저장 흐름에 연결한다.
- `dateCandidates` 요청 형식과 `items` 응답 형식은 기존 `extract-items` 계약을 유지한다.
- `meta`는 운영 보조 정보로 받고, 현재 BE 저장 모델에는 직접 저장하지 않는다.
- AI 서버 호출 실패 정책은 `docs/newsletter-ai-failure-policy.md`를 따른다.

## 저장 정책

- `title`: AI 응답 제목을 우선 저장한다. 빈 값이면 기존 BE fallback 제목을 사용한다.
- `summary`: AI 응답 요약을 우선 저장한다. 빈 값이면 기존 BE fallback 요약을 사용한다.
- `items`: `title`이 비어 있지 않은 항목만 checklist로 저장한다.
- `datetime`: TODO 저장 시 `targetDate`, `targetDateLabel` 생성에 사용한다. 파싱 실패 시 날짜 없이 저장한다.
- `dateStatus`: checklist 저장 모델에는 별도 컬럼이 없으므로 직접 저장하지 않는다. 단, `confirmed`이고 `datetime`이 파싱 가능한 항목은 calendar preview 생성 대상으로 사용한다.
- `calendar preview`: AI 분석으로 저장된 checklist/TODO ID와 확정 날짜를 묶어 Redis preview에 저장한다. `ambiguous`, `missing`, 날짜 파싱 실패 항목은 사용자가 확인해야 하므로 preview에 자동 포함하지 않는다.

## 호환 정책

- AI 서버의 `extract-items` baseline API는 유지하지만, BE 파이프라인은 `analyze`를 우선 사용한다.
- AI 서버가 `items`를 빈 배열로 반환해도 `title`, `summary`는 저장할 수 있다.
- 확정 날짜가 없는 분석 결과는 기존 Redis preview를 비워, 재분석 후 오래된 일정 후보가 남지 않게 한다.
- AI 서버 장애 시에는 OCR/번역/dateCandidates 스냅샷을 보존하고 newsletter 상태를 `FAILED`로 둔다.
43 changes: 43 additions & 0 deletions docs/newsletter-ai-failure-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# AI 서버 장애 시 가정통신문 처리 정책

이 문서는 API 명세서가 아니라 BE가 장애 상황에서 어떤 상태와 데이터를 남길지 정한 운영 정책이다.
상세 API 명세는 노션을 기준으로 관리한다.

## 결정 사항

- AI 서버 호출 실패 시 newsletter 상태는 `FAILED`로 둔다.
- 별도 부분 성공 상태는 이번 범위에서 추가하지 않는다.
- OCR, 정제 원문, 번역 결과, 날짜 후보는 가능한 범위까지 저장한다.
- AI 분석 결과에서 파생되는 제목, 요약, checklist, calendar event는 생성하지 않는다.
- 실패 원인 추적을 위해 `failureStage`, `failureReason`을 저장한다.
- 상태 조회 응답은 `FAILED`일 때 `canRetry=true`와 실패 단계를 반환한다.
- 사용자는 `POST /api/v1/newsletters/{newsletterId}/analysis/retry`로 같은 문서를 다시 분석할 수 있다.

## 실패 시 저장 범위

AI 서버 단계에서 실패하면 다음 데이터는 남긴다.

- `status = FAILED`
- `ocrText`
- `originalText`
- `translatedText`
- `dateCandidates`
- `failureStage = AI_SERVER`
- `failureReason`

다음 데이터는 비워 둔다.

- `title`
- `summary`
- checklist
- calendar event

## 재시도 정책

재시도는 `FAILED` 상태에서만 허용한다.

재시도 요청이 들어오면 기존 checklist와 calendar event 파생 데이터를 삭제하고, newsletter 상태를 `PENDING`으로 되돌린 뒤 파이프라인을 다시 실행한다.

자동 재시도 큐는 이번 범위에서 만들지 않는다. AI 서버 장애와 문서 입력 문제를 자동으로 구분하기 어렵고, 외부 API 비용과 장애 확산 위험이 있기 때문이다.

추후 필요하면 `failureStage=AI_SERVER`인 건만 백오프 큐에 넣는 방식으로 확장한다.
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ public record SignupRequest(
@NotBlank
@Pattern(regexp = PhoneNumberValidation.REGEXP, message = PhoneNumberValidation.MESSAGE)
String phoneNumber,
@NotNull Boolean consentAgreed) {}
@NotNull Boolean consentAgreed,
@NotNull(message = "languageCode는 필수입니다.")
@Pattern(
regexp = "^(KO|US|ZH|VI)$",
message = "지원하지 않는 언어 코드입니다. KO, US, ZH, VI 중 하나여야 합니다.")
String languageCode) {}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.gachi.be.domain.auth.service;

import com.gachi.be.domain.user.entity.User;
import com.gachi.be.domain.user.entity.UserStatus;
import com.gachi.be.domain.user.entity.enums.UserStatus;
import com.gachi.be.domain.user.repository.UserRepository;
import com.gachi.be.global.code.ErrorCode;
import com.gachi.be.global.exception.BusinessException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import com.gachi.be.domain.auth.service.TokenHashService;
import com.gachi.be.domain.auth.service.password.PasswordStrengthEvaluator;
import com.gachi.be.domain.user.entity.User;
import com.gachi.be.domain.user.entity.UserStatus;
import com.gachi.be.domain.user.entity.enums.UserStatus;
import com.gachi.be.domain.user.repository.UserRepository;
import com.gachi.be.global.code.ErrorCode;
import com.gachi.be.global.exception.AppException;
Expand Down Expand Up @@ -138,6 +138,7 @@ public SignupResponse signup(SignupRequest request) {
.name(name)
.phoneNumber(phoneNumber)
.status(UserStatus.ACTIVE)
.languageCode(request.languageCode())
.emailVerifiedAt(now)
.consentAgreedAt(now)
.consentVersion(authProperties.getConsentVersion())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
/**
* 캘린더 일정 등록 플로우에서 사용하는 Redis 임시 데이터 관리 서비스.
*
* <p>흐름 요약: 1. 가정통신문 AI 분석 완료 → AI 파이프라인에서 Redis에 preview 데이터 저장 (추후 연결) 2. GET /calendar/preview →
* Redis에서 읽어 팝업에 표시 3. PATCH /calendar/dates → Redis에서 날짜 수정 후 다시 저장 4. POST /calendar → Redis 데이터
* 기반으로 calendar_events insert → Redis 키 삭제
* <p>흐름 요약: 1. 가정통신문 AI 분석 완료 → AI 파이프라인에서 Redis에 preview 데이터 저장 2. GET /calendar/preview → Redis에서
* 읽어 팝업에 표시 3. PATCH /calendar/dates → Redis에서 날짜 수정 후 다시 저장 4. POST /calendar → Redis 데이터 기반으로
* calendar_events insert → Redis 키 삭제
*
* <p>Redis 키 형식: newsletter:preview:{newsletterId} - TTL: 1시간 (사용자가 팝업을 열고 이탈해도 자동 만료)
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,10 @@ public ApiResponse<NewsletterUploadResponse> upload(
schema = @Schema(type = "string", format = "binary")))
@RequestPart("file")
MultipartFile file,
@Parameter(description = "연결할 자녀 ID. 미선택 시 생략") @RequestParam(required = false) Long childId,
@Parameter(description = "언어 코드 (KO/US/ZH/VI). 기본값 KO")
@RequestParam(defaultValue = "KO")
@Pattern(regexp = "KO|US|ZH|VI", message = "language는 KO/US/ZH/VI 중 하나여야 합니다.")
String language) {
@Parameter(description = "연결할 자녀 ID. 미선택 시 생략") @RequestParam(required = false)
Long childId) {

NewsletterUploadResponse response = newsletterService.upload(userId, file, childId, language);
NewsletterUploadResponse response = newsletterService.upload(userId, file, childId);
return ApiResponse.success(SuccessCode.NEWSLETTER_UPLOAD_SUCCESS, response);
}

Expand All @@ -92,6 +89,24 @@ public ApiResponse<NewsletterStatusResponse> getStatus(
return ApiResponse.success(SuccessCode.NEWSLETTER_STATUS_SUCCESS, response);
}

/** 실패한 가정통신문 분석 재시도 API */
@Operation(
summary = "가정통신문 분석 재시도",
description =
"""
FAILED 상태의 가정통신문 분석을 다시 시작합니다.
AI 서버 장애로 실패한 경우 기존 OCR/번역 결과는 보존되어 있고, 재시도 시 파이프라인이 다시 실행됩니다.
""")
@PostMapping("/{newsletterId}/analysis/retry")
@ResponseStatus(HttpStatus.ACCEPTED)
public ApiResponse<NewsletterUploadResponse> retryAnalysis(
@AuthenticationPrincipal Long userId,
@Parameter(description = "가정통신문 ID", required = true) @PathVariable Long newsletterId) {

NewsletterUploadResponse response = newsletterService.retryAnalysis(userId, newsletterId);
return ApiResponse.success(SuccessCode.NEWSLETTER_RETRY_ACCEPTED, response);
}

/** 번역 결과 조회 API. */
@Operation(
summary = "번역 결과 조회",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.gachi.be.domain.newsletter.dto.response;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.gachi.be.domain.newsletter.entity.Newsletter;
import com.gachi.be.domain.newsletter.entity.enums.NewsletterStatus;

/**
Expand All @@ -10,19 +11,31 @@
*/
@JsonInclude(JsonInclude.Include.NON_NULL) // null인 필드는 JSON 응답에서 제외
public record NewsletterStatusResponse(
NewsletterStatus status, int progressPercent, String progressMessage, String errorMessage) {
NewsletterStatus status,
int progressPercent,
String progressMessage,
String errorMessage,
String failureStage,
boolean canRetry) {

/**
* 분석 상태에 따라 적절한 진행률과 에러메시지를 자동 계산하는 팩토리 메서드. TODO: 현재는 고정값으로 처리, 추후 AI 서버에서 단계별 진행률을 받아 세분화 예정
*/
public static NewsletterStatusResponse of(NewsletterStatus status) {
String safeErrorMessage = "분석 중 오류가 발생했어요. 잠시 후 다시 시도해 주세요.";
public static NewsletterStatusResponse of(Newsletter newsletter) {
NewsletterStatus status = newsletter.getStatus();
return switch (status) {
case PENDING -> new NewsletterStatusResponse(status, 0, "문서를 준비하고 있어요", null);
case PROCESSING -> new NewsletterStatusResponse(status, 60, "텍스트를 인식하고 번역하고 있어요", null);
case COMPLETED -> new NewsletterStatusResponse(status, 100, "분석이 완료되었어요", null);
case PENDING -> new NewsletterStatusResponse(status, 0, "문서를 준비하고 있어요", null, null, false);
case PROCESSING ->
new NewsletterStatusResponse(status, 60, "텍스트를 인식하고 번역하고 있어요", null, null, false);
case COMPLETED -> new NewsletterStatusResponse(status, 100, "분석이 완료되었어요", null, null, false);
case FAILED ->
new NewsletterStatusResponse(status, 0, null, "분석 중 오류가 발생했어요. 잠시 후 다시 시도해 주세요.");
new NewsletterStatusResponse(
status,
0,
null,
"분석 중 오류가 발생했어요. 다시 분석을 시도할 수 있어요.",
newsletter.getFailureStage(),
true);
};
}
}
Loading