180 lines
5.1 KiB
JavaScript
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;
|
|
}
|