floatctf-api/
├── Cargo.toml
├── src/
│ ├── main.rs # Application entry point
│ ├── api/
│ │ ├── mod.rs # API module exports
│ │ ├── admin/ # Admin routes (SuperAdmin JWT required)
│ │ │ ├── mod.rs # Admin route configuration
│ │ │ ├── dto.rs # Shared DTOs
│ │ │ ├── announcements.rs
│ │ │ ├── challenge_sets.rs
│ │ │ ├── challenges.rs
│ │ │ ├── database.rs
│ │ │ ├── docker.rs
│ │ │ ├── event_announcements.rs
│ │ │ ├── event_challenges.rs
│ │ │ ├── event_logs.rs
│ │ │ ├── event_teams.rs
│ │ │ ├── event_users.rs
│ │ │ ├── event_writeups.rs
│ │ │ ├── events.rs
│ │ │ ├── instances.rs
│ │ │ ├── logs.rs
│ │ │ ├── scheduled_tasks.rs
│ │ │ ├── settings.rs
│ │ │ ├── super_admin.rs
│ │ │ ├── system.rs
│ │ │ ├── users.rs
│ │ │ └── weapons.rs
│ │ └── service/ # Service routes (User JWT required)
│ │ ├── mod.rs # Service route configuration
│ │ ├── announcements.rs
│ │ ├── challenge_sets.rs
│ │ ├── challenge_solves.rs
│ │ ├── challenge_writeups.rs
│ │ ├── challenges.rs
│ │ ├── events.rs
│ │ ├── instances.rs
│ │ ├── submit.rs
│ │ ├── super_admin.rs
│ │ ├── uploads.rs
│ │ ├── users.rs
│ │ └── weapons.rs
│ ├── auth.rs # JWT authentication (HS512)
│ ├── config.rs # Configuration management
│ ├── db.rs # Database initialization
│ ├── entity/ # SeaORM entity definitions
│ │ ├── challenges.rs
│ │ ├── events.rs
│ │ ├── instances.rs
│ │ ├── users.rs
│ │ └── ...
│ └── strategies/ # Event strategies
│ └── event/
│ ├── implementations/
│ │ ├── jeopardy_practice.rs
│ │ ├── jeopardy_single.rs
│ │ └── jeopardy_team.rs
│ └── trait_def.rs
- Web Framework: Actix-web 4
- ORM: Sea-ORM 1.1 (PostgreSQL)
- Authentication: JWT (HS512 algorithm, 8-hour default expiration)
- Docker Management: Bollard 0.19
- Password Hashing: Argon2
Both User and SuperAdmin JWTs contain:
sub: User ID (UUID)role: Role enum (User,SuperAdmin,ResetAccount,AwdJudger)exp: Expiration timestamp
Authorization: Bearer <token>
| Role | Description |
|---|---|
User |
Regular user access |
SuperAdmin |
Admin panel access |
ResetAccount |
Password reset token |
AwdJudger |
Award judging access |
Super admin authentication.
Request Body:
{
"username": "string",
"password": "string"
}Response: UniResponse<String> - JWT token
User authentication.
Request Body:
{
"username": "string",
"password": "string"
}Response: UniResponse<String> - JWT token
Create a new user account.
Request Body:
{
"username": "string",
"nickname": "string",
"password": "string",
"email": "string"
}Response: UniResponse<String> - Success message
Send password reset link via email.
Request Body:
{
"email": "string" // optional
"username": "string" // optional (one required)
}Response: UniResponse<()>
Reset password using the token from email.
Query Parameters:
token: Password reset token
Request Body:
{
"password": "string",
"confirmed_password": "string"
}Response: UniResponse<()>
Response: UniResponse<users::Model>
{
"id": "uuid",
"username": "string",
"nickname": "string",
"email": "string",
"password": "string",
"created_at": "datetime",
"updated_at": "datetime"
}Request Body:
{
"nickname": "string", // optional
"email": "string", // optional
"password": "string" // optional
}Response: UniResponse<()>
Response: UniResponse<Vec<announcements::Model>>
Response: UniResponse<Vec<weapons::Model>>
Query Parameters:
page: Page number (optional)limit: Items per page (optional)filter: JSON filter expression (optional)id: Filter by challenge IDname: Filter by name (contains)category: Filter by category (contains)description: Filter by description (contains)
Response: UniResponse<Vec<challenges::Model>> with meta
{
"data": [
{
"id": "uuid",
"name": "string",
"safe_name": "string",
"category": "string",
"description": "string",
"attachment": "string|null",
"hidden": false,
"toml_str": "string",
"created_at": "datetime",
"updated_at": "datetime"
}
],
"meta": {
"page": 1,
"limit": 10,
"total": 100
}
}Path Parameters:
challenge_id: Challenge UUID
Response: UniResponse<challenges::Model>
Get user's practice instance for a challenge.
Path Parameters:
challenge_id: Challenge UUID
Response: UniResponse<instances::Model>
Path Parameters:
challenge_id: Challenge UUID
Request Body:
{
"content": "string"
}Response: UniResponse<challenge_writeup::Model>
Path Parameters:
challenge_id: Challenge UUID
Response: UniResponse<challenge_writeup::Model>
Path Parameters:
challenge_id: Challenge UUID
Response: UniResponse<Vec<ChallengeWriteupResult>>
{
"data": [
{
"nickname": "string",
"email": "string",
"challenge": {...},
"writeup": {...}
}
]
}Response: UniResponse<Vec<challenge_sets::Model>>
Path Parameters:
challenge_set_id: Challenge Set UUID
Response: UniResponse<challenge_sets::Model>
Query Parameters:
page: Page number (optional)limit: Items per page (optional)filter: JSON filter expression (optional)id: Filter by instance IDstatus: Filter by status (Running,Stopped, etc.)ref: Filter by reference (contains)challenge_id: Filter by challenge IDgamebox_id: Filter by gamebox ID
Response: UniResponse<Vec<instances::Model>> with meta
Path Parameters:
instance_id: Instance UUID
Response: UniResponse<instances::Model>
Request Body:
{
"event_id": "uuid|null", // optional, for event challenges
"challenge_id": "uuid"
}Response: UniResponse<instances::Model>
Path Parameters:
instance_id: Instance UUID
Response: UniResponse<()>
Response: UniResponse<Vec<challenge_solves::Model>>
Response: UniResponse<Vec<...>>
Query Parameters:
page: Page number (optional)limit: Items per page (optional)filter: JSON filter expression (optional)id: Filter by event IDtitle: Filter by title (contains)type: Filter by event type (JeopardyPractice,JeopardySingle,JeopardyTeam)allow_join: Filter by allow_join boolean
Response: UniResponse<Vec<EventInfo>>
{
"data": [
{
"event": {...},
"team_result": {...} | null,
"joined": true | false
}
]
}Path Parameters:
event_id: Event UUID
Response: UniResponse<EventInfo>
Path Parameters:
event_id: Event UUID
Response: UniResponse<Vec<EventChallengeResult>>
{
"data": [
{
"challenge": {...},
"current_points": 500.0,
"solved_count": 10,
"solved": true | false,
"solved_no": 3
}
]
}Path Parameters:
event_id: Event UUID
Response: UniResponse<Vec<EventInstanceResult>>
Path Parameters:
event_id: Event UUIDchallenge_id: Challenge UUID
Response: UniResponse<instances::Model>
Path Parameters:
event_id: Event UUID
Response: UniResponse<Vec<ScoreboardItem>>
{
"data": [
{
"no": 1,
"name": "string",
"score": 1000.0,
"solved_count": 5,
"challenges": [
{
"name": "string",
"solved": true,
"solved_no": 2
}
]
}
]
}Path Parameters:
event_id: Event UUID
Response: UniResponse<Vec<event_announcements::Model>>
Path Parameters:
event_id: Event UUID
Response: UniResponse<Vec<TrendItem>>
Path Parameters:
event_id: Event UUID
Response: UniResponse<DateTime> - Last writeup submission time
Path Parameters:
event_id: Event UUID
Response: UniResponse<event_users::Model>
Path Parameters:
event_id: Event UUID
Response: UniResponse<u64> - Deleted count
Path Parameters:
event_id: Event UUID
Request Body:
{
"name": "string"
}Response: UniResponse<event_teams::Model>
Path Parameters:
event_id: Event UUIDteam_id: Team UUID
Response: UniResponse<()>
Path Parameters:
event_id: Event UUIDteam_id: Team UUID
Response: UniResponse<()> - Error if captain
Path Parameters:
event_id: Event UUIDteam_id: Team UUID
Response: UniResponse<()> - Deletes team if captain, removes member otherwise
Response: UniResponse<Vec<ChallengeWriteupResult>>
Path Parameters:
writeup_id: Writeup UUID
Response: UniResponse<ChallengeWriteupResult>
Request Body:
{
"event_id": "uuid|null", // optional
"instance_id": "uuid|null", // optional (for single mode)
"flag": "string"
}Response: UniResponse<()> - Empty on success
Multipart Form:
writeup_pdf: PDF file (max 1GB)event_id: Event UUID (text)team_id: Team UUID (optional text)
Response: UniResponse<()>
Multipart Form:
file: Image file
Response: UniResponse<String> - File path
Response: System monitoring data
Response: Changelog data
Request Body:
{
"sql": "string"
}Response: Query results
Query Parameters:
page: Page number (optional)limit: Items per page (optional)filter: JSON filter (optional)
Response: UniResponse<Vec<announcements::Model>> with meta
Request Body:
{
"title": "string",
"content": "string"
}Response: UniResponse<announcements::Model>
Request Body:
{
"id_list": ["uuid", ...]
}Response: UniResponse<u64> - Deleted count
Path Parameters:
announcement_id: Announcement UUID
Request Body:
{
"title": "string", // optional
"content": "string" // optional
}Response: UniResponse<announcements::Model>
Response: UniResponse<Vec<settings::Model>>
Request Body:
{
"key": "string",
"value": "string"
}Response: UniResponse<settings::Model>
Request Body:
{
"id_list": ["uuid", ...]
}Response: UniResponse<u64> - Deleted count
Path Parameters:
setting_id: Setting UUID
Request Body:
{
"key": "string", // optional
"value": "string" // optional
}Response: UniResponse<settings::Model>
Response: UniResponse<Vec<weapons::Model>>
Request Body:
{
"name": "string",
"description": "string"
}Response: UniResponse<weapons::Model>
Request Body:
{
"id_list": ["uuid", ...]
}Response: UniResponse<u64> - Deleted count
Path Parameters:
weapon_id: Weapon UUID
Request Body:
{
"name": "string", // optional
"description": "string" // optional
}Response: UniResponse<weapons::Model>
Path Parameters:
weapon_id: Weapon UUID
Multipart Form:
file: Weapon file
Response: UniResponse<...>
Query Parameters:
page: Page number (optional)limit: Items per page (optional)filter: JSON filter (optional)id: Filter by user IDusername: Filter by username (contains)nickname: Filter by nickname (contains)email: Filter by email (contains)
Response: UniResponse<Vec<users::Model>> with meta
Path Parameters:
user_id: User UUID
Response: UniResponse<users::Model>
Request Body:
{
"username": "string",
"password": "string",
"nickname": "string",
"email": "string"
}Response: UniResponse<users::Model>
Request Body:
{
"id_list": ["uuid", ...]
}Response: UniResponse<u64> - Deleted count
Path Parameters:
user_id: User UUID
Request Body:
{
"username": "string", // optional
"nickname": "string", // optional
"password": "string", // optional
"email": "string" // optional
}Response: UniResponse<users::Model>
Query Parameters:
page: Page number (optional)limit: Items per page (optional)filter: JSON filter (optional)id: Filter by challenge IDname: Filter by name (contains)category: Filter by category (contains)hidden: Filter by hidden statusdescription: Filter by description (contains)
Response: UniResponse<Vec<challenges::Model>> with meta
Path Parameters:
challenge_id: Challenge UUID
Response: UniResponse<challenges::Model>
Request Body:
{
"name": "string",
"category": "string",
"description": "string",
"hidden": false,
"attachment": "string|null",
"toml_str": "string"
}Response: UniResponse<challenges::Model>
Request Body:
{
"id_list": ["uuid", ...]
}Response: UniResponse<u64> - Deleted count
Path Parameters:
challenge_id: Challenge UUID
Request Body:
{
"name": "string", // optional
"category": "string", // optional
"description": "string", // optional
"attachment": "string", // optional
"hidden": true | false, // optional
"toml_str": "string" // optional
}Response: UniResponse<challenges::Model>
Validate challenge Docker images and attachments.
Request Body:
{
"challenge_id_list": ["uuid", ...] // optional, checks all if empty
}Response: UniResponse<Vec<ChallengeCheckResult>>
{
"data": [
{
"id": "uuid",
"challenge_name": "string",
"is_ok": true | false,
"docker_image": true | false,
"attachment": true | false
}
]
}Import challenges from ZIP or base64-encoded TOML.
Multipart Form:
challenge_zip: Single challenge ZIP file (optional, max 1GB)challenge_list_zip: ZIP containing multiple challenges (optional, max 10GB)toml_str_b64: Base64-encoded TOML string (optional)
Response: UniResponse<Vec<challenges::Model>>
Request Body:
{
"challenge_id": "uuid", // optional
"challenge_id_list": ["uuid", ...] // optional
}Response: UniResponse<Vec<BuildChallengeResult>>
{
"data": [
{
"challenge_name": "string",
"is_ok": true | false,
"message": "string"
}
]
}Response: UniResponse<Vec<challenge_sets::Model>>
Path Parameters:
challenge_set_id: Challenge Set UUID
Response: UniResponse<challenge_sets::Model>
Request Body:
{
"name": "string",
"description": "string"
}Response: UniResponse<challenge_sets::Model>
Request Body:
{
"id_list": ["uuid", ...]
}Response: UniResponse<u64> - Deleted count
Path Parameters:
challenge_set_id: Challenge Set UUID
Request Body:
{
"name": "string", // optional
"description": "string" // optional
}Response: UniResponse<challenge_sets::Model>
Path Parameters:
challenge_set_id: Challenge Set UUID
Request Body:
{
"challenge_id": "uuid"
}Response: UniResponse<()>
Path Parameters:
challenge_set_id: Challenge Set UUID
Request Body:
{
"id_list": ["uuid", ...]
}Response: UniResponse<u64> - Deleted count
Response: UniResponse<Vec<super_admin::Model>>
Path Parameters:
super_admin_id: Super Admin UUID
Response: UniResponse<super_admin::Model>
Request Body:
{
"username": "string",
"password": "string",
"email": "string"
}Response: UniResponse<super_admin::Model>
Request Body:
{
"id_list": ["uuid", ...]
}Response: UniResponse<u64> - Deleted count
Path Parameters:
super_admin_id: Super Admin UUID
Request Body:
{
"username": "string", // optional
"password": "string", // optional
"email": "string" // optional
}Response: UniResponse<super_admin::Model>
Response: UniResponse<Vec<instances::Model>>
Path Parameters:
instance_id: Instance UUID
Response: UniResponse<instances::Model>
Query Parameters:
page: Page number (optional)limit: Items per page (optional)filter: JSON filter (optional)id: Filter by event IDtype: Filter by event typetitle: Filter by title (contains)hidden: Filter by hidden statusallow_join: Filter by allow_join status
Response: UniResponse<Vec<events::Model>> with meta
Path Parameters:
event_id: Event UUID
Response: UniResponse<events::Model>
Request Body:
{
"type": "JeopardyPractice | JeopardySingle | JeopardyTeam",
"title": "string",
"description": "string|null",
"hidden": false,
"allow_join": true | false,
"rules": "string",
"start_time": "datetime",
"end_time": "datetime"
}Response: UniResponse<events::Model>
Request Body:
{
"id_list": ["uuid", ...]
}Response: UniResponse<u64> - Deleted count
Path Parameters:
event_id: Event UUID
Request Body:
{
"type": "JeopardyPractice | JeopardySingle | JeopardyTeam", // optional
"title": "string", // optional
"description": "string", // optional
"hidden": true | false, // optional
"allow_join": true | false, // optional
"rules": "string", // optional
"flag_prefix": "string", // optional
"start_time": "datetime", // optional
"end_time": "datetime" // optional
}Response: UniResponse<events::Model>
Path Parameters:
event_id: Event UUID
Response: UniResponse<DataPresent>
{
"data": {
"event": {...},
"user_count": 100,
"team_count": 20,
"solved_recent_15": [...],
"event_challenges": [...],
"scoreboard_top10": [...],
"trend": [...]
}
}Path Parameters:
event_id: Event UUID
Response: UniResponse<String> - ZIP file path containing report.html and writeups
Path Parameters:
event_id: Event UUID
Response: UniResponse<Vec<event_users::Model>>
Path Parameters:
event_id: Event UUID
Request Body:
{
"user_id": "uuid"
}Response: UniResponse<event_users::Model>
Path Parameters:
event_id: Event UUID
Request Body:
{
"id_list": ["uuid", ...]
}Response: UniResponse<u64> - Deleted count
Path Parameters:
event_id: Event UUIDuser_id: User UUID
Response: UniResponse<()>
Path Parameters:
event_id: Event UUIDuser_id: User UUID
Response: UniResponse<()>
Path Parameters:
event_id: Event UUID
Response: UniResponse<Vec<event_teams::Model>>
Path Parameters:
event_id: Event UUIDteam_id: Team UUID
Response: UniResponse<Vec<TeamMemberResult>>
Path Parameters:
event_id: Event UUID
Request Body:
{
"name": "string"
}Response: UniResponse<event_teams::Model>
Path Parameters:
event_id: Event UUID
Request Body:
{
"id_list": ["uuid", ...]
}Response: UniResponse<u64> - Deleted count
Path Parameters:
event_id: Event UUIDteam_id: Team UUID
Request Body:
{
"user_id": "uuid"
}Response: UniResponse<()>
Path Parameters:
event_id: Event UUIDteam_id: Team UUID
Request Body:
{
"id_list": ["uuid", ...]
}Response: UniResponse<u64> - Deleted count
Path Parameters:
event_id: Event UUIDteam_id: Team UUID
Response: UniResponse<()>
Path Parameters:
event_id: Event UUIDteam_id: Team UUID
Response: UniResponse<()>
Path Parameters:
event_id: Event UUID
Response: UniResponse<Vec<event_challenges::Model>>
Path Parameters:
event_id: Event UUID
Request Body:
{
"challenge_id": "uuid",
"points": 500.0
}Response: UniResponse<event_challenges::Model>
Path Parameters:
event_id: Event UUID
Request Body:
{
"id_list": ["uuid", ...]
}Response: UniResponse<u64> - Deleted count
POST /api/admin/events/{event_id}/challenges/hidden - Hide Challenges
Path Parameters:
event_id: Event UUID
Request Body:
{
"challenge_id_list": ["uuid", ...]
}Response: UniResponse<()>
Path Parameters:
event_id: Event UUID
Request Body:
{
"challenge_id_list": ["uuid", ...]
}Response: UniResponse<()>
Path Parameters:
event_id: Event UUID
Response: UniResponse<Vec<event_announcements::Model>>
Path Parameters:
event_id: Event UUIDannouncement_id: Announcement UUID
Response: UniResponse<event_announcements::Model>
Path Parameters:
event_id: Event UUID
Request Body:
{
"title": "string",
"content": "string"
}Response: UniResponse<event_announcements::Model>
Path Parameters:
event_id: Event UUIDannouncement_id: Announcement UUID
Request Body:
{
"title": "string", // optional
"content": "string" // optional
}Response: UniResponse<event_announcements::Model>
Path Parameters:
event_id: Event UUID
Request Body:
{
"id_list": ["uuid", ...]
}Response: UniResponse<u64> - Deleted count
Path Parameters:
event_id: Event UUID
Response: UniResponse<Vec<event_writeup::Model>>
Path Parameters:
event_id: Event UUID
Response: UniResponse<Vec<logs::Model>>
Response: UniResponse<Vec<scheduled_tasks::Model>>
Path Parameters:
task_id: Task UUID
Response: UniResponse<scheduled_tasks::Model>
Request Body:
{
"name": "string",
"cron": "string",
"action": "string"
}Response: UniResponse<scheduled_tasks::Model>
Request Body:
{
"id_list": ["uuid", ...]
}Response: UniResponse<u64> - Deleted count
Path Parameters:
task_id: Task UUID
Request Body:
{
"name": "string", // optional
"cron": "string", // optional
"action": "string" // optional
}Response: UniResponse<scheduled_tasks::Model>
Response: UniResponse<Vec<logs::Model>>
Path Parameters:
log_id: Log UUID
Response: UniResponse<logs::Model>
| Field | Type | Description |
|---|---|---|
| id | Uuid | Primary key |
| name | String | Challenge name (unique) |
| safe_name | String | URL-safe name (unique) |
| category | String | Challenge category |
| description | String | Challenge description |
| attachment | Option | Attachment filename |
| hidden | bool | Whether challenge is hidden |
| toml_str | String | Challenge configuration TOML |
| created_at | DateTimeWithTimeZone | Creation timestamp |
| updated_at | DateTimeWithTimeZone | Last update timestamp |
| Field | Type | Description |
|---|---|---|
| id | Uuid | Primary key |
| type | EventType | Event type enum |
| title | String | Event title |
| description | Option | Event description |
| hidden | bool | Whether event is hidden |
| allow_join | bool | Whether users can join |
| rules | String | Event rules |
| start_time | DateTimeWithTimeZone | Start time |
| end_time | DateTimeWithTimeZone | End time |
| flag_prefix | Option | Custom flag prefix |
| Value | Description |
|---|---|
JeopardyPractice |
Practice mode (solo) |
JeopardySingle |
Competitive single-player |
JeopardyTeam |
Competitive team-based |
| Field | Type | Description |
|---|---|---|
| id | Uuid | Primary key |
| challenge_id | Uuid | Related challenge |
| user_id | Uuid | Instance owner |
| gamebox_id | Option | Gamebox reference |
| status | InstanceStatus | Running/Stopped/Error |
| flag | String | Instance flag |
| ref | String | Reference type (JeopardyPractice, event_id, etc.) |
| created_at | DateTimeWithTimeZone | Creation timestamp |
| updated_at | DateTimeWithTimeZone | Last update timestamp |
| Value | Description |
|---|---|
Running |
Instance is running |
Stopped |
Instance is stopped |
Error |
Instance error state |
All list endpoints support pagination and filtering:
| Parameter | Type | Description |
|---|---|---|
| page | usize | Page number (1-indexed) |
| limit | usize | Items per page |
| Parameter | Type | Description |
|---|---|---|
| filter | JSON string | Filter expression |
{
"key": "value",
"key2": "value2"
}Common filter keys:
id: UUID exact matchname: String containscategory: String containstype: Enum valuehidden: Booleanallow_join: Boolean
All responses follow UniResponse<T> format:
{
"data": T,
"meta": {
"page": 1,
"limit": 10,
"total": 100
}
}For endpoints returning no data (UniResponse<()>):
{
"data": null
}Errors return UniError with HTTP status codes:
| Status | Description |
|---|---|
| 400 | Bad Request |
| 401 | Unauthorized (AuthError) |
| 404 | Not Found |
| 500 | Internal Server Error |
{
"error": {
"code": "ERROR_CODE",
"message": "Human readable message"
}
}Events use dynamic scoring that decays based on solve count:
score = min_points + (base_points - min_points) * sqrt(decay / (decay + solves))
Where:
min_points = base_points * event_score_min_percentdecayandevent_score_min_percentare system settings
本项目以 GNU AGPLv3 协议发布。 Copyright (C) 2025-2026 fb0sh@outlook.com