Object patcher
This commit is contained in:
@@ -7,8 +7,8 @@ import mimetypes
|
|||||||
import logging
|
import logging
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
import base64
|
import base64
|
||||||
|
import traceback
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, validator
|
||||||
|
|
||||||
from packetserver.http.dependencies import get_current_http_user
|
from packetserver.http.dependencies import get_current_http_user
|
||||||
from packetserver.http.auth import HttpUser
|
from packetserver.http.auth import HttpUser
|
||||||
@@ -253,4 +253,97 @@ async def create_binary_object(
|
|||||||
private=new_object.private,
|
private=new_object.private,
|
||||||
created_at=new_object.created_at,
|
created_at=new_object.created_at,
|
||||||
modified_at=new_object.modified_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
|
||||||
)
|
)
|
||||||
Reference in New Issue
Block a user