Add utoipa and Scalar for API documentation

- Add utoipa and utoipa-scalar dependencies
- Add ToSchema derives to all models
- Add OpenAPI path annotations to auth, users, and health handlers
- Create openapi.rs module with API documentation
- Serve Scalar UI at /docs endpoint
- Include JWT bearer auth security scheme

Co-Authored-By: Warp <agent@warp.dev>
This commit is contained in:
VinceC
2026-01-20 01:28:17 -06:00
parent c26a1820d5
commit cac4b83140
16 changed files with 235 additions and 37 deletions

View File

@@ -50,6 +50,10 @@ rand = "0.8"
base64 = "0.22" base64 = "0.22"
libm = "0.2" # Math functions for rating calculations libm = "0.2" # Math functions for rating calculations
# API Documentation
utoipa = { version = "4", features = ["axum_extras", "chrono"] }
utoipa-scalar = { version = "0.1", features = ["axum"] }
# S3 compatible storage # S3 compatible storage
aws-sdk-s3 = "1" aws-sdk-s3 = "1"
aws-config = "1" aws-config = "1"

View File

@@ -41,6 +41,8 @@ CREATE TABLE IF NOT EXISTS team_players (
date_created TIMESTAMPTZ NOT NULL DEFAULT NOW(), date_created TIMESTAMPTZ NOT NULL DEFAULT NOW(),
team_id INTEGER NOT NULL REFERENCES teams(id) ON DELETE CASCADE, team_id INTEGER NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
player_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, player_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
game_name TEXT NOT NULL DEFAULT '',
game_mode TEXT NOT NULL DEFAULT '',
position VARCHAR(20) NOT NULL DEFAULT 'member', position VARCHAR(20) NOT NULL DEFAULT 'member',
status INTEGER NOT NULL DEFAULT 0, status INTEGER NOT NULL DEFAULT 0,
UNIQUE(team_id, player_id) UNIQUE(team_id, player_id)
@@ -70,6 +72,12 @@ CREATE TABLE IF NOT EXISTS ladder_teams (
date_created TIMESTAMPTZ NOT NULL DEFAULT NOW(), date_created TIMESTAMPTZ NOT NULL DEFAULT NOW(),
ladder_id INTEGER NOT NULL REFERENCES ladders(id) ON DELETE CASCADE, ladder_id INTEGER NOT NULL REFERENCES ladders(id) ON DELETE CASCADE,
team_id INTEGER NOT NULL REFERENCES teams(id) ON DELETE CASCADE, team_id INTEGER NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
status VARCHAR(255) NOT NULL DEFAULT 'active',
seed INTEGER,
result_position INTEGER,
win_count INTEGER NOT NULL DEFAULT 0,
loss_count INTEGER NOT NULL DEFAULT 0,
UNIQUE(ladder_id, team_id) UNIQUE(ladder_id, team_id)
); );

View File

