Ariweb Suite 0.1.1 기반: 공통 라이브러리(db·http·세션·작업 엔진·할당/예약·알림·감사·보관소), 포털 뼈대, Webmaker/AriMail 도입 소스와 manifest
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
9ca4cee341
commit
c7aea8d419
@@ -0,0 +1,73 @@
|
||||
'use strict';
|
||||
// 사이트 메타데이터/명령 이력/토큰 사용량을 data/sites/<id>.json 에 저장하는 단순 JSON 저장소.
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, '..', 'data', 'sites');
|
||||
fs.mkdirSync(DATA_DIR, { recursive: true });
|
||||
|
||||
const ID_RE = /^[a-f0-9]{32}$/;
|
||||
|
||||
function fileOf(id) { return path.join(DATA_DIR, `${id}.json`); }
|
||||
|
||||
function emptyTotals() {
|
||||
return { input: 0, output: 0, cacheCreate: 0, cacheRead: 0, thinking: 0, total: 0, costUsd: 0, chargeKrw: 0, commands: 0 };
|
||||
}
|
||||
|
||||
function recomputeTotals(site) {
|
||||
const t = emptyTotals();
|
||||
for (const c of site.commands) {
|
||||
if (!c.usage) continue;
|
||||
t.input += c.usage.input; t.output += c.usage.output;
|
||||
t.cacheCreate += c.usage.cacheCreate; t.cacheRead += c.usage.cacheRead;
|
||||
t.thinking += c.usage.thinking || 0; t.total += c.usage.total;
|
||||
t.costUsd += c.costUsd || 0; t.chargeKrw += c.chargeKrw || 0; t.commands += 1;
|
||||
}
|
||||
site.totals = t;
|
||||
return site;
|
||||
}
|
||||
|
||||
// v1.6.31 (요구 10): 원자적 쓰기(임시 파일 + fsync + rename) + 직전 정상본 .bak 보존. 읽기 실패는 손상으로 기록해 관리자 운영 복구 화면에 보인다(빈 값으로 숨기지 않음)
|
||||
const js = require('./jsonstore');
|
||||
function save(site) {
|
||||
recomputeTotals(site);
|
||||
site.updatedAt = new Date().toISOString();
|
||||
js.writeJsonAtomic(fileOf(site.id), JSON.stringify(site, null, 2), { mode: 0o600, keepBackup: true });
|
||||
return site;
|
||||
}
|
||||
/** 사이트 기록 읽기. 없으면 null. 깨졌으면 .bak 으로 복구를 시도하고 손상 목록에 남긴다(복구도 안 되면 null — 화면에는 "손상" 으로 보인다) */
|
||||
function load(id) {
|
||||
if (!ID_RE.test(id)) return null;
|
||||
return js.readJson(fileOf(id), null, {});
|
||||
}
|
||||
/** 손상된 사이트 파일 목록 (운영 복구 화면) */
|
||||
function health() { return js.health().filter((h) => h.file.startsWith(DATA_DIR)); }
|
||||
|
||||
function list(ownerId) {
|
||||
return fs.readdirSync(DATA_DIR)
|
||||
.filter((f) => f.endsWith('.json'))
|
||||
.map((f) => load(f.slice(0, -5)))
|
||||
.filter(Boolean)
|
||||
.filter((s) => !ownerId || s.ownerId === ownerId)
|
||||
.sort((a, b) => (b.createdAt || '').localeCompare(a.createdAt || ''))
|
||||
// v1.2.0: 상단 사이트 선택 상자가 "생성 중 / 완성 / 가져옴·복사" 로 묶어 보여 주도록 mode·plan 단계도 함께 준다
|
||||
.map((s) => ({ id: s.id, title: s.title, createdAt: s.createdAt, status: s.status, totals: s.totals, commands: s.commands.length, mode: s.mode || null, stage: (s.plan && s.plan.stage) || null }));
|
||||
}
|
||||
|
||||
function create(fields) {
|
||||
const site = {
|
||||
id: fields.id,
|
||||
createdAt: new Date().toISOString(),
|
||||
ownerId: fields.ownerId || null, ownerName: fields.ownerName || '',
|
||||
title: fields.title, purpose: fields.purpose, content: fields.content,
|
||||
design: fields.design, extra: fields.extra,
|
||||
git: { seq: 0, changes: [] },
|
||||
sessionId: fields.sessionId,
|
||||
status: 'idle',
|
||||
commands: [],
|
||||
totals: emptyTotals(),
|
||||
};
|
||||
return save(site);
|
||||
}
|
||||
|
||||
module.exports = { health, ID_RE, create, save, load, list, DATA_DIR };
|
||||
Reference in New Issue
Block a user