feat: add batched recipe import queue

This commit is contained in:
2026-08-28 11:09:36 +09:00
parent 4491c91455
commit f65e660184
19 changed files with 1080 additions and 262 deletions
+16 -10
View File
@@ -7,9 +7,10 @@ Instagram 게시물과 YouTube 영상의 텍스트를 레시피로 구조화해
- Google Authorization Code/OIDC 로그인, PKCE `S256`, 이메일 allowlist
- HttpOnly 자체 인증 cookie와 그룹 멤버 간 recipe 공유
- Instagram caption 추출 및 YouTube 설명·자막·timestamp 추출
- MiniMax OpenAI 호환 Chat Completions API를 이용한 레시피 구조화
- 여러 URL을 영속 대기열에 등록하고 사용자가 요청할 때 서버에서 비동기 처리
- 추출한 본문을 최대 3개·10만 자 단위로 묶어 MiniMax Chat Completions API로 레시피 구조화
- 영어 등 외국어 원문의 recipe 필드를 자연스러운 한국어로 번역
- AI 결과 미리보기/편집 후 저장
- AI 결과를 schema로 검증해 자동 저장하고 실패 작업은 대기열에서 재시도
- Recipe CRUD, 그룹 멤버 조회와 소유자·원본별 중복 방지
- 외부 이미지를 최대 1280px WebP로 변환해 `IMAGE_ROOT`에 저장
- 반응형 Recipe 목록/상세 화면과 YouTube timestamp 링크
@@ -59,7 +60,7 @@ MONGO_FALLBACK_URI mongodb://172.16.0.7:27017/our_recipe_atlas
IMAGE_ROOT ./data/images
```
개발과 운영 모두 `our_recipe_atlas` DB를 사용합니다. 개발 데이터는 `users_dev`, `recipes_dev` 컬렉션으로 분리되며, 로그인 허용 계정은 공통 `allowed_google_emails` 컬렉션을 사용합니다. 프로젝트를 `D:\project\our_recipe_atlas`에서 실행하면 이미지는 `D:\project\our_recipe_atlas\data\images\recipes\...`에 저장됩니다.
개발과 운영 모두 `our_recipe_atlas` DB를 사용합니다. 개발 데이터는 `users_dev`, `groups_dev`, `import_jobs_dev`, `recipes_dev` 컬렉션으로 분리되며, 로그인 허용 계정은 공통 `allowed_google_emails` 컬렉션을 사용합니다. 프로젝트를 `D:\project\our_recipe_atlas`에서 실행하면 이미지는 `D:\project\our_recipe_atlas\data\images\recipes\...`에 저장됩니다.
Linux에서 자동 적용되는 운영 프로필:
@@ -70,7 +71,7 @@ MONGO_FALLBACK_URI mongodb://172.16.0.7:27017/our_recipe_atlas
IMAGE_ROOT /mnt/recipe-ssd/our_recipe_atlas/images
```
운영 데이터는 같은 DB의 `users`, `recipes` 컬렉션에 저장되고 이미지는 `/mnt/recipe-ssd/our_recipe_atlas/images/recipes/...`에 저장됩니다. 이미지의 DB 값은 두 OS 모두 `recipes/<recipe-id>/cover.webp` 형식입니다.
운영 데이터는 같은 DB의 `users`, `groups`, `import_jobs`, `recipes` 컬렉션에 저장되고 이미지는 `/mnt/recipe-ssd/our_recipe_atlas/images/recipes/...`에 저장됩니다. 이미지의 DB 값은 두 OS 모두 `recipes/<recipe-id>/cover.webp` 형식입니다.
Windows에서 만든 `node_modules`는 운영 서버로 복사하지 말고, `sharp` 등 OS별 바이너리가 Linux용으로 설치되도록 운영 서버에서 `npm ci --omit=dev`를 실행해야 합니다.
@@ -138,7 +139,7 @@ MINIMAX_BASE_URL=https://api.minimax.io/v1
MINIMAX_MODEL=MiniMax-M2.7
```
파서는 최대 한 번만 repair 요청을 수행하며, 결과 Zod schema로 검증한 뒤 미리보기로 반환합니다. 원문에 없는 재료와 수량을 추측하지 않도록 system prompt에 제한을 둡니다.
URL별로 본문을 먼저 추출한 뒤 최대 3개·원문 합계 10만 자씩 한 요청에 넣습니다. 배치 전체 또는 일부 결과가 유효하지 않으면 해당 원문만 개별 요청으로 재시도합니다. 각 결과 Zod schema로 검증하며, 원문에 없는 재료와 수량을 추측하지 않도록 system prompt에 제한을 둡니다.
### Instagram
@@ -159,10 +160,11 @@ ID와 비밀번호로 서버에서 자동 로그인하지 않습니다. cookie
MongoDB는 `our_recipe_atlas` 하나만 사용하고 collection으로 환경을 분리합니다.
- 공통: `allowed_google_emails`
- 개발: `users_dev`, `groups_dev`, `recipes_dev`
- 운영: `users`, `groups`, `recipes`
- 개발: `users_dev`, `groups_dev`, `import_jobs_dev`, `recipes_dev`
- 운영: `users`, `groups`, `import_jobs`, `recipes`
-`users*.googleSub`: unique index
-`groups*.memberGoogleSubs`: 조회 index
-`import_jobs*`: 사용자·원본별 unique index와 상태 조회 index
-`recipes*`: `ownerGoogleSub + source.platform + source.sourceId` unique index
- 원본 text와 YouTube transcript를 recipe source에 보존해 재분석에 사용할 수 있습니다.
@@ -189,7 +191,11 @@ recipes/<recipe-id>/cover.webp
| Method | Path | 역할 |
| --- | --- | --- |
| `POST` | `/api/import/preview` | URL 추출 및 AI recipe 미리보기 |
| `POST` | `/api/import/jobs` | URL 목록을 사용자 대기열에 등록 |
| `GET` | `/api/import/jobs` | 대기·처리·실패 작업 조회 |
| `POST` | `/api/import/jobs/process` | 대기 작업의 서버 처리를 요청하고 즉시 `202` 반환 |
| `POST` | `/api/import/jobs/:id/retry` | 실패 작업을 대기 상태로 되돌림 |
| `POST` | `/api/import/preview` | 이전 클라이언트 호환용 단일 URL 미리보기 |
| `GET` | `/api/recipes` | 내 그룹의 recipe 목록 |
| `GET` | `/api/recipes/:id` | 내 그룹의 recipe 상세 |
| `POST` | `/api/recipes` | 미리보기 확인 후 저장 |
@@ -210,13 +216,13 @@ npm test
npm run lint
```
테스트 범위에는 URL/ID 판별, AI schema, timestamp 정규화, allowlist, 인증 middleware, 그룹 공유와 소유자 쓰기 권한, CRUD, 중복 source 처리, 이미지 상대경로와 SSRF 차단이 포함됩니다.
테스트 범위에는 URL/ID 판별, MiniMax 본문 배치와 AI schema, 대기열 상태 전이, timestamp 정규화, allowlist, 인증 middleware, 그룹 공유와 소유자 쓰기 권한, CRUD, 중복 source 처리, 이미지 상대경로와 SSRF 차단이 포함됩니다.
실제 계정과 네트워크가 준비된 뒤에는 별도 smoke test로 다음을 확인합니다.
1. Instagram Reel/Post의 긴 caption 추출
2. 실제 YouTube 레시피 영상의 description, transcript, timestamp 추출
3. MiniMax 응답 품질과 미리보기 수정/저장
3. 여러 본문의 MiniMax 응답 품질과 자동 저장
4. 서버 재시작 후 MongoDB recipe와 SSD 이미지 표시
## 운영 배포 예시