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:
2026-08-14 12:21:42 -06:00
parent 559adf9b7e
commit f2a59f798b
27 changed files with 829 additions and 40 deletions
+14
View File
@@ -107,6 +107,7 @@ sudo apt install -y python3 python3-venv nodejs npm git
```bash
sudo useradd --system --shell /usr/sbin/nologin --home-dir /srv/chatapp --create-home chatapp
sudo chown chatapp:chatapp /srv/chatapp
sudo -u chatapp mkdir -p /srv/chatapp/uploads
```
### 3b. Clone the repo (deploy key, not a personal token)
@@ -278,6 +279,13 @@ producing a gzipped `pg_dump` in `/var/backups/chatapp/` with 14-day local
rotation. Off-box shipping is a placeholder in that script (commented-out
rsync/S3 examples) — decide where those need to go and fill it in.
That script covers Postgres only. Uploaded chat images live on the **app**
server's disk (`/srv/chatapp/uploads`, created in §3a) — a separate machine
from the data server this script runs on — and currently have no backup
mechanism at all. Whatever off-box destination you pick above, include
`/srv/chatapp/uploads` in it too (e.g. a second `rsync` line run from the
app server).
**Test a restore** (against a scratch database, never directly onto
`chatapp`):
@@ -320,6 +328,12 @@ scope decisions" for the full detail on each):
re-validated per delivery (DNS-rebinding gap).
- Backup off-box shipping is a placeholder — decide a destination and fill
in `deploy/backup-postgres.sh`.
- Uploaded chat images (`/srv/chatapp/uploads` on the app server) have no
backup coverage at all yet, on-box or off — see §7.
- Uploaded-but-never-sent images (a user attaches a file, then never hits
Send) leak an orphaned file on disk — no cleanup job for this yet. Not a
security issue (still gated by room membership to view), just an eventual
disk-space housekeeping item.
None of these are new to this phase — deploying doesn't change any of them,
just makes them reachable from the internet instead of localhost, which is