fix: 전 페이지 서버사이드 렌더링으로 전환 (초기 데이터 fetch 제거)

- 모든 관리자 페이지에서 DB 데이터를 서버에서 직접 HTML에 주입
- __INIT__ 글로벌 변수로 초기 데이터 전달 (fetch 불필요)
- 대시보드/사이트관리/AdSense/도메인/로그/사이트상세 전부 적용
- trust proxy 설정 (Traefik 뒤 동작)
- 저장/삭제/크롤링 등 액션은 여전히 API fetch 사용

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
chpark
2026-03-27 11:24:57 +09:00
parent e560a2faa2
commit dcae228a24
8 changed files with 248 additions and 386 deletions
+44 -13
View File
@@ -97,29 +97,60 @@ app.get('/logout', (req, res) => {
// ===== 루트 → 관리자로 리다이렉트 =====
app.get('/', (req, res) => res.redirect('/admin'));
// ===== 관리자 페이지 =====
app.get('/admin', adminAuth, (req, res) => {
res.render('admin/dashboard');
// ===== 관리자 페이지 (데이터 서버사이드 렌더링) =====
app.get('/admin', adminAuth, async (req, res) => {
try {
const sites = await db.query(`SELECT s.*, (SELECT COUNT(*) FROM crawl_results WHERE site_id = s.id) AS crawl_count FROM sites s ORDER BY s.id`);
const adsense = await db.query('SELECT * FROM adsense_configs ORDER BY id');
const logs = await db.query(`SELECT l.*, s.name AS site_name FROM crawl_logs l LEFT JOIN sites s ON s.id = l.site_id ORDER BY l.created_at DESC LIMIT 10`);
res.render('admin/dashboard', {
initialData: JSON.stringify({ sites: sites.rows, adsense: adsense.rows, logs: logs.rows })
});
} catch (e) { res.render('admin/dashboard', { initialData: '{}' }); }
});
app.get('/admin/sites', adminAuth, (req, res) => {
res.render('admin/sites');
app.get('/admin/sites', adminAuth, async (req, res) => {
try {
const sites = await db.query(`SELECT s.*, ac.name AS adsense_name, (SELECT COUNT(*) FROM crawl_results WHERE site_id = s.id) AS crawl_count FROM sites s LEFT JOIN adsense_configs ac ON ac.id = s.adsense_config_id ORDER BY s.id`);
const adsense = await db.query('SELECT * FROM adsense_configs ORDER BY id');
res.render('admin/sites', {
initialData: JSON.stringify({ sites: sites.rows, adsense: adsense.rows })
});
} catch (e) { res.render('admin/sites', { initialData: '{}' }); }
});
app.get('/admin/sites/:id', adminAuth, (req, res) => {
res.render('admin/site-detail', { siteId: req.params.id });
app.get('/admin/sites/:id', adminAuth, async (req, res) => {
try {
const site = await db.query('SELECT * FROM sites WHERE id = $1', [req.params.id]);
const results = await db.query(`SELECT id, site_id, status, error_message, crawled_at, jsonb_array_length(COALESCE(parsed_data->'items','[]'::jsonb)) AS item_count FROM crawl_results WHERE site_id = $1 ORDER BY crawled_at DESC LIMIT 20`, [req.params.id]);
res.render('admin/site-detail', {
siteId: req.params.id,
initialData: JSON.stringify({ site: site.rows[0] || {}, results: results.rows })
});
} catch (e) { res.render('admin/site-detail', { siteId: req.params.id, initialData: '{}' }); }
});
app.get('/admin/adsense', adminAuth, (req, res) => {
res.render('admin/adsense');
app.get('/admin/adsense', adminAuth, async (req, res) => {
try {
const adsense = await db.query('SELECT * FROM adsense_configs ORDER BY id');
res.render('admin/adsense', { initialData: JSON.stringify(adsense.rows) });
} catch (e) { res.render('admin/adsense', { initialData: '[]' }); }
});
app.get('/admin/domains', adminAuth, (req, res) => {
res.render('admin/domains');
app.get('/admin/domains', adminAuth, async (req, res) => {
try {
const domains = await db.query(`SELECT d.*, s.name AS site_name FROM domain_mappings d LEFT JOIN sites s ON s.id = d.site_id ORDER BY d.id`);
const sites = await db.query('SELECT id, name FROM sites ORDER BY id');
const adsense = await db.query('SELECT id, name FROM adsense_configs ORDER BY id');
res.render('admin/domains', { initialData: JSON.stringify({ domains: domains.rows, sites: sites.rows, adsense: adsense.rows }) });
} catch (e) { res.render('admin/domains', { initialData: '{}' }); }
});
app.get('/admin/logs', adminAuth, (req, res) => {
res.render('admin/logs');
app.get('/admin/logs', adminAuth, async (req, res) => {
try {
const logs = await db.query(`SELECT l.*, s.name AS site_name FROM crawl_logs l LEFT JOIN sites s ON s.id = l.site_id ORDER BY l.created_at DESC LIMIT 100`);
res.render('admin/logs', { initialData: JSON.stringify(logs.rows) });
} catch (e) { res.render('admin/logs', { initialData: '[]' }); }
});
// ===== API =====