Add complete ai.card Rust implementation
- Implement complete Rust API server with axum framework - Add database abstraction supporting PostgreSQL and SQLite - Implement comprehensive gacha system with probability calculations - Add JWT authentication with atproto DID integration - Create card master data system with rarities (Normal, Rare, SuperRare, Kira, Unique) - Implement draw history tracking and collection management - Add API endpoints for authentication, card drawing, and collection viewing - Include database migrations for both PostgreSQL and SQLite - Maintain full compatibility with Python API implementation - Add comprehensive documentation and development guide 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
326
src/models.rs
Normal file
326
src/models.rs
Normal file
@@ -0,0 +1,326 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{FromRow, Type};
|
||||
use uuid::Uuid;
|
||||
use validator::Validate;
|
||||
|
||||
/// Card rarity enum matching Python implementation
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Type)]
|
||||
#[sqlx(type_name = "card_rarity", rename_all = "lowercase")]
|
||||
pub enum CardRarity {
|
||||
#[serde(rename = "normal")]
|
||||
Normal,
|
||||
#[serde(rename = "rare")]
|
||||
Rare,
|
||||
#[serde(rename = "super_rare")]
|
||||
SuperRare,
|
||||
#[serde(rename = "kira")]
|
||||
Kira,
|
||||
#[serde(rename = "unique")]
|
||||
Unique,
|
||||
}
|
||||
|
||||
impl CardRarity {
|
||||
pub fn multiplier(&self) -> f64 {
|
||||
match self {
|
||||
CardRarity::Normal => 1.0,
|
||||
CardRarity::Rare => 1.5,
|
||||
CardRarity::SuperRare => 2.0,
|
||||
CardRarity::Kira => 3.0,
|
||||
CardRarity::Unique => 5.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
CardRarity::Normal => "normal",
|
||||
CardRarity::Rare => "rare",
|
||||
CardRarity::SuperRare => "super_rare",
|
||||
CardRarity::Kira => "kira",
|
||||
CardRarity::Unique => "unique",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Database Models
|
||||
|
||||
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
pub id: i32,
|
||||
pub did: String,
|
||||
pub handle: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
||||
pub struct CardMaster {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub base_cp_min: i32,
|
||||
pub base_cp_max: i32,
|
||||
pub color: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
||||
pub struct UserCard {
|
||||
pub id: i32,
|
||||
pub user_id: i32,
|
||||
pub card_id: i32,
|
||||
pub cp: i32,
|
||||
pub status: CardRarity,
|
||||
pub skill: Option<String>,
|
||||
pub obtained_at: DateTime<Utc>,
|
||||
pub is_unique: bool,
|
||||
pub unique_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
||||
pub struct UniqueCardRegistry {
|
||||
pub id: i32,
|
||||
pub unique_id: Uuid,
|
||||
pub card_id: i32,
|
||||
pub owner_did: String,
|
||||
pub obtained_at: DateTime<Utc>,
|
||||
pub verse_skill_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
||||
pub struct DrawHistory {
|
||||
pub id: i32,
|
||||
pub user_id: i32,
|
||||
pub card_id: i32,
|
||||
pub status: CardRarity,
|
||||
pub cp: i32,
|
||||
pub is_paid: bool,
|
||||
pub drawn_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
||||
pub struct GachaPool {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub is_active: bool,
|
||||
pub start_at: Option<DateTime<Utc>>,
|
||||
pub end_at: Option<DateTime<Utc>>,
|
||||
pub pickup_card_ids: Vec<i32>,
|
||||
pub rate_up_multiplier: f64,
|
||||
}
|
||||
|
||||
/// API Request/Response Models
|
||||
|
||||
#[derive(Debug, Deserialize, Validate)]
|
||||
pub struct LoginRequest {
|
||||
#[validate(length(min = 1))]
|
||||
pub identifier: String,
|
||||
#[validate(length(min = 1))]
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct LoginResponse {
|
||||
pub access_token: String,
|
||||
pub token_type: String,
|
||||
pub expires_in: u64,
|
||||
pub user: UserInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct UserInfo {
|
||||
pub did: String,
|
||||
pub handle: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Validate)]
|
||||
pub struct CardDrawRequest {
|
||||
pub user_did: String,
|
||||
#[serde(default)]
|
||||
pub is_paid: bool,
|
||||
pub pool_id: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct CardDrawResponse {
|
||||
pub card: UserCardResponse,
|
||||
pub master: CardMasterResponse,
|
||||
pub is_unique: bool,
|
||||
pub animation_type: String,
|
||||
pub draw_history_id: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct UserCardResponse {
|
||||
pub id: i32,
|
||||
pub card_id: i32,
|
||||
pub cp: i32,
|
||||
pub status: CardRarity,
|
||||
pub skill: Option<String>,
|
||||
pub obtained_at: DateTime<Utc>,
|
||||
pub is_unique: bool,
|
||||
pub unique_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct CardMasterResponse {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub base_cp_min: i32,
|
||||
pub base_cp_max: i32,
|
||||
pub color: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct UserCardCollectionResponse {
|
||||
pub user_did: String,
|
||||
pub cards: Vec<UserCardWithMaster>,
|
||||
pub total_count: i32,
|
||||
pub unique_count: i32,
|
||||
pub rarity_breakdown: RarityBreakdown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct UserCardWithMaster {
|
||||
pub card: UserCardResponse,
|
||||
pub master: CardMasterResponse,
|
||||
}
|
||||
|
||||
/// Database query result for JOIN operations
|
||||
#[derive(Debug, Clone, FromRow)]
|
||||
pub struct UserCardWithMasterQuery {
|
||||
// user_cards fields
|
||||
pub id: i32,
|
||||
pub user_id: i32,
|
||||
pub card_id: i32,
|
||||
pub cp: i32,
|
||||
pub status: String,
|
||||
pub obtained_at: DateTime<Utc>,
|
||||
pub is_unique: bool,
|
||||
pub unique_id: Option<Uuid>,
|
||||
// card_master fields
|
||||
pub master_id: i32,
|
||||
pub name: String,
|
||||
pub base_cp_min: i32,
|
||||
pub base_cp_max: i32,
|
||||
pub color: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
/// Database query result for unique card registry
|
||||
#[derive(Debug, Clone, FromRow)]
|
||||
pub struct UniqueCardQuery {
|
||||
pub card_id: i32,
|
||||
pub card_name: String,
|
||||
pub owner_did: Option<String>,
|
||||
pub owner_handle: Option<String>,
|
||||
pub obtained_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct RarityBreakdown {
|
||||
pub normal: i32,
|
||||
pub rare: i32,
|
||||
pub super_rare: i32,
|
||||
pub kira: i32,
|
||||
pub unique: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct UniqueCardRegistryResponse {
|
||||
pub unique_cards: Vec<UniqueCardInfo>,
|
||||
pub total_unique_cards: i32,
|
||||
pub available_unique_cards: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct UniqueCardInfo {
|
||||
pub card_id: i32,
|
||||
pub card_name: String,
|
||||
pub owner_did: Option<String>,
|
||||
pub owner_handle: Option<String>,
|
||||
pub obtained_at: Option<DateTime<Utc>>,
|
||||
pub is_available: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct GachaStatsResponse {
|
||||
pub probabilities: GachaProbabilities,
|
||||
pub total_draws: i32,
|
||||
pub total_unique_cards: i32,
|
||||
pub available_unique_cards: i32,
|
||||
pub rarity_distribution: RarityBreakdown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct GachaProbabilities {
|
||||
pub normal: f64,
|
||||
pub rare: f64,
|
||||
pub super_rare: f64,
|
||||
pub kira: f64,
|
||||
pub unique: f64,
|
||||
pub paid_multiplier: f64,
|
||||
}
|
||||
|
||||
/// External Data Models (from ai.json)
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ExternalCardData {
|
||||
pub ai: AiData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct AiData {
|
||||
pub card: CardData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CardData {
|
||||
pub cards: Vec<ExternalCard>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ExternalCard {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub cp: CpRange,
|
||||
pub color: String,
|
||||
pub skill: String,
|
||||
pub lang: Option<LangData>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CpRange {
|
||||
pub min: i32,
|
||||
pub max: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct LangData {
|
||||
pub ja: Option<JapaneseData>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct JapaneseData {
|
||||
pub name: Option<String>,
|
||||
pub skill: Option<String>,
|
||||
}
|
||||
|
||||
/// atproto Models
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct AtprotoCardRecord {
|
||||
#[serde(rename = "$type")]
|
||||
pub record_type: String,
|
||||
#[serde(rename = "cardId")]
|
||||
pub card_id: i32,
|
||||
pub cp: i32,
|
||||
pub status: String,
|
||||
#[serde(rename = "obtainedAt")]
|
||||
pub obtained_at: DateTime<Utc>,
|
||||
#[serde(rename = "isUnique")]
|
||||
pub is_unique: bool,
|
||||
#[serde(rename = "uniqueId")]
|
||||
pub unique_id: Option<Uuid>,
|
||||
}
|
Reference in New Issue
Block a user