{"id":51398783,"url":"https://github.com/revfactory/opengraph","last_synced_at":"2026-07-04T04:38:20.013Z","repository":{"id":368577861,"uuid":"1285793747","full_name":"revfactory/opengraph","owner":"revfactory","description":"URL 입력 → Open Graph 추출 서비스 · 설계·참조구현(Node/TS)·운영 체계 · 에이전트 하네스","archived":false,"fork":false,"pushed_at":"2026-07-01T07:01:32.000Z","size":214,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-01T09:05:34.955Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://opengraph-unfurl.netlify.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/revfactory.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-01T06:38:09.000Z","updated_at":"2026-07-01T07:02:38.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/revfactory/opengraph","commit_stats":null,"previous_names":["revfactory/opengraph"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/revfactory/opengraph","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/revfactory%2Fopengraph","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/revfactory%2Fopengraph/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/revfactory%2Fopengraph/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/revfactory%2Fopengraph/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/revfactory","download_url":"https://codeload.github.com/revfactory/opengraph/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/revfactory%2Fopengraph/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35110270,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-04T04:38:19.504Z","updated_at":"2026-07-04T04:38:19.998Z","avatar_url":"https://github.com/revfactory.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# opengraph — URL → Open Graph 추출 서비스\n\n사용자가 URL을 입력하면 해당 URL의 Open Graph 메타데이터(제목·설명·이미지 등)를 추출해 링크 미리보기를 제공하는 서비스의 **설계 · 참조 구현 · 운영 체계**.\n\n\u003e 이 저장소는 `og-extraction-orchestrator` 하네스(전문 에이전트 팀)로 산출됐다. **실행 가능한 서비스 소스는 [`service/`](service/)**, 설계 상세는 [`docs/`](docs/). 하네스 재실행/부분 수정은 CLAUDE.md의 트리거 규칙 참조. (`_workspace/`는 팀 실행의 감사 추적용 중간 산출물)\n\n---\n\n## 4개 핵심 결정 (요약)\n\n| # | 주제 | 결정 | 상세 |\n|---|------|------|------|\n| ① | **런타임** | 그린필드 = **Node.js 22 + TypeScript + Fastify**. 조직이 Python 중심이면 **FastAPI로 반전**. 크롤러를 독립 마이크로서비스로 격리해 결정을 되돌릴 수 있게 함 | [docs/01_runtime.md](docs/01_runtime.md) |\n| ② | **실패 추적 / 실패율 감소** | 단일 **에러 taxonomy(28코드)** → `crawl_attempts`/`failed_crawls`(Postgres) **SQL 집계** → **per-domain 규칙 테이블(hot-reload 레버)** → **주간 플라이휠** | [docs/01_reliability_ops.md](docs/01_reliability_ops.md) |\n| ③ | **정적이지 않은 링크** | **비용 순 승격 래더**(static→oEmbed→headless), 신호 기반 헤드리스, 리다이렉트/단축링크 최종 URL 확정, **SSRF: DNS 해석 후 + IP 핀 + 홉마다 재검증** | [docs/01_crawl_engine.md](docs/01_crawl_engine.md) |\n| ④ | **아키텍처 / 캐싱 / DB / 속도** | **2단계 캐시 key**(정규화 URL→payload_key) + SWR + single-flight, **Redis(핫) + Postgres(집계/내구) + 오브젝트 스토리지(이미지)** | [docs/01_platform.md](docs/01_platform.md) |\n\n**통합 설계 + 경계면 확정**: [docs/02_integrated_architecture.md](docs/02_integrated_architecture.md) · **경계면 검증(QA)**: [docs/04_integration_review.md](docs/04_integration_review.md)\n\n---\n\n## 아키텍처 한눈에\n\n```\n클라이언트 ─GET /unfurl?url=─▶ [CDN] ─▶ [API 티어(stateless)]\n                                          │ normalize_url → 2단계 캐시 조회(L1 LRU → Redis)\n                                          │  hit → 즉시 / stale → SWR / miss → single-flight\n                                          ▼\n                              [크롤/추출 마이크로서비스]  ← 런타임 결정 봉인 지점\n                                Stage0 정규화+SSRF(DNS후 IP핀)\n                                Stage1 static(수동 리다이렉트·홉마다 SSRF·Range)\n                                승격신호→ Stage2 oEmbed / Stage3 headless(Playwright, 별도 풀)\n                                폴백파싱 OG→Twitter→oEmbed→JSON-LD→HTML (completeness)\n                                          │\n                     ┌────────────────────┼─────────────────────┐\n                     ▼                    ▼                     ▼\n              Redis(핫캐시/락)   Postgres(crawl_attempts·      오브젝트스토리지\n                                 failed_crawls·domain_rules·dlq)   +CDN(이미지)\n                                          │\n                          [비동기] 큐→재시도(백오프)→DLQ→주간 플라이휠(집계→진단→규칙→백필→측정)\n```\n\n**3대 불변식**: ① 최종 URL이 진실의 원천 · ② 비용 순 승격(헤드리스는 신호가 있을 때만) · ③ 분류 없는 실패는 못 고친다(단일 에러 코드).\n\n---\n\n## 참조 구현\n\nNode.js 22 + TypeScript. 소스는 [`service/`](service/)(23개 모듈). 코어 로직(URL 정규화·SSRF-safe 페치·계층적 승격·OG 폴백 파싱·2단계 캐시 key·에러 taxonomy)은 완전 구현. 상세 실행법은 [`service/README.md`](service/README.md).\n\n```bash\ncd service\nnpm install\nnpm run typecheck      # 타입 검증\nnpm run smoke          # 순수 로직 검증 15종\n# 로컬 인메모리 모드로 기동 (외부 의존 0):\nnpm run dev\nopen http://localhost:8080/   # ← 웹 콘솔(플레이그라운드): URL 입력 → 미리보기 + 진단\n# 실제 Redis+Postgres 모드:\ndocker compose up\n```\n\n**웹 콘솔** (`GET /`, [`service/public/index.html`](service/public/index.html)) — URL을 입력하면 추출된 OG 미리보기 카드와 진단 리드아웃(fetch 전략·캐시 상태·완성도 게이지·**리다이렉트/단축링크 체인**·source map·에러 taxonomy)을 보여주는 단일 파일 플레이그라운드. 같은 오리진의 `/unfurl?...\u0026debug=true`를 호출한다.\n\n- **검증 상태**: `typecheck` 에러 0 · `smoke` 15/15 · 라이브 e2e(정상 추출 / SSRF 차단 / INVALID_URL) 통과 · 경계면 QA 11 PASS / 0 MISMATCH.\n\n---\n\n## 디렉토리\n\n```\nopengraph/\n├── README.md                 # (이 파일) 최종 패키지\n├── CLAUDE.md                 # 하네스 포인터 + 트리거 규칙 + 변경 이력\n├── service/                  # ★ 실행 가능한 Node/TS 서비스 (src/ 23개 모듈, docker compose up)\n│   ├── src/                  #   normalize·safe-fetch·ssrf·extract·cache·taxonomy·api ...\n│   ├── migrations/ · docker-compose.yml · Dockerfile · test/\n│   └── README.md             #   서비스 실행 안내\n├── docs/                     # 확정 설계 문서 (4개 고려사항 + 통합 + QA)\n├── .claude/agents/ (6) · skills/ (7)   # 재사용 하네스\n└── _workspace/               # 팀 실행 중간 산출물 (설계 원본 01~04 · 감사 추적)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frevfactory%2Fopengraph","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frevfactory%2Fopengraph","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frevfactory%2Fopengraph/lists"}