7.1 KiB
type, title, description, tags, timestamp
| type | title | description | tags | timestamp | |||||
|---|---|---|---|---|---|---|---|---|---|
| database | Seed Sample Challenges Migration (1700000000600) | Forward-only migration that inserts a representative sample set of challenges on a fresh database so the /challenges board renders cards without manual admin action. |
|
2026-07-23T02:23:34Z |
Purpose
A brand-new HIPCTF database (default path
./data/db.sqlite) has no challenge rows until
an admin imports or creates them. The
SeedSampleChallenges1700000000600 migration bridges that gap by
inserting a small, representative sample set so the authenticated
/challenges board (see Challenges Board)
already has cards on the very first visit and the public landing can
demonstrate the platform with real content.
The migration runs at the end of the MIGRATIONS array in
DatabaseModule so every other
category / challenge schema migration has already landed and the
category rows it joins against are present.
File
backend/src/database/migrations/1700000000600-SeedSampleChallenges.ts
Registered last in backend/src/database/database.module.ts so it
runs after UpgradeChallengeAdminSchema1700000000500.
Sample set
Eight challenges, one or more per canonical system category. Source
of truth is the typed SAMPLE_CHALLENGES array exported by the
migration:
| Name | system_key |
Difficulty | initial_points |
minimum_points |
decay_solves |
Flag |
|---|---|---|---|---|---|---|
| Alpha Cipher | CRY |
LOW |
200 | 100 | 5 | flag{alpha} |
| Beta Cipher | CRY |
MEDIUM |
300 | 100 | 10 | flag{beta} |
| Zeta Key | CRY |
HIGH |
500 | 100 | 10 | flag{zeta} |
| Web Welcome | WEB |
LOW |
100 | 50 | 10 | flag{web1} |
| Mobile Mayhem | PWN |
MEDIUM |
300 | 100 | 10 | flag{mob1} |
| Hardware Hello | HW |
LOW |
100 | 50 | 10 | flag{hw1} |
| Markdown Mystery | MSC |
LOW |
100 | 50 | 10 | flag{md} |
| Reverse Ranger | REV |
MEDIUM |
300 | 100 | 10 | flag{rev1} |
All rows are inserted with protocol='WEB', port=NULL,
ip_address='', and enabled=1 so they appear on the board the
moment the event window opens (see Event Window).
Categories are resolved by system_key rather than UUID so the seed
works regardless of the UUIDs the category migration assigned at
install time. Sample flags follow the flag{<token>} convention and
are exposed only inside admin views — the public challenge DTO
continues to strip them (see Challenges Endpoints).
What the up() does
- Idempotency guard —
SELECT COUNT(*) FROM "challenge". If the table already contains rows the migration returns immediately; an admin that imported a custom challenge set first is never overwritten. - System-category lookup —
SELECT id, system_key FROM "category" WHERE system_key IS NOT NULLand builds an in-memoryMap<system_key, id>. If fewer than six canonical keys are present (the seed cannot bind) the migration aborts as a no-op rather than inserting challenges into the wrong category. - Insert sample rows — for each entry in
SAMPLE_CHALLENGES, anINSERT INTO "challenge"with a fresh UUID v4, ISO 8601created_at/updated_atviastrftime('%Y-%m-%dT%H:%M:%fZ','now'), andenabled=1. Categories without a matchingsystem_keyare silently skipped (defensive — only happens if the canonical six are missing).
Idempotency
- Running the migration twice (or restarting after it has been recorded) leaves the same eight rows. The early-return count guard skips the second run.
- Re-running the migration after a custom
challengerow exists also no-ops, so manually-imported content from the Admin — Challenges Full-Replace Import flow is preserved.
down()
Scoped to the seeded set: DELETE FROM "challenge" WHERE "name" IN (...)
using the eight fixed names from SAMPLE_CHALLENGES. It deliberately
matches only the sample names so unrelated admin rows are not
removed.
Tests
| File | What it asserts |
|---|---|
tests/backend/migrations.spec.ts — "seeds sample challenges on a fresh database (Job 906)" |
After all migrations run on an in-memory SQLite DB, the eight expected sample names are present. |
tests/backend/migrations.spec.ts — "seeded sample challenges are enabled, schema-compatible, and point at the right categories (Job 906)" |
Every seeded row has enabled=true, a valid difficulty, initial_points >= minimum_points, a non-empty flag, and a category_id that resolves to one of the six canonical system categories. |
tests/backend/migrations.spec.ts — "SeedSampleChallenges down() scope (Job 906)" |
down() removes only rows whose name is in the seed list and leaves manually-inserted rows of similar names (e.g. Other) intact. |
tests/backend/challenges-board.spec.ts |
Board-listing test now allows extra seeded cards inside the CRY column (Alpha Cipher, Beta Cipher, Zeta Key) while still asserting that the hand-inserted alphacipher LOW and Zeta-key HIGH rows occupy the bookended slots and that the disabled hidden row is excluded. |
Operational notes
- On a fresh database the seed fires before the first app boot, so
GET /api/v1/challenges/boardreturns the sample columns immediately and the player-facing Challenges Board shows eight cards. An admin deleting every challenge is the only way to reach an empty board. - To confirm on the command line after
npm run setup:Expected count:sqlite3 ./data/db.sqlite 'SELECT COUNT(*) FROM challenge;'8. Re-running setup on a populated DB leaves the count unchanged (idempotent guard). - Played flags are usable for end-to-end demos: e.g. submitting
flag{alpha}against the Alpha Cipher card immediately awards points and broadcasts the solve on the Scoreboard Stream.
See also
- Database Schema Overview — full migration list and bootstrap behavior.
- Challenge Tables —
challengeschema, indexes, and provenance. - Challenges Endpoints — board query and flag submission.
- Challenges Board — user-facing guide.
- Admin — Challenges — how an admin replaces or extends the sample set.
- Category Repair Migration — earlier canonical-six migration this seed depends on.