@@ -1,6 +1,7 @@
use axum::{extract::State, http::StatusCode, Json}; use axum::{extract::State, http::StatusCode, Json};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::PgPool; use sqlx::PgPool;
use utoipa::ToSchema;
use validator::Validate; use validator::Validate;
use crate::{ use crate::{
@@ -11,7 +12,7 @@ use crate::{
}; };
/// Login request /// Login request
#[derive(Debug, Deserialize, Validate)] #[derive(Debug, Deserialize, Validate, ToSchema)]
pub struct LoginRequest { pub struct LoginRequest {
#[validate(email(message = "Invalid email format"))] #[validate(email(message = "Invalid email format"))]
pub email: String, pub email: String,
@@ -20,14 +21,14 @@ pub struct LoginRequest {
} }
/// Login response /// Login response
#[derive(Debug, Serialize)] #[derive(Debug, Serialize, ToSchema)]
pub struct AuthResponse { pub struct AuthResponse {
pub token: String, pub token: String,
pub user: UserResponse, pub user: UserResponse,
} }
/// User data in response /// User data in response
#[derive(Debug, Serialize)] #[derive(Debug, Serialize, ToSchema)]
pub struct UserResponse { pub struct UserResponse {
pub id: i32, pub id: i32,
pub email: String, pub email: String,
@@ -53,7 +54,7 @@ impl From<User> for UserResponse {
} }
/// Register request /// Register request
#[derive(Debug, Deserialize, Validate)] #[derive(Debug, Deserialize, Validate, ToSchema)]
pub struct RegisterRequest { pub struct RegisterRequest {
#[validate(length(min = 1, max = 100, message = "First name is required"))] #[validate(length(min = 1, max = 100, message = "First name is required"))]
pub firstname: String, pub firstname: String,
@@ -76,6 +77,16 @@ pub struct AppState {
} }
/// POST /api/auth/login /// POST /api/auth/login
#[utoipa::path(
post,
path = "/api/auth/login",
tag = "auth",
request_body = LoginRequest,
responses(
(status = 200, description = "Login successful", body = AuthResponse),
(status = 401, description = "Invalid email or password")
)
)]
pub async fn login( pub async fn login(
State(state): State<AppState>, State(state): State<AppState>,
Json(payload): Json<LoginRequest>, Json(payload): Json<LoginRequest>,
@@ -113,6 +124,16 @@ pub async fn login(
} }
/// POST /api/auth/register /// POST /api/auth/register
#[utoipa::path(
post,
path = "/api/auth/register",
tag = "auth",
request_body = RegisterRequest,
responses(
(status = 201, description = "Registration successful", body = AuthResponse),
(status = 409, description = "Email or username already exists")
)
)]
pub async fn register( pub async fn register(
State(state): State<AppState>, State(state): State<AppState>,
Json(payload): Json<RegisterRequest>, Json(payload): Json<RegisterRequest>,

View File

@@ -1,9 +1,10 @@
use axum::{extract::State, http::StatusCode, Json}; use axum::{extract::State, http::StatusCode, Json};
use serde::Serialize; use serde::Serialize;
use utoipa::ToSchema;
use super::auth::AppState; use super::auth::AppState;
#[derive(Serialize)] #[derive(Serialize, ToSchema)]
pub struct HealthResponse { pub struct HealthResponse {
pub status: String, pub status: String,
pub version: String, pub version: String,
@@ -12,6 +13,15 @@ pub struct HealthResponse {
/// Health check endpoint /// Health check endpoint
/// GET /health /// GET /health
#[utoipa::path(
get,
path = "/health",
tag = "health",
responses(
(status = 200, description = "API is healthy", body = HealthResponse),
(status = 503, description = "Database connection error")
)
)]
pub async fn health_check(State(state): State<AppState>) -> (StatusCode, Json<HealthResponse>) { pub async fn health_check(State(state): State<AppState>) -> (StatusCode, Json<HealthResponse>) {
// Check database connectivity // Check database connectivity
let db_status = match sqlx::query("SELECT 1").fetch_one(&state.pool).await { let db_status = match sqlx::query("SELECT 1").fetch_one(&state.pool).await {

View File

@@ -1,5 +1,6 @@
use axum::{extract::State, Json}; use axum::{extract::State, Json};
use serde::Serialize; use serde::Serialize;
use utoipa::ToSchema;
use crate::{ use crate::{
auth::AuthUser, auth::AuthUser,
@@ -10,7 +11,7 @@ use crate::{
use super::auth::AppState; use super::auth::AppState;
/// User profile response /// User profile response
#[derive(Debug, Serialize)] #[derive(Debug, Serialize, ToSchema)]
pub struct UserProfileResponse { pub struct UserProfileResponse {
pub id: i32, pub id: i32,
pub email: String, pub email: String,
@@ -39,6 +40,18 @@ impl From<User> for UserProfileResponse {
/// GET /api/users/me /// GET /api/users/me
/// Get current authenticated user's profile /// Get current authenticated user's profile
#[utoipa::path(
get,
path = "/api/users/me",
tag = "users",
responses(
(status = 200, description = "Current user profile", body = UserProfileResponse),
(status = 401, description = "Unauthorized")
),
security(
("bearer_auth" = [])
)
)]
pub async fn get_current_user( pub async fn get_current_user(
user: AuthUser, user: AuthUser,
State(state): State<AppState>, State(state): State<AppState>,

View File

@@ -8,6 +8,7 @@ pub mod db;
pub mod error; pub mod error;
pub mod handlers; pub mod handlers;
pub mod models; pub mod models;
pub mod openapi;
pub mod services; pub mod services;
// Re-export commonly used types // Re-export commonly used types

View File

@@ -8,6 +8,8 @@ use tower_http::{
trace::TraceLayer, trace::TraceLayer,
}; };
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use utoipa::OpenApi;
use utoipa_scalar::{Scalar, Servable};
use vrbattles_api::{ use vrbattles_api::{
auth::middleware::JwtSecret, auth::middleware::JwtSecret,
@@ -38,6 +40,7 @@ use vrbattles_api::{
}, },
users::get_current_user, users::get_current_user,
}, },
openapi::ApiDoc,
services::storage::StorageService, services::storage::StorageService,
}; };
@@ -86,6 +89,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Build router // Build router
let app = Router::new() let app = Router::new()
// API Documentation
.merge(Scalar::with_url("/docs", ApiDoc::openapi()))
// Health check // Health check
.route("/health", get(health_check)) .route("/health", get(health_check))
// Auth routes // Auth routes

View File

@@ -1,9 +1,10 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::FromRow; use sqlx::FromRow;
use utoipa::ToSchema;
/// Featured player model representing the featured_players table /// Featured player model representing the featured_players table
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] #[derive(Debug, Clone, Serialize, Deserialize, FromRow, ToSchema)]
pub struct FeaturedPlayer { pub struct FeaturedPlayer {
pub id: i32, pub id: i32,
pub date_created: DateTime<Utc>, pub date_created: DateTime<Utc>,
@@ -12,7 +13,7 @@ pub struct FeaturedPlayer {
} }
/// Featured player with user details (joined query result) /// Featured player with user details (joined query result)
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] #[derive(Debug, Clone, Serialize, Deserialize, FromRow, ToSchema)]
pub struct FeaturedPlayerWithUser { pub struct FeaturedPlayerWithUser {
pub id: i32, pub id: i32,
pub player_id: i32, pub player_id: i32,

View File

@@ -1,9 +1,10 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::FromRow; use sqlx::FromRow;
use utoipa::ToSchema;
/// Ladder status /// Ladder status
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)]
pub enum LadderStatus { pub enum LadderStatus {
Open = 0, Open = 0,
Closed = 1, Closed = 1,
@@ -19,7 +20,7 @@ impl From<i32> for LadderStatus {
} }
/// Ladder model representing the ladders table /// Ladder model representing the ladders table
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] #[derive(Debug, Clone, Serialize, Deserialize, FromRow, ToSchema)]
pub struct Ladder { pub struct Ladder {
pub id: i32, pub id: i32,
pub date_created: DateTime<Utc>, pub date_created: DateTime<Utc>,
@@ -32,7 +33,7 @@ pub struct Ladder {
} }
/// Create ladder request /// Create ladder request
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, ToSchema)]
pub struct CreateLadder { pub struct CreateLadder {
pub name: String, pub name: String,
pub date_start: String, pub date_start: String,
@@ -40,7 +41,7 @@ pub struct CreateLadder {
} }
/// Update ladder request /// Update ladder request
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, ToSchema)]
pub struct UpdateLadder { pub struct UpdateLadder {
pub name: Option<String>, pub name: Option<String>,
pub date_start: Option<String>, pub date_start: Option<String>,

View File

@@ -1,9 +1,10 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::FromRow; use sqlx::FromRow;
use utoipa::ToSchema;
/// Ladder team model representing the ladder_teams table /// Ladder team model representing the ladder_teams table
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] #[derive(Debug, Clone, Serialize, Deserialize, FromRow, ToSchema)]
pub struct LadderTeam { pub struct LadderTeam {
pub id: i32, pub id: i32,
pub date_created: DateTime<Utc>, pub date_created: DateTime<Utc>,
@@ -12,7 +13,7 @@ pub struct LadderTeam {
} }
/// Join ladder request /// Join ladder request
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, ToSchema)]
pub struct JoinLadderRequest { pub struct JoinLadderRequest {
pub team_id: i32, pub team_id: i32,
} }

View File

@@ -1,9 +1,10 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::FromRow; use sqlx::FromRow;
use utoipa::ToSchema;
/// Match status values /// Match status values
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)]
pub enum MatchStatus { pub enum MatchStatus {
Open = 0, // Initial state Open = 0, // Initial state
Scheduled = 1, // Both teams accepted Scheduled = 1, // Both teams accepted
@@ -25,7 +26,7 @@ impl From<i32> for MatchStatus {
} }
/// Team challenge status /// Team challenge status
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)]
pub enum TeamChallengeStatus { pub enum TeamChallengeStatus {
Challenging = 0, // Sent challenge Challenging = 0, // Sent challenge
PendingResponse = 1, // Waiting for response PendingResponse = 1, // Waiting for response
@@ -47,7 +48,7 @@ impl From<i32> for TeamChallengeStatus {
} }
/// Match model representing the matches table /// Match model representing the matches table
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] #[derive(Debug, Clone, Serialize, Deserialize, FromRow, ToSchema)]
pub struct Match { pub struct Match {
pub id: i32, pub id: i32,
pub date_created: DateTime<Utc>, pub date_created: DateTime<Utc>,
@@ -62,14 +63,14 @@ pub struct Match {
} }
/// Create match/challenge request /// Create match/challenge request
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, ToSchema)]
pub struct CreateChallengeRequest { pub struct CreateChallengeRequest {
pub to_team_id: i32, pub to_team_id: i32,
pub challenge_date: String, pub challenge_date: String,
} }
/// Accept/Counter challenge request /// Accept/Counter challenge request
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, ToSchema)]
pub struct RespondChallengeRequest { pub struct RespondChallengeRequest {
pub challenge_date: Option<String>, pub challenge_date: Option<String>,
} }

View File

@@ -1,9 +1,10 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::FromRow; use sqlx::FromRow;
use utoipa::ToSchema;
/// Score acceptance status /// Score acceptance status
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)]
pub enum ScoreAcceptanceStatus { pub enum ScoreAcceptanceStatus {
Pending = 0, Pending = 0,
Submitted = 1, Submitted = 1,
@@ -23,7 +24,7 @@ impl From<i32> for ScoreAcceptanceStatus {
} }
/// Match round model representing the match_rounds table /// Match round model representing the match_rounds table
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] #[derive(Debug, Clone, Serialize, Deserialize, FromRow, ToSchema)]
pub struct MatchRound { pub struct MatchRound {
pub id: i32, pub id: i32,
pub date_created: DateTime<Utc>, pub date_created: DateTime<Utc>,
@@ -35,7 +36,7 @@ pub struct MatchRound {
} }
/// Submit score request /// Submit score request
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, ToSchema)]
pub struct SubmitScoreRequest { pub struct SubmitScoreRequest {
pub team_1_score: i32, pub team_1_score: i32,
pub team_2_score: i32, pub team_2_score: i32,

View File

@@ -1,9 +1,10 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::FromRow; use sqlx::FromRow;
use utoipa::ToSchema;
/// Team model representing the teams table /// Team model representing the teams table
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] #[derive(Debug, Clone, Serialize, Deserialize, FromRow, ToSchema)]
pub struct Team { pub struct Team {
pub id: i32, pub id: i32,
pub date_created: DateTime<Utc>, pub date_created: DateTime<Utc>,
@@ -20,7 +21,7 @@ pub struct Team {
} }
/// Team with full stats (for API responses) /// Team with full stats (for API responses)
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize, ToSchema)]
pub struct TeamWithStats { pub struct TeamWithStats {
pub id: i32, pub id: i32,
pub date_created: DateTime<Utc>, pub date_created: DateTime<Utc>,
@@ -52,14 +53,14 @@ impl From<Team> for TeamWithStats {
} }
/// Create team request /// Create team request
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, ToSchema)]
pub struct CreateTeam { pub struct CreateTeam {
pub name: String, pub name: String,
pub bio: Option<String>, pub bio: Option<String>,
} }
/// Update team request /// Update team request
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, ToSchema)]
pub struct UpdateTeam { pub struct UpdateTeam {
pub name: Option<String>, pub name: Option<String>,
pub bio: Option<String>, pub bio: Option<String>,
@@ -67,7 +68,7 @@ pub struct UpdateTeam {
} }
/// Team member info /// Team member info
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize, ToSchema)]
pub struct TeamMember { pub struct TeamMember {
pub player_id: i32, pub player_id: i32,
pub username: String, pub username: String,

View File

@@ -1,9 +1,10 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::FromRow; use sqlx::FromRow;
use utoipa::ToSchema;
/// Team player positions /// Team player positions
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum PlayerPosition { pub enum PlayerPosition {
Captain, Captain,
@@ -32,7 +33,7 @@ impl PlayerPosition {
} }
/// Team player status /// Team player status
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)]
pub enum TeamPlayerStatus { pub enum TeamPlayerStatus {
Pending = 0, Pending = 0,
Active = 1, Active = 1,
@@ -48,7 +49,7 @@ impl From<i32> for TeamPlayerStatus {
} }
/// Team player model representing the team_players table /// Team player model representing the team_players table
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] #[derive(Debug, Clone, Serialize, Deserialize, FromRow, ToSchema)]
pub struct TeamPlayer { pub struct TeamPlayer {
pub id: i32, pub id: i32,
pub date_created: DateTime<Utc>, pub date_created: DateTime<Utc>,
@@ -59,13 +60,13 @@ pub struct TeamPlayer {
} }
/// Join team request /// Join team request
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, ToSchema)]
pub struct JoinTeamRequest { pub struct JoinTeamRequest {
pub team_id: i32, pub team_id: i32,
} }
/// Change position request /// Change position request
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, ToSchema)]
pub struct ChangePositionRequest { pub struct ChangePositionRequest {
pub position: String, pub position: String,
} }

