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:
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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>,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>,
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
128
src/openapi.rs
Normal 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"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user