diff --git a/app/api/proxy/router.py b/app/api/proxy/router.py index ef12b0d..ccffb56 100644 --- a/app/api/proxy/router.py +++ b/app/api/proxy/router.py @@ -1,13 +1,13 @@ from fastapi import APIRouter, HTTPException, status -from models.proxy import ProxyCreateInput, ProxyUpdateInput -from utils.proxy.manager import ( +from db.proxy import ( create_proxy, delete_proxy, get_proxy_by_port, list_proxies as list_proxy_documents, update_proxy, ) +from models.proxy import ProxyCreateInput, ProxyUpdateInput router = APIRouter( prefix="/proxy", @@ -15,12 +15,12 @@ router = APIRouter( ) -@router.get("/") +@router.get("") async def list_proxies_handler(): return await list_proxy_documents() -@router.get("/{proxy_port}/") +@router.get("/{proxy_port}") async def get_proxy(proxy_port: int): proxy = await get_proxy_by_port(proxy_port) if proxy is None: @@ -28,7 +28,7 @@ async def get_proxy(proxy_port: int): return proxy -@router.post("/", status_code=status.HTTP_201_CREATED) +@router.post("", status_code=status.HTTP_201_CREATED) async def create_proxy_handler(payload: ProxyCreateInput): existing_proxy = await get_proxy_by_port(payload.proxy_port) if existing_proxy is not None: @@ -45,7 +45,7 @@ async def create_proxy_handler(payload: ProxyCreateInput): ) -@router.put("/{proxy_port}/") +@router.put("/{proxy_port}") async def edit_proxy(proxy_port: int, payload: ProxyUpdateInput): updated_proxy = await update_proxy(proxy_port, payload) if updated_proxy is None: @@ -53,7 +53,7 @@ async def edit_proxy(proxy_port: int, payload: ProxyUpdateInput): return updated_proxy -@router.delete("/{proxy_port}/", status_code=status.HTTP_204_NO_CONTENT) +@router.delete("/{proxy_port}", status_code=status.HTTP_204_NO_CONTENT) async def remove_proxy(proxy_port: int): deleted = await delete_proxy(proxy_port) if not deleted: diff --git a/app/db/proxy.py b/app/db/proxy.py new file mode 100644 index 0000000..5096b91 --- /dev/null +++ b/app/db/proxy.py @@ -0,0 +1,95 @@ +from datetime import datetime, timezone +from typing import Any, Mapping + +from motor.motor_asyncio import AsyncIOMotorCollection +from pymongo import ReturnDocument + +from core.mongodb import get_database +from models.proxy import ProxyDocument, ProxyUpdateInput +from models.service import ServiceEndpointDocument +from utils.proxy.types import ServiceConfig + +COLLECTION_NAME = "proxies" + + +def _utcnow() -> datetime: + return datetime.now(timezone.utc) + + +async def _collection() -> AsyncIOMotorCollection[dict[str, Any]]: + database = await get_database() + return database[COLLECTION_NAME] + + +def _deserialize_proxy(raw: Mapping[str, Any] | None) -> ProxyDocument | None: + if raw is None: + return None + document = dict(raw) + document.pop("_id", None) + return ProxyDocument.model_validate(document) + + +async def ensure_proxy_indexes() -> None: + collection = await _collection() + await collection.create_index("proxy_port", unique=True, name="uq_proxy_port") + + +async def create_proxy( + proxy_port: int, + name: str, + service: ServiceConfig, + code: str, +) -> ProxyDocument: + now = _utcnow() + proxy = ProxyDocument( + proxy_port=proxy_port, + name=name, + code=code, + service=ServiceEndpointDocument(address=service["address"], port=service["port"]), + created_at=now, + updated_at=now, + ) + + collection = await _collection() + await collection.insert_one(proxy.model_dump(exclude_none=True)) + return proxy + + +async def get_proxy_by_port(proxy_port: int) -> ProxyDocument | None: + collection = await _collection() + raw = await collection.find_one({"proxy_port": proxy_port}) + return _deserialize_proxy(raw) + + +async def list_proxies() -> list[ProxyDocument]: + collection = await _collection() + cursor = collection.find({}).sort("proxy_port", 1) + + proxies: list[ProxyDocument] = [] + async for raw in cursor: + proxy = _deserialize_proxy(raw) + if proxy is not None: + proxies.append(proxy) + return proxies + + +async def update_proxy(proxy_port: int, updates: ProxyUpdateInput) -> ProxyDocument | None: + payload = updates.model_dump(exclude_none=True) + if not payload: + return await get_proxy_by_port(proxy_port) + + payload["updated_at"] = _utcnow() + + collection = await _collection() + raw = await collection.find_one_and_update( + {"proxy_port": proxy_port}, + {"$set": payload}, + return_document=ReturnDocument.AFTER, + ) + return _deserialize_proxy(raw) + + +async def delete_proxy(proxy_port: int) -> bool: + collection = await _collection() + result = await collection.delete_one({"proxy_port": proxy_port}) + return result.deleted_count > 0 diff --git a/app/main.py b/app/main.py index 42a662c..febde27 100644 --- a/app/main.py +++ b/app/main.py @@ -3,9 +3,9 @@ from contextlib import asynccontextmanager from fastapi import FastAPI from api.proxy.router import router as proxy_router +from db.proxy import ensure_proxy_indexes from core.mongodb import close_mongodb, init_mongodb from core.redis import close_redis, init_redis -from utils.proxy.manager import ensure_proxy_indexes @asynccontextmanager diff --git a/app/utils/proxy/manager.py b/app/utils/proxy/manager.py index c518448..58ad605 100644 --- a/app/utils/proxy/manager.py +++ b/app/utils/proxy/manager.py @@ -1,95 +1,10 @@ -from datetime import datetime, timezone -from typing import Any, Mapping - -from motor.motor_asyncio import AsyncIOMotorCollection -from pymongo import ReturnDocument - -from core.mongodb import get_database -from models.proxy import ProxyDocument, ProxyUpdateInput -from models.service import ServiceEndpointDocument from utils.proxy.types import ServiceConfig -COLLECTION_NAME = "proxies" - - -def _utcnow() -> datetime: - return datetime.now(timezone.utc) - - -async def _collection() -> AsyncIOMotorCollection[dict[str, Any]]: - database = await get_database() - return database[COLLECTION_NAME] - - -def _deserialize_proxy(raw: Mapping[str, Any] | None) -> ProxyDocument | None: - if raw is None: - return None - document = dict(raw) - document.pop("_id", None) - return ProxyDocument.model_validate(document) - - -async def ensure_proxy_indexes() -> None: - collection = await _collection() - await collection.create_index("proxy_port", unique=True, name="uq_proxy_port") - async def create_proxy( - proxy_port: int, - name: str, - service: ServiceConfig, - code: str, -) -> ProxyDocument: - now = _utcnow() - proxy = ProxyDocument( - proxy_port=proxy_port, - name=name, - code=code, - service=ServiceEndpointDocument(address=service["address"], port=service["port"]), - created_at=now, - updated_at=now, - ) - - collection = await _collection() - await collection.insert_one(proxy.model_dump(exclude_none=True)) - return proxy - - -async def get_proxy_by_port(proxy_port: int) -> ProxyDocument | None: - collection = await _collection() - raw = await collection.find_one({"proxy_port": proxy_port}) - return _deserialize_proxy(raw) - - -async def list_proxies() -> list[ProxyDocument]: - collection = await _collection() - cursor = collection.find({}).sort("proxy_port", 1) - - proxies: list[ProxyDocument] = [] - async for raw in cursor: - proxy = _deserialize_proxy(raw) - if proxy is not None: - proxies.append(proxy) - return proxies - - -async def update_proxy(proxy_port: int, updates: ProxyUpdateInput) -> ProxyDocument | None: - payload = updates.model_dump(exclude_none=True) - if not payload: - return await get_proxy_by_port(proxy_port) - - payload["updated_at"] = _utcnow() - - collection = await _collection() - raw = await collection.find_one_and_update( - {"proxy_port": proxy_port}, - {"$set": payload}, - return_document=ReturnDocument.AFTER, - ) - return _deserialize_proxy(raw) - - -async def delete_proxy(proxy_port: int) -> bool: - collection = await _collection() - result = await collection.delete_one({"proxy_port": proxy_port}) - return result.deleted_count > 0 + port: int, + name: str, + service: ServiceConfig, + code: str +): + return \ No newline at end of file