init
This commit is contained in:
0
backend/app/services/__init__.py
Normal file
0
backend/app/services/__init__.py
Normal file
42
backend/app/services/auth.py
Normal file
42
backend/app/services/auth.py
Normal 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
|
||||
77
backend/app/services/s3.py
Normal file
77
backend/app/services/s3.py
Normal 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()
|
||||
48
backend/app/services/sync.py
Normal file
48
backend/app/services/sync.py
Normal 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()
|
||||
Reference in New Issue
Block a user