Spaces:
Sleeping
Sleeping
File size: 16,239 Bytes
c05ab2d e39877e 34f76dc c05ab2d 8d69ae4 34f76dc c05ab2d e39877e c05ab2d 34f76dc 050d8f8 34f76dc 050d8f8 8d69ae4 050d8f8 1bd7131 19e4a8c 1bd7131 40628e2 050d8f8 50c20bf 050d8f8 34f76dc 050d8f8 34f76dc 050d8f8 34f76dc 050d8f8 34f76dc 050d8f8 8d69ae4 34f76dc 8d69ae4 34f76dc 50c20bf 34f76dc 050d8f8 34f76dc 050d8f8 34f76dc 050d8f8 8d69ae4 34f76dc 8d69ae4 34f76dc 050d8f8 34f76dc 50c20bf 34f76dc 1bd7131 34f76dc 1bd7131 816ccbe 1bd7131 8d69ae4 1bd7131 8d69ae4 1bd7131 8d69ae4 1bd7131 8d69ae4 816ccbe 8d69ae4 816ccbe 1bd7131 ec0e527 1bd7131 50c20bf fc638eb 1bd7131 34f76dc 1bd7131 816ccbe b49d66f 34f76dc de3cb16 8d69ae4 de3cb16 8d69ae4 de3cb16 aed0646 de3cb16 50c20bf de3cb16 34f76dc de3cb16 9869f13 34f76dc 9869f13 8d69ae4 9869f13 50c20bf 9869f13 34f76dc 9869f13 34f76dc 8d69ae4 34f76dc 50c20bf 34f76dc a650e63 cfe2de7 a650e63 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 |
"""
SQLAlchemy models for the APIGateway application.
Tables:
- User: Server-side users with credits
- ClientUser: Client identifiers mapping to server users
- AuditLog: Unified client/server audit logging
- GeminiJob: AI job queue
- PaymentTransaction: Credit purchases
- Contact: Support tickets
- ApiKeyUsage: API key load balancing
- RateLimit: Rate limiting
"""
from sqlalchemy import Column, Integer, String, Text, DateTime, JSON, Boolean, ForeignKey, Index
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from core.database import Base
# =============================================================================
# User & Client Tracking
# =============================================================================
class User(Base):
"""
User model for credit system.
Supports Google OAuth authentication.
One User can have many ClientUser mappings.
"""
__tablename__ = "users"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(String(50), unique=True, index=True, nullable=False) # Backend generated UUID (public-facing)
email = Column(String(255), unique=True, index=True, nullable=False)
# Google OAuth fields
google_id = Column(String(255), unique=True, index=True, nullable=True) # Google sub claim
name = Column(String(255), nullable=True) # Display name from Google
profile_picture = Column(Text, nullable=True) # Google profile picture URL
# Token versioning for JWT invalidation
token_version = Column(Integer, default=1, nullable=False)
# Credits and status
credits = Column(Integer, default=0)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
last_used_at = Column(DateTime(timezone=True), nullable=True)
is_active = Column(Boolean, default=True)
deleted_at = Column(DateTime(timezone=True), nullable=True, index=True) # Soft delete timestamp
# Relationships
client_users = relationship("ClientUser", back_populates="user", lazy="dynamic")
jobs = relationship("GeminiJob", back_populates="user", lazy="dynamic")
payments = relationship("PaymentTransaction", back_populates="user", lazy="dynamic")
contacts = relationship("Contact", back_populates="user", lazy="dynamic")
audit_logs = relationship("AuditLog", back_populates="user", lazy="dynamic")
def __repr__(self):
return f"<User(id={self.id}, email={self.email})>"
class ClientUser(Base):
"""
Map server users to multiple client identifiers.
Enables tracking users across devices, IPs, and login states.
Use cases:
- Track same user across multiple devices
- Link anonymous activity to logged-in user
- Detect same device before/after login
- Track users by network/location
"""
__tablename__ = "client_users"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True, nullable=True) # FK to users.id (nullable for anonymous)
client_user_id = Column(String(100), index=True, nullable=True) # Client-side temp identifier
# IP tracking for network/location correlation (standardized to single column)
ip_address = Column(String(45), nullable=True, index=True) # Supports both IPv4 and IPv6
# Device identification
device_fingerprint = Column(String(255), nullable=True, index=True) # Browser fingerprint hash
device_info = Column(JSON, nullable=True) # Browser, OS, screen size, language, etc.
# Timestamps
created_at = Column(DateTime(timezone=True), server_default=func.now())
last_seen_at = Column(DateTime(timezone=True), nullable=True)
deleted_at = Column(DateTime(timezone=True), nullable=True, index=True) # Soft delete timestamp
# Relationship
user = relationship("User", back_populates="client_users")
def __repr__(self):
return f"<ClientUser(user_id={self.user_id}, client_user_id={self.client_user_id})>"
# =============================================================================
# Audit Logging
# =============================================================================
class AuditLog(Base):
"""
Unified audit log for client and server events.
Replaces BlinkData - captures all trackable events.
log_type:
- "client": Login attempts, page views, API calls from client
- "server": Job processing, credit changes, internal operations
"""
__tablename__ = "audit_logs"
id = Column(Integer, primary_key=True, autoincrement=True)
log_type = Column(String(20), index=True, nullable=False) # "client" or "server"
# User tracking
user_id = Column(Integer, ForeignKey("users.id"), nullable=True, index=True) # FK to users.id
client_user_id = Column(String(100), nullable=True, index=True) # For anonymous client logs
# Event details
action = Column(String(50), index=True, nullable=False) # login, page_view, job_created, etc.
details = Column(JSON, nullable=True) # Flexible data storage
# Request context
ip_address = Column(String(45), nullable=True)
user_agent = Column(String(500), nullable=True)
refer_url = Column(Text, nullable=True)
# Outcome
status = Column(String(20), nullable=False) # success, failure, error
error_message = Column(Text, nullable=True)
# Timestamp
timestamp = Column(DateTime(timezone=True), server_default=func.now(), index=True)
deleted_at = Column(DateTime(timezone=True), nullable=True, index=True) # Soft delete timestamp
# Relationship
user = relationship("User", back_populates="audit_logs")
def __repr__(self):
return f"<AuditLog(type={self.log_type}, action={self.action}, status={self.status})>"
# =============================================================================
# Job Queue
# =============================================================================
class GeminiJob(Base):
"""
Generic job queue for Gemini operations (video, image, text).
Uses priority-tier system for worker assignment.
"""
__tablename__ = "gemini_jobs"
# Composite index for efficient queue polling: WHERE status = X AND next_process_at <= NOW() ORDER BY priority
__table_args__ = (
Index('ix_jobs_queue_poll', 'status', 'next_process_at', 'priority'),
)
id = Column(Integer, primary_key=True, autoincrement=True)
job_id = Column(String(100), unique=True, index=True, nullable=False) # Our ID for client
user_id = Column(Integer, ForeignKey("users.id"), index=True, nullable=False) # FK to users.id
job_type = Column(String(20), index=True, nullable=False) # video, image, text, analyze
third_party_id = Column(String(255), nullable=True) # Gemini operation name (for video)
status = Column(String(20), default="queued") # queued, processing, completed, failed, cancelled
# Priority-tier worker system (indexed via composite index above)
priority = Column(String(10), default="fast") # fast (5s), medium (30s), slow (60s)
next_process_at = Column(DateTime(timezone=True), nullable=True) # When worker should pick up again
retry_count = Column(Integer, default=0) # Number of status check retries
input_data = Column(JSON, nullable=True) # Request details (prompt, settings, etc.)
output_data = Column(JSON, nullable=True) # Result (filename, text, etc.)
api_response = Column(JSON, nullable=True) # Raw response from third-party API (success or error)
error_message = Column(Text, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
started_at = Column(DateTime(timezone=True), nullable=True)
completed_at = Column(DateTime(timezone=True), nullable=True)
deleted_at = Column(DateTime(timezone=True), nullable=True, index=True) # Soft delete timestamp
# Credit tracking for reservation pattern
credits_reserved = Column(Integer, default=0) # Credits reserved for this job
credits_refunded = Column(Boolean, default=False) # Whether credits were refunded
# Relationship
user = relationship("User", back_populates="jobs")
def __repr__(self):
return f"<GeminiJob(job_id={self.job_id}, type={self.job_type}, status={self.status}, priority={self.priority})>"
# =============================================================================
# Payments
# =============================================================================
class PaymentTransaction(Base):
"""
Track payment transactions for credit purchases.
Supports multiple payment gateways (Razorpay now, Stripe later).
"""
__tablename__ = "payment_transactions"
id = Column(Integer, primary_key=True, autoincrement=True)
transaction_id = Column(String(50), unique=True, index=True, nullable=False) # Our internal ID
user_id = Column(Integer, ForeignKey("users.id"), index=True, nullable=False) # FK to users.id
# Order details
gateway = Column(String(20), nullable=False) # razorpay, stripe
gateway_order_id = Column(String(100), index=True, nullable=True) # Razorpay order_id
gateway_payment_id = Column(String(100), index=True, nullable=True) # Razorpay payment_id
# Package details
package_id = Column(String(50), nullable=False) # starter, standard, pro
credits_amount = Column(Integer, nullable=False) # Credits to add
amount_paise = Column(Integer, nullable=False) # Amount in paise (INR * 100)
currency = Column(String(3), default="INR")
# Status tracking
status = Column(String(20), default="created", index=True) # created, paid, failed, refunded
verified_by = Column(String(20), nullable=True) # client, webhook, or both
# Timestamps
created_at = Column(DateTime(timezone=True), server_default=func.now())
paid_at = Column(DateTime(timezone=True), nullable=True)
deleted_at = Column(DateTime(timezone=True), nullable=True, index=True) # Soft delete timestamp
# Metadata
razorpay_signature = Column(String(255), nullable=True) # For verification audit
error_message = Column(Text, nullable=True)
extra_data = Column(JSON, nullable=True) # Additional gateway-specific data
# Relationship
user = relationship("User", back_populates="payments")
def __repr__(self):
return f"<PaymentTransaction(id={self.transaction_id}, status={self.status}, amount={self.amount_paise})>"
# =============================================================================
# Support & Utilities
# =============================================================================
class Contact(Base):
"""
Contact form submissions from authenticated users.
For customer support inquiries.
"""
__tablename__ = "contacts"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True, nullable=False) # FK to users.id
email = Column(String(255), nullable=False, index=True) # User's email
subject = Column(String(500), nullable=True)
message = Column(Text, nullable=False)
ip_address = Column(String(45), nullable=True) # IPv6 can be up to 45 chars
created_at = Column(DateTime(timezone=True), server_default=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True, index=True) # Soft delete timestamp
# Relationship
user = relationship("User", back_populates="contacts")
def __repr__(self):
return f"<Contact(id={self.id}, user_id={self.user_id}, email={self.email})>"
class RateLimit(Base):
"""
Rate limit tracking table.
"""
__tablename__ = "rate_limits"
id = Column(Integer, primary_key=True, autoincrement=True)
identifier = Column(String(255), index=True, nullable=False) # IP or email
endpoint = Column(String(255), index=True, nullable=False)
attempts = Column(Integer, default=0)
window_start = Column(DateTime(timezone=True), nullable=False)
expires_at = Column(DateTime(timezone=True), nullable=False)
deleted_at = Column(DateTime(timezone=True), nullable=True, index=True) # Soft delete timestamp
class ApiKeyUsage(Base):
"""
Track API key usage for round-robin load balancing.
Only stores the key index, not the actual key for security.
"""
__tablename__ = "api_key_usage"
id = Column(Integer, primary_key=True, autoincrement=True)
key_index = Column(Integer, unique=True, index=True, nullable=False) # Index of key in GEMINI_API_KEYS
total_requests = Column(Integer, default=0)
success_count = Column(Integer, default=0)
failure_count = Column(Integer, default=0)
last_error = Column(Text, nullable=True) # Stores the last error message for review
last_used_at = Column(DateTime(timezone=True), nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
def __repr__(self):
return f"<ApiKeyUsage(index={self.key_index}, total={self.total_requests}, success={self.success_count}, failed={self.failure_count})>"
# =============================================================================
# Credit Transactions
# =============================================================================
class CreditTransaction(Base):
"""
Complete audit trail for all credit operations.
All credit changes (reserve, refund, confirm, purchase) are recorded here.
Enables balance verification, transaction history, and debugging.
Transaction types:
- reserve: Credits deducted when request arrives (negative amount)
- refund: Credits returned due to failure (positive amount)
- confirm: Credits confirmed as used (amount = 0, marks finalization)
- purchase: Credits added via payment (positive amount)
- adjustment: Manual admin adjustment (positive or negative)
"""
__tablename__ = "credit_transactions"
# Composite indexes for efficient queries
__table_args__ = (
Index('ix_user_transactions', 'user_id', 'created_at'),
Index('ix_transaction_type', 'transaction_type'),
Index('ix_reference', 'reference_type', 'reference_id'),
)
id = Column(Integer, primary_key=True, autoincrement=True)
transaction_id = Column(String(100), unique=True, index=True, nullable=False) # ctx_<uuid>, ref_<uuid>, etc.
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True) # FK to users.id
# Transaction details
transaction_type = Column(String(20), nullable=False) # reserve, refund, confirm, purchase, adjustment
amount = Column(Integer, nullable=False) # Positive for credits added, negative for removed
balance_before = Column(Integer, nullable=False) # User balance before transaction
balance_after = Column(Integer, nullable=False) # User balance after transaction
# Context
source = Column(String(50), nullable=False) # middleware, payment, job_completion, manual, admin
reference_type = Column(String(30), nullable=True) # job, payment, request, NULL
reference_id = Column(String(100), nullable=True) # job_id, transaction_id, request endpoint, etc.
# Request/Response metadata (for middleware transactions)
request_path = Column(String(500), nullable=True)
request_method = Column(String(10), nullable=True)
response_status = Column(Integer, nullable=True)
# Additional details
reason = Column(Text, nullable=True) # Human-readable reason
extra_data = Column(JSON, nullable=True) # Additional context (endpoint_type, error_message, etc.)
# Timestamps
created_at = Column(DateTime(timezone=True), server_default=func.now(), index=True)
deleted_at = Column(DateTime(timezone=True), nullable=True, index=True) # Soft delete
# Relationship
user = relationship("User", backref="credit_transactions")
def __repr__(self):
return f"<CreditTransaction(id={self.transaction_id}, type={self.transaction_type}, amount={self.amount}, user={self.user_id})>"
|