Object patcher

This commit is contained in:
Michael Woods
2025-12-25 23:47:22 -05:00
parent 1ab752d170
commit 1566bc4093

View File

@@ -7,8 +7,8 @@ import mimetypes
import logging
from traceback import format_exc
import base64
from pydantic import BaseModel
import traceback
from pydantic import BaseModel, validator
from packetserver.http.dependencies import get_current_http_user
from packetserver.http.auth import HttpUser
@@ -253,4 +253,97 @@ async def create_binary_object(
private=new_object.private,
created_at=new_object.created_at,
modified_at=new_object.modified_at
)
class ObjectUpdate(BaseModel):
name: Optional[str] = None
private: Optional[bool] = None
data_text: Optional[str] = None # New: update to text content (forces binary=False)
data_base64: Optional[str] = None # New: update to binary content (forces binary=True)
@validator('data_text', 'data_base64', pre=True, always=True)
def check_mutually_exclusive(cls, v, values, field):
other_field = 'data_base64' if field.name == 'data_text' else 'data_text'
if v is not None and values.get(other_field) is not None:
raise ValueError('data_text and data_base64 cannot be provided together')
return v
@router.patch("/objects/{uuid}", response_model=ObjectSummary)
async def update_object(
uuid: UUID,
payload: ObjectUpdate,
db: DbDependency,
current_user: HttpUser = Depends(get_current_http_user)
):
username = current_user.username
if all(v is None for v in [payload.name, payload.private, payload.data_text, payload.data_base64]):
raise HTTPException(status_code=400, detail="No updates provided")
try:
with db.transaction() as conn:
root = conn.root()
obj = Object.get_object_by_uuid(uuid, root)
if not obj:
raise HTTPException(status_code=404, detail="Object not found")
user = User.get_user_by_username(username, root)
if not user or user.uuid != obj.owner:
raise HTTPException(status_code=403, detail="Not authorized to modify this object")
updated = False
if payload.name is not None:
new_name = payload.name.strip()
if len(new_name) > 300:
raise HTTPException(status_code=400, detail="Object name too long (max 300 chars)")
if not new_name:
raise HTTPException(status_code=400, detail="Invalid object name")
obj.name = new_name
updated = True
if payload.private is not None:
obj.private = payload.private
updated = True
if payload.data_text is not None:
if not payload.data_text:
raise HTTPException(status_code=400, detail="Text content cannot be empty")
obj.data = payload.data_text # str → forces binary=False, calls touch()
updated = True
if payload.data_base64 is not None:
try:
content = base64.b64decode(payload.data_base64, validate=True)
except Exception:
raise HTTPException(status_code=400, detail="Invalid base64 encoding")
if not content:
raise HTTPException(status_code=400, detail="Binary content cannot be empty")
obj.data = content # bytes → forces binary=True, calls touch()
updated = True
if not updated:
raise HTTPException(status_code=400, detail="No valid updates applied")
logging.info(f"User {username} updated object {uuid}")
except HTTPException:
raise
except Exception as e:
logging.error(f"Object update failed for {username} on {uuid}: {e}\n{traceback.format_exc()}")
raise HTTPException(status_code=500, detail="Failed to update object")
content_type, _ = mimetypes.guess_type(obj.name)
if content_type is None:
content_type = "application/octet-stream" if obj.binary else "text/plain"
return ObjectSummary(
uuid=obj.uuid,
name=obj.name,
binary=obj.binary,
size=obj.size,
content_type=content_type,
private=obj.private,
created_at=obj.created_at,
modified_at=obj.modified_at
)