feat(web): serve vehicle icons statically + deploy symlink + dev guard for logs/lang

This commit is contained in:
FURRO404
2026-06-18 00:38:00 -07:00
parent 49e75cc8f0
commit 177ddd408d
3 changed files with 63 additions and 0 deletions
+33
View File
@@ -56,6 +56,10 @@ const TURNSTILE_VERIFY_TIMEOUT_MS = Number(process.env.TURNSTILE_VERIFY_TIMEOUT_
const TURNSTILE_MAX_TOKEN_LENGTH = 2048 const TURNSTILE_MAX_TOKEN_LENGTH = 2048
const TURNSTILE_TOKEN_MAX_AGE_MS = 5 * 60 * 1000 const TURNSTILE_TOKEN_MAX_AGE_MS = 5 * 60 * 1000
const DIST_DIR = path.join(__dirname, 'dist') const DIST_DIR = path.join(__dirname, 'dist')
const VEHICLE_ICONS_DIR = path.resolve(
__dirname,
process.env.VEHICLE_ICONS_DIR || path.join('dist', 'vehicle-icons'),
)
const MAX_TEAM_NAME_LENGTH = 80 const MAX_TEAM_NAME_LENGTH = 80
const MAX_CACHE_ENTRIES = 200 const MAX_CACHE_ENTRIES = 200
const MAX_RATE_LIMIT_KEYS = 1000 const MAX_RATE_LIMIT_KEYS = 1000
@@ -2027,6 +2031,30 @@ function serveStatic(req, res) {
}) })
} }
function serveVehicleIcon(req, res) {
let requestPath = '/'
try {
requestPath = decodeURIComponent(new URL(req.url, `http://localhost:${PORT}`).pathname)
} catch {
return send(res, 400, 'Bad request', { 'content-type': 'text/plain; charset=utf-8' })
}
const name = requestPath.slice('/vehicle-icons/'.length)
const filePath = path.resolve(VEHICLE_ICONS_DIR, `./${name}`)
const relative = path.relative(VEHICLE_ICONS_DIR, filePath)
if (relative.startsWith('..') || path.isAbsolute(relative) || path.extname(filePath) !== '.png') {
return send(res, 403, 'Forbidden', { 'content-type': 'text/plain; charset=utf-8' })
}
fs.readFile(filePath, (error, data) => {
if (error) {
return send(res, 404, 'Not found', { 'content-type': 'text/plain; charset=utf-8' })
}
send(res, 200, data, {
'content-type': 'image/png',
'cache-control': 'public, max-age=604800',
})
})
}
const server = http.createServer((req, res) => { const server = http.createServer((req, res) => {
if (req.method === 'GET' && req.url === '/robots.txt') { if (req.method === 'GET' && req.url === '/robots.txt') {
sendRobotsTxt(req, res) sendRobotsTxt(req, res)
@@ -2152,6 +2180,11 @@ const server = http.createServer((req, res) => {
return return
} }
if (req.method === 'GET' && req.url.startsWith('/vehicle-icons/')) {
serveVehicleIcon(req, res)
return
}
if (req.url.startsWith('/api/')) { if (req.url.startsWith('/api/')) {
proxyRequest(req, res) proxyRequest(req, res)
return return
+6
View File
@@ -27,6 +27,12 @@ function isAllowedApiUrl(req) {
} }
if (/^\/api\/tss\/games\/[A-Za-z0-9_-]{1,96}$/.test(url.pathname)) { if (/^\/api\/tss\/games\/[A-Za-z0-9_-]{1,96}$/.test(url.pathname)) {
const keys = [...params.keys()]
const lang = params.get('lang') || 'en'
return keys.every((key) => key === 'lang') && /^[A-Za-z-]{2,8}$/.test(lang)
}
if (/^\/api\/tss\/games\/[A-Za-z0-9_-]{1,96}\/logs$/.test(url.pathname)) {
return [...params.keys()].length === 0 return [...params.keys()].length === 0
} }
+24
View File
@@ -348,6 +348,29 @@ function promoteBuiltDist() {
} }
} }
function syncVehicleIcons() {
// Point the served icon dir at the bot-managed SHARED/ICONS/VEHICLES so the
// scoreboard can render vehicle icons without bloating this repo. Symlink when
// possible; fall back to skipping (icons hide gracefully) if the source is absent.
const src = process.env.VEHICLE_ICONS_SRC || '/home/deploy/BOTS/SHARED/ICONS/VEHICLES'
const dst = path.resolve(
__dirname,
process.env.VEHICLE_ICONS_DIR || path.join('dist', 'vehicle-icons'),
)
if (!fs.existsSync(src)) {
console.warn(`vehicle icons source missing, skipping: ${src}`)
return
}
try {
fs.rmSync(dst, { recursive: true, force: true })
fs.mkdirSync(path.dirname(dst), { recursive: true })
fs.symlinkSync(src, dst)
console.log(`linked vehicle icons ${dst} -> ${src}`)
} catch (error) {
console.error(`vehicle icon sync failed: ${error.message}`)
}
}
function validateBuiltDist() { function validateBuiltDist() {
const indexPath = path.join(NEXT_DIST_DIR, 'index.html') const indexPath = path.join(NEXT_DIST_DIR, 'index.html')
if (!fs.existsSync(indexPath)) { if (!fs.existsSync(indexPath)) {
@@ -535,6 +558,7 @@ async function deploy(push) {
validateBuiltDist() validateBuiltDist()
await run('cargo', ['build', '--manifest-path', 'backend/Cargo.toml', '--release']) await run('cargo', ['build', '--manifest-path', 'backend/Cargo.toml', '--release'])
promoteBuiltDist() promoteBuiltDist()
syncVehicleIcons()
for (const target of RESTART_TARGETS) { for (const target of RESTART_TARGETS) {
await run('pm2', ['reload', target, '--update-env']) await run('pm2', ['reload', target, '--update-env'])