67 lines
2.0 KiB
Python
67 lines
2.0 KiB
Python
import logging
|
|
from datetime import datetime, timezone
|
|
|
|
from eth_account import Account
|
|
from eth_account.messages import encode_defunct
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.database import get_db
|
|
from app.models import Subscription
|
|
from app.schemas import SubscribeRequest, SubscribeResponse
|
|
|
|
router = APIRouter()
|
|
logger = logging.getLogger(__name__)
|
|
|
|
SIGN_MESSAGE = "Sign in to TrumpSignal"
|
|
|
|
|
|
from typing import Optional
|
|
|
|
def _recover_signer(signature: str) -> Optional[str]:
|
|
"""Recover signer address from an EIP-191 personal_sign signature."""
|
|
try:
|
|
message = encode_defunct(text=SIGN_MESSAGE)
|
|
recovered = Account.recover_message(message, signature=signature)
|
|
return recovered.lower()
|
|
except Exception as exc:
|
|
logger.warning("Signature recovery failed: %s", exc)
|
|
return None
|
|
|
|
|
|
@router.post("/subscribe", response_model=SubscribeResponse)
|
|
async def subscribe(body: SubscribeRequest, db: AsyncSession = Depends(get_db)):
|
|
wallet = body.wallet.lower().strip()
|
|
|
|
# Verify EIP-191 signature
|
|
recovered = _recover_signer(body.signature)
|
|
if recovered is None or recovered != wallet:
|
|
raise HTTPException(
|
|
status_code=401,
|
|
detail="Signature verification failed: recovered address does not match wallet",
|
|
)
|
|
|
|
# Upsert subscription
|
|
result = await db.execute(
|
|
select(Subscription).where(Subscription.wallet_address == wallet)
|
|
)
|
|
sub = result.scalar_one_or_none()
|
|
now = datetime.now(timezone.utc).replace(tzinfo=None)
|
|
|
|
if sub is None:
|
|
sub = Subscription(
|
|
wallet_address=wallet,
|
|
active=True,
|
|
subscribed_at=now,
|
|
)
|
|
db.add(sub)
|
|
else:
|
|
sub.active = True
|
|
if sub.subscribed_at is None:
|
|
sub.subscribed_at = now
|
|
|
|
await db.commit()
|
|
logger.info("Subscription activated for wallet %s", wallet)
|
|
return SubscribeResponse(status="ok", wallet=wallet)
|