138 lines
4.8 KiB
JavaScript
138 lines
4.8 KiB
JavaScript
const express = require('express')
|
||
const Database = require('better-sqlite3')
|
||
const path = require('path')
|
||
const fs = require('fs')
|
||
|
||
const dataDir = path.join(process.cwd(), 'data')
|
||
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true })
|
||
const dbPath = path.join(dataDir, 'dlt_draws.db')
|
||
const db = new Database(dbPath)
|
||
|
||
// Initialize Tables
|
||
db.exec(`CREATE TABLE IF NOT EXISTS draws (
|
||
issue TEXT PRIMARY KEY,
|
||
date TEXT,
|
||
front TEXT,
|
||
back TEXT,
|
||
pool INTEGER DEFAULT 0,
|
||
created_at TEXT DEFAULT (datetime('now'))
|
||
);`)
|
||
|
||
// Auth Helpers
|
||
const readCookieCfg = () => {
|
||
const cfgPath = path.join(process.cwd(), 'public', 'tools', 'dlt_draws', 'assets', 'auth_config.json')
|
||
let name = 'dlt_draws_gate'
|
||
let maxAge = 90 * 24 * 3600 * 1000
|
||
try {
|
||
if (fs.existsSync(cfgPath)) {
|
||
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'))
|
||
if (cfg.cookieName) name = String(cfg.cookieName)
|
||
const days = Number(cfg.max_age_days || 0)
|
||
if (Number.isFinite(days) && days > 0) maxAge = Math.min(days, 365) * 24 * 3600 * 1000
|
||
}
|
||
} catch {}
|
||
return { name, maxAge }
|
||
}
|
||
const parseCookie = (cookieStr) => {
|
||
const out = {}
|
||
String(cookieStr || '').split(/;\s*/).forEach(p => { const idx = p.indexOf('='); if (idx > 0) out[p.slice(0, idx)] = decodeURIComponent(p.slice(idx + 1)) })
|
||
return out
|
||
}
|
||
|
||
const checkAuth = (req, res, next) => {
|
||
try {
|
||
const cookies = parseCookie(req.headers.cookie || '')
|
||
const { name, maxAge } = readCookieCfg()
|
||
if (cookies[name] === '1' || cookies['nav_gate'] === '1') {
|
||
res.cookie(name, '1', { httpOnly: true, sameSite: 'lax', maxAge })
|
||
return next()
|
||
}
|
||
} catch {}
|
||
return res.status(401).json({ error: 'unauthorized' })
|
||
}
|
||
|
||
// Helpers
|
||
const pad2 = (n) => String(n).padStart(2, '0')
|
||
const arrToCsv = (arr) => (Array.isArray(arr) ? arr : String(arr).split(/[,,\s]+/).filter(Boolean))
|
||
.map((x) => pad2(parseInt(x, 10)))
|
||
.join(',')
|
||
const csvToArr = (csv) => String(csv).split(/[,,\s]+/).filter(Boolean).map((x) => parseInt(x, 10))
|
||
|
||
const bindRoutes = (app) => {
|
||
// Routes prefixed with /api/dlt
|
||
app.get('/auth/handshake', (req, res) => {
|
||
try {
|
||
const cookies = parseCookie(req.headers.cookie || '')
|
||
const { name } = readCookieCfg()
|
||
if (cookies[name] === '1' || cookies['nav_gate'] === '1') return res.json({ ok: true })
|
||
return res.status(401).json({ error: 'unauthorized' })
|
||
} catch (e) {
|
||
res.status(500).json({ ok: false, error: String(e.message || e) })
|
||
}
|
||
})
|
||
app.get('/auth/token', (req, res) => {
|
||
const cookies = parseCookie(req.headers.cookie || '')
|
||
const { name } = readCookieCfg()
|
||
if (cookies[name] === '1' || cookies['nav_gate'] === '1') return res.json({ ok: true })
|
||
return res.status(401).json({ error: 'unauthorized' })
|
||
})
|
||
|
||
app.get('/api/dlt/health', (req, res) => res.json({ status: 'ok' }))
|
||
|
||
app.get('/api/dlt/draws', checkAuth, (req, res) => {
|
||
const limit = Math.max(0, parseInt(req.query.limit || '0', 10) || 0)
|
||
const base = `SELECT issue, date, front, back, pool FROM draws ORDER BY CAST(issue AS INTEGER) ASC`
|
||
const sql = limit ? `${base} LIMIT ?` : base
|
||
try {
|
||
const rows = db.prepare(sql).all(limit ? [limit] : [])
|
||
res.json(rows.map((r) => ({
|
||
issue: r.issue,
|
||
date: r.date,
|
||
front: csvToArr(r.front),
|
||
back: csvToArr(r.back),
|
||
pool: r.pool || 0
|
||
})))
|
||
} catch (err) {
|
||
res.status(500).json({ error: err.message })
|
||
}
|
||
})
|
||
|
||
app.post('/api/dlt/draws/merge', checkAuth, (req, res) => {
|
||
const items = Array.isArray(req.body.rows) ? req.body.rows : []
|
||
if (!items.length) return res.status(400).json({ error: 'rows empty' })
|
||
let inserted = 0
|
||
try {
|
||
const insert = db.transaction((rows) => {
|
||
let count = 0
|
||
const stmt = db.prepare('INSERT OR IGNORE INTO draws(issue,date,front,back,pool) VALUES (?,?,?,?,?)')
|
||
for (const it of rows) {
|
||
const issue = String(it.issue || '').trim()
|
||
const date = String(it.date || '').trim()
|
||
const front = arrToCsv(it.front || it.frontArr || [])
|
||
const back = arrToCsv(it.back || it.backArr || [])
|
||
const pool = Number(it.pool || 0)
|
||
if (!issue || csvToArr(front).length !== 5 || csvToArr(back).length !== 2) continue
|
||
const info = stmt.run(issue, date, front, back, pool)
|
||
count += info.changes
|
||
}
|
||
return count
|
||
})
|
||
inserted = insert(items)
|
||
res.json({ inserted })
|
||
} catch (err) {
|
||
res.status(500).json({ error: err.message })
|
||
}
|
||
})
|
||
|
||
app.post('/api/dlt/draws/clear', checkAuth, (req, res) => {
|
||
try {
|
||
db.prepare('DELETE FROM draws').run()
|
||
res.json({ cleared: true })
|
||
} catch (err) {
|
||
res.status(500).json({ error: err.message })
|
||
}
|
||
})
|
||
}
|
||
|
||
module.exports = { bindRoutes }
|