Private
Public Access
Add generic file attachments to chat messages
Messages can now carry an arbitrary file (MessageFile), parallel to the existing MessageImage feature rather than a refactor of it. Files serve with Content-Disposition: attachment to force a download and prevent an uploaded HTML/SVG from executing same-origin. No content-type allowlist, same 8MB cap as images for now (a separate size-limit redesign is tracked as its own issue). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+19
-4
@@ -6,7 +6,7 @@ from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import get_db
|
||||
from app.models import ApiToken, Message, MessageImage, RoomMembership, User
|
||||
from app.models import ApiToken, Message, MessageFile, MessageImage, RoomMembership, User
|
||||
from app.services.bot_service import resolve_token
|
||||
from app.services.message_events import (
|
||||
broadcast_message_update,
|
||||
@@ -31,6 +31,7 @@ class ClientEnvelope(BaseModel):
|
||||
room_id: uuid.UUID | None = None
|
||||
content: str | None = None
|
||||
image_id: uuid.UUID | None = None
|
||||
file_id: uuid.UUID | None = None
|
||||
message_id: uuid.UUID | None = None
|
||||
emoji: str | None = None
|
||||
|
||||
@@ -110,9 +111,16 @@ async def chat_endpoint(websocket: WebSocket, db: AsyncSession = Depends(get_db)
|
||||
joined_rooms.discard(envelope.room_id)
|
||||
|
||||
elif envelope.type == "message":
|
||||
if envelope.room_id is None or (not envelope.content and envelope.image_id is None):
|
||||
if envelope.room_id is None or (
|
||||
not envelope.content
|
||||
and envelope.image_id is None
|
||||
and envelope.file_id is None
|
||||
):
|
||||
await websocket.send_json(
|
||||
{"type": "error", "detail": "room_id and content or image_id required"}
|
||||
{
|
||||
"type": "error",
|
||||
"detail": "room_id and content or image_id or file_id required",
|
||||
}
|
||||
)
|
||||
continue
|
||||
if _missing_scope(api_token, "write:messages"):
|
||||
@@ -134,8 +142,15 @@ async def chat_endpoint(websocket: WebSocket, db: AsyncSession = Depends(get_db)
|
||||
await websocket.send_json({"type": "error", "detail": "Invalid image"})
|
||||
continue
|
||||
image_id = image.id
|
||||
file_id = None
|
||||
if envelope.file_id is not None:
|
||||
message_file = await db.get(MessageFile, envelope.file_id)
|
||||
if message_file is None or message_file.room_id != envelope.room_id:
|
||||
await websocket.send_json({"type": "error", "detail": "Invalid file"})
|
||||
continue
|
||||
file_id = message_file.id
|
||||
message = await create_message(
|
||||
db, envelope.room_id, user.id, envelope.content, image_id
|
||||
db, envelope.room_id, user.id, envelope.content, image_id, file_id
|
||||
)
|
||||
await broadcast_new_message(db, broadcaster, presence, envelope.room_id, message, user)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user