View File

@@ -1,9 +1,10 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::FromRow; use sqlx::FromRow;
use utoipa::ToSchema;
/// User model representing the users table /// User model representing the users table
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] #[derive(Debug, Clone, Serialize, Deserialize, FromRow, ToSchema)]
pub struct User { pub struct User {
pub id: i32, pub id: i32,
pub date_registered: DateTime<Utc>, pub date_registered: DateTime<Utc>,
@@ -25,7 +26,7 @@ pub struct User {
} }
/// User public profile (without sensitive data) /// User public profile (without sensitive data)
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize, ToSchema)]
pub struct UserProfile { pub struct UserProfile {
pub id: i32, pub id: i32,
pub date_registered: DateTime<Utc>, pub date_registered: DateTime<Utc>,
@@ -57,7 +58,7 @@ impl From<User> for UserProfile {
} }
/// Create user request (internal use) /// Create user request (internal use)
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, ToSchema)]
pub struct CreateUser { pub struct CreateUser {
pub firstname: String, pub firstname: String,
pub lastname: String, pub lastname: String,
@@ -67,7 +68,7 @@ pub struct CreateUser {
} }
/// Update user request /// Update user request
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, ToSchema)]
pub struct UpdateUser { pub struct UpdateUser {
pub firstname: Option<String>, pub firstname: Option<String>,
pub lastname: Option<String>, pub lastname: Option<String>,
@@ -76,7 +77,7 @@ pub struct UpdateUser {
} }
/// Player stats response /// Player stats response
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize, ToSchema)]
pub struct PlayerStats { pub struct PlayerStats {
pub id: i32, pub id: i32,
pub username: String, pub username: String,

128
src/openapi.rs Normal file
View File

@@ -0,0 +1,128 @@
use utoipa::OpenApi;
use crate::{
handlers::{
auth::{AuthResponse, LoginRequest, RegisterRequest, UserResponse},
health::HealthResponse,
users::UserProfileResponse,
},
models::{
featured_player::{FeaturedPlayer, FeaturedPlayerWithUser},
ladder::{CreateLadder, Ladder, LadderStatus, UpdateLadder},
ladder_team::{JoinLadderRequest, LadderTeam},
match_model::{CreateChallengeRequest, Match, MatchStatus, RespondChallengeRequest, TeamChallengeStatus},
match_round::{MatchRound, ScoreAcceptanceStatus, SubmitScoreRequest},
team::{CreateTeam, Team, TeamMember, TeamWithStats, UpdateTeam},
team_player::{ChangePositionRequest, JoinTeamRequest, PlayerPosition, TeamPlayer, TeamPlayerStatus},
user::{CreateUser, PlayerStats, UpdateUser, User, UserProfile},
},
};
#[derive(OpenApi)]
#[openapi(
info(
title = "VRBattles API",
version = "1.0.0",
description = "VRBattles Esports Platform API - Manage teams, matches, tournaments, and rankings for competitive VR gaming.",
contact(
name = "VRBattles Team",
url = "https://vrbattles.gg"
),
license(
name = "Proprietary"
)
),
servers(
(url = "https://api.vrb.gg", description = "Production server"),
(url = "http://localhost:3000", description = "Local development")
),
tags(
(name = "health", description = "Health check"),
(name = "auth", description = "Authentication endpoints"),
(name = "users", description = "User management"),
(name = "teams", description = "Team management"),
(name = "ladders", description = "Ladder/league management"),
(name = "matches", description = "Match and challenge management"),
(name = "players", description = "Player profiles and rankings"),
(name = "uploads", description = "File uploads (logos, profile pictures)")
),
paths(
// Health
crate::handlers::health::health_check,
// Auth
crate::handlers::auth::login,
crate::handlers::auth::register,
// Users
crate::handlers::users::get_current_user,
),
components(
schemas(
// Health
HealthResponse,
// Auth
LoginRequest,
RegisterRequest,
AuthResponse,
UserResponse,
UserProfileResponse,
// User
User,
UserProfile,
CreateUser,
UpdateUser,
PlayerStats,
// Team
Team,
TeamWithStats,
CreateTeam,
UpdateTeam,
TeamMember,
// Team Player
TeamPlayer,
JoinTeamRequest,
ChangePositionRequest,
PlayerPosition,
TeamPlayerStatus,
// Ladder
Ladder,
LadderStatus,
CreateLadder,
UpdateLadder,
LadderTeam,
JoinLadderRequest,
// Match
Match,
MatchStatus,
TeamChallengeStatus,
CreateChallengeRequest,
RespondChallengeRequest,
// Match Round
MatchRound,
ScoreAcceptanceStatus,
SubmitScoreRequest,
// Featured Player
FeaturedPlayer,
FeaturedPlayerWithUser,
)
),
modifiers(&SecurityAddon)
)]
pub struct ApiDoc;
struct SecurityAddon;
impl utoipa::Modify for SecurityAddon {
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
if let Some(components) = openapi.components.as_mut() {
components.add_security_scheme(
"bearer_auth",
utoipa::openapi::security::SecurityScheme::Http(
utoipa::openapi::security::Http::new(
utoipa::openapi::security::HttpAuthScheme::Bearer,
)
.bearer_format("JWT"),
),
);
}
}
}