Private
Public Access
Add image uploads in chat messages (Gitea issue #10)
Images live on the app server's local disk (uploads/), served through an authenticated, room-membership-gated endpoint since rooms can be private. Uploads are streamed with a byte-count cap, validated as genuine decodable images with Pillow (not just a spoofed Content-Type), and downscaled to 2000px on the longer side (except GIF, to preserve animation). Backend: MessageImage model + nullable Message.content/image_id with a content-or-image CheckConstraint, upload/serve endpoints in rooms.py, WS message envelope gains image_id, push notification body says "sent an image" for image-only messages. Frontend: Composer gets an attach button with upload progress and a thumbnail chip; MessageList renders images inline with a click-to-zoom ImageLightbox.
This commit is contained in:
+14
-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, RoomMembership, User
|
||||
from app.models import ApiToken, MessageImage, RoomMembership, User
|
||||
from app.services.bot_service import resolve_token
|
||||
from app.services.message_events import broadcast_message_update, broadcast_new_message
|
||||
from app.services.message_service import (
|
||||
@@ -25,6 +25,7 @@ class ClientEnvelope(BaseModel):
|
||||
type: str
|
||||
room_id: uuid.UUID | None = None
|
||||
content: str | None = None
|
||||
image_id: uuid.UUID | None = None
|
||||
message_id: uuid.UUID | None = None
|
||||
|
||||
|
||||
@@ -103,9 +104,9 @@ 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:
|
||||
if envelope.room_id is None or (not envelope.content and envelope.image_id is None):
|
||||
await websocket.send_json(
|
||||
{"type": "error", "detail": "room_id and content required"}
|
||||
{"type": "error", "detail": "room_id and content or image_id required"}
|
||||
)
|
||||
continue
|
||||
if _missing_scope(api_token, "write:messages"):
|
||||
@@ -120,7 +121,16 @@ async def chat_endpoint(websocket: WebSocket, db: AsyncSession = Depends(get_db)
|
||||
{"type": "error", "detail": "Not a member of this room"}
|
||||
)
|
||||
continue
|
||||
message = await create_message(db, envelope.room_id, user.id, envelope.content)
|
||||
image_id = None
|
||||
if envelope.image_id is not None:
|
||||
image = await db.get(MessageImage, envelope.image_id)
|
||||
if image is None or image.room_id != envelope.room_id:
|
||||
await websocket.send_json({"type": "error", "detail": "Invalid image"})
|
||||
continue
|
||||
image_id = image.id
|
||||
message = await create_message(
|
||||
db, envelope.room_id, user.id, envelope.content, image_id
|
||||
)
|
||||
await broadcast_new_message(db, broadcaster, presence, envelope.room_id, message, user)
|
||||
|
||||
elif envelope.type == "edit":
|
||||
|
||||
Reference in New Issue
Block a user