- Frontend: Next.js 14 with TypeScript - Backend: FastAPI with SQLAlchemy - Agent: Carmodoo sync agent - Deployment: Docker Compose based staging/production setup - Scripts: Automated deployment with rollback support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
139 lines
6.6 KiB
Python
139 lines
6.6 KiB
Python
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, Float
|
|
from sqlalchemy.orm import relationship
|
|
from sqlalchemy.sql import func
|
|
import uuid
|
|
import hashlib
|
|
from ..database import Base
|
|
|
|
|
|
def generate_referral_code():
|
|
"""Generate a unique 8-character referral code"""
|
|
unique_id = uuid.uuid4().hex
|
|
return hashlib.sha256(unique_id.encode()).hexdigest()[:8].upper()
|
|
|
|
|
|
class User(Base):
|
|
__tablename__ = "users"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
email = Column(String(255), unique=True, nullable=False, index=True)
|
|
password_hash = Column(String(255), nullable=False)
|
|
name = Column(String(100))
|
|
phone = Column(String(50))
|
|
country = Column(String(50), default="Mongolia")
|
|
is_active = Column(Boolean, default=True)
|
|
is_admin = Column(Boolean, default=False)
|
|
is_dealer = Column(Boolean, default=False) # Dealer status
|
|
cc_balance = Column(Float, default=3.0) # CC coin balance, 3 free on signup
|
|
referral_code = Column(String(8), unique=True, index=True) # Unique referral code for sharing
|
|
referred_by = Column(String(8), nullable=True) # Referral code of the user who referred this user
|
|
|
|
# Email verification
|
|
email_verified = Column(Boolean, default=False)
|
|
email_verified_at = Column(DateTime(timezone=True), nullable=True)
|
|
|
|
# Phone verification
|
|
phone_verified = Column(Boolean, default=False)
|
|
phone_verified_at = Column(DateTime(timezone=True), nullable=True)
|
|
|
|
# Account withdrawal/deletion
|
|
withdrawal_requested_at = Column(DateTime(timezone=True), nullable=True) # 탈퇴 요청 시각
|
|
withdrawal_reason = Column(String(500), nullable=True) # 탈퇴 사유
|
|
deleted_at = Column(DateTime(timezone=True), nullable=True) # 실제 삭제 시각 (soft delete)
|
|
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
# Note: foreign_keys specified as string to avoid circular import
|
|
inquiries = relationship("Inquiry", back_populates="user", primaryjoin="User.id == Inquiry.user_id")
|
|
car_views = relationship("CarView", back_populates="user")
|
|
performance_check_views = relationship("PerformanceCheckView", back_populates="user")
|
|
charge_history = relationship("ChargeHistory", back_populates="user", primaryjoin="User.id == ChargeHistory.user_id")
|
|
dealer_application = relationship("DealerApplication", back_populates="user", uselist=False)
|
|
dealer_info = relationship("DealerInfo", back_populates="user", uselist=False)
|
|
|
|
|
|
class VerificationCode(Base):
|
|
"""Store temporary verification codes for email and phone"""
|
|
__tablename__ = "verification_codes"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
user_id = Column(Integer, ForeignKey("users.id"), nullable=True) # Nullable for pre-registration
|
|
email = Column(String(255), nullable=True, index=True) # For email verification
|
|
phone = Column(String(50), nullable=True, index=True) # For phone verification
|
|
code = Column(String(10), nullable=False) # 6-digit code
|
|
code_type = Column(String(20), nullable=False) # 'email' or 'phone'
|
|
purpose = Column(String(50), default="verification") # 'verification', 'password_reset'
|
|
attempts = Column(Integer, default=0) # Failed verification attempts
|
|
max_attempts = Column(Integer, default=5)
|
|
expires_at = Column(DateTime(timezone=True), nullable=False)
|
|
verified_at = Column(DateTime(timezone=True), nullable=True)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
|
|
class CarView(Base):
|
|
"""Track which cars a user has purchased (paid CC to view full details)"""
|
|
__tablename__ = "car_views"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
|
car_id = Column(Integer, ForeignKey("cars.id"), nullable=False)
|
|
cc_paid = Column(Integer, default=1) # CC paid for this view
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
user = relationship("User", back_populates="car_views")
|
|
car = relationship("Car", back_populates="views")
|
|
|
|
|
|
class PerformanceCheckView(Base):
|
|
"""Track which performance checks a user has purchased (paid 0.1 CC to view)"""
|
|
__tablename__ = "performance_check_views"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
|
car_id = Column(Integer, ForeignKey("cars.id"), nullable=False)
|
|
cc_paid = Column(Float, default=0.1) # CC paid for this view (0.1 CC)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
user = relationship("User", back_populates="performance_check_views")
|
|
|
|
|
|
class ChargeHistory(Base):
|
|
"""Track CC charge history for users"""
|
|
__tablename__ = "charge_history"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
|
package_id = Column(Integer, ForeignKey("cc_packages.id"), nullable=True) # CC package purchased
|
|
amount = Column(Integer, nullable=False) # Amount in selected currency
|
|
amount_usd = Column(Integer, nullable=True) # Amount in USD (for backwards compatibility)
|
|
cc_amount = Column(Integer, nullable=False) # CC received
|
|
bonus_cc = Column(Integer, default=0) # Bonus CC received
|
|
currency = Column(String(10), default="USD") # USD, USDC, KRW
|
|
payment_method = Column(String(50), default="stripe") # stripe, manual, usdc, bank_transfer
|
|
|
|
# Stripe fields
|
|
stripe_session_id = Column(String(200), nullable=True) # Stripe Checkout Session ID
|
|
stripe_payment_intent_id = Column(String(200), nullable=True) # Stripe Payment Intent ID
|
|
|
|
# Legacy fields
|
|
transaction_id = Column(String(100), nullable=True) # External transaction ID (crypto tx hash)
|
|
wallet_address = Column(String(100), nullable=True) # User's wallet address for refunds
|
|
|
|
admin_note = Column(String(500), nullable=True) # Admin notes
|
|
status = Column(String(20), default="pending") # pending, completed, failed, cancelled
|
|
verified_at = Column(DateTime(timezone=True), nullable=True)
|
|
verified_by = Column(Integer, ForeignKey("users.id"), nullable=True)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
user = relationship("User", back_populates="charge_history", foreign_keys=[user_id])
|
|
|
|
|
|
# Payment settings constants
|
|
class PaymentSettings:
|
|
USDC_WALLET_ADDRESS = "0x1234567890abcdef1234567890abcdef12345678" # Platform USDC receiving address
|
|
USDC_NETWORK = "Polygon" # Default network (Polygon for low fees)
|
|
MIN_CHARGE_USD = 10
|
|
MAX_CHARGE_USD = 10000
|
|
SUPPORTED_CURRENCIES = ["USD", "USDC", "KRW"]
|
|
SUPPORTED_METHODS = ["card", "usdc", "bank_transfer"]
|