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 (출금 불가) - 가입보너스, SNS공유, 레퍼럴 cash_cc_balance = Column(Float, default=0.0) # 현금성 CC (출금 가능) - 딜러수수료 88% 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) # Login security failed_login_attempts = Column(Integer, default=0) # 로그인 실패 횟수 locked_until = Column(DateTime(timezone=True), nullable=True) # 계정 잠금 해제 시간 password_reset_required = Column(Boolean, default=False) # 비밀번호 재설정 필요 여부 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"]