arena/server/about.js

180 lines
5.1 KiB
JavaScript

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. 이용자의 권리
이용자는 브라우저의 쿠키를 삭제함으로써 언제든지 식별 정보를 초기화할 수 있습니다.
**공고일자**: 2024년 5월 23일
**시행일자**: 2024년 5월 23일
`;
let aboutCache;
let aboutIndexesReady;
let aboutWarmupPromise;
export async function aboutRoutes(fastify) {
fastify.get("/about", async () => getAboutContent());
fastify.get("/about/", async () => getAboutContent());
}
export async function warmAboutContent() {
if (!hasMongoConfig()) {
aboutCache = formatAboutContent();
return aboutCache;
}
if (!aboutWarmupPromise) {
aboutWarmupPromise = loadAboutContent()
.then((content) => {
aboutCache = content;
return content;
})
.finally(() => {
aboutWarmupPromise = undefined;
});
}
return aboutWarmupPromise;
}
async function getAboutContent() {
if (aboutCache) {
return aboutCache;
}
return warmAboutContent();
}
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: {
$set: {
markdown: DEFAULT_PRIVACY_POLICY_MARKDOWN,
updatedAt: now,
},
$setOnInsert: {
_id: PRIVACY_POLICY_ID,
type: "privacyPolicy",
createdAt: 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;
}