Files
our_recipe_atlas/README.md
T
2026-08-13 18:36:31 +09:00

11 KiB

Our Recipe Atlas

Instagram 게시물과 YouTube 영상의 텍스트를 레시피로 구조화해 개인 MongoDB와 로컬 SSD에 보관하는 웹 애플리케이션입니다. Fastify가 REST API와 Vanilla JavaScript 화면을 함께 제공합니다.

현재 구현 범위

  • Google Authorization Code/OIDC 로그인, PKCE S256, 이메일 allowlist
  • HttpOnly 자체 인증 cookie와 소유자별 API 접근 제어
  • Instagram caption 추출 및 YouTube 설명·자막·timestamp 추출
  • MiniMax OpenAI 호환 Chat Completions API를 이용한 레시피 구조화
  • AI 결과 미리보기/편집 후 저장
  • Recipe CRUD, 소유자·원본별 중복 방지
  • 외부 이미지를 최대 1280px WebP로 변환해 IMAGE_ROOT에 저장
  • 반응형 Recipe 목록/상세 화면과 YouTube timestamp 링크
  • nginx, systemd 운영 예제

영상 다운로드, STT, 브라우저 자동화 scraping, 회원가입, 결제, 소셜 기능은 포함하지 않습니다.

요구 사항

  • Node.js 22 이상
  • MongoDB
  • Google OAuth 2.0 Web client
  • MiniMax API/Token Plan key
  • Instagram caption import를 사용할 경우 유효한 Instagram session cookie

YouTube.js와 insta-fetcher는 각 서비스의 비공식 API를 사용하므로 원본 서비스 변경에 따라 조정이 필요할 수 있습니다. 구현은 src/services/extractors/에 격리되어 있습니다.

개발 환경 실행

npm install
cp .env.example .env
npm start

Windows PowerShell에서는 다음처럼 복사할 수 있습니다.

Copy-Item .env.example .env
npm start

기본 주소는 http://localhost:3000입니다. MongoDB가 실행 중이어야 서버가 시작됩니다. Google 설정이 비어 있으면 정적 로그인 화면은 열리지만 /auth/google은 설정 오류를 반환합니다.

개발 중 파일 변경을 감시하려면 npm run dev를 사용합니다.

Windows 개발과 Linux 운영 환경 분리

실행 시 process.platform을 기준으로 프로필을 자동 선택합니다. 환경파일에 NODE_ENV, MongoDB URI, 이미지 경로를 넣어도 Windows/Linux 프로필 값이 우선합니다.

Windows에서 자동 적용되는 개발 프로필:

NODE_ENV             development
MONGO_URI            mongodb://192.168.0.240:27017/our_recipe_atlas
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\...에 저장됩니다.

Linux에서 자동 적용되는 운영 프로필:

NODE_ENV             production
MONGO_URI            mongodb://192.168.0.240:27017/our_recipe_atlas
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 형식입니다.

Windows에서 만든 node_modules는 운영 서버로 복사하지 말고, sharp 등 OS별 바이너리가 Linux용으로 설치되도록 운영 서버에서 npm ci --omit=dev를 실행해야 합니다.

환경변수

변수 설명
HOST, PORT Fastify listen 주소와 포트
PUBLIC_BASE_URL 브라우저가 접근하는 외부 기준 URL
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET Google OAuth Web client 자격 증명
GOOGLE_CALLBACK_URL Google Console에 등록한 정확한 callback URL
ALLOWED_GOOGLE_EMAILS DB가 비어 있을 때 최초 관리자로 등록할 이메일 목록
AUTH_JWT_SECRET 자체 session token 서명 비밀값. 운영에서 필수
AUTH_SESSION_TTL_HOURS 로그인 세션의 절대 만료시간(시간). 기본값 24, 범위 1~720
MINIMAX_API_KEY MiniMax API 또는 Token Plan key
MINIMAX_BASE_URL 기본값 https://api.minimax.io/v1
MINIMAX_MODEL 기본값 MiniMax-M2.7
INSTAGRAM_SESSION_COOKIE insta-fetcher에 전달할 session cookie

MongoDB 주소와 이미지 루트는 위의 OS 프로필에 고정됩니다. 서버 시작 시 primary MongoDB를 먼저 시도하고, 3초 내 연결되지 않으면 VPN fallback으로 연결합니다. 실행 중인 연결이 끊겼을 때 주소를 자동 전환하는 기능은 아닙니다.

.env는 Git에 포함되지 않습니다. Google secret, MiniMax key, Instagram cookie를 브라우저 코드나 로그에 넣지 마세요.

안전한 AUTH_JWT_SECRET 예시는 Node로 생성할 수 있습니다.

node -e "console.log(require('node:crypto').randomBytes(48).toString('base64url'))"

Google OAuth 설정

Google Cloud Console에서 OAuth 동의 화면과 OAuth 2.0 Web application client를 준비합니다.

개발용 Authorized redirect URI:

http://localhost:3000/auth/google/callback

운영용 Authorized redirect URI:

https://recipe.example.com/auth/google/callback

운영에서는 http://192.168.0.250:3000/... 같은 raw IP callback을 사용하지 않습니다. 실제 도메인과 HTTPS를 nginx 또는 Caddy에서 종료하고 내부 192.168.0.250:3000으로 proxy합니다. scope는 openid email profile이며 ID Token의 서명, audience, issuer, expiry를 google-auth-library로 검증합니다. 내부 사용자 키는 이메일이 아닌 Google sub입니다.

허용할 계정은 다음처럼 설정합니다.

