Initial implementation of Bluetooth toy control app
CI / Static checks (push) Successful in 1m27s
CI / Build and push image (push) Skipped

Next.js app with a browser-side buttplug/buttplug-wasm control layer
(server never touches real-time device commands), SQLite storage via
Drizzle, single-secret auth, recordings/replay with device remapping,
a usage stats dashboard, Docker deployment, and Gitea CI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W8WkFF5ppURBAB918593Eb
This commit is contained in:
2026-08-25 07:51:38 +02:00
co-authored by Claude Sonnet 5
commit 1119c8eea0
112 changed files with 15522 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
import { z } from "zod";
// ACCESS_PASSWORD gates the login form; AUTH_SECRET signs the session JWT.
// These are deliberately separate - reusing the login password as the
// signing key would let a JWT-signing weakness leak the login secret itself.
const envSchema = z.object({
ACCESS_PASSWORD: z.string().min(1, "ACCESS_PASSWORD must be set"),
AUTH_SECRET: z.string().min(16, "AUTH_SECRET must be at least 16 characters"),
DATABASE_PATH: z.string().min(1).default("./data/app.db"),
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).optional(),
});
export type Env = z.infer<typeof envSchema>;
let cached: Env | undefined;
export function getEnv(): Env {
if (cached) return cached;
const parsed = envSchema.safeParse(process.env);
if (!parsed.success) {
throw new Error(`Invalid environment configuration: ${parsed.error.message}`);
}
cached = parsed.data;
return cached;
}