Tactical Media Broadcast Hub — initial release

Self-hosted multi-platform broadcast newsroom for activist collectives:
publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style
column wall with hostile-content-safe rendering, live control room with Auto
Director, bridged chat governance with polls and quorum, distributed sandboxed
transcode workers, encrypted content-addressed archive, monthly analytics.

MIT licensed. See README.md and docs/.
This commit is contained in:
glass beadian 2026-10-10 11:56:42 +00:00
commit aa5e262f3a
880 changed files with 111042 additions and 0 deletions

View file

@ -0,0 +1,79 @@
import { BadRequestException } from "@nestjs/common";
import type { SessionPayload } from "../auth/session.js";
import type { AuthenticatedRequest } from "../rbac/rbac.guard.js";
/** Shared request-shape guards for controllers that take `unknown` bodies. */
export function requireSession(request: AuthenticatedRequest): SessionPayload {
if (request.session === undefined) throw new Error("unreachable: RBAC guard did not attach a session");
return request.session;
}
export function requireObject(value: unknown, field: string): Record<string, unknown> {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
throw new BadRequestException(`${field} must be an object`);
}
return value as Record<string, unknown>;
}
export function requireString(value: unknown, field: string): string {
if (typeof value !== "string" || value.trim() === "") {
throw new BadRequestException(`${field} must be a non-empty string`);
}
return value;
}
export function optionalString(value: unknown, field: string): string | undefined {
if (value === undefined || value === null || value === "") return undefined;
return requireString(value, field);
}
const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
export function requireEmail(value: unknown, field = "email"): string {
const email = requireString(value, field).trim().toLowerCase();
if (!EMAIL.test(email) || email.length > 254) throw new BadRequestException(`${field} must be an email address`);
return email;
}
export function optionalEmail(value: unknown, field = "email"): string | undefined {
const email = optionalString(value, field);
return email === undefined ? undefined : requireEmail(email, field);
}
export const USERNAME_PATTERN = /^[a-z0-9][a-z0-9_.-]{1,31}$/;
export function requireUsername(value: unknown, field = "username"): string {
const username = requireString(value, field).trim().toLowerCase();
if (!USERNAME_PATTERN.test(username)) {
throw new BadRequestException(`${field} must be 2-32 characters: lowercase letters, digits, dot, underscore or hyphen`);
}
return username;
}
/** Positive integer within an inclusive cap (e.g. expiresInHours 1..720, maxUses 1..10000). */
export function requirePositiveInt(value: unknown, field: string, max: number): number {
const n = typeof value === "number" ? value : NaN;
if (!Number.isInteger(n) || n < 1 || n > max) {
throw new BadRequestException(`${field} must be an integer between 1 and ${max}`);
}
return n;
}
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
export function isUuid(value: string): boolean {
return UUID.test(value);
}
export function parseId(value: string, field = "id"): string {
if (!isUuid(value)) throw new BadRequestException(`${field} must be a UUID`);
return value;
}
export function parseLimit(value: unknown, fallback: number, max: number): number {
if (value === undefined || value === "") return fallback;
const n = typeof value === "string" ? Number(value) : NaN;
if (!Number.isInteger(n) || n < 1) throw new BadRequestException("limit must be a positive integer");
return Math.min(n, max);
}