Files
AutonetSellCar/backend/app/models/user.py
AutonetSellCar Deploy 7c943d8553 Add SNS Marketing Campaign feature
- Add cash_cc_balance to User model (withdrawable CC)
- Create SnsShareSubmission model for SNS share verification
- Add marketing campaign settings to SystemSettings
- Add reward_type to ReferralReward model
- Create /api/sns-share endpoints for submission and verification
- Add referral signup reward logic (10CC on signup)
- Create /sns-share user page for SNS sharing
- Create /admin/sns-shares management page
- Add marketing settings UI to admin settings page
- Add SNS Shares menu to admin sidebar

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 18:21:17 +09:00

145 lines
7.0 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 (출금 불가) - 가입보너스, 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"]