import { getConfig } from "./config.js"; import { getDb, hasMongoConfig } from "./db.js"; const DEFAULT_ABOUT_COLLECTION_NAME = "about_content"; const DEVELOPER_INFO_ID = "developer-info"; const PRIVACY_POLICY_ID = "privacy-policy"; const DEFAULT_DEVELOPER_INFO = { alias: "horoli", email: "sunha321@gmail.com", github: "https://github.com/Horoli", }; const DEFAULT_PRIVACY_POLICY_MARKDOWN = ` ### 개인정보처리방침 **Arena Picker**는 이용자의 개인정보를 최소한으로 수집하며, 투명하게 관리하기 위해 노력합니다. #### 1. 수집하는 개인정보 항목 및 방법 본 서비스는 별도의 회원가입 없이 이용 가능하며, 서비스 운영 지표 측정을 위해 아래와 같은 정보를 수집합니다. - **수집 항목**: 방문자 식별값 (브라우저 쿠키를 기반으로 생성된 암호화된 UUID 해시), 방문 일시, 서비스 이용 기록 (전투 시작/종료, 버튼 클릭 등) - **수집 방법**: 서비스 접속 시 자동으로 생성 및 서버로 전송 #### 2. 개인정보의 수집 및 이용 목적 수집된 정보는 오직 서비스 품질 개선 및 통계 분석을 위해서만 활용됩니다. - 중복되지 않는 일일 방문자 수 측정 - 서비스 이용 통계 (전투 횟수, 선호 캐릭터 등) 분석 - 서비스 안정성 확인 및 버그 진단 #### 3. 개인정보의 보유 및 이용 기간 - 수집된 활동 로그 및 통계 데이터는 수집일로부터 **60일**간 보관 후 복구 불가능한 방법으로 파기됩니다. #### 4. 개인정보의 제3자 제공 본 서비스는 이용자의 개인정보를 외부에 제공하거나 공유하지 않습니다. #### 5. 이용자의 권리 이용자는 브라우저의 쿠키를 삭제함으로써 언제든지 식별 정보를 초기화할 수 있습니다. **공고일자**: 2026년 5월 23일 **시행일자**: 2026년 5월 23일 `; let aboutIndexesReady; let aboutWarmupPromise; export async function aboutRoutes(fastify) { fastify.get("/about", async () => getAboutContent()); fastify.get("/about/", async () => getAboutContent()); // 관리용: 초기 데이터 보장 및 인덱스 생성을 위한 명시적 호출용 (필요시) fastify.post("/about/warmup", async (request, reply) => { await warmAboutContent(); return { success: true }; }); } export async function warmAboutContent() { if (!hasMongoConfig()) { return formatAboutContent(); } if (!aboutWarmupPromise) { aboutWarmupPromise = loadAboutContent() .finally(() => { aboutWarmupPromise = undefined; }); } return aboutWarmupPromise; } async function getAboutContent() { // 실시간 반영을 위해 캐시를 사용하지 않고 매번 DB에서 로드 return loadAboutContent(); } async function loadAboutContent() { const collection = await getAboutCollection(); // 서버 시작 시나 첫 호출 시 기본값 보장 (이미 있으면 유지됨) await ensureAboutDefaults(collection); const [developerInfo, privacyPolicy] = await Promise.all([ collection.findOne({ _id: DEVELOPER_INFO_ID }), collection.findOne({ _id: PRIVACY_POLICY_ID }), ]); return formatAboutContent(developerInfo, privacyPolicy); } async function getAboutCollection() { const db = await getDb(); const collection = db.collection( getConfig().MONGODB_ABOUT_COLLECTION || DEFAULT_ABOUT_COLLECTION_NAME, ); await ensureAboutIndexes(collection); return collection; } async function ensureAboutIndexes(collection) { if (!aboutIndexesReady) { aboutIndexesReady = collection.createIndex({ type: 1 }); } return aboutIndexesReady; } async function ensureAboutDefaults(collection) { const now = new Date(); await collection.bulkWrite( [ { updateOne: { filter: { _id: DEVELOPER_INFO_ID }, update: { $setOnInsert: { _id: DEVELOPER_INFO_ID, type: "developerInfo", ...DEFAULT_DEVELOPER_INFO, createdAt: now, }, }, upsert: true, }, }, { updateOne: { filter: { _id: PRIVACY_POLICY_ID }, update: { $setOnInsert: { _id: PRIVACY_POLICY_ID, type: "privacyPolicy", markdown: DEFAULT_PRIVACY_POLICY_MARKDOWN, createdAt: now, updatedAt: now, }, }, upsert: true, }, }, ], { ordered: false }, ); } function formatAboutContent(developerInfo = {}, privacyPolicy = {}) { return { developer: normalizeDeveloperInfo(developerInfo), privacyPolicy: { markdown: stringValue( privacyPolicy?.markdown, DEFAULT_PRIVACY_POLICY_MARKDOWN, ), updatedAt: dateString( privacyPolicy?.updatedAt || privacyPolicy?.createdAt, ), }, }; } function normalizeDeveloperInfo(document = {}) { return { alias: stringValue(document?.alias, DEFAULT_DEVELOPER_INFO.alias), email: stringValue(document?.email, DEFAULT_DEVELOPER_INFO.email), github: stringValue(document?.github, DEFAULT_DEVELOPER_INFO.github), }; } function stringValue(...values) { const value = values.find((candidate) => typeof candidate === "string"); return value?.trim() ?? ""; } function dateString(value) { return value?.toISOString?.() ?? null; }