The official website for the BIBS-C Network Hackathon (season 2, 2025–26).
| Layer | Technology |
|---|---|
| Framework | Nuxt 4 (^4.4.8) |
| UI | @nuxt/ui ^4.9.0 (Tailwind CSS v4) |
| Language | TypeScript ^5.9.3 |
| Runtime | Node.js >= v24 or Bun (dual-runtime support) |
| Package Manager | Bun (preferred); npm works |
| Database | SQLite via Drizzle ORM (bun:sqlite under Bun, better-sqlite3 under Node.js) |
| Auth | nuxt-auth-utils 0.5.25 (session-based) |
| Validation | Zod 4.x (^4.4.3) |
| Fonts | @nuxt/fonts ^0.14.0 (local provider) |
| Icons | @iconify-json/lucide, @iconify-json/material-symbols |
| Linting | @nuxt/eslint 1.10.0 + Prettier ^3.9.4 |
| Deployment | Node.js server (VPS) |
- Node.js >= v24, or Bun (any recent 1.x release)
- Bun is the preferred runtime and package manager; npm works as a fallback
- A SQLite-compatible filesystem (the local DB file lives at
./database/basishacks.sqlite)
bun install
# or
npm installThe postinstall script (nuxt prepare) runs automatically and generates Nuxt's typed references.
Copy the example env file and fill in your credentials:
cp .env.example .envThe canonical list of variables lives in .env.example. The table below summarizes which are required and which are optional.
| Variable | Required? | Purpose |
|---|---|---|
NUXT_SESSION_PASSWORD |
Required | Session encryption key. Must be at least 32 bytes. Generate with openssl rand -base64 32 |
NUXT_OAUTH2_JWT_SECRET |
Required | JWT signing secret for OAuth2 token exchange. Must be at least 32 bytes. Generate with openssl rand -base64 32 |
ONSITE_LOGIN_CLIENT_ID |
Required for onsite login | OAuth2 client_id of the basishacks app used by the /api/login -> /api/oauth2/authorize onsite flow. The server auto-adds ${CURRENT_URL_ORIGIN}${REDIRECT_URI} to this app's allowed redirect URIs on startup |
MICROSOFT_TENANT_ID |
Required for MS login | Microsoft Entra ID tenant (directory) ID. Must be paired with MICROSOFT_CLIENT_ID |
MICROSOFT_CLIENT_ID |
Required for MS login | Microsoft Entra ID application (client) ID. Must be paired with MICROSOFT_TENANT_ID |
MICROSOFT_CLIENT_SECRET |
Optional | Microsoft Entra ID app secret for MS Graph API integration |
CURRENT_URL_ORIGIN |
Optional | Base origin for OAuth2 redirect callbacks (no trailing slash). Defaults to http://localhost:3000; set to your real domain in production |
MICROSOFT_REDIRECT_URI |
Optional | Microsoft OAuth2 redirect URI path (must start with /). Defaults to /api/oauth2/mscallback |
REDIRECT_URI |
Optional | Onsite OAuth2 redirect URI path used by /api/login. Defaults to /api/oauth2/dccallback. The server auto-registers it for ONSITE_LOGIN_CLIENT_ID |
DEEPSEEK_API_KEY |
Optional | DeepSeek API key for AI chat features (debug routes only) |
PORT / HOST |
Optional | Server port/host override (defaults: 3000 / 0.0.0.0) |
Note:
MICROSOFT_TENANT_ID,MICROSOFT_CLIENT_ID, andONSITE_LOGIN_CLIENT_IDwere previously hardcoded in themainbranch. They are now read from environment variables and must be set explicitly.
The database auto-migrates on startup — no manual SQL is required. The init plugin server/plugins/init-database.ts calls createDrizzleDatabase() (in server/database/index.ts), which:
- Selects the runtime's native SQLite driver (
bun:sqliteunder Bun,better-sqlite3under Node.js) via dynamic import - Applies
PRAGMA journal_mode = WALandPRAGMA foreign_keys = ON - Runs
createAndMigrateDatabase()fromserver/database/migrate.tsto bring the schema up to date
For manual schema management (e.g., generating migration files after editing server/database/schema.ts), Drizzle Kit is available:
bun run db:migrate # apply pending Drizzle Kit migrations
bun run db:generate # generate new migration from schema changes
bun run db:studio # open Drizzle StudioA database created on the main branch will be auto-repaired on first startup by migrateLegacySchema() in server/database/migrate.ts. No manual SQL needs to be run.
The legacy SQL schema and migration files that used to live in sql/ have been archived under sql/archive/. They are kept for historical reference only and are not applied at runtime.
Start the dev server (port 24598, configured in nuxt.config.ts):
bun dev
# or
npm run devThe dev server defaults to HTTP. For HTTPS (required by some OAuth2 flows during local development), use bun dev --https or npm run dev -- --https.
Dependency pins in
package.json:
viteis pinned to8.0.16. Vite8.1.xcombined with Nuxt4.4.8creates two HMR WebSocket listeners on the same HTTP server, crashing the dev server withserver.handleUpgrade() was called more than once with the same socket(nuxt/nuxt#35450).entitiesis pinned to7.0.1.entities@8changes theentities/decodeexport and breaks@vue/compiler-core's entity decoder, causing errors such asdecode.fromCodePoint is not a functionwhen Vue parses SFC templates (e.g.rules.vue).Keep both overrides in place until the affected packages release compatible versions.
Build the production bundle (Nitro node-server preset):
bun run buildThe same .output/ artifact runs under both Bun and Node.js — the SQLite driver is selected at runtime based on typeof Bun.
Tests are run with Vitest:
bun run test # one-shot run
bun run test:watch # watch mode
bun run test:coverage # with coverageDo not use bun test. Bun's native test runner cannot resolve Nuxt's ~~/ and ~/ path aliases (which are configured in vitest.config.ts), and the test files import their assertions from vitest rather than bun:test. Running bun test is intentionally redirected by bunfig.toml to a shim (bun-shim/shim.test.ts) that prints guidance pointing you to bun run test.
Two production start options are supported.
After a sucessful merge into main, you can do the following to create a commit build:
git checkout main
git pull origin main
git tag v<Specify Version> # MUST START WITH A "v" !!!
git push origin v<Specify Version>bun startThis runs start-fix.mjs, which sets globalThis._importMeta_ so the Nitro server's import.meta.url resolves correctly under Bun, then boots .output/server/index.mjs. start-fix.mjs must be kept — it is the Bun production entrypoint.
node .output/server/index.mjsSet NODE_ENV=production and ensure all environment variables are present in the server environment before starting.
The application is deployed as a Node.js server on a VPS:
- Build the production bundle on the server (or locally and sync
.output/):bun run build - Ensure all required environment variables (see Environment Setup) are set in the server environment
- Start the server with
bun start(Bun) ornode .output/server/index.mjs(Node.js) - Put a reverse proxy (e.g., Nginx, Caddy) in front of the Node server for TLS termination and to forward traffic to the configured
PORT(default3000)
The SQLite database file lives at ./database/basishacks.sqlite and uses WAL mode. Back up the database/ directory (including -wal and -shm files) for point-in-time snapshots.
app/ # Nuxt app (Vue frontend)
assets/css/ # Global styles (Tailwind + custom utilities)
components/ # Vue components
layouts/ # Nuxt layouts
middleware/ # Route middleware
pages/ # File-based routing
utils/ # Frontend utilities
server/ # Nitro backend
api/ # API route handlers (file-based)
middleware/ # Server middleware (OAuth2 authorize)
plugins/ # Nitro plugins (DB init, MS Graph token, JWT secret guard)
database/ # Drizzle ORM: schema, migrations, dual-runtime init
schema.ts # Drizzle schema definition
migrate.ts # createAndMigrateDatabase() + migrateLegacySchema()
index.ts # createDrizzleDatabase() — selects bun:sqlite or better-sqlite3
utils/ # Server utilities
database/ # Per-table Drizzle helpers
auth.ts # requireUser / requireJudge / requireAdmin / requirePermission
convert.ts # DB row -> public API object transformers
rateLimit.ts # In-memory rate limiter
oauth2.ts # Microsoft OAuth2 URL construction
oauth2-validate.ts # OAuth2 authorization request validation
oauth2-jwt.ts # JWT verification and withOAuth2JWT() wrapper
profile.ts # Profile picture helpers
assets.ts # Static and user asset helpers
scoring.ts # Score aggregation and final ranking
url-validation.ts # Redirect URI validation
validate-oauth2-jwt-secret.ts # JWT secret guard
deepseek-store.ts # DeepSeek AI chat session store
types/ # Type augmentations (H3EventContext)
shared/ # Code shared between client and server
schemas.ts # Zod validation schemas
database.d.ts # DB TypeScript types
responses.d.ts # API response interface definitions
auth.d.ts # nuxt-auth-utils session type augmentation
permissions.ts # Fine-grained permission constants and helpers
oauth2-scopes.ts # OAuth2 scope definitions
oauth2.ts # Microsoft OAuth2 static configuration
rubric.ts # Judging rubric definitions
awards.ts # Award registry definitions
seasons.ts # Static season metadata
sql/archive/ # ARCHIVED legacy SQL schema and migrations (not active)
init.sql # Historical base schema
migration-*.sql # Historical dated migrations
patch-*.sql # Historical feature patches
database/ # Local SQLite file (basishacks.sqlite, WAL mode)
drizzle/ # Drizzle Kit generated migration files
tests/ # Vitest test suite (server, api, shared, frontend, etc.)
setup.ts # Vitest setupFile
documentation/ # VitePress documentation site
.vitepress/config.ts # Sidebar / nav config
start-fix.mjs # Bun production start entrypoint (kept intentionally)
bunfig.toml # Redirects `bun test` to a guidance shim
bun-shim/shim.test.ts # Shim that prints "use bun run test" guidance
The enhance-and-debloat branch merges cleanly with origin/main (only one new commit on main: 129c1ca Update .gitignore). The steps below cover both fresh setups and upgrades from an existing main deployment.
git checkout enhance-and-debloat
bun install
cp .env.example .env # then edit .env and fill in credentials
bun run build
bun run test
bun start # or: node .output/server/index.mjsNo manual SQL is required. On first startup against an existing main-era database:
migrateLegacySchema()inserver/database/migrate.tsdetects the legacy schema and applies the necessary repairscreateAndMigrateDatabase()brings the schema up to date via Drizzle- The server boots normally
Back up the database/ directory before the first boot as a precaution.
The following variables were hardcoded on main and must now be set explicitly in .env (or the server environment):
MICROSOFT_TENANT_ID— previously hardcoded Microsoft Entra tenant IDMICROSOFT_CLIENT_ID— previously hardcoded Microsoft Entra client IDONSITE_LOGIN_CLIENT_ID— previously hardcoded OAuth2 client_id for the onsite login flow
Without these, Microsoft OAuth2 login and the onsite login flow will not function.
Merging origin/main into enhance-and-debloat (or vice versa) produces no conflicts. The only incoming change from main is .gitignore, which applies cleanly.