ALLOWED_GOOGLE_EMAILS=user1@gmail.com,user2@gmail.com

이 값은 allowed_google_emails 컬렉션이 비어 있을 때 한 번만 이관됩니다. 이후 로그인 허용 계정은 앱 하단의 로그인 허용 계정에서 이메일만 입력해 관리합니다. 최초 등록된 계정은 관리자이며, 관리자가 추가한 계정은 로그인만 허용되고 다른 계정을 초대할 수 없습니다.

외부 서비스 설정

MiniMax

MiniMax의 OpenAI 호환 endpoint를 사용합니다. key와 model은 서버 환경변수로만 전달합니다.

MINIMAX_API_KEY=...
MINIMAX_BASE_URL=https://api.minimax.io/v1
MINIMAX_MODEL=MiniMax-M2.7

파서는 최대 한 번만 repair 요청을 수행하며, 결과를 Zod schema로 검증한 뒤 미리보기로 반환합니다. 원문에 없는 재료와 수량을 추측하지 않도록 system prompt에 제한을 둡니다.

Instagram

insta-fetcher가 사용할 유효한 session cookie를 넣습니다.

INSTAGRAM_SESSION_COOKIE=...

ID와 비밀번호로 서버에서 자동 로그인하지 않습니다. cookie는 만료될 수 있으며, caption 추출이 갑자기 실패하면 먼저 cookie 상태를 확인합니다.

YouTube

youtubei.js로 title, author, description, thumbnail, transcript를 가져옵니다. 자막이 없더라도 description이 있으면 분석을 계속합니다. 영상 파일은 다운로드하지 않습니다.

데이터와 이미지

MongoDB는 our_recipe_atlas 하나만 사용하고 collection으로 환경을 분리합니다.

  • 공통: allowed_google_emails
  • 개발: users_dev, recipes_dev
  • 운영: users, recipes
  • users*.googleSub: unique index
  • recipes*: ownerGoogleSub + source.platform + source.sourceId unique index
  • 원본 text와 YouTube transcript를 recipe source에 보존해 재분석에 사용할 수 있습니다.

이미지의 DB 값은 다음과 같은 상대 경로뿐입니다.

recipes/<recipe-id>/cover.webp

실제 파일은 IMAGE_ROOT 아래에 있고 /media/ 경로로 제공됩니다. 이미지 다운로드는 HTTPS만 허용하고 DNS가 사설/loopback 주소로 해석되면 차단합니다.

API

모든 /api/** endpoint는 인증 cookie가 필요하며 로그인한 사용자의 데이터만 반환합니다.

Method Path 역할
POST /api/import/preview URL 추출 및 AI recipe 미리보기
GET /api/recipes 내 recipe 목록
GET /api/recipes/:id 내 recipe 상세
POST /api/recipes 미리보기 확인 후 저장
PATCH /api/recipes/:id recipe 필드 수정
DELETE /api/recipes/:id recipe와 로컬 이미지 삭제
GET /auth/me 현재 session 조회
POST /auth/logout session cookie 삭제
GET /api/allowed-emails 관리자용 허용 이메일 목록
POST /api/allowed-emails 관리자용 허용 이메일 추가
DELETE /api/allowed-emails/:email 관리자용 허용 이메일 삭제

테스트

외부 서비스와 MongoDB를 계속 호출하지 않도록 repository, extractor, parser, image storage를 모킹합니다.

npm test
npm run lint

테스트 범위에는 URL/ID 판별, AI schema, timestamp 정규화, allowlist, 인증 middleware, 소유자 격리, CRUD, 중복 source 처리, 이미지 상대경로와 SSRF 차단이 포함됩니다.

실제 계정과 네트워크가 준비된 뒤에는 별도 smoke test로 다음을 확인합니다.

  1. Instagram Reel/Post의 긴 caption 추출
  2. 실제 YouTube 레시피 영상의 description, transcript, timestamp 추출
  3. MiniMax 응답 품질과 미리보기 수정/저장
  4. 서버 재시작 후 MongoDB recipe와 SSD 이미지 표시

운영 배포 예시

권장 구성:

Browser
  → HTTPS reverse proxy
  → 192.168.0.250:3000 Fastify + SSD image storage
  → 192.168.0.240:27017 MongoDB

deploy/nginx.example.conf의 도메인과 인증서 경로를 바꾸고, deploy/our-recipe-atlas.service의 사용자·설치 경로·ReadWritePaths를 실제 환경에 맞춥니다.

Linux 운영 환경파일은 deploy/our-recipe-atlas.env.example을 기준으로 /etc/our-recipe-atlas.env에 만들 수 있습니다. 서비스 시작 전에 이미지 디렉터리를 서비스 계정 소유로 준비합니다.

sudo install -d -o recipe-atlas -g recipe-atlas /mnt/recipe-ssd/our_recipe_atlas/images
sudo cp deploy/our-recipe-atlas.env.example /etc/our-recipe-atlas.env
sudo chmod 600 /etc/our-recipe-atlas.env
npm ci --omit=dev

환경파일의 도메인, Google 설정, 이메일 allowlist, JWT secret, MiniMax key를 실제 값으로 교체한 뒤 서비스를 재시작합니다.

운영 환경 예시:

HOST=0.0.0.0
PORT=3000
PUBLIC_BASE_URL=https://recipe.example.com
GOOGLE_CALLBACK_URL=https://recipe.example.com/auth/google/callback

MongoDB 포트는 인터넷에 공개하지 말고 API 서버에서만 접근 가능하게 제한합니다.

참고 문서