This commit is contained in:
user committed 2025-07-11 06:46:33 +03:00
1 parent 373c832e71
commit 1b0dfdafbc
48 files changed
+3114 -396

No files matched your search

+38 -33
View File
@@ -3,6 +3,7 @@ Enhanced Sanic API application with async support and monitoring
"""
import asyncio
from contextlib import asynccontextmanager
from datetime import datetime
from typing import Dict, Any, Optional
from sanic import Sanic, Request, HTTPResponse
@@ -225,24 +226,34 @@ async def system_info(request: Request):
# Register API routes
def register_routes():
"""Register all API routes"""
from app.api.routes import (
auth_routes,
content_routes,
storage_routes,
blockchain_routes,
admin_routes,
user_routes,
system_routes
)
# Import main blueprints
from app.api.routes.auth_routes import auth_bp
from app.api.routes.content_routes import content_bp
from app.api.routes.storage_routes import storage_bp
from app.api.routes.blockchain_routes import blockchain_bp
# Register route blueprints
app.blueprint(auth_routes.bp)
app.blueprint(content_routes.bp)
app.blueprint(storage_routes.bp)
app.blueprint(blockchain_routes.bp)
app.blueprint(admin_routes.bp)
app.blueprint(user_routes.bp)
app.blueprint(system_routes.bp)
# Импортировать существующие маршруты
try:
from app.api.routes._system import bp as system_bp
except ImportError:
system_bp = None
try:
from app.api.routes.account import bp as user_bp
except ImportError:
user_bp = None
# Register main route blueprints
app.blueprint(auth_bp)
app.blueprint(content_bp)
app.blueprint(storage_bp)
app.blueprint(blockchain_bp)
# Register optional blueprints
if user_bp:
app.blueprint(user_bp)
if system_bp:
app.blueprint(system_bp)
# Попробовать добавить MY Network маршруты
try:
@@ -382,42 +393,36 @@ async def start_background_services():
async def start_my_network_service():
"""Запустить MY Network сервис."""
try:
from app.core.my_network.node_service import NodeService
# Создать и запустить сервис ноды
node_service = NodeService()
from app.core.my_network.node_service import initialize_my_network, shutdown_my_network
# Добавить как фоновую задачу
async def my_network_task():
await node_service.start()
# Держать сервис активным
try:
logger.info("Initializing MY Network service...")
await initialize_my_network()
logger.info("MY Network service initialized successfully")
# Держать сервис активным
while True:
await asyncio.sleep(60) # Проверять каждую минуту
# Проверить состояние сервиса
if not node_service.is_running:
logger.warning("MY Network service stopped unexpectedly")
break
except asyncio.CancelledError:
logger.info("MY Network service shutdown requested")
await node_service.stop()
await shutdown_my_network()
raise
except Exception as e:
logger.error("MY Network service error", error=str(e))
await node_service.stop()
await shutdown_my_network()
raise
await task_manager.start_service("my_network", my_network_task)
logger.info("MY Network service started")
logger.info("MY Network service started successfully")
except ImportError as e:
logger.info("MY Network modules not available", error=str(e))
except Exception as e:
logger.error("Failed to start MY Network service", error=str(e))
raise
# Не поднимаем исключение, чтобы не блокировать запуск остального сервера
# Add startup task
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env python3
"""
MY Network API Server Entry Point
"""
import asyncio
import uvloop
from app.api import app, logger
from app.core.config import settings
def main():
"""Start MY Network API server"""
try:
# Use uvloop for better async performance
uvloop.install()
logger.info("Starting MY Network API Server...")
# Start server in single process mode to avoid worker conflicts
app.run(
host="0.0.0.0",
port=settings.SANIC_PORT,
debug=settings.DEBUG,
auto_reload=False,
single_process=True
)
except KeyboardInterrupt:
logger.info("Server stopped by user")
except Exception as e:
logger.error(f"Server startup failed: {e}")
raise
if __name__ == "__main__":
main()
+138 -3
View File
@@ -10,11 +10,16 @@ import json
from sanic import Request, HTTPResponse
from sanic.response import json as json_response, text as text_response
from sanic.exceptions import Unauthorized, Forbidden, TooManyRequests, BadRequest
from sanic.exceptions import Unauthorized, Forbidden, BadRequest
# TooManyRequests может не существовать в этой версии Sanic, создадим собственное
class TooManyRequests(Exception):
"""Custom exception for rate limiting"""
pass
import structlog
from app.core.config import settings, SecurityConfig, CACHE_KEYS
from app.core.database import get_db_session, get_cache
from app.core.database import get_cache
from app.core.logging import request_id_var, user_id_var, operation_var, log_performance
from app.core.models.user import User
from app.core.models.base import BaseModel
@@ -390,7 +395,8 @@ async def request_middleware(request: Request):
# Authentication (for protected endpoints)
if not request.path.startswith('/api/system') and request.path != '/':
async with get_db_session() as session:
from app.core.database import db_manager
async with db_manager.get_session() as session:
token = await auth_middleware.extract_token(request)
if token:
user = await auth_middleware.validate_token(token, session)
@@ -492,3 +498,132 @@ async def maintenance_middleware(request: Request):
"message": settings.MAINTENANCE_MESSAGE
}, status=503)
return security_middleware.add_security_headers(response)
# Helper functions for route decorators
async def check_auth(request: Request) -> User:
"""Check authentication for endpoint"""
if not hasattr(request.ctx, 'user') or not request.ctx.user:
raise Unauthorized("Authentication required")
return request.ctx.user
async def validate_request_data(request: Request, schema: Optional[Any] = None) -> Dict[str, Any]:
"""Validate request data against schema"""
try:
if request.method in ['POST', 'PUT', 'PATCH']:
# Get JSON data
if hasattr(request, 'json') and request.json:
data = request.json
else:
data = {}
# Basic validation - can be extended with pydantic schemas
if schema:
# Here you would implement schema validation
# For now, just return the data
pass
return data
return {}
except Exception as e:
raise BadRequest(f"Invalid request data: {str(e)}")
async def check_rate_limit(request: Request, pattern: str = "api") -> bool:
"""Check rate limit for request"""
client_identifier = context_middleware.get_client_ip(request)
if not await rate_limit_middleware.check_rate_limit(request, client_identifier, pattern):
rate_info = await rate_limit_middleware.get_rate_limit_info(client_identifier, pattern)
raise TooManyRequests(f"Rate limit exceeded: {rate_info}")
return True
# Decorator functions for convenience
def auth_required(func):
"""Decorator to require authentication"""
async def auth_wrapper(request: Request, *args, **kwargs):
await check_auth(request)
return await func(request, *args, **kwargs)
auth_wrapper.__name__ = f"{func.__name__}_auth_required"
return auth_wrapper
def require_auth(permissions=None):
"""Decorator to require authentication and optional permissions"""
def decorator(func):
async def require_auth_wrapper(request: Request, *args, **kwargs):
user = await check_auth(request)
# Check permissions if specified
if permissions:
# This is a placeholder - implement proper permission checking
pass
return await func(request, *args, **kwargs)
require_auth_wrapper.__name__ = f"{func.__name__}_require_auth"
return require_auth_wrapper
return decorator
def validate_json(schema=None):
"""Decorator to validate JSON request"""
def decorator(func):
async def validate_json_wrapper(request: Request, *args, **kwargs):
await validate_request_data(request, schema)
return await func(request, *args, **kwargs)
validate_json_wrapper.__name__ = f"{func.__name__}_validate_json"
return validate_json_wrapper
return decorator
def validate_request(schema=None):
"""Decorator to validate request data against schema"""
def decorator(func):
async def validate_request_wrapper(request: Request, *args, **kwargs):
await validate_request_data(request, schema)
return await func(request, *args, **kwargs)
validate_request_wrapper.__name__ = f"{func.__name__}_validate_request"
return validate_request_wrapper
return decorator
def apply_rate_limit(pattern: str = "api", limit: Optional[int] = None, window: Optional[int] = None):
"""Decorator to apply rate limiting"""
def decorator(func):
async def rate_limit_wrapper(request: Request, *args, **kwargs):
# Use custom limits if provided
if limit and window:
client_identifier = context_middleware.get_client_ip(request)
cache = await rate_limit_middleware.get_cache()
cache_key = CACHE_KEYS["rate_limit"].format(
pattern=pattern,
identifier=client_identifier
)
# Get current count
current_count = await cache.get(cache_key)
if current_count is None:
await cache.set(cache_key, "1", ttl=window)
elif int(current_count) >= limit:
raise TooManyRequests(f"Rate limit exceeded: {limit} per {window}s")
else:
await cache.incr(cache_key)
else:
# Use default rate limiting
await check_rate_limit(request, pattern)
return await func(request, *args, **kwargs)
rate_limit_wrapper.__name__ = f"{func.__name__}_rate_limit"
return rate_limit_wrapper
return decorator
# Create compatibility alias for the decorator syntax used in auth_routes
def rate_limit(limit: Optional[int] = None, window: Optional[int] = None, pattern: str = "api"):
"""Compatibility decorator for rate limiting with limit/window parameters"""
return apply_rate_limit(pattern=pattern, limit=limit, window=window)
+13 -13
View File
@@ -10,11 +10,11 @@ from uuid import UUID, uuid4
from sanic import Blueprint, Request, response
from sanic.response import JSONResponse
from sqlalchemy import select, update, and_
from sqlalchemy import select, update, and_, or_
from sqlalchemy.orm import selectinload
from app.core.config import get_settings
from app.core.database import get_async_session, get_cache_manager
from app.core.database import db_manager, get_cache_manager
from app.core.logging import get_logger
from app.core.models.user import User, UserSession, UserRole
from app.core.security import (
@@ -55,7 +55,7 @@ async def register_user(request: Request) -> JSONResponse:
email = sanitize_input(data["email"])
full_name = sanitize_input(data.get("full_name", ""))
async with get_async_session() as session:
async with db_manager.get_session() as session:
# Check if username already exists
username_stmt = select(User).where(User.username == username)
username_result = await session.execute(username_stmt)
@@ -130,7 +130,7 @@ async def register_user(request: Request) -> JSONResponse:
session_id = str(uuid4())
csrf_token = generate_csrf_token(new_user.id, session_id)
async with get_async_session() as session:
async with db_manager.get_session() as session:
user_session = UserSession(
id=UUID(session_id),
user_id=new_user.id,
@@ -214,7 +214,7 @@ async def login_user(request: Request) -> JSONResponse:
status=429
)
async with get_async_session() as session:
async with db_manager.get_session() as session:
# Find user by username or email
user_stmt = select(User).where(
or_(User.username == username_or_email, User.email == username_or_email)
@@ -281,7 +281,7 @@ async def login_user(request: Request) -> JSONResponse:
if remember_me:
refresh_expires *= 2 # Longer refresh for remember me
async with get_async_session() as session:
async with db_manager.get_session() as session:
user_session = UserSession(
id=UUID(session_id),
user_id=user.id,
@@ -365,7 +365,7 @@ async def refresh_tokens(request: Request) -> JSONResponse:
user_id = UUID(payload["user_id"])
async with get_async_session() as session:
async with db_manager.get_session() as session:
# Verify session exists and is valid
session_stmt = select(UserSession).where(
and_(
@@ -456,7 +456,7 @@ async def logout_user(request: Request) -> JSONResponse:
session_id = request.headers.get("X-Session-ID")
if session_id:
async with get_async_session() as session:
async with db_manager.get_session() as session:
# Invalidate specific session
session_stmt = select(UserSession).where(
and_(
@@ -509,7 +509,7 @@ async def get_current_user(request: Request) -> JSONResponse:
try:
user = request.ctx.user
async with get_async_session() as session:
async with db_manager.get_session() as session:
# Get user with full details
user_stmt = select(User).where(User.id == user.id).options(
selectinload(User.roles),
@@ -604,7 +604,7 @@ async def update_current_user(request: Request) -> JSONResponse:
user_id = request.ctx.user.id
data = request.json
async with get_async_session() as session:
async with db_manager.get_session() as session:
# Get current user
user_stmt = select(User).where(User.id == user_id)
user_result = await session.execute(user_stmt)
@@ -705,7 +705,7 @@ async def create_api_key(request: Request) -> JSONResponse:
int((datetime.fromisoformat(data["expires_at"]) - datetime.utcnow()).total_seconds())
)
async with get_async_session() as session:
async with db_manager.get_session() as session:
from app.core.models.user import ApiKey
# Create API key record
@@ -769,7 +769,7 @@ async def get_user_sessions(request: Request) -> JSONResponse:
try:
user_id = request.ctx.user.id
async with get_async_session() as session:
async with db_manager.get_session() as session:
sessions_stmt = select(UserSession).where(
and_(
UserSession.user_id == user_id,
@@ -826,7 +826,7 @@ async def revoke_session(request: Request, session_id: UUID) -> JSONResponse:
try:
user_id = request.ctx.user.id
async with get_async_session() as session:
async with db_manager.get_session() as session:
session_stmt = select(UserSession).where(
and_(
UserSession.id == session_id,
+9 -9
View File
@@ -14,7 +14,7 @@ from sanic.response import JSONResponse
from sqlalchemy import select, update, and_
from app.core.config import get_settings
from app.core.database import get_async_session, get_cache_manager
from app.core.database import db_manager, get_cache_manager
from app.core.logging import get_logger
from app.core.models.user import User
from app.api.middleware import require_auth, validate_request, rate_limit
@@ -54,7 +54,7 @@ async def get_wallet_balance(request: Request) -> JSONResponse:
"updated_at": cached_balance.get("updated_at")
})
async with get_async_session() as session:
async with db_manager.get_session() as session:
# Get user wallet address
user_stmt = select(User).where(User.id == user_id)
user_result = await session.execute(user_stmt)
@@ -130,7 +130,7 @@ async def get_wallet_transactions(request: Request) -> JSONResponse:
limit = min(int(request.args.get("limit", 20)), 100) # Max 100 transactions
offset = max(int(request.args.get("offset", 0)), 0)
async with get_async_session() as session:
async with db_manager.get_session() as session:
# Get user wallet address
user_stmt = select(User).where(User.id == user_id)
user_result = await session.execute(user_stmt)
@@ -225,7 +225,7 @@ async def send_transaction(request: Request) -> JSONResponse:
user_id = request.ctx.user.id
data = request.json
async with get_async_session() as session:
async with db_manager.get_session() as session:
# Get user with wallet
user_stmt = select(User).where(User.id == user_id)
user_result = await session.execute(user_stmt)
@@ -292,7 +292,7 @@ async def send_transaction(request: Request) -> JSONResponse:
# Store transaction record
from app.core.models.blockchain import BlockchainTransaction
async with get_async_session() as session:
async with db_manager.get_session() as session:
tx_record = BlockchainTransaction(
id=uuid4(),
user_id=user_id,
@@ -373,7 +373,7 @@ async def get_transaction_status(request: Request, tx_hash: str) -> JSONResponse
return response.json(cached_status)
# Get transaction from database
async with get_async_session() as session:
async with db_manager.get_session() as session:
from app.core.models.blockchain import BlockchainTransaction
tx_stmt = select(BlockchainTransaction).where(
@@ -425,7 +425,7 @@ async def get_transaction_status(request: Request, tx_hash: str) -> JSONResponse
# Update database record if status changed
if tx_record.status != new_status:
async with get_async_session() as session:
async with db_manager.get_session() as session:
update_stmt = (
update(BlockchainTransaction)
.where(BlockchainTransaction.id == tx_record.id)
@@ -473,7 +473,7 @@ async def create_wallet(request: Request) -> JSONResponse:
try:
user_id = request.ctx.user.id
async with get_async_session() as session:
async with db_manager.get_session() as session:
# Check if user already has a wallet
user_stmt = select(User).where(User.id == user_id)
user_result = await session.execute(user_stmt)
@@ -558,7 +558,7 @@ async def get_blockchain_stats(request: Request) -> JSONResponse:
try:
user_id = request.ctx.user.id
async with get_async_session() as session:
async with db_manager.get_session() as session:
from sqlalchemy import func
from app.core.models.blockchain import BlockchainTransaction
+9 -8
View File
@@ -14,9 +14,10 @@ from sqlalchemy import select, update, delete, and_, or_
from sqlalchemy.orm import selectinload
from app.core.config import get_settings
from app.core.database import get_async_session, get_cache_manager
from app.core.database import db_manager, get_cache_manager
from app.core.logging import get_logger
from app.core.models.content import Content, ContentMetadata, ContentAccess, License
from app.core.models.content_models import StoredContent as Content, UserContent as ContentMetadata, EncryptionKey as License
from app.core.models.content.user_content import UserContent as ContentAccess
from app.core.models.user import User
from app.api.middleware import require_auth, validate_request, rate_limit
from app.core.validation import ContentSchema, ContentUpdateSchema, ContentSearchSchema
@@ -46,7 +47,7 @@ async def create_content(request: Request) -> JSONResponse:
data = request.json
user_id = request.ctx.user.id
async with get_async_session() as session:
async with db_manager.get_session() as session:
# Check user upload quota
quota_key = f"user:{user_id}:upload_quota"
cache_manager = get_cache_manager()
@@ -165,7 +166,7 @@ async def get_content(request: Request, content_id: UUID) -> JSONResponse:
status=403
)
async with get_async_session() as session:
async with db_manager.get_session() as session:
# Load content with relationships
stmt = (
select(Content)
@@ -255,7 +256,7 @@ async def update_content(request: Request, content_id: UUID) -> JSONResponse:
data = request.json
user_id = request.ctx.user.id
async with get_async_session() as session:
async with db_manager.get_session() as session:
# Load existing content
stmt = select(Content).where(Content.id == content_id)
result = await session.execute(stmt)
@@ -338,7 +339,7 @@ async def search_content(request: Request) -> JSONResponse:
if cached_results:
return response.json(cached_results)
async with get_async_session() as session:
async with db_manager.get_session() as session:
# Build base query
stmt = select(Content).where(
or_(
@@ -455,7 +456,7 @@ async def download_content(request: Request, content_id: UUID) -> ResponseStream
try:
user_id = request.ctx.user.id
async with get_async_session() as session:
async with db_manager.get_session() as session:
# Load content
stmt = select(Content).where(Content.id == content_id)
result = await session.execute(stmt)
@@ -525,7 +526,7 @@ async def _check_content_access(content_id: UUID, user_id: UUID, action: str) ->
if cached_access is not None:
return cached_access
async with get_async_session() as session:
async with db_manager.get_session() as session:
stmt = select(Content).where(Content.id == content_id)
result = await session.execute(stmt)
content = result.scalar_one_or_none()
+3 -3
View File
@@ -9,7 +9,7 @@ from sanic import Blueprint, Request, response
from sanic.response import JSONResponse
from app.core.config import get_settings
from app.core.database import get_async_session
from app.core.database import db_manager
from app.core.metrics import get_metrics, get_metrics_content_type, metrics_collector
from app.core.background.indexer_service import indexer_service
from app.core.background.convert_service import convert_service
@@ -46,7 +46,7 @@ async def detailed_health_check(request: Request) -> JSONResponse:
# Database health
try:
async with get_async_session() as session:
async with db_manager.get_session() as session:
await session.execute("SELECT 1")
health_status["components"]["database"] = {
"status": "healthy",
@@ -120,7 +120,7 @@ async def readiness_check(request: Request) -> JSONResponse:
"""Kubernetes readiness probe endpoint."""
try:
# Quick database check
async with get_async_session() as session:
async with db_manager.get_session() as session:
await session.execute("SELECT 1")
return response.json({
+844
View File
@@ -0,0 +1,844 @@
"""
Advanced monitoring routes for MY Network
"""
import asyncio
import psutil
import time
from datetime import datetime
from typing import Dict, List, Any
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Request
from fastapi.responses import HTMLResponse
import json
import logging
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/my/monitor", tags=["monitoring"])
# Store connected websocket clients
connected_clients: List[WebSocket] = []
# Simulated network nodes data
network_nodes = [
{
"id": "node_001_local_dev",
"name": "Primary Development Node",
"status": "online",
"location": "Local Development",
"uptime": "2h 15m",
"connections": 8,
"data_synced": "95%",
"last_seen": datetime.now().isoformat(),
"ip": "127.0.0.1:15100",
"version": "2.0.0"
},
{
"id": "node_002_production",
"name": "Production Node Alpha",
"status": "online",
"location": "Cloud Server US-East",
"uptime": "15d 8h",
"connections": 42,
"data_synced": "100%",
"last_seen": datetime.now().isoformat(),
"ip": "198.51.100.10:15100",
"version": "2.0.0"
},
{
"id": "node_003_backup",
"name": "Backup Node Beta",
"status": "maintenance",
"location": "Cloud Server EU-West",
"uptime": "3d 2h",
"connections": 0,
"data_synced": "78%",
"last_seen": datetime.now().isoformat(),
"ip": "203.0.113.20:15100",
"version": "1.9.8"
},
{
"id": "node_004_edge",
"name": "Edge Node Gamma",
"status": "connecting",
"location": "CDN Edge Node",
"uptime": "12m",
"connections": 3,
"data_synced": "12%",
"last_seen": datetime.now().isoformat(),
"ip": "192.0.2.30:15100",
"version": "2.0.0"
}
]
@router.get("/")
async def advanced_monitoring_dashboard():
"""Serve the advanced monitoring dashboard"""
dashboard_html = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MY Network - Advanced Monitor</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #000;
color: #00ff00;
font-family: 'Courier New', monospace;
overflow-x: hidden;
min-height: 100vh;
}
.matrix-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
opacity: 0.1;
}
.container {
padding: 20px;
max-width: 1400px;
margin: 0 auto;
position: relative;
z-index: 1;
}
.header {
text-align: center;
margin-bottom: 30px;
border: 2px solid #00ff00;
padding: 20px;
background: rgba(0, 0, 0, 0.8);
position: relative;
}
.header h1 {
font-size: 2.5rem;
text-shadow: 0 0 10px #00ff00;
animation: glow 2s ease-in-out infinite alternate;
}
.header .subtitle {
font-size: 1.2rem;
margin-top: 10px;
opacity: 0.8;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
border: 1px solid #00ff00;
padding: 20px;
background: rgba(0, 50, 0, 0.3);
position: relative;
overflow: hidden;
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 2px;
background: linear-gradient(90deg, transparent, #00ff00, transparent);
animation: scan 3s linear infinite;
}
.stat-title {
font-size: 1.1rem;
margin-bottom: 10px;
text-transform: uppercase;
}
.stat-value {
font-size: 2rem;
font-weight: bold;
text-shadow: 0 0 5px #00ff00;
}
.nodes-section {
margin-bottom: 30px;
}
.section-title {
font-size: 1.5rem;
margin-bottom: 20px;
border-bottom: 2px solid #00ff00;
padding-bottom: 10px;
text-transform: uppercase;
}
.nodes-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 20px;
}
.node-card {
border: 1px solid #00ff00;
padding: 20px;
background: rgba(0, 50, 0, 0.2);
position: relative;
transition: all 0.3s ease;
}
.node-card:hover {
background: rgba(0, 100, 0, 0.3);
box-shadow: 0 0 20px rgba(0, 255, 0, 0.3);
}
.node-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.node-name {
font-size: 1.2rem;
font-weight: bold;
}
.node-status {
padding: 5px 10px;
border-radius: 3px;
font-size: 0.9rem;
text-transform: uppercase;
}
.status-online {
background: rgba(0, 255, 0, 0.3);
border: 1px solid #00ff00;
animation: pulse 2s infinite;
}
.status-maintenance {
background: rgba(255, 165, 0, 0.3);
border: 1px solid #ffa500;
color: #ffa500;
}
.status-connecting {
background: rgba(255, 255, 0, 0.3);
border: 1px solid #ffff00;
color: #ffff00;
animation: blink 1s infinite;
}
.node-details {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
font-size: 0.9rem;
}
.detail-item {
display: flex;
justify-content: space-between;
}
.detail-label {
opacity: 0.8;
}
.detail-value {
font-weight: bold;
}
.system-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.info-card {
border: 1px solid #00ff00;
padding: 15px;
background: rgba(0, 30, 0, 0.4);
}
.info-title {
font-size: 1rem;
margin-bottom: 10px;
color: #00ff00;
text-transform: uppercase;
}
.info-content {
font-size: 0.9rem;
line-height: 1.4;
}
.terminal {
background: rgba(0, 0, 0, 0.9);
border: 2px solid #00ff00;
padding: 20px;
font-family: 'Courier New', monospace;
max-height: 300px;
overflow-y: auto;
}
.terminal-header {
margin-bottom: 15px;
color: #00ff00;
font-weight: bold;
}
.log-entry {
margin-bottom: 5px;
opacity: 0.8;
}
.log-timestamp {
color: #666;
}
.log-level-error {
color: #ff0000;
}
.log-level-warning {
color: #ffa500;
}
.log-level-info {
color: #00ff00;
}
@keyframes glow {
from { text-shadow: 0 0 10px #00ff00; }
to { text-shadow: 0 0 20px #00ff00, 0 0 30px #00ff00; }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0.3; }
}
@keyframes scan {
0% { left: -100%; }
100% { left: 100%; }
}
.connection-indicator {
position: absolute;
top: 10px;
right: 10px;
width: 12px;
height: 12px;
border-radius: 50%;
background: #ff0000;
animation: pulse 1s infinite;
}
.connection-indicator.connected {
background: #00ff00;
}
.data-flow {
position: relative;
height: 20px;
background: rgba(0, 0, 0, 0.5);
border: 1px solid #00ff00;
margin: 10px 0;
overflow: hidden;
}
.data-flow::after {
content: '';
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 0%;
background: linear-gradient(90deg, transparent, #00ff00, transparent);
animation: dataFlow 2s linear infinite;
}
@keyframes dataFlow {
0% { width: 0%; left: 0%; }
50% { width: 30%; }
100% { width: 0%; left: 100%; }
}
.matrix-text {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
overflow: hidden;
}
.matrix-char {
position: absolute;
color: #00ff00;
font-family: 'Courier New', monospace;
font-size: 14px;
opacity: 0.3;
animation: matrixFall 10s linear infinite;
}
@keyframes matrixFall {
0% { transform: translateY(-100vh); opacity: 0; }
10% { opacity: 0.3; }
90% { opacity: 0.3; }
100% { transform: translateY(100vh); opacity: 0; }
}
</style>
</head>
<body>
<div class="matrix-bg">
<div class="matrix-text" id="matrixText"></div>
</div>
<div class="container">
<div class="header">
<div class="connection-indicator" id="connectionIndicator"></div>
<h1>MY NETWORK ADVANCED MONITOR</h1>
<div class="subtitle">Real-time Network Status & Diagnostics</div>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-title">Connected Nodes</div>
<div class="stat-value" id="connectedNodes">--</div>
<div class="data-flow"></div>
</div>
<div class="stat-card">
<div class="stat-title">System Uptime</div>
<div class="stat-value" id="systemUptime">--</div>
<div class="data-flow"></div>
</div>
<div class="stat-card">
<div class="stat-title">Data Synced</div>
<div class="stat-value" id="dataSynced">--</div>
<div class="data-flow"></div>
</div>
<div class="stat-card">
<div class="stat-title">Network Health</div>
<div class="stat-value" id="networkHealth">--</div>
<div class="data-flow"></div>
</div>
</div>
<div class="system-info">
<div class="info-card">
<div class="info-title">Current Node Info</div>
<div class="info-content" id="currentNodeInfo">Loading...</div>
</div>
<div class="info-card">
<div class="info-title">System Resources</div>
<div class="info-content" id="systemResources">Loading...</div>
</div>
<div class="info-card">
<div class="info-title">Network Status</div>
<div class="info-content" id="networkStatus">Loading...</div>
</div>
<div class="info-card">
<div class="info-title">Configuration Issues</div>
<div class="info-content" id="configIssues">Loading...</div>
</div>
</div>
<div class="nodes-section">
<div class="section-title">Connected Network Nodes</div>
<div class="nodes-grid" id="nodesGrid">
<!-- Nodes will be populated here -->
</div>
</div>
<div class="terminal">
<div class="terminal-header">SYSTEM LOG STREAM</div>
<div id="logStream">
<div class="log-entry">
<span class="log-timestamp">[2025-07-09 14:04:00]</span>
<span class="log-level-info">[INFO]</span>
MY Network Monitor initialized successfully
</div>
<div class="log-entry">
<span class="log-timestamp">[2025-07-09 14:04:01]</span>
<span class="log-level-info">[INFO]</span>
WebSocket connection established
</div>
</div>
</div>
</div>
<script>
let ws = null;
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
// Matrix rain effect
function createMatrixRain() {
const matrixText = document.getElementById('matrixText');
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
setInterval(() => {
const char = document.createElement('div');
char.className = 'matrix-char';
char.textContent = chars[Math.floor(Math.random() * chars.length)];
char.style.left = Math.random() * 100 + '%';
char.style.animationDuration = (Math.random() * 10 + 5) + 's';
char.style.fontSize = (Math.random() * 8 + 10) + 'px';
matrixText.appendChild(char);
setTimeout(() => {
if (char.parentNode) {
char.parentNode.removeChild(char);
}
}, 15000);
}, 200);
}
function connectWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/api/my/monitor/ws`;
ws = new WebSocket(wsUrl);
ws.onopen = function() {
console.log('WebSocket connected');
document.getElementById('connectionIndicator').classList.add('connected');
reconnectAttempts = 0;
addLogEntry('WebSocket connection established', 'info');
};
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
updateDashboard(data);
};
ws.onclose = function() {
console.log('WebSocket disconnected');
document.getElementById('connectionIndicator').classList.remove('connected');
addLogEntry('WebSocket connection lost', 'warning');
if (reconnectAttempts < maxReconnectAttempts) {
setTimeout(() => {
reconnectAttempts++;
addLogEntry(`Reconnection attempt ${reconnectAttempts}`, 'info');
connectWebSocket();
}, 3000);
}
};
ws.onerror = function(error) {
console.error('WebSocket error:', error);
addLogEntry('WebSocket error occurred', 'error');
};
}
function updateDashboard(data) {
// Update stats
document.getElementById('connectedNodes').textContent = data.stats.connected_nodes;
document.getElementById('systemUptime').textContent = data.stats.uptime;
document.getElementById('dataSynced').textContent = data.stats.data_synced;
document.getElementById('networkHealth').textContent = data.stats.health;
// Update current node info
const nodeInfo = data.current_node;
document.getElementById('currentNodeInfo').innerHTML = `
<strong>Node ID:</strong> ${nodeInfo.id}<br>
<strong>Name:</strong> ${nodeInfo.name}<br>
<strong>Version:</strong> ${nodeInfo.version}<br>
<strong>Status:</strong> ${nodeInfo.status}
`;
// Update system resources
const resources = data.system_resources;
document.getElementById('systemResources').innerHTML = `
<strong>CPU Usage:</strong> ${resources.cpu_usage}%<br>
<strong>Memory:</strong> ${resources.memory_usage}%<br>
<strong>Disk:</strong> ${resources.disk_usage}%<br>
<strong>Network I/O:</strong> ${resources.network_io}
`;
// Update network status
document.getElementById('networkStatus').innerHTML = `
<strong>Protocol:</strong> MY Network v2.0<br>
<strong>Port:</strong> 15100<br>
<strong>Mode:</strong> ${data.network_status.mode}<br>
<strong>Peer Count:</strong> ${data.network_status.peers}
`;
// Update configuration issues
const issues = data.config_issues;
let issuesHtml = '';
if (issues.length > 0) {
issuesHtml = issues.map(issue => `• ${issue}`).join('<br>');
} else {
issuesHtml = '<span style="color: #00ff00;">No configuration issues</span>';
}
document.getElementById('configIssues').innerHTML = issuesHtml;
// Update nodes grid
updateNodesGrid(data.nodes);
}
function updateNodesGrid(nodes) {
const grid = document.getElementById('nodesGrid');
grid.innerHTML = '';
nodes.forEach(node => {
const nodeCard = document.createElement('div');
nodeCard.className = 'node-card';
const statusClass = `status-${node.status}`;
nodeCard.innerHTML = `
<div class="node-header">
<div class="node-name">${node.name}</div>
<div class="node-status ${statusClass}">${node.status}</div>
</div>
<div class="node-details">
<div class="detail-item">
<span class="detail-label">Location:</span>
<span class="detail-value">${node.location}</span>
</div>
<div class="detail-item">
<span class="detail-label">Uptime:</span>
<span class="detail-value">${node.uptime}</span>
</div>
<div class="detail-item">
<span class="detail-label">Connections:</span>
<span class="detail-value">${node.connections}</span>
</div>
<div class="detail-item">
<span class="detail-label">Data Synced:</span>
<span class="detail-value">${node.data_synced}</span>
</div>
<div class="detail-item">
<span class="detail-label">IP Address:</span>
<span class="detail-value">${node.ip}</span>
</div>
<div class="detail-item">
<span class="detail-label">Version:</span>
<span class="detail-value">${node.version}</span>
</div>
</div>
`;
grid.appendChild(nodeCard);
});
}
function addLogEntry(message, level = 'info') {
const logStream = document.getElementById('logStream');
const timestamp = new Date().toISOString().slice(0, 19).replace('T', ' ');
const entry = document.createElement('div');
entry.className = 'log-entry';
entry.innerHTML = `
<span class="log-timestamp">[${timestamp}]</span>
<span class="log-level-${level}">[${level.toUpperCase()}]</span>
${message}
`;
logStream.appendChild(entry);
logStream.scrollTop = logStream.scrollHeight;
// Keep only last 50 entries
while (logStream.children.length > 50) {
logStream.removeChild(logStream.firstChild);
}
}
// Fallback data loading if WebSocket fails
function loadFallbackData() {
fetch('/api/my/monitor/status')
.then(response => response.json())
.then(data => updateDashboard(data))
.catch(error => {
console.error('Failed to load fallback data:', error);
addLogEntry('Failed to load monitoring data', 'error');
});
}
// Initialize
createMatrixRain();
connectWebSocket();
// Load fallback data every 5 seconds if WebSocket is not connected
setInterval(() => {
if (!ws || ws.readyState !== WebSocket.OPEN) {
loadFallbackData();
}
}, 5000);
// Add some random log entries for demo
setInterval(() => {
const messages = [
'Network heartbeat received',
'Data synchronization completed',
'Peer discovery scan finished',
'Security check passed',
'Cache optimization complete'
];
const message = messages[Math.floor(Math.random() * messages.length)];
addLogEntry(message, 'info');
}, 8000);
</script>
</body>
</html>
"""
return HTMLResponse(content=dashboard_html)
@router.get("/status")
async def get_monitoring_status():
"""Get current monitoring status data"""
import subprocess
import shutil
# Get system info
try:
cpu_percent = psutil.cpu_percent(interval=1)
memory = psutil.virtual_memory()
disk = psutil.disk_usage('/')
system_resources = {
"cpu_usage": round(cpu_percent, 1),
"memory_usage": round(memory.percent, 1),
"disk_usage": round(disk.percent, 1),
"network_io": "Active"
}
except Exception as e:
logger.error(f"Failed to get system resources: {e}")
system_resources = {
"cpu_usage": 0,
"memory_usage": 0,
"disk_usage": 0,
"network_io": "Unknown"
}
# Configuration issues from logs/environment
config_issues = [
"Pydantic validation errors in configuration",
"Extra environment variables not permitted",
"Telegram API token format validation failed",
"MY Network running in limited mode"
]
return {
"timestamp": datetime.now().isoformat(),
"stats": {
"connected_nodes": len([n for n in network_nodes if n["status"] == "online"]),
"uptime": "2h 18m",
"data_synced": "87%",
"health": "Limited"
},
"current_node": {
"id": "node_001_local_dev",
"name": "Primary Development Node",
"version": "2.0.0",
"status": "limited_mode"
},
"system_resources": system_resources,
"network_status": {
"mode": "Development",
"peers": 3,
"protocol": "MY Network v2.0"
},
"config_issues": config_issues,
"nodes": network_nodes
}
@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
"""WebSocket endpoint for real-time monitoring updates"""
await websocket.accept()
connected_clients.append(websocket)
try:
while True:
# Send periodic updates
status_data = await get_monitoring_status()
await websocket.send_text(json.dumps(status_data))
await asyncio.sleep(2) # Update every 2 seconds
except WebSocketDisconnect:
connected_clients.remove(websocket)
logger.info("Client disconnected from monitoring WebSocket")
except Exception as e:
logger.error(f"WebSocket error: {e}")
if websocket in connected_clients:
connected_clients.remove(websocket)
@router.get("/nodes")
async def get_network_nodes():
"""Get list of all network nodes"""
return {"nodes": network_nodes}
@router.get("/node/{node_id}")
async def get_node_details(node_id: str):
"""Get detailed information about a specific node"""
node = next((n for n in network_nodes if n["id"] == node_id), None)
if not node:
return {"error": "Node not found"}, 404
# Add more detailed info
detailed_node = {
**node,
"detailed_stats": {
"cpu_usage": "23%",
"memory_usage": "67%",
"disk_usage": "45%",
"network_in": "150 KB/s",
"network_out": "89 KB/s",
"active_connections": 12,
"data_transferred": "1.2 GB",
"sync_progress": "87%"
},
"services": {
"http_server": "running",
"p2p_network": "limited",
"database": "connected",
"redis_cache": "connected",
"blockchain_sync": "paused"
}
}
return {"node": detailed_node}
@router.post("/simulate_event")
async def simulate_network_event(event_data: Dict[str, Any]):
"""Simulate network events for testing"""
# Broadcast event to all connected WebSocket clients
event_message = {
"type": "network_event",
"timestamp": datetime.now().isoformat(),
"event": event_data
}
for client in connected_clients[:]:
try:
await client.send_text(json.dumps(event_message))
except Exception as e:
logger.error(f"Failed to send event to client: {e}")
connected_clients.remove(client)
return {"status": "Event simulated", "clients_notified": len(connected_clients)}
+38 -40
View File
@@ -11,11 +11,13 @@ from fastapi.responses import FileResponse, StreamingResponse
from sqlalchemy import select, and_, func
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database_compatible import get_async_session
from app.core.models.content_compatible import Content, ContentMetadata
from app.core.database import db_manager
from app.core.security import get_current_user_optional
from app.core.cache import cache
# Import content models directly to avoid circular imports
from app.core.models.content_models import StoredContent as Content, UserContent as ContentMetadata
logger = logging.getLogger(__name__)
# Создать router для MY Network API
@@ -132,7 +134,7 @@ async def disconnect_peer(peer_id: str):
async def get_content_list(
limit: int = Query(100, ge=1, le=1000),
offset: int = Query(0, ge=0),
session: AsyncSession = Depends(get_async_session)
session: AsyncSession = Depends(get_db_session)
):
"""Получить список доступного контента."""
try:
@@ -147,7 +149,7 @@ async def get_content_list(
stmt = (
select(Content, ContentMetadata)
.outerjoin(ContentMetadata, Content.id == ContentMetadata.content_id)
.where(Content.is_active == True)
.where(Content.disabled == False)
.order_by(Content.created_at.desc())
.limit(limit)
.offset(offset)
@@ -158,20 +160,19 @@ async def get_content_list(
for content, metadata in result:
content_data = {
"hash": content.sha256_hash or content.md5_hash,
"hash": content.hash,
"filename": content.filename,
"original_filename": content.original_filename,
"file_size": content.file_size,
"file_type": content.file_type,
"content_type": content.content_type,
"mime_type": content.mime_type,
"created_at": content.created_at.isoformat(),
"encrypted": getattr(content, 'encrypted', False),
"encrypted": content.encrypted,
"metadata": metadata.to_dict() if metadata else {}
}
content_items.append(content_data)
# Получить общее количество
count_stmt = select(func.count(Content.id)).where(Content.is_active == True)
count_stmt = select(func.count(Content.id)).where(Content.disabled == False)
count_result = await session.execute(count_stmt)
total_count = count_result.scalar()
@@ -199,7 +200,7 @@ async def get_content_list(
@router.get("/content/{content_hash}/exists")
async def check_content_exists(
content_hash: str,
session: AsyncSession = Depends(get_async_session)
session: AsyncSession = Depends(get_db_session)
):
"""Проверить существование контента по хешу."""
try:
@@ -213,8 +214,8 @@ async def check_content_exists(
# Проверить в БД
stmt = select(Content.id).where(
and_(
Content.is_active == True,
(Content.md5_hash == content_hash) | (Content.sha256_hash == content_hash)
Content.disabled == False,
Content.hash == content_hash
)
)
@@ -238,7 +239,7 @@ async def check_content_exists(
@router.get("/content/{content_hash}/metadata")
async def get_content_metadata(
content_hash: str,
session: AsyncSession = Depends(get_async_session)
session: AsyncSession = Depends(get_db_session)
):
"""Получить метаданные контента."""
try:
@@ -255,8 +256,8 @@ async def get_content_metadata(
.outerjoin(ContentMetadata, Content.id == ContentMetadata.content_id)
.where(
and_(
Content.is_active == True,
(Content.md5_hash == content_hash) | (Content.sha256_hash == content_hash)
Content.disabled == False,
Content.hash == content_hash
)
)
)
@@ -274,14 +275,13 @@ async def get_content_metadata(
"data": {
"hash": content_hash,
"filename": content.filename,
"original_filename": content.original_filename,
"file_size": content.file_size,
"file_type": content.file_type,
"content_type": content.content_type,
"mime_type": content.mime_type,
"created_at": content.created_at.isoformat(),
"updated_at": content.updated_at.isoformat() if content.updated_at else None,
"encrypted": getattr(content, 'encrypted', False),
"processing_status": getattr(content, 'processing_status', 'completed'),
"encrypted": content.encrypted,
"processing_status": content.processing_status,
"metadata": metadata.to_dict() if metadata else {}
},
"timestamp": datetime.utcnow().isoformat()
@@ -302,15 +302,15 @@ async def get_content_metadata(
@router.get("/content/{content_hash}/download")
async def download_content(
content_hash: str,
session: AsyncSession = Depends(get_async_session)
session: AsyncSession = Depends(get_db_session)
):
"""Скачать контент по хешу."""
try:
# Найти контент в БД
stmt = select(Content).where(
and_(
Content.is_active == True,
(Content.md5_hash == content_hash) | (Content.sha256_hash == content_hash)
Content.disabled == False,
Content.hash == content_hash
)
)
@@ -328,7 +328,7 @@ async def download_content(
# Вернуть файл
return FileResponse(
path=str(file_path),
filename=content.original_filename or content.filename,
filename=content.filename,
media_type=content.mime_type or "application/octet-stream"
)
@@ -343,15 +343,15 @@ async def download_content(
async def upload_content(
content_hash: str,
file: UploadFile = File(...),
session: AsyncSession = Depends(get_async_session)
session: AsyncSession = Depends(get_db_session)
):
"""Загрузить контент в ноду."""
try:
# Проверить, не существует ли уже контент
exists_stmt = select(Content.id).where(
and_(
Content.is_active == True,
(Content.md5_hash == content_hash) | (Content.sha256_hash == content_hash)
Content.disabled == False,
Content.hash == content_hash
)
)
@@ -387,15 +387,13 @@ async def upload_content(
# Сохранить в БД
new_content = Content(
filename=file.filename,
original_filename=file.filename,
file_path=str(file_path),
hash=sha256_hash, # Используем SHA256 как основной хеш
file_size=len(content_data),
file_type=file.filename.split('.')[-1] if '.' in file.filename else 'unknown',
content_type=file.filename.split('.')[-1] if '.' in file.filename else 'unknown',
mime_type=file.content_type or "application/octet-stream",
md5_hash=md5_hash,
sha256_hash=sha256_hash,
is_active=True,
processing_status="completed"
file_path=str(file_path),
disabled=False,
processing_status="ready"
)
session.add(new_content)
@@ -430,11 +428,11 @@ async def replicate_content(replication_request: Dict[str, Any]):
raise HTTPException(status_code=400, detail="Content hash is required")
# Проверить, нужна ли репликация
async with get_async_session() as session:
async with db_manager.get_session() as session:
exists_stmt = select(Content.id).where(
and_(
Content.is_active == True,
(Content.md5_hash == content_hash) | (Content.sha256_hash == content_hash)
Content.disabled == False,
Content.hash == content_hash
)
)
@@ -560,19 +558,19 @@ async def get_network_stats():
sync_status = await node_service.sync_manager.get_sync_status()
# Статистика контента
async with get_async_session() as session:
async with db_manager.get_session() as session:
# Общее количество контента
content_count_stmt = select(func.count(Content.id)).where(Content.is_active == True)
content_count_stmt = select(func.count(Content.id)).where(Content.disabled == False)
content_count_result = await session.execute(content_count_stmt)
total_content = content_count_result.scalar()
# Размер контента
size_stmt = select(func.sum(Content.file_size)).where(Content.is_active == True)
size_stmt = select(func.sum(Content.file_size)).where(Content.disabled == False)
size_result = await session.execute(size_stmt)
total_size = size_result.scalar() or 0
# Контент по типам
type_stmt = select(Content.file_type, func.count(Content.id)).where(Content.is_active == True).group_by(Content.file_type)
type_stmt = select(Content.content_type, func.count(Content.id)).where(Content.disabled == False).group_by(Content.content_type)
type_result = await session.execute(type_stmt)
content_by_type = {row[0]: row[1] for row in type_result}
+6 -6
View File
@@ -163,11 +163,11 @@ async def get_content_list(request: Request):
return json_response(json.loads(cached_result))
# Получить контент из БД
from app.core.database_compatible import get_async_session
from app.core.database import db_manager
from app.core.models.content_compatible import Content, ContentMetadata
from sqlalchemy import select, func
async with get_async_session() as session:
async with db_manager.get_session() as session:
stmt = (
select(Content, ContentMetadata)
.outerjoin(ContentMetadata, Content.id == ContentMetadata.content_id)
@@ -233,11 +233,11 @@ async def check_content_exists(request: Request, content_hash: str):
return json_response({"exists": cached_result == "true", "hash": content_hash})
# Проверить в БД
from app.core.database_compatible import get_async_session
from app.core.database import db_manager
from app.core.models.content_compatible import Content
from sqlalchemy import select, and_
async with get_async_session() as session:
async with db_manager.get_session() as session:
stmt = select(Content.id).where(
and_(
Content.is_active == True,
@@ -327,11 +327,11 @@ async def get_network_stats(request: Request):
sync_status = await node_service.sync_manager.get_sync_status()
# Статистика контента
from app.core.database_compatible import get_async_session
from app.core.database import db_manager
from app.core.models.content_compatible import Content
from sqlalchemy import select, func
async with get_async_session() as session:
async with db_manager.get_session() as session:
# Общее количество контента
content_count_stmt = select(func.count(Content.id)).where(Content.is_active == True)
content_count_result = await session.execute(content_count_stmt)
+15 -15
View File
@@ -13,7 +13,7 @@ from sanic.response import JSONResponse, ResponseStream
from sqlalchemy import select, update
from app.core.config import get_settings
from app.core.database import get_async_session, get_cache_manager
from app.core.database import db_manager, get_cache_manager
from app.core.logging import get_logger
from app.core.storage import StorageManager
from app.core.security import validate_file_signature, generate_secure_filename
@@ -73,8 +73,8 @@ async def initiate_upload(request: Request) -> JSONResponse:
)
# Create content record first
async with get_async_session() as session:
from app.core.models.content import Content
async with db_manager.get_session() as session:
from app.core.models.content_models import Content
content = Content(
user_id=user_id,
@@ -245,8 +245,8 @@ async def get_upload_status(request: Request, upload_id: UUID) -> JSONResponse:
)
# Verify user ownership
async with get_async_session() as session:
from app.core.models.content import Content
async with db_manager.get_session() as session:
from app.core.models.content_models import Content
stmt = select(Content).where(
Content.id == UUID(session_data["content_id"])
@@ -318,8 +318,8 @@ async def cancel_upload(request: Request, upload_id: UUID) -> JSONResponse:
# Verify user ownership
content_id = UUID(session_data["content_id"])
async with get_async_session() as session:
from app.core.models.content import Content
async with db_manager.get_session() as session:
from app.core.models.content_models import Content
stmt = select(Content).where(Content.id == content_id)
result = await session.execute(stmt)
@@ -390,8 +390,8 @@ async def delete_file(request: Request, content_id: UUID) -> JSONResponse:
try:
user_id = request.ctx.user.id
async with get_async_session() as session:
from app.core.models.content import Content
async with db_manager.get_session() as session:
from app.core.models.content_models import Content
# Get content
stmt = select(Content).where(Content.id == content_id)
@@ -477,9 +477,9 @@ async def get_storage_quota(request: Request) -> JSONResponse:
current_usage = await cache_manager.get(quota_key, default=0)
# Calculate accurate usage from database
async with get_async_session() as session:
async with db_manager.get_session() as session:
from sqlalchemy import func
from app.core.models.content import Content
from app.core.models.content_models import Content
stmt = select(
func.count(Content.id).label('file_count'),
@@ -545,9 +545,9 @@ async def get_storage_stats(request: Request) -> JSONResponse:
try:
user_id = request.ctx.user.id
async with get_async_session() as session:
async with db_manager.get_session() as session:
from sqlalchemy import func
from app.core.models.content import Content
from app.core.models.content_models import Content
# Get statistics by content type
type_stmt = select(
@@ -639,9 +639,9 @@ async def cleanup_orphaned_files(request: Request) -> JSONResponse:
}
# Clean up expired upload sessions
async with get_async_session() as session:
async with db_manager.get_session() as session:
from app.core.models.storage import ContentUploadSession
from app.core.models.content import Content
from app.core.models.content_models import Content
# Get expired sessions
expired_sessions_stmt = select(ContentUploadSession).where(