From 19cdfe58a4c97fdf623ed6d741e2277041cc64b9 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Sun, 22 Mar 2026 00:44:24 +0100 Subject: [PATCH] feat: add bitrateMatrixOverrides validation, raise bitrate caps to 1 Gbps - Import BITRATE_MATRIX_KBPS from shared constants - Parse and return bitrateMatrixOverrides in rowToLimits - Widen updateData type to accept null values - Raise maxBitrateKbps and minBitrateKbps caps from 50 000 to 1 000 000 kbps - Add full validation for bitrateMatrixOverrides (null to clear, key/value checks) --- packages/server/src/routes/settings.ts | 46 ++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/packages/server/src/routes/settings.ts b/packages/server/src/routes/settings.ts index b5206283..8e03a1ff 100644 --- a/packages/server/src/routes/settings.ts +++ b/packages/server/src/routes/settings.ts @@ -4,7 +4,7 @@ import { getDb, schema } from '../db/index.js'; import { authenticate, requireAdmin } from '../utils/auth.js'; import { config } from '../config.js'; import type { InstanceStreamingLimits, InstanceAdminSettings } from '@backspace/shared'; -import { STANDARD_RESOLUTIONS, STANDARD_FRAMERATES } from '@backspace/shared/src/constants.js'; +import { STANDARD_RESOLUTIONS, STANDARD_FRAMERATES, BITRATE_MATRIX_KBPS } from '@backspace/shared/src/constants.js'; function rowToLimits(row: typeof schema.instanceSettings.$inferSelect): InstanceStreamingLimits { return { @@ -23,6 +23,15 @@ function rowToLimits(row: typeof schema.instanceSettings.$inferSelect): Instance maxResolution: row.maxResolution, maxFramerate: row.maxFramerate, discoveryEnabled: row.discoveryEnabled === 1, + bitrateMatrixOverrides: (() => { + const raw = row.bitrateMatrixOverrides as string | null; + if (!raw) return null; + try { + const parsed = JSON.parse(raw); + if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return null; + return Object.keys(parsed).length > 0 ? parsed as Record : null; + } catch { return null; } + })(), }; } @@ -42,18 +51,18 @@ export async function settingsRoutes(app: FastifyInstance): Promise { const db = getDb(); const body = request.body; - const updateData: Record = { updatedAt: Date.now() }; + const updateData: Record = { updatedAt: Date.now() }; if (body.maxBitrateKbps !== undefined) { - if (typeof body.maxBitrateKbps !== 'number' || body.maxBitrateKbps < 500 || body.maxBitrateKbps > 50000) { - return reply.code(400).send({ error: 'maxBitrateKbps must be between 500 and 50000', statusCode: 400 }); + if (typeof body.maxBitrateKbps !== 'number' || body.maxBitrateKbps < 500 || body.maxBitrateKbps > 1000000) { + return reply.code(400).send({ error: 'maxBitrateKbps must be between 500 and 1000000', statusCode: 400 }); } updateData.maxBitrateKbps = body.maxBitrateKbps; } if (body.minBitrateKbps !== undefined) { - if (typeof body.minBitrateKbps !== 'number' || body.minBitrateKbps < 100 || body.minBitrateKbps > 50000) { - return reply.code(400).send({ error: 'minBitrateKbps must be between 100 and 50000', statusCode: 400 }); + if (typeof body.minBitrateKbps !== 'number' || body.minBitrateKbps < 100 || body.minBitrateKbps > 1000000) { + return reply.code(400).send({ error: 'minBitrateKbps must be between 100 and 1000000', statusCode: 400 }); } updateData.minBitrateKbps = body.minBitrateKbps; } @@ -110,6 +119,31 @@ export async function settingsRoutes(app: FastifyInstance): Promise { updateData.discoveryEnabled = body.discoveryEnabled ? 1 : 0; } + if (body.bitrateMatrixOverrides !== undefined) { + if (body.bitrateMatrixOverrides === null) { + updateData.bitrateMatrixOverrides = null; + } else if (typeof body.bitrateMatrixOverrides !== 'object' || Array.isArray(body.bitrateMatrixOverrides)) { + return reply.code(400).send({ error: 'bitrateMatrixOverrides must be an object or null', statusCode: 400 }); + } else { + // Validate each key and value + const validKeys = new Set(); + for (const res of STANDARD_RESOLUTIONS) { + for (const fps of STANDARD_FRAMERATES) { + validKeys.add(`${res}_${fps}`); + } + } + for (const [key, value] of Object.entries(body.bitrateMatrixOverrides)) { + if (!validKeys.has(key)) { + return reply.code(400).send({ error: `Invalid matrix key: "${key}". Keys must be {resolution}_{framerate}, e.g. "1080_60"`, statusCode: 400 }); + } + if (typeof value !== 'number' || value <= 0 || value > 1000000) { + return reply.code(400).send({ error: `Invalid value for "${key}": must be a positive number up to 1000000 kbps`, statusCode: 400 }); + } + } + updateData.bitrateMatrixOverrides = JSON.stringify(body.bitrateMatrixOverrides); + } + } + // Cross-field validation: min < max const currentRow = db.select().from(schema.instanceSettings).where(eq(schema.instanceSettings.id, 1)).get(); if (!currentRow) {