AI Implementation feature(906): Challenges Page and Challenge Solve Modal 1.02 (#48)
This commit was merged in pull request #48.
This commit is contained in:
@@ -85,6 +85,11 @@ column are sorted by `difficulty` (`LOW < MEDIUM < HIGH`) then by name.
|
||||
Only `enabled = 1` rows are returned. The query joins
|
||||
`challenge` and `category` once and groups in memory.
|
||||
|
||||
On a fresh database the `SeedSampleChallenges1700000000600`
|
||||
migration populates the board with a representative sample set
|
||||
spanning every canonical system category so the page renders cards
|
||||
without any manual admin action.
|
||||
|
||||
# `GET /api/v1/challenges/status`
|
||||
|
||||
Authenticated event-window snapshot (the same `EventStatePayload`
|
||||
|
||||
@@ -56,6 +56,14 @@ canonical enums, the `enabled` flag, `updated_at`, the
|
||||
category FK stays at `ON DELETE RESTRICT` so the
|
||||
`CATEGORY_HAS_CHALLENGES` business rule is preserved.
|
||||
|
||||
The `SeedSampleChallenges1700000000600` migration inserts a small
|
||||
representative set of sample challenges (one or more per canonical
|
||||
system category) on a fresh database so the `/challenges` board has
|
||||
cards immediately after bootstrap. It is idempotent: if the
|
||||
`challenge` table already contains rows (e.g. an admin imported a
|
||||
custom challenge set), it does nothing. Sample flags follow the
|
||||
`flag{<token>}` convention and are exposed only inside admin views.
|
||||
|
||||
## `challenge_file`
|
||||
|
||||
| Column | Type | Description |
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
---
|
||||
type: database
|
||||
title: Seed Sample Challenges Migration (1700000000600)
|
||||
description: 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.
|
||||
tags: [database, migration, challenge, seed, sample]
|
||||
timestamp: 2026-07-23T02:23:34Z
|
||||
---
|
||||
|
||||
# Purpose
|
||||
|
||||
A brand-new HIPCTF database (default path
|
||||
[`./data/db.sqlite`](/database/schema.md)) 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](/guides/challenges-board.md))
|
||||
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`](/architecture/backend-modules.md) 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](/guides/event-window.md)).
|
||||
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](/api/challenges.md)).
|
||||
|
||||
# What the `up()` does
|
||||
|
||||
1. **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.
|
||||
2. **System-category lookup** —
|
||||
`SELECT id, system_key FROM "category" WHERE system_key IS NOT NULL`
|
||||
and builds an in-memory `Map<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.
|
||||
3. **Insert sample rows** — for each entry in `SAMPLE_CHALLENGES`, an
|
||||
`INSERT INTO "challenge"` with a fresh UUID v4, ISO 8601
|
||||
`created_at` / `updated_at` via `strftime('%Y-%m-%dT%H:%M:%fZ','now')`,
|
||||
and `enabled=1`. Categories without a matching `system_key` are
|
||||
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 `challenge` row exists also
|
||||
no-ops, so manually-imported content from the
|
||||
[Admin — Challenges Full-Replace Import](/guides/admin-challenges-import.md)
|
||||
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/board`](/api/challenges.md) returns the
|
||||
sample columns immediately and the player-facing [Challenges Board](/guides/challenges-board.md)
|
||||
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`:
|
||||
```
|
||||
sqlite3 ./data/db.sqlite 'SELECT COUNT(*) FROM challenge;'
|
||||
```
|
||||
Expected count: `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](/guides/scoreboard-stream.md).
|
||||
|
||||
# See also
|
||||
|
||||
* [Database Schema Overview](/database/schema.md) — full migration list
|
||||
and bootstrap behavior.
|
||||
* [Challenge Tables](/database/challenges.md) — `challenge` schema,
|
||||
indexes, and provenance.
|
||||
* [Challenges Endpoints](/api/challenges.md) — board query and flag
|
||||
submission.
|
||||
* [Challenges Board](/guides/challenges-board.md) — user-facing guide.
|
||||
* [Admin — Challenges](/guides/admin-challenges.md) — how an admin
|
||||
replaces or extends the sample set.
|
||||
* [Category Repair Migration](/database/category-repair-migration.md)
|
||||
— earlier canonical-six migration this seed depends on.
|
||||
@@ -10,7 +10,14 @@ timestamp: 2026-07-23T00:10:00Z
|
||||
|
||||
`/challenges` is rendered by `ChallengesPage`
|
||||
(`frontend/src/app/features/challenges/challenges.page.ts`) inside the
|
||||
authenticated shell. The page is gated by:
|
||||
authenticated shell.
|
||||
|
||||
On a fresh database the `SeedSampleChallenges1700000000600`
|
||||
migration loads a representative sample set of challenges so the
|
||||
board has cards the moment an admin configures the event window; an
|
||||
empty board is only possible when the admin deleted every challenge.
|
||||
|
||||
The page is gated by:
|
||||
|
||||
| Gate | Where |
|
||||
|---------------------------------------|--------------------------------------------------|
|
||||
|
||||
+5
-1
@@ -11,7 +11,7 @@ scoreboard, an event window with a public countdown, theming, and admin
|
||||
controls.
|
||||
|
||||
The docs below are organized by purpose so agents can pull just the slice
|
||||
they need. Last regenerated 2026-07-23T00:10:00Z.
|
||||
they need. Last regenerated 2026-07-23T02:23:34Z.
|
||||
|
||||
# Architecture
|
||||
|
||||
@@ -35,6 +35,10 @@ they need. Last regenerated 2026-07-23T00:10:00Z.
|
||||
Forward-only migration that adds `created_at`/`updated_at` to legacy
|
||||
`category` tables and reconciles system rows to the canonical
|
||||
CRY/HW/MSC/PWN/REV/WEB set.
|
||||
* [Seed Sample Challenges Migration](/database/seed-sample-challenges-migration.md) -
|
||||
Forward-only migration that inserts a representative sample set of
|
||||
challenges on a fresh database so the `/challenges` board has cards
|
||||
immediately, with idempotency guards and scoped `down()`.
|
||||
* [Auth and Settings Tables](/database/auth-settings.md) - `refresh_token`
|
||||
and `setting` tables.
|
||||
* [Blog Post Table](/database/blog-posts.md) - `blog_post` table.
|
||||
|
||||
Reference in New Issue
Block a user