The seed migration inserts one row per SYSTEM_CATEGORY_KEYS value from
backend/src/config/env.schema.ts.
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.)