Internal portal for TrickFire Robotics. Members submit part orders and Minecraft whitelist requests; admins review and action them. A service API used by simulation scripts is also exposed through the same server.
Production: https://dashboard.trickfirerobotics.com - runs a server in our lab room behind a Cloudflare Tunnel.
| Layer | Choice |
|---|---|
| Framework | Next.js 15 - App Router, React Server Components, output: "standalone" |
| Database | SQLite via Drizzle ORM + better-sqlite3 |
| Auth | better-auth - email/password, session cookies |
| UI | Tailwind CSS v4, shadcn/ui, Lucide icons |
| Resend | |
| Network | Tailscale — shared tailnet, managed via Tailscale API |
| Package manager | pnpm |
Open the project in VS Code with the Dev Containers extension installed, then run Dev Containers: Reopen in Container from the command palette. The container automatically installs dependencies, copies .env.example → .env.local, and seeds the database. Once it finishes, run pnpm dev and open http://localhost:3000.
- Node.js ≥ 20
pnpmpackage manager- A C++ build toolchain (only if
better-sqlite3has no prebuilt for your platform)
pnpm installcp .env.example .env.localOpen .env.local and fill in values. For local dev you only strictly need BETTER_AUTH_SECRET; everything else has a workable default or degrades gracefully. See Environment Variables for the full reference.
pnpm db:migrate # apply schema migrations
pnpm db:seed # seed base databaseNote
The seed is safe to run multiple times - teams use ON CONFLICT DO NOTHING and the admin account is only created if the email doesn't already exist.
pnpm devOpen http://localhost:3000 and log in with the credentials from SEED_ADMIN_* in your .env.local.
| Variable | Description |
|---|---|
NEXT_PUBLIC_APP_URL |
Public origin of the app, e.g. http://localhost:3000 |
BETTER_AUTH_SECRET |
Random secret for signing sessions — generate with openssl rand -hex 32 |
BETTER_AUTH_URL |
Same value as NEXT_PUBLIC_APP_URL |
BETTER_AUTH_TRUSTED_ORIGINS |
Comma-separated extra origins allowed to make auth requests, e.g. http://192.168.1.50:3000. Required when accessing the dev server from another machine on the LAN. |
DATABASE_PATH |
Path to the SQLite file, e.g. ./db/dashboard.db |
RESEND_API_KEY |
API key from Resend for transactional email |
EMAIL_FROM |
Sender shown in outgoing emails, e.g. TrickFire Robotics <noreply@trickfirerobotics.com> |
MINECRAFT_SERVER_HOST |
Hostname/IP of the Minecraft server (default localhost) |
MINECRAFT_SERVER_PORT |
Query port of the Minecraft server (default 25565) |
MINECRAFT_SERVER_PATH |
Absolute path to the Minecraft server directory — used to detect if the server is installed |
MINECRAFT_WORLD_PATH |
Absolute path to the Minecraft world directory. Used to read per-player stat files for the playtime leaderboard. |
MINECRAFT_RCON_PORT |
RCON port (default 25575). Must match rcon.port in server.properties. |
MINECRAFT_RCON_PASSWORD |
RCON password. Must match rcon.password in server.properties. |
MINECRAFT_BOT_NAMES |
Comma-separated list of carpet bot names, optionally with a custom skin URL: BotA:https://s.namemc.com/i/abc123.png. Bots are tagged in the UI. |
BLUEMAP_URL |
Internal URL of the BlueMap web server, e.g. http://localhost:8100. The dashboard proxies this at /bluemap so it's accessible without exposing a second port. |
TAILSCALE_API_KEY |
API key from the Tailscale admin console (Settings → Keys). Powers the Network tab. |
TAILSCALE_TAILNET |
Tailnet name, e.g. trickfirerobotics.com. Use - to default to the tailnet that owns the API key. |
ONSHAPE_BASE_URL |
OnShape API base URL, e.g. https://cad.onshape.com/api |
ONSHAPE_ACCESS_KEY |
OnShape API access key |
ONSHAPE_SECRET_KEY |
OnShape API secret key |
ONSHAPE_COMPANY_ID |
Optional. Pin requests to a specific OnShape company — auto-detected from the access key if omitted. |
SEED_ADMIN_EMAIL |
Email for the seeded admin account |
SEED_ADMIN_PASSWORD |
Password for the seeded admin account |
SEED_ADMIN_NAME |
Display name for the seeded admin |
Tip
Most variables are optional for local dev. The app degrades gracefully: Minecraft cards show "offline", the leaderboard and map show an unavailable state, email is skipped, and the Network tab shows an error state. The only var you strictly need to set is BETTER_AUTH_SECRET.
Caution
Never commit .env.local or .env.production. The BETTER_AUTH_SECRET value lets anyone forge session tokens — if it leaks, rotate it immediately by changing the value and restarting the server (all existing sessions are invalidated).
dashboard/
├── src/
│ ├── app/ # Next.js App Router - pages and API routes
│ │ ├── (auth)/ # Login / register pages (unauthenticated layout)
│ │ ├── (dashboard)/ # Authenticated dashboard pages + shared layout
│ │ │ ├── layout.tsx # Auth gate + sidebar/topnav shell
│ │ │ ├── dashboard/ # Member overview page
│ │ │ ├── orders/ # Order submission + history
│ │ │ ├── api-keys/ # API key management
│ │ │ ├── minecraft/ # Minecraft status, whitelist requests, leaderboard
│ │ │ ├── headscale/ # Network join requests
│ │ │ └── admin/ # Admin-only pages (order queue, users, etc.)
│ │ ├── api/ # Route handlers
│ │ │ ├── service/ # External service endpoints (e.g. API key verify)
│ │ │ └── minecraft/ # Internal endpoints (status, leaderboard)
│ │ └── bluemap/ # BlueMap reverse proxy (catch-all, forwards to BLUEMAP_URL)
│ ├── components/
│ │ ├── layout/ # Sidebar, TopNav
│ │ ├── minecraft/ # Minecraft-specific components (status, leaderboard, map)
│ │ └── ui/ # shadcn/ui primitives (buttons, cards, dialogs, etc.)
│ └── lib/
│ ├── db/
│ │ ├── schema.ts # Application tables (edit this for schema changes)
│ │ └── auth-schema.ts # better-auth managed tables - do not edit
│ ├── auth.ts # better-auth server configuration
│ ├── auth-client.ts # better-auth browser client
│ ├── minecraft.ts # Minecraft server status ping + bot name resolution
│ └── minecraft-stats.ts # Playtime leaderboard from world stat files + Mojang API
├── drizzle/ # Auto-generated migration files - do not edit by hand
├── scripts/
│ └── seed.ts # Database seed (teams + admin)
└── public/ # Static assets served as-is
Caution
src/lib/db/auth-schema.ts is owned by better-auth. Do not edit it directly - your changes will be overwritten the next time better-auth regenerates it. To customize auth-related columns, go through src/lib/auth.ts.
The app uses a single SQLite file on disk. All queries go through Drizzle ORM - there is no separate database server to manage.
| Table | Description |
|---|---|
user |
Registered members. role is "admin" or "member". isActive = false blocks dashboard access without deleting the account. |
session |
Active auth sessions - managed by better-auth, don't touch. |
account |
Auth provider records - managed by better-auth, don't touch. |
verification |
Email verification tokens - managed by better-auth, don't touch. |
team |
The 6 robot sub-teams. Seeded once; not user-editable through the UI. |
orders |
Part order requests. Status flows: pending → approved / rejected → ordered. |
api_key |
Hashed service API keys issued to members. Only the prefix and hash are stored - the raw key is shown once on creation. |
minecraft_whitelist |
Minecraft username whitelist requests. addedDirectly marks entries added by admins without a member request. |
headscale_join_request |
Requests to join the Tailscale network. Approved requests still require manual action in the Tailscale admin console. |
sqlite3 db/dashboard.dbUseful commands inside the SQLite shell:
.tables -- list all tables
.schema orders -- show CREATE TABLE statement
SELECT * FROM user;
SELECT * FROM orders WHERE status = 'pending';
datetime(created_at / 1000, 'unixepoch') -- convert ms timestamp to readable date
.quitNote
All timestamps are stored as millisecond integers (timestamp_ms). Divide by 1000 or use datetime(col / 1000, 'unixepoch') when querying raw SQL.
# 1. Edit src/lib/db/schema.ts
# 2. Generate the migration
pnpm db:generate
# 3. Apply it locally
pnpm db:migrate
# 4. Commit schema.ts and the new file in drizzle/ togetherWarning
Never edit files inside drizzle/ after they have been committed. Drizzle checksums each file and will refuse to run migrations if it detects manual edits.
| Command | Description |
|---|---|
pnpm dev |
Start the dev server with hot reload |
pnpm build |
Production build |
pnpm start |
Start the production server (requires a prior build) |
pnpm lint |
Run ESLint |
pnpm format |
Auto-format all files with Prettier |
pnpm format:check |
Check formatting without writing (used in CI) |
pnpm db:generate |
Generate migrations from schema changes |
pnpm db:migrate |
Apply all pending migrations |
pnpm db:seed |
Seed the 6 teams + admin user (idempotent) |
pnpm db:reset |
Drop and recreate the local database. Blocked in production by a guard |
pnpm db:studio |
Open Drizzle Studio — visual database browser (dev only) |
See DEPLOY.md for full production setup - Jetson Xavier (ARM64), systemd service, Cloudflare Tunnel, and Headscale configuration.
See CONTRIBUTING.md for workflow, branch naming, code style, and PR guidelines.