Files
TradeMate Dev bed5c7abef Add landing page, referral system, usage quotas, search API management, and yearly pricing
- Separate workspace landing from login for better UX
- Referral system rewards both parties with Pro days
- Quota enforcement prevents abuse without breaking endpoints
- 7-day free trial with auto-downgrade on expiry
- Admin-managed search provider config (SearXNG, Bing)
- 15% discount on annual subscriptions
- MCP search server wrapping opencode search
- Fix discovery module field name mismatch causing 422
2026-05-26 11:40:13 +08:00

143 lines
4.7 KiB
Python

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.database import get_db
from app.api.v1.deps import get_current_user_id
from app.models.referral import ReferralCode, Referral
from app.models.subscription import Subscription
from app.models.user import User
from app.config import settings
from datetime import datetime, timedelta
import uuid
import secrets
import string
router = APIRouter()
def generate_code() -> str:
return "TM" + "".join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(6))
@router.post("/code")
async def get_or_create_code(
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
result = await db.execute(select(ReferralCode).where(ReferralCode.user_id == user_id))
existing = result.scalar_one_or_none()
if existing:
return {"code": existing.code, "url": f"/workspace/?ref={existing.code}"}
code = generate_code()
while True:
check = await db.execute(select(ReferralCode).where(ReferralCode.code == code))
if not check.scalar_one_or_none():
break
code = generate_code()
rc = ReferralCode(user_id=user_id, code=code)
db.add(rc)
await db.commit()
return {"code": code, "url": f"/workspace/?ref={code}"}
@router.get("/stats")
async def get_referral_stats(
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
result = await db.execute(select(Referral).where(Referral.referrer_id == user_id))
referrals = result.scalars().all()
total_reward_days = sum(r.reward_days for r in referrals if r.status == "completed")
return {
"total_referrals": len(referrals),
"completed": sum(1 for r in referrals if r.status == "completed"),
"total_reward_days": total_reward_days,
}
async def apply_referral(code: str, new_user_id: str, db: AsyncSession):
rc_result = await db.execute(select(ReferralCode).where(ReferralCode.code == code))
rc = rc_result.scalar_one_or_none()
if not rc:
return
if str(rc.user_id) == new_user_id:
return
existing = await db.execute(select(Referral).where(Referral.referred_id == new_user_id))
if existing.scalar_one_or_none():
return
reward_days = 15
referrer_sub = await db.execute(
select(Subscription).where(
Subscription.user_id == rc.user_id,
Subscription.status == "active",
).order_by(Subscription.created_at.desc()).limit(1)
)
referrer_sub_row = referrer_sub.scalar_one_or_none()
if referrer_sub_row:
old_expiry = referrer_sub_row.expires_at or datetime.utcnow()
referrer_sub_row.expires_at = old_expiry + timedelta(days=reward_days)
else:
new_sub = Subscription(
user_id=rc.user_id,
plan="pro_trial",
status="active",
started_at=datetime.utcnow(),
expires_at=datetime.utcnow() + timedelta(days=reward_days),
)
db.add(new_sub)
user_result = await db.execute(select(User).where(User.id == rc.user_id))
u = user_result.scalar_one_or_none()
if u and u.tier == "free":
u.tier = "pro"
user_result = await db.execute(select(User).where(User.id == new_user_id))
ru = user_result.scalar_one_or_none()
if ru and ru.tier in ("free", "guest"):
ru.tier = "pro"
ref_sub = Subscription(
user_id=new_user_id,
plan="pro_trial",
status="active",
started_at=datetime.utcnow(),
expires_at=datetime.utcnow() + timedelta(days=reward_days),
)
db.add(ref_sub)
referral = Referral(
referrer_id=rc.user_id,
referred_id=new_user_id,
code=code,
reward_days=reward_days,
)
db.add(referral)
await db.flush()
@router.post("/claim")
async def claim_referral(
code: str,
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
rc_result = await db.execute(select(ReferralCode).where(ReferralCode.code == code))
rc = rc_result.scalar_one_or_none()
if not rc:
raise HTTPException(status_code=404, detail="无效的邀请码")
if str(rc.user_id) == user_id:
raise HTTPException(status_code=400, detail="不能使用自己的邀请码")
existing = await db.execute(select(Referral).where(Referral.referred_id == user_id))
if existing.scalar_one_or_none():
raise HTTPException(status_code=400, detail="已经使用过邀请码了")
await apply_referral(code, user_id, db)
await db.commit()
return {"success": True, "reward_days": 15}