BeamNG.Earth
A 1:1 scale streamed Earth for BeamNG.drive: real elevation and satellite imagery, floating origin, LOD streaming from a companion server
Track game collaborations and crossover events: which anime, films, characters and brands teamed up with which games, when, and how. Hand-reviewed from public sources.
GamesTypeScript · CSSGitHub @daniel2231OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗게임 × IP 콜라보 트래커의 Next.js 프론트엔드. PRD의 M2 범위(F-01~F-08)와 디자인 초안 4개 화면(목록·상세·작품·관리자)을 구현했다.
/ko, /en 경로src/schema, 나중에 packages/schema로 이동)corepack enable # pnpm 12
pnpm install
cp .env.example .env.local
pnpm dev # http://localhost:3000/ko
API_BASE_URL을 비워 두면 메모리 목(mock) API로 동작한다. 목 데이터는 PRD에 나온 콜라보 3건(태고의 달인 × 자가리코, Blood Strike / WePlay × 진격의 거인)과 검수용 초안 1건뿐이고, 확인되지 않은 값은 비워 두었다. 관리자에서 수정한 내용은 서버를 재시작하면 사라진다. MOCK_LATENCY_MS=800처럼 지정하면 목 API 호출마다 그만큼 지연을 넣어, 로딩·대기 화면을 실제 API와 비슷하게 확인할 수 있다. MOCK_QUEUE_PAGE_SIZE=1처럼 검수 큐 한 페이지 크기(기본 100, 백엔드와 같음)를 줄이면 “더 보기”를 확인할 수 있다.
개발 중에는 ADMIN_DEV_BYPASS=1이면 로그인 없이 /ko/admin에 들어갈 수 있다(production에서는 무시).
pnpm typecheck # next typegen + tsc (TS 7)
pnpm test # vitest: 진행 상태 계산, URL 필터, 목 API 규칙
pnpm build
| 경로 | 내용 | PRD |
|---|---|---|
/[locale] | 홈: 현황 한 줄(등록된 콜라보 전체 수, 진행 중·예정·종료 수) → 대표 콜라보 큰 카드와 진행 중 번호 목록 → IP로 찾아보기(관리자가 고른 작품 타일) → 새로 등록됨 카드(위에 나온 콜라보 제외) → 일정표(곧 시작·곧 종료). 옛 목록 주소(/[locale]?필터…)는 /[locale]/collabs로 301 | — |
/[locale]/collabs | 목록·검색·필터(카테고리, 파트너 분류, 권역, 플랫폼, 유형, 기간, 진행 상태). 필터 상태는 URL 쿼리에 그대로 들어가 공유 가능. 상위 키를 고르면 하위 항목 포함. 필터를 하나만 건 목록(진행 상태 하나 또는 분류 항목 하나, 결과 1건 이상)은 랜딩 페이지로 색인하고 sitemap에 넣는다. 그 외 필터 조합은 noindex | F-01 |
/[locale]/collabs/[slug] | 상세: 기간·진행 상태, 요약(ko/en), 참여 작품·회사(역할), 출처(확인일), 관련 콜라보. ISR | F-02 |
/[locale]/properties/[slug] | 작품(게임·IP) 페이지: 연도별 타임라인, 건수, 협업 작품, 유형 분포. ISR | F-03 |
/[locale]/companies/[slug] | 회사 페이지: 참여 콜라보와 역할. ISR | F-03 |
/[locale]/admin | 검수 대기열 + 편집기. draft → in_review → published → archived, 발행 조건 체크리스트, 중복 후보 비교, 분류 매핑 실패 표시, If-Match: rev 충돌 처리 | F-04 |
/[locale]/admin/entities | 작품·회사 자동완성(별칭 일치 표시), 생성·수정, 병합. 작품은 홈 ‘IP로 찾아보기’ 노출 순서와 대표 이미지를 정한다(백엔드 명세: docs/api/featured-properties.md) | F-05 |
/[locale]/admin/taxonomy | 통제 어휘 조회·추가(ko/en 라벨, 기존 표기) | F-06 |
/[locale]/stats | 통계: 요약 숫자(전체, 진행 상태별, 올해 시작) → 수집 시작(2026년 4월)부터 월별 막대(최대 24개월, 시작일 기준, 이번 달 강조, 예정은 옅게, 막대마다 툴팁·표 보기) → 분류별 분포(파트너 분류, 콜라보 유형, 카테고리, 권역, 플랫폼. 각 항목은 필터 목록으로) → 순위(게임, 파트너 IP, 역할별 회사, 각 5개) → 집계 기준. Dataset JSON-LD, OG 이미지, sitemap. ISR | — |
/[locale]/stats/[year] | 연도 리포트: 그해 시작한 콜라보만 센다. 전년 대비, 가장 많은 달, 1~12월 막대, 분포·순위(분류 링크는 그해 기간 목록). 시작한 콜라보가 없는 해는 404. ISR | — |
/[locale]/report | 콜라보 제보: 아직 없는 콜라보의 출처 링크(필수)와 메모. 상세 페이지의 “오류 제보” 버튼은 같은 폼을 대화상자로 열어 그 콜라보의 틀린 내용(필수)과 근거 링크를 받는다. 푸터에서 연결, noindex | F-15 |
/[locale]/admin/submissions | 제보 큐: 미처리·반영함·버림, 오류 제보/새 콜라보로 거르기. 오류 제보는 편집기에서 고친 뒤 “반영함”, 새 콜라보는 등록한 뒤 “반영함”. 메뉴에 미처리 건수 | F-15 |
/[locale]/changelog | 변경 내역: 관리자가 공개한 릴리스 노트를 최신순으로(버전·날짜·마크다운 본문, 버전별 앵커 #v1-4-0). 푸터에서 연결, sitemap 포함, changelog 태그로 갱신. ISR | — |
/[locale]/admin/changelog | 변경 내역 작성·수정·삭제·공개(관리자 전용, 편집자는 안내만). 본문은 ko/en 마크다운, 작성/미리보기 전환 | — |
/[locale]/rss.xml | 최신 발행 콜라보 피드(ko/en 분리) | F-11 |
/sitemap.xml, /robots.txt, OG 이미지, /tools/game-ip-collab-tracker/* → 301 | SEO | F-08 |
다국어(F-07): 모든 콜라보·작품·회사는 한국어와 영어를 모두 갖고 올라온다(백엔드가 둘 다 필수로 검증). 그래서 언어 대체나 “기계 번역” 표시는 없다. 관리자 화면은 운영자 전용이라 한국어만 제공한다.
src/
app/
[locale]/(site)/ 공개 페이지 (헤더·고지·푸터 레이아웃)
[locale]/admin/ 관리자 (login, (protected)/…, actions.ts)
api/ export, revalidate 웹훅, GitHub OAuth
components/{site,collab,entity,admin}/
i18n/ next-intl 라우팅
lib/
api/ PublicApi·AdminApi 계약, http.ts(Express), mock/
auth/session.ts 관리자 JWT
phase.ts filters.ts format.ts taxonomy.ts
schema/ Zod 스키마 (공유 대상)
messages/{ko,en}.json
브라우저는 Express를 직접 부르지 않는다. 모든 호출은 Next.js 서버(서버 컴포넌트, Server Action, Route Handler)에서 나간다.
Authorization: Bearer $API_SERVICE_TOKEN, 응답은 태그 캐시(collabs, collab:<slug>, property:<slug>, company:<slug>, taxonomies, 홈 노출 작품은 featured).POST /api/revalidate를 호출한다: Authorization: Bearer $REVALIDATE_SECRET, 본문 { "tags": ["collabs", "collab:<slug>"] }. 작품의 홈 노출 순서·대표 이미지를 바꾸면 featured를 보낸다.AUTH_SECRET, iss=gamecollabs-web, aud=gamecollabs-api, sub=GitHub 로그인, 8시간)를 그대로 Bearer로 넘긴다. Express는 같은 값인 ADMIN_JWT_SECRET으로 검증하고, users 컬렉션에 등록된 GitHub 계정만 통과시킨다(cli create-user --github <login> --role admin).{ data, meta }, 이름은 요청 언어의 문자열 하나)은 src/lib/api/adapt.ts가 화면 스키마(src/schema/index.ts)로 바꾸고, http.ts가 그 스키마로 다시 검증한다. 형태가 어긋나면 조용히 깨지지 않고 바로 오류가 난다.화면이 쓰는 엔드포인트와 변환 (adapt.ts):
| 화면 | Express | 변환 |
|---|---|---|
| 목록·RSS | GET /v1/collabs | 필터는 쉼표 목록, 월 범위 YYYY-MM은 그 달 첫날·마지막 날, sort=recent는 발행일순 |
| 상세 | GET /v1/collabs/:slug | 관련 콜라보의 same_host/same_partner는 호스트 작품 공유 여부로 판정 |
| 작품·회사 | GET /v1/properties/:slug, /v1/companies/:slug | 상대 작품·유형 분포·회사 역할은 타임라인에서 집계 |
| 필터·편집기 분류 | GET /v1/taxonomies | 트리 → 평면 목록 |
| 홈 요약 | GET /v1/stats | total, byPhase |
| 홈 IP로 찾아보기 | GET /v1/properties?featured=true (예정, 명세) | 대체 텍스트가 없으면 작품 이름. 404·형태 불일치면 섹션을 숨긴다 |
| sitemap | GET /v1/sitemap | 그대로 |
| 검수 대기열 | GET /v1/admin/collabs?status=draft,in_review[&cover=missing] | meta.counts(상태별 건수), 항목별 duplicateCount. cover=missing(커버 없음만)을 백엔드가 모르면 받은 100건 안에서 프론트가 거른다 |
| 편집기 | GET /v1/admin/collabs/:id + /duplicates | origin.unmapped → 필드별 미해결 값 |
| 저장 | POST·PATCH /v1/admin/collabs[/:id] (If-Match: <rev>) | 빈 메모는 null, 월 단위 날짜는 YYYY-MM |
| 엔티티 | GET /v1/admin/{properties,companies}[?q=] | q가 없으면 전체 목록 |
| 병합 | POST /v1/admin/{…}/:targetId/merge { from } | from(원본)이 :targetId에 흡수된다 |
| 커버 업로드 | POST /v1/admin/media (multipart, file) | 응답 url이 없으면(null) MEDIA_BASE_URL + storageKey |
편집기의 “커버 이미지”에서 끌어다 놓기·클릭·붙여넣기로 올린다. 브라우저가 긴 변 1600px 이하 WebP(안 되면 JPEG)로 줄인 뒤
POST /api/admin/media(Next Route Handler, 세션·Origin·파일 형식·4MB 확인)로 보내고, 여기서 관리자 JWT를 붙여 Express로 넘긴다.
관리자 토큰이 httpOnly 쿠키라 브라우저가 Express로 직접 보낼 수 없어서다. 목 모드에서는 메모리에 data: 주소로 둔다.
Express(gamecollabs-backend의 POST /v1/admin/media)가 형식 확인(JPEG·PNG·WebP, 5MB, 4천만 픽셀), sharp 재인코딩(메타데이터 제거,
긴 변 1600px, WebP), covers/<sha256>.webp 저장을 맡고 { storageKey, url, width, height }를 돌려준다. 편집기는 저장할 때
PATCH의 cover로 연결하고, 7일 넘게 어느 콜라보도 쓰지 않는 업로드는 백엔드 주간 작업이 지운다.
<img src>(링크로 감싼 이미지면 링크가 아니라 이미지) 주소를 originalUrl로 두고 미리 보기만 하며, 저장 직후
POST /v1/admin/collabs/:id/cover/mirror로 백엔드가 우리 저장소로 복사한다(백엔드의 safeFetch가 사설 주소 등을 막는다).
복사에 실패하면 원본 링크로 저장된 채 편집기에 이유가 보이고, 백엔드 시간별 mirror-covers 작업도 다시 시도한다.cover 필드가 없으면(이전 백엔드) 편집기는 현재 커버를 “모름”으로 보고, 사용자가 커버를 건드리지 않는 한
저장 본문에서 cover를 빼서 기존 값을 지우지 않는다.storage_unavailable)을 준다..env.example 참고. 백엔드(홈서버 deploy/.env)와 짝이 맞아야 하는 값:
| 프론트 (Vercel) | 백엔드 (deploy/.env) |
|---|---|
API_BASE_URL=https://api.<도메인> (/v1 없이) | Cloudflare Tunnel의 api 호스트 |
API_SERVICE_TOKEN | SERVICE_TOKENS에 들어 있는 값 하나 |
AUTH_SECRET (32자 이상) | ADMIN_JWT_SECRET (같은 값) |
REVALIDATE_SECRET | WEB_REVALIDATE_SECRET (같은 값), WEB_REVALIDATE_URL=https://<사이트>/api/revalidate |
MEDIA_BASE_URL | MEDIA_BASE_URL (같은 값, 커버 이미지 주소) |
제보 폼의 Cloudflare Turnstile은 백엔드에 TURNSTILE_SECRET을 넣었을 때 프론트에 짝이 되는 사이트 키 NEXT_PUBLIC_TURNSTILE_SITE_KEY를 넣으면 켜진다(빌드 때 들어가므로 바꾸면 다시 배포). 둘 다 비우면 honeypot과 IP당 시간당 10건 제한만 쓴다.
방문자는 두 가지를 보낼 수 있다. 이름·연락처는 받지 않는다.
/[locale]/report): 출처 링크(필수)와 메모.폼은 서버 액션(src/app/[locale]/(site)/actions.ts)으로 보내고, 서버 액션이 입력을 다시 검증한 뒤 서비스 토큰과 방문자 IP(X-Forwarded-For의 첫 값 → X-Client-IP)를 붙여 Express로 넘긴다. 봇 거르기는 숨은 website 칸(채워져 있으면 받은 척하고 버림), 백엔드의 IP당 시간당 10건 제한, 설정했을 때 Turnstile. 아직 열린 같은 제보(같은 URL, 같은 콜라보·같은 내용)는 한 번만 쌓인다. 운영자는 /admin/submissions에서 처리한다. 목 모드에서도 같은 규칙으로 메모리에 쌓인다.
그 밖에 NEXT_PUBLIC_SITE_URL, GITHUB_CLIENT_ID·GITHUB_CLIENT_SECRET(콜백 https://<사이트>/api/auth/github/callback), ADMIN_GITHUB_LOGINS.
tsc(TS 7)와 next build가 맡는다.| 제보 | POST /v1/submissions (서비스 토큰, X-Client-IP) | kind: tip(url 필수) 또는 correction(collab slug, 5자 이상 note). 429·bot_check_failed·404는 폼에 안내 문구로 |
| 제보 큐 | GET /v1/admin/submissions?status=&kind=, PATCH …/:id { status } | meta.counts(종류별 미처리 수)가 없으면 받은 항목으로 센다. kind가 없는 이전 문서는 tip |