feat(web): serve vehicle icons statically + deploy symlink + dev guard for logs/lang
This commit is contained in:
+33
@@ -56,6 +56,10 @@ const TURNSTILE_VERIFY_TIMEOUT_MS = Number(process.env.TURNSTILE_VERIFY_TIMEOUT_
|
||||
const TURNSTILE_MAX_TOKEN_LENGTH = 2048
|
||||
const TURNSTILE_TOKEN_MAX_AGE_MS = 5 * 60 * 1000
|
||||
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_CACHE_ENTRIES = 200
|
||||
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) => {
|
||||
if (req.method === 'GET' && req.url === '/robots.txt') {
|
||||
sendRobotsTxt(req, res)
|
||||
@@ -2152,6 +2180,11 @@ const server = http.createServer((req, res) => {
|
||||
return
|
||||
}
|
||||
|
||||
if (req.method === 'GET' && req.url.startsWith('/vehicle-icons/')) {
|
||||
serveVehicleIcon(req, res)
|
||||
return
|
||||
}
|
||||
|
||||
if (req.url.startsWith('/api/')) {
|
||||
proxyRequest(req, res)
|
||||
return
|
||||
|
||||
@@ -27,6 +27,12 @@ function isAllowedApiUrl(req) {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
+24
@@ -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() {
|
||||
const indexPath = path.join(NEXT_DIST_DIR, 'index.html')
|
||||
if (!fs.existsSync(indexPath)) {
|
||||
@@ -535,6 +558,7 @@ async function deploy(push) {
|
||||
validateBuiltDist()
|
||||
await run('cargo', ['build', '--manifest-path', 'backend/Cargo.toml', '--release'])
|
||||
promoteBuiltDist()
|
||||
syncVehicleIcons()
|
||||
|
||||
for (const target of RESTART_TARGETS) {
|
||||
await run('pm2', ['reload', target, '--update-env'])
|
||||
|
||||
Reference in New Issue
Block a user