From 07e651967990104fd0ec06f201df8adabb298818 Mon Sep 17 00:00:00 2001 From: Michael Woods Date: Thu, 25 Dec 2025 22:32:44 -0500 Subject: [PATCH] Added some logging and object upload code. --- packetserver/http/logging.py | 13 +++++ packetserver/http/routers/objects.py | 85 +++++++++++++++++++++++++++- packetserver/http/server.py | 3 + 3 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 packetserver/http/logging.py diff --git a/packetserver/http/logging.py b/packetserver/http/logging.py new file mode 100644 index 0000000..e9c67a1 --- /dev/null +++ b/packetserver/http/logging.py @@ -0,0 +1,13 @@ +from .config import Settings +import logging + + +def init_logging(): + settings = Settings() + + desired_level = settings.log_level.upper().strip() + + if desired_level not in logging.getLevelNamesMapping(): + raise ValueError(f"Invalid log level '{desired_level}'") + + logging.basicConfig(level=logging.getLevelName(desired_level)) diff --git a/packetserver/http/routers/objects.py b/packetserver/http/routers/objects.py index c8e2349..a1438fc 100644 --- a/packetserver/http/routers/objects.py +++ b/packetserver/http/routers/objects.py @@ -1,13 +1,17 @@ -from fastapi import APIRouter, Depends, HTTPException -from typing import List +from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form +from fastapi.responses import JSONResponse +from typing import List, Optional from datetime import datetime from uuid import UUID import mimetypes +import logging +from traceback import format_exc from packetserver.http.dependencies import get_current_http_user from packetserver.http.auth import HttpUser from packetserver.http.database import DbDependency from packetserver.server.objects import Object +from packetserver.server.users import User from pydantic import BaseModel router = APIRouter(prefix="/api/v1", tags=["objects"]) @@ -49,4 +53,79 @@ async def list_my_objects(db: DbDependency, current_user: HttpUser = Depends(get modified_at=obj.modified_at )) - return user_objects \ No newline at end of file + return user_objects + +@router.post("/objects", response_model=ObjectSummary) +async def upload_object( + db: DbDependency, + file: UploadFile = File(...), + name: Optional[str] = Form(None), + private: bool = Form(True), + force_text: bool = Form(False), # NEW: force treat as UTF-8 text + current_user: HttpUser = Depends(get_current_http_user) +): + username = current_user.username + + content = await file.read() + if not content: + raise HTTPException(status_code=400, detail="Empty file upload") + + obj_name = (name or file.filename or "unnamed_object").strip() + if len(obj_name) > 300: + raise HTTPException(status_code=400, detail="Object name too long (max 300 chars)") + if not obj_name: + raise HTTPException(status_code=400, detail="Invalid object name") + + try: + with db.transaction() as conn: + root = conn.root() + user = User.get_user_by_username(username, root) + if not user: + raise HTTPException(status_code=404, detail="User not found") + + # Handle force_text logic + if force_text: + try: + text_content = content.decode('utf-8', errors='strict') + object_data = text_content # str → will set binary=False + except UnicodeDecodeError: + raise HTTPException(status_code=400, detail="Content is not valid UTF-8 and cannot be forced as text") + else: + object_data = content # bytes → will set binary=True + + # Create and persist the object + new_object = Object(name=obj_name, data=object_data) + new_object.private = private + + obj_uuid = new_object.write_new(db) # assuming write_new takes db and handles root.objects storage + + new_object.chown(username, db) + + if force_text: + obj_type = 'string' + else: + obj_type = 'binary' + + logging.info(f"User {username} uploaded {obj_type} object {obj_uuid} ({obj_name}, {len(content)} bytes, force_text={force_text})") + + except HTTPException: + raise + except Exception as e: + logging.error(f"Object upload failed for {username}: {e}\n{format_exc()}") + raise HTTPException(status_code=500, detail="Failed to store object") + + # Build summary (matching your existing list endpoint) + content_type, _ = mimetypes.guess_type(new_object.name) + if content_type is None: + content_type = "application/octet-stream" if new_object.binary else "text/plain" + + return ObjectSummary( + uuid=obj_uuid, + name=new_object.name, + binary=new_object.binary, + size=new_object.size, + content_type=content_type, + private=new_object.private, + created_at=new_object.created_at, + modified_at=new_object.modified_at + ) \ No newline at end of file diff --git a/packetserver/http/server.py b/packetserver/http/server.py index 2eecf5b..53e2540 100644 --- a/packetserver/http/server.py +++ b/packetserver/http/server.py @@ -6,6 +6,9 @@ from pathlib import Path from .database import init_db from .routers import public, profile, messages, send +from .logging import init_logging + +init_logging() BASE_DIR = Path(__file__).parent.resolve()