from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from datetime import datetime, timedelta from typing import Dict, Any from app.db.database import get_db from app.models.models import License, Activation, Version from app.schemas.license import ( LicenseActivationRequest, LicenseActivationResponse, LicenseVerificationRequest, LicenseVerificationResponse ) from app.core.security import get_api_key from app.core.config import settings router = APIRouter() @router.post("/activate", response_model=LicenseActivationResponse) async def activate_license( request: LicenseActivationRequest, db: Session = Depends(get_db), api_key = Depends(get_api_key) ): license = db.query(License).filter( License.license_key == request.license_key, License.is_active == True ).first() if not license: return LicenseActivationResponse( success=False, message="Invalid license key" ) if license.expires_at and license.expires_at < datetime.utcnow(): return LicenseActivationResponse( success=False, message="License has expired" ) existing_activations = db.query(Activation).filter( Activation.license_id == license.id, Activation.is_active == True ).all() existing_machine = next( (a for a in existing_activations if a.machine_id == request.machine_id), None ) if existing_machine: if existing_machine.hardware_hash != request.hardware_hash: return LicenseActivationResponse( success=False, message="Hardware mismatch for this machine" ) existing_machine.last_heartbeat = datetime.utcnow() existing_machine.app_version = request.app_version existing_machine.os_info = request.os_info db.commit() return LicenseActivationResponse( success=True, message="License reactivated successfully", activation_id=existing_machine.id, expires_at=license.expires_at, features={"all_features": True} ) if len(existing_activations) >= license.max_activations: return LicenseActivationResponse( success=False, message=f"Maximum activations ({license.max_activations}) reached" ) new_activation = Activation( license_id=license.id, machine_id=request.machine_id, hardware_hash=request.hardware_hash, os_info=request.os_info, app_version=request.app_version ) db.add(new_activation) db.commit() db.refresh(new_activation) return LicenseActivationResponse( success=True, message="License activated successfully", activation_id=new_activation.id, expires_at=license.expires_at, features={"all_features": True} ) @router.post("/verify", response_model=LicenseVerificationResponse) async def verify_license( request: LicenseVerificationRequest, db: Session = Depends(get_db), api_key = Depends(get_api_key) ): activation = db.query(Activation).filter( Activation.id == request.activation_id, Activation.machine_id == request.machine_id, Activation.is_active == True ).first() if not activation: return LicenseVerificationResponse( valid=False, message="Invalid activation" ) license = activation.license if not license.is_active: return LicenseVerificationResponse( valid=False, message="License is no longer active" ) if license.license_key != request.license_key: return LicenseVerificationResponse( valid=False, message="License key mismatch" ) if activation.hardware_hash != request.hardware_hash: grace_period = datetime.utcnow() - timedelta(days=settings.OFFLINE_GRACE_PERIOD_DAYS) if activation.last_heartbeat < grace_period: return LicenseVerificationResponse( valid=False, message="Hardware mismatch and grace period expired" ) else: return LicenseVerificationResponse( valid=True, message="Hardware mismatch but within grace period", expires_at=license.expires_at, features={"all_features": True} ) if license.expires_at and license.expires_at < datetime.utcnow(): return LicenseVerificationResponse( valid=False, message="License has expired" ) activation.last_heartbeat = datetime.utcnow() db.commit() latest_version = db.query(Version).order_by(Version.release_date.desc()).first() requires_update = False update_url = None if latest_version and activation.app_version: if latest_version.version_number > activation.app_version: requires_update = True update_url = latest_version.download_url return LicenseVerificationResponse( valid=True, message="License is valid", expires_at=license.expires_at, features={"all_features": True}, requires_update=requires_update, update_url=update_url ) @router.get("/info/{license_key}") async def get_license_info( license_key: str, db: Session = Depends(get_db), api_key = Depends(get_api_key) ): license = db.query(License).filter( License.license_key == license_key ).first() if not license: raise HTTPException(status_code=404, detail="License not found") activations = db.query(Activation).filter( Activation.license_id == license.id, Activation.is_active == True ).all() return { "license_key": license.license_key, "product_id": license.product_id, "customer_email": license.customer_email, "customer_name": license.customer_name, "is_active": license.is_active, "expires_at": license.expires_at, "max_activations": license.max_activations, "current_activations": len(activations), "activations": [ { "id": a.id, "machine_id": a.machine_id, "activation_date": a.activation_date, "last_heartbeat": a.last_heartbeat, "app_version": a.app_version } for a in activations ] }