Files
HIPCTF2/docs/database/schema.md
T
2026-07-22 14:52:57 +00:00

3.9 KiB


type: database title: Database Schema Overview description: SQLite (better-sqlite3) schema for HIPCTF: tables, relationships, indexes, and WAL journal mode. tags: [database, sqlite, typeorm, schema] timestamp: 2026-07-22T14:50:25Z

Overview

HIPCTF uses SQLite via better-sqlite3 with TypeORM migrations. The database is a single file (DATABASE_PATH, default /data/hipctf/db.sqlite) running in WAL journal mode for better concurrent read/write performance.

The schema is created by a single migration (backend/src/database/migrations/1700000000000-InitSchema.ts) and seeded by a second migration (backend/src/database/migrations/1700000000100-SeedSystemData.ts). The initial migration declares every column used by the entities (including category.created_at and category.updated_at), so the later AddCategoryTimestampsAndUniqueAbbrev1700000000200 and UpdateSystemCategoryKeys1700000000300 migrations only patch legacy databases that pre-date those columns and reseed the canonical system rows.

Tables

Table Purpose
user Authenticated users with role + status.
setting Key/value site configuration.
category Challenge categories (system + user-defined).
challenge CTF challenges with scoring + protocol metadata.
challenge_file Files attached to challenges.
solve User-solved challenges (scoring ledger).
refresh_token Rotating refresh tokens (hash + revocation).
blog_post Markdown blog posts in draft/published states.

Relationships

user ───< solve >─── challenge
                │
                └─── challenge_file >── challenge ──> category

user ───< refresh_token
  • challenge.category_idcategory.id (ON DELETE RESTRICT).
  • challenge_file.challenge_idchallenge.id (ON DELETE RESTRICT).
  • solve.challenge_idchallenge.id (ON DELETE RESTRICT).
  • solve.user_iduser.id (ON DELETE RESTRICT).

Indexes

Table Index
category uq_category_system_key (unique on system_key where NOT NULL)
challenge idx_challenge_category
challenge_file idx_challenge_file_challenge
solve uq_solve_challenge_user (unique), idx_solve_user, idx_solve_challenge
refresh_token idx_refresh_token_user, unique on token_hash
blog_post idx_blog_status_published (status, published_at)

PRAGMAs

PRAGMA Where set Effect
foreign_keys = ON Migration InitSchema1700000000000 Enforce FK constraints.
journal_mode = WAL Migration + DatabaseInitService.ensureWalMode() on every startup Persisted in DB header; allows concurrent readers.

Bootstrap behavior

On every process start, DatabaseInitService.init():

  1. Initializes the TypeORM DataSource (idempotent).
  2. Calls ensureWalMode() — idempotent; if WAL is already set it only logs.
  3. Runs pending migrations if any (or if the category table is missing).
  4. Logs the seed row counts (categories, settings) via verifySeed().

See also