feat: 初始提交(仅核心代码,已排除大文件)
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const crypto = require('crypto')
|
||||
const Database = require('better-sqlite3')
|
||||
const { initCrypto, dec, ensureDecrypted } = require('./weekly_crypto')
|
||||
|
||||
const cfgPath = path.join(process.cwd(), 'config', 'weekly_embed.json')
|
||||
if (!fs.existsSync(path.dirname(cfgPath))) fs.mkdirSync(path.dirname(cfgPath), { recursive: true })
|
||||
if (!fs.existsSync(cfgPath)) {
|
||||
fs.writeFileSync(cfgPath, JSON.stringify({ machine_salt: crypto.randomBytes(16).toString('hex'), access_key: 'K049876543' }, null, 2))
|
||||
}
|
||||
const getConfig = () => {
|
||||
try { return JSON.parse(fs.readFileSync(cfgPath, 'utf-8')) } catch { return {} }
|
||||
}
|
||||
const cfg = getConfig()
|
||||
|
||||
const passphrase = process.env.SYSTEM_AUTH_PASS_93220 || process.env.WEEKLY_PASSWORD || ''
|
||||
const salt = cfg.machine_salt || 'salt'
|
||||
initCrypto(passphrase, salt)
|
||||
|
||||
const dataDir = path.join(process.cwd(), 'data')
|
||||
const dbPath = path.join(dataDir, 'weekly.db')
|
||||
const sealPath = path.join(dataDir, 'weekly.enc')
|
||||
|
||||
let db = null
|
||||
const getDB = () => {
|
||||
if (db) return db
|
||||
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true })
|
||||
ensureDecrypted(dbPath, sealPath)
|
||||
db = new Database(dbPath, { readonly: true })
|
||||
try { db.pragma('journal_mode = WAL') } catch {}
|
||||
try { db.pragma('synchronous = NORMAL') } catch {}
|
||||
try { db.pragma('busy_timeout = 4000') } catch {}
|
||||
return db
|
||||
}
|
||||
|
||||
const weekIdForDate = (dateStr) => {
|
||||
try {
|
||||
const d = new Date(dateStr)
|
||||
const day = d.getUTCDay()
|
||||
const diffToMon = (day + 6) % 7
|
||||
const mon = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() - diffToMon))
|
||||
const firstDay = new Date(Date.UTC(mon.getUTCFullYear(), 0, 1))
|
||||
const firstDayWeekday = (firstDay.getUTCDay() + 6) % 7
|
||||
const days = Math.floor((mon - firstDay) / 86400000) + firstDayWeekday + 1
|
||||
const week = Math.ceil(days / 7)
|
||||
return `${mon.getUTCFullYear()}-W${String(week).padStart(2, '0')}`
|
||||
} catch {
|
||||
return `${new Date().getUTCFullYear()}-W01`
|
||||
}
|
||||
}
|
||||
|
||||
const weeksForYear = (year) => {
|
||||
const y = parseInt(year, 10)
|
||||
const tz = 'Asia/Shanghai'
|
||||
const dateInTZ = (d) => new Date(d)
|
||||
const firstThursday = new Date(Date.UTC(y, 0, 4))
|
||||
const day = firstThursday.getUTCDay()
|
||||
const diffToMon = (day + 6) % 7
|
||||
let mon = new Date(Date.UTC(y, 0, 4 - diffToMon))
|
||||
const out = []
|
||||
let weekNum = 1
|
||||
while (weekNum <= 53) {
|
||||
const thu = new Date(Date.UTC(mon.getUTCFullYear(), mon.getUTCMonth(), mon.getUTCDate() + 3))
|
||||
const isoYear = thu.getUTCFullYear()
|
||||
if (isoYear !== y) break
|
||||
const sun = new Date(Date.UTC(mon.getUTCFullYear(), mon.getUTCMonth(), mon.getUTCDate() + 6))
|
||||
const weekLabel = `${y}-W${String(weekNum).padStart(2, '0')}`
|
||||
const startStr = new Intl.DateTimeFormat('zh-CN', { timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit' }).format(dateInTZ(mon)).replace(/\//g, '-')
|
||||
const endStr = new Intl.DateTimeFormat('zh-CN', { timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit' }).format(dateInTZ(sun)).replace(/\//g, '-')
|
||||
out.push({ week_id: weekLabel, start: startStr, end: endStr })
|
||||
mon = new Date(Date.UTC(mon.getUTCFullYear(), mon.getUTCMonth(), mon.getUTCDate() + 7))
|
||||
weekNum++
|
||||
}
|
||||
|
||||
try {
|
||||
const db = getDB()
|
||||
const reports = db.prepare(`SELECT id, week_id FROM reports WHERE week_id LIKE ?`).all(`${y}-%`)
|
||||
const reportMap = new Map()
|
||||
reports.forEach(r => reportMap.set(r.week_id, r.id))
|
||||
if (reports.length > 0) {
|
||||
const placeholders = reports.map(() => '?').join(',')
|
||||
const reportIds = reports.map(r => r.id)
|
||||
const eventRows = db.prepare(`SELECT DISTINCT report_id FROM events WHERE report_id IN (${placeholders})`).all(...reportIds)
|
||||
const eventSet = new Set(eventRows.map(r => r.report_id))
|
||||
out.forEach(w => {
|
||||
const rid = reportMap.get(w.week_id)
|
||||
w.has_report = !!rid
|
||||
w.has_events = rid ? eventSet.has(rid) : false
|
||||
})
|
||||
} else {
|
||||
out.forEach(w => { w.has_report = false; w.has_events = false })
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
const getReportByWeek = (week_id) => {
|
||||
const row = getDB().prepare(`SELECT * FROM reports WHERE week_id=? ORDER BY id DESC LIMIT 1`).get(week_id)
|
||||
if (!row) return null
|
||||
return {
|
||||
id: row.id,
|
||||
week_id: row.week_id,
|
||||
title: dec(row.title_enc, row.title_iv, row.title_tag),
|
||||
summary: dec(row.summary_enc, row.summary_iv, row.summary_tag),
|
||||
content: dec(row.content_enc, row.content_iv, row.content_tag),
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at
|
||||
}
|
||||
}
|
||||
|
||||
const listEvents = (report_id) => {
|
||||
const rows = getDB().prepare(`SELECT * FROM events WHERE report_id=? ORDER BY id ASC`).all(parseInt(report_id, 10))
|
||||
return rows.map(r => ({
|
||||
id: r.id,
|
||||
report_id: r.report_id,
|
||||
title: dec(r.title_enc, r.title_iv, r.title_tag),
|
||||
content: dec(r.content_enc, r.content_iv, r.content_tag),
|
||||
status_code: r.status_code,
|
||||
status: dec(r.status_enc, r.status_iv, r.status_tag),
|
||||
deadline: r.deadline,
|
||||
parent_event_id: r.parent_event_id,
|
||||
completed_at: r.completed_at,
|
||||
push_required: !!r.push_required,
|
||||
created_at: r.created_at,
|
||||
updated_at: r.updated_at
|
||||
}))
|
||||
}
|
||||
|
||||
const bindRoutes = (app) => {
|
||||
const accessKey = String(cfg.access_key || 'K049876543')
|
||||
const mustKey = (req, res) => {
|
||||
const key = String(req.get('X-Weekly-Embed-Key') || req.query.key || '')
|
||||
if (!key || key !== accessKey) {
|
||||
res.status(403).json({ ok: false, error: 'forbidden' })
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
app.use('/api/weekly_embed', (req, res, next) => {
|
||||
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate')
|
||||
res.set('Pragma', 'no-cache')
|
||||
res.set('Expires', '0')
|
||||
res.set('Surrogate-Control', 'no-store')
|
||||
if (!mustKey(req, res)) return
|
||||
next()
|
||||
})
|
||||
|
||||
app.get('/api/weekly_embed/years', (req, res) => {
|
||||
try {
|
||||
const trace = []
|
||||
trace.push('打开只读周报数据库')
|
||||
const db = getDB()
|
||||
trace.push('查询可用年份')
|
||||
const rows = db.prepare(`SELECT DISTINCT SUBSTR(week_id,1,4) AS y FROM reports WHERE week_id IS NOT NULL AND LENGTH(week_id)>=4 ORDER BY y DESC`).all()
|
||||
trace.push(`返回年份 ${rows.length} 条`)
|
||||
const years = rows.map(r => parseInt(String(r.y), 10)).filter(y => y > 0)
|
||||
res.json({ ok: true, years, trace })
|
||||
} catch (e) {
|
||||
res.status(500).json({ ok: false, error: String(e && e.message ? e.message : e) })
|
||||
}
|
||||
})
|
||||
|
||||
app.get('/api/weekly_embed/weeks', (req, res) => {
|
||||
const trace = []
|
||||
const year = parseInt(String(req.query.year || new Date().getFullYear()), 10)
|
||||
trace.push(`计算 ${year} 年 ISO 周列表`)
|
||||
const weeks = weeksForYear(year)
|
||||
trace.push(`合并周报/事件存在标记 ${weeks.length} 条`)
|
||||
const currentWeekId = weekIdForDate(new Date().toISOString())
|
||||
trace.push(`定位当前周 ${currentWeekId}`)
|
||||
res.json({ ok: true, weeks, currentWeekId, trace })
|
||||
})
|
||||
|
||||
app.get('/api/weekly_embed/report', (req, res) => {
|
||||
const trace = []
|
||||
const weekId = String(req.query.week_id || '')
|
||||
trace.push(`按 week_id 读取周报 ${weekId}`)
|
||||
const report = weekId ? getReportByWeek(weekId) : null
|
||||
trace.push(report ? `读取到周报 id=${report.id}` : '未找到周报')
|
||||
res.json({ ok: true, report, trace })
|
||||
})
|
||||
|
||||
app.get('/api/weekly_embed/events', (req, res) => {
|
||||
const trace = []
|
||||
const reportId = String(req.query.report_id || '')
|
||||
if (!reportId) return res.json({ ok: true, events: [], rows: [], trace: ['缺少 report_id,返回空列表'] })
|
||||
trace.push(`按 report_id=${reportId} 读取事件列表`)
|
||||
const events = listEvents(reportId)
|
||||
trace.push(`事件数量 ${events.length}`)
|
||||
res.json({ ok: true, events, rows: events, trace })
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { bindRoutes }
|
||||
Reference in New Issue
Block a user