Files
HIPCTF2/docs/database/challenges.md
T
2026-07-22 12:05:11 +00:00

93 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
type: database
title: Challenge Tables
description: category, challenge, challenge_file, and solve tables — how CTF challenges and scoring are stored.
tags: [database, challenge, category, solve]
timestamp: 2026-07-21T14:18:00Z
---
# Tables
## `category`
| Column | Type | Description |
|----------------|---------|------------------------------------------------------------------------------|
| `id` | TEXT PK | UUID v4. |
| `system_key` | TEXT | Non-null for system categories (e.g. `CRY`, `MSC`, `PWN`, `REV`, `WEB`, `HW`); unique where NOT NULL; `NULL` for user-created categories. |
| `name` | TEXT | Display name (e.g. `Cryptography`). |
| `abbreviation` | TEXT | Short label (e.g. `CRY`), 26 chars, server-uppercased. Globally unique — enforced by `uq_category_abbreviation`. |
| `description` | TEXT | Optional description. |
| `icon_path` | TEXT | Default icon URL (e.g. `/uploads/icons/CRY.png`). |
| `created_at` | TEXT | ISO 8601 timestamp the row was created (added by `AddCategoryTimestampsAndUniqueAbbrev1700000000200`). |
| `updated_at` | TEXT | ISO 8601 timestamp the row was last updated (added by the same migration; bumped on `PUT /api/v1/admin/categories/:id`). |
System rows are seeded by the
`UpdateSystemCategoryKeys1700000000300` migration so the canonical
keys are `CRY` (Cryptography), `MSC` (Misc), `PWN` (Binary
exploitation), `REV` (Reverse Engineering), `WEB` (Web), and `HW`
(Hardware). The migration drops any legacy seeded rows that no
longer match the canonical set before inserting the missing entries.
User-created categories leave `system_key` as `NULL`.
The uniqueness on `abbreviation` is enforced both by application
validation (uppercased on save, duplicates rejected) and by the
`uq_category_abbreviation` index created in migration
`1700000000200`.
## `challenge`
| Column | Type | Description |
|-------------------|----------|-----------------------------------------------------------------------------------|
| `id` | TEXT PK | UUID v4. |
| `name` | TEXT | Display name. |
| `description_md` | TEXT | Markdown description. |
| `category_id` | TEXT | FK → `category.id`. |
| `difficulty` | TEXT | `'low'` / `'med'` / `'high'`. |
| `initial_points` | INTEGER | Starting point value. |
| `minimum_points` | INTEGER | Floor after decay. |
| `decay_solves` | INTEGER | Number of solves at which decay reaches the minimum. |
| `flag` | TEXT | Submission string expected from players. |
| `protocol` | TEXT | `'nc'` (netcat-style connection) or `'web'` (browser-based). |
| `port` | INTEGER | TCP port when relevant (nullable). |
| `ip_address` | TEXT | IP the challenge listens on (defaulted from `setting.defaultChallengeIp`). |
| `created_at` | TEXT | ISO 8601 timestamp. |
## `challenge_file`
| Column | Type | Description |
|---------------------|---------|--------------------------------------|
| `id` | TEXT PK | UUID v4. |
| `challenge_id` | TEXT | FK → `challenge.id`. |
| `original_filename` | TEXT | Filename from upload. |
| `stored_path` | TEXT | Path on disk under `UPLOAD_DIR/challenges/`. |
## `solve`
| Column | Type | Description |
|------------------|---------|----------------------------------------------------------------------|
| `id` | TEXT PK | UUID v4. |
| `challenge_id` | TEXT | FK → `challenge.id`. |
| `user_id` | TEXT | FK → `user.id`. |
| `solved_at` | TEXT | ISO 8601 timestamp the solve was recorded. |
| `points_awarded` | INTEGER | Points actually awarded (after decay). |
| `base_points` | INTEGER | Snapshot of `challenge.initial_points` at solve time. |
| `rank_bonus` | INTEGER | Extra points from rank (e.g. first-blood). |
Uniqueness is enforced by `uq_solve_challenge_user` on
`(challenge_id, user_id)` — a user can solve a given challenge only once.
# Scoring formula
`points_awarded = clamp(initial_points - decay, minimum_points, initial_points)`
where `decay = max(0, base_points - minimum_points) * solvesBefore / decay_solves`,
and additional `rank_bonus` may be added for early solves.
(The exact scoring algorithm lives in the future solve-recording service
and is consumed by `SseHubService` whenever a new solve is published.)
# See also
- [Database Schema Overview](/database/schema.md)
- [System Endpoints](/api/system.md) (SSE scoreboard stream)
- [Scoreboard Stream](/guides/scoreboard-stream.md)