This commit is contained in:
2025-12-12 13:30:09 +03:00
commit 2f1e1f35e3
75 changed files with 4603 additions and 0 deletions

View File

View File

@@ -0,0 +1,42 @@
from uuid import UUID
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from ..models.user import User
from ..database import get_db
from ..utils.security import decode_token
security = HTTPBearer()
async def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security),
db: AsyncSession = Depends(get_db),
) -> User:
token = credentials.credentials
payload = decode_token(token)
if payload is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token",
)
user_id = payload.get("sub")
if user_id is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token",
)
result = await db.execute(select(User).where(User.id == UUID(user_id)))
user = result.scalar_one_or_none()
if user is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User not found",
)
return user

View File

@@ -0,0 +1,77 @@
import boto3
import urllib3
from botocore.config import Config
from ..config import get_settings
# Suppress SSL warnings for self-signed certificate
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
settings = get_settings()
def get_s3_client():
return boto3.client(
"s3",
endpoint_url=settings.s3_endpoint_url,
aws_access_key_id=settings.s3_access_key,
aws_secret_access_key=settings.s3_secret_key,
region_name=settings.s3_region,
config=Config(signature_version="s3v4"),
verify=False, # FirstVDS uses self-signed certificate
)
async def get_total_storage_size() -> int:
"""Returns total size of all objects in bucket in bytes"""
client = get_s3_client()
total_size = 0
paginator = client.get_paginator("list_objects_v2")
for page in paginator.paginate(Bucket=settings.s3_bucket_name):
for obj in page.get("Contents", []):
total_size += obj["Size"]
return total_size
async def can_upload_file(file_size: int) -> bool:
"""Check if file can be uploaded without exceeding storage limit"""
max_bytes = settings.max_storage_gb * 1024 * 1024 * 1024
current_size = await get_total_storage_size()
return (current_size + file_size) <= max_bytes
async def upload_file(file_content: bytes, s3_key: str, content_type: str = "audio/mpeg") -> str:
"""Upload file to S3 and return the key"""
client = get_s3_client()
client.put_object(
Bucket=settings.s3_bucket_name,
Key=s3_key,
Body=file_content,
ContentType=content_type,
)
return s3_key
async def delete_file(s3_key: str) -> None:
"""Delete file from S3"""
client = get_s3_client()
client.delete_object(Bucket=settings.s3_bucket_name, Key=s3_key)
def generate_presigned_url(s3_key: str, expiration: int = 3600) -> str:
"""Generate presigned URL for file access"""
client = get_s3_client()
url = client.generate_presigned_url(
"get_object",
Params={"Bucket": settings.s3_bucket_name, "Key": s3_key},
ExpiresIn=expiration,
)
return url
def get_file_content(s3_key: str) -> bytes:
"""Get full file content from S3"""
client = get_s3_client()
response = client.get_object(Bucket=settings.s3_bucket_name, Key=s3_key)
return response["Body"].read()

View File

@@ -0,0 +1,48 @@
from typing import Dict, Set
from fastapi import WebSocket
from uuid import UUID
import json
class ConnectionManager:
def __init__(self):
# room_id -> set of (websocket, user_id)
self.active_connections: Dict[UUID, Set[tuple[WebSocket, UUID]]] = {}
async def connect(self, websocket: WebSocket, room_id: UUID, user_id: UUID):
await websocket.accept()
if room_id not in self.active_connections:
self.active_connections[room_id] = set()
self.active_connections[room_id].add((websocket, user_id))
def disconnect(self, websocket: WebSocket, room_id: UUID, user_id: UUID):
if room_id in self.active_connections:
self.active_connections[room_id].discard((websocket, user_id))
if not self.active_connections[room_id]:
del self.active_connections[room_id]
async def broadcast_to_room(self, room_id: UUID, message: dict, exclude_user: UUID = None):
if room_id not in self.active_connections:
return
message_json = json.dumps(message, default=str)
disconnected = []
for websocket, user_id in self.active_connections[room_id]:
if exclude_user and user_id == exclude_user:
continue
try:
await websocket.send_text(message_json)
except Exception:
disconnected.append((websocket, user_id))
for conn in disconnected:
self.active_connections[room_id].discard(conn)
def get_room_user_count(self, room_id: UUID) -> int:
if room_id not in self.active_connections:
return 0
return len(self.active_connections[room_id])
manager = ConnectionManager()