228 Zeilen
8.7 KiB
Python
228 Zeilen
8.7 KiB
Python
from typing import Optional, List, Dict, Any
|
|
from datetime import datetime, timedelta
|
|
from .base import BaseRepository
|
|
from ..models import License, LicenseToken, ActivationEvent, EventType
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class LicenseRepository(BaseRepository):
|
|
"""Repository for license-related database operations"""
|
|
|
|
def get_license_by_key(self, license_key: str) -> Optional[Dict[str, Any]]:
|
|
"""Get license by key"""
|
|
query = """
|
|
SELECT l.*, c.name as customer_name, c.email as customer_email
|
|
FROM licenses l
|
|
JOIN customers c ON l.customer_id = c.id
|
|
WHERE l.license_key = %s
|
|
"""
|
|
return self.execute_one(query, (license_key,))
|
|
|
|
def get_license_by_id(self, license_id: str) -> Optional[Dict[str, Any]]:
|
|
"""Get license by ID"""
|
|
query = """
|
|
SELECT l.*, c.name as customer_name, c.email as customer_email
|
|
FROM licenses l
|
|
JOIN customers c ON l.customer_id = c.id
|
|
WHERE l.id = %s
|
|
"""
|
|
return self.execute_one(query, (license_id,))
|
|
|
|
def get_active_devices(self, license_id: str) -> List[Dict[str, Any]]:
|
|
"""Get active devices for a license"""
|
|
query = """
|
|
SELECT DISTINCT ON (hardware_id)
|
|
hardware_id,
|
|
ip_address,
|
|
user_agent,
|
|
app_version,
|
|
timestamp as last_seen
|
|
FROM license_heartbeats
|
|
WHERE license_id = %s
|
|
AND timestamp > NOW() - INTERVAL '15 minutes'
|
|
ORDER BY hardware_id, timestamp DESC
|
|
"""
|
|
return self.execute_query(query, (license_id,))
|
|
|
|
def get_device_count(self, license_id: str) -> int:
|
|
"""Get count of active devices"""
|
|
query = """
|
|
SELECT COUNT(DISTINCT hardware_id) as device_count
|
|
FROM license_heartbeats
|
|
WHERE license_id = %s
|
|
AND timestamp > NOW() - INTERVAL '15 minutes'
|
|
"""
|
|
result = self.execute_one(query, (license_id,))
|
|
return result['device_count'] if result else 0
|
|
|
|
def create_license_token(self, license_id: str, hardware_id: str,
|
|
valid_hours: int = 24) -> Optional[str]:
|
|
"""Create offline validation token"""
|
|
import secrets
|
|
token = secrets.token_urlsafe(64)
|
|
valid_until = datetime.utcnow() + timedelta(hours=valid_hours)
|
|
|
|
query = """
|
|
INSERT INTO license_tokens (license_id, token, hardware_id, valid_until)
|
|
VALUES (%s, %s, %s, %s)
|
|
RETURNING id
|
|
"""
|
|
|
|
result = self.execute_insert(query, (license_id, token, hardware_id, valid_until))
|
|
return token if result else None
|
|
|
|
def validate_token(self, token: str) -> Optional[Dict[str, Any]]:
|
|
"""Validate offline token"""
|
|
query = """
|
|
SELECT lt.*, l.license_key, l.is_active, l.expires_at
|
|
FROM license_tokens lt
|
|
JOIN licenses l ON lt.license_id = l.id
|
|
WHERE lt.token = %s
|
|
AND lt.valid_until > NOW()
|
|
AND l.is_active = true
|
|
"""
|
|
|
|
result = self.execute_one(query, (token,))
|
|
|
|
if result:
|
|
# Update validation count and timestamp
|
|
update_query = """
|
|
UPDATE license_tokens
|
|
SET validation_count = validation_count + 1,
|
|
last_validated = NOW()
|
|
WHERE token = %s
|
|
"""
|
|
self.execute_update(update_query, (token,))
|
|
|
|
return result
|
|
|
|
def record_heartbeat(self, license_id: str, hardware_id: str,
|
|
ip_address: str = None, user_agent: str = None,
|
|
app_version: str = None, session_data: Dict = None) -> None:
|
|
"""Record license heartbeat"""
|
|
query = """
|
|
INSERT INTO license_heartbeats
|
|
(license_id, hardware_id, ip_address, user_agent, app_version, session_data)
|
|
VALUES (%s, %s, %s, %s, %s, %s)
|
|
"""
|
|
|
|
import json
|
|
session_json = json.dumps(session_data) if session_data else None
|
|
|
|
self.execute_insert(query, (
|
|
license_id, hardware_id, ip_address,
|
|
user_agent, app_version, session_json
|
|
))
|
|
|
|
def record_activation_event(self, license_id: str, event_type: EventType,
|
|
hardware_id: str = None, previous_hardware_id: str = None,
|
|
ip_address: str = None, user_agent: str = None,
|
|
success: bool = True, error_message: str = None,
|
|
metadata: Dict = None) -> str:
|
|
"""Record activation event"""
|
|
query = """
|
|
INSERT INTO activation_events
|
|
(license_id, event_type, hardware_id, previous_hardware_id,
|
|
ip_address, user_agent, success, error_message, metadata)
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
RETURNING id
|
|
"""
|
|
|
|
import json
|
|
metadata_json = json.dumps(metadata) if metadata else None
|
|
|
|
return self.execute_insert(query, (
|
|
license_id, event_type.value, hardware_id, previous_hardware_id,
|
|
ip_address, user_agent, success, error_message, metadata_json
|
|
))
|
|
|
|
def get_recent_activations(self, license_id: str, hours: int = 24) -> List[Dict[str, Any]]:
|
|
"""Get recent activation events"""
|
|
query = """
|
|
SELECT * FROM activation_events
|
|
WHERE license_id = %s
|
|
AND created_at > NOW() - INTERVAL '%s hours'
|
|
ORDER BY created_at DESC
|
|
"""
|
|
return self.execute_query(query, (license_id, hours))
|
|
|
|
def check_hardware_id_exists(self, license_id: str, hardware_id: str) -> bool:
|
|
"""Check if hardware ID is already registered"""
|
|
query = """
|
|
SELECT 1 FROM activation_events
|
|
WHERE license_id = %s
|
|
AND hardware_id = %s
|
|
AND event_type IN ('activation', 'reactivation')
|
|
AND success = true
|
|
LIMIT 1
|
|
"""
|
|
result = self.execute_one(query, (license_id, hardware_id))
|
|
return result is not None
|
|
|
|
def deactivate_device(self, license_id: str, hardware_id: str) -> bool:
|
|
"""Deactivate a device"""
|
|
# Record deactivation event
|
|
self.record_activation_event(
|
|
license_id=license_id,
|
|
event_type=EventType.DEACTIVATION,
|
|
hardware_id=hardware_id,
|
|
success=True
|
|
)
|
|
|
|
# Remove any active tokens for this device
|
|
query = """
|
|
DELETE FROM license_tokens
|
|
WHERE license_id = %s AND hardware_id = %s
|
|
"""
|
|
affected = self.execute_delete(query, (license_id, hardware_id))
|
|
|
|
return affected > 0
|
|
|
|
def transfer_license(self, license_id: str, from_hardware_id: str,
|
|
to_hardware_id: str, ip_address: str = None) -> bool:
|
|
"""Transfer license from one device to another"""
|
|
try:
|
|
# Deactivate old device
|
|
self.deactivate_device(license_id, from_hardware_id)
|
|
|
|
# Record transfer event
|
|
self.record_activation_event(
|
|
license_id=license_id,
|
|
event_type=EventType.TRANSFER,
|
|
hardware_id=to_hardware_id,
|
|
previous_hardware_id=from_hardware_id,
|
|
ip_address=ip_address,
|
|
success=True
|
|
)
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"License transfer failed: {e}")
|
|
return False
|
|
|
|
def get_license_usage_stats(self, license_id: str, days: int = 30) -> Dict[str, Any]:
|
|
"""Get usage statistics for a license"""
|
|
query = """
|
|
WITH daily_stats AS (
|
|
SELECT
|
|
DATE(timestamp) as date,
|
|
COUNT(*) as validations,
|
|
COUNT(DISTINCT hardware_id) as unique_devices,
|
|
COUNT(DISTINCT ip_address) as unique_ips
|
|
FROM license_heartbeats
|
|
WHERE license_id = %s
|
|
AND timestamp > NOW() - INTERVAL '%s days'
|
|
GROUP BY DATE(timestamp)
|
|
)
|
|
SELECT
|
|
COUNT(*) as total_days,
|
|
SUM(validations) as total_validations,
|
|
AVG(validations) as avg_daily_validations,
|
|
MAX(unique_devices) as max_devices,
|
|
MAX(unique_ips) as max_ips
|
|
FROM daily_stats
|
|
"""
|
|
|
|
return self.execute_one(query, (license_id, days)) or {} |