Skip to content

Choosing a group picture replaced your own avatar (GRYT-1182) - #185

Merged
sivert-io merged 1 commit into
mainfrom
claude/GRYT-1182-group-picture-not-avatar
Sep 15, 2026
Merged

sivert-io merged 1 commit into
mainfrom
claude/GRYT-1182-group-picture-not-avatar

Conversation

@sivert-io

@sivert-io sivert-io commented Sep 15, 2026 •

Copy link
Copy Markdown
Member

Review-required path: this touches src/db/sqlite/conversations.ts. It's one new read-only query, used by the media sweep.

Vikunja: GRYT-1182. Client: Gryt-chat/client#561. Mobile: Gryt-chat/mobile#214. Docs: Gryt-chat/docs#108.

What was wrong

The client and the mobile app both uploaded a group picture through POST /api/uploads/avatar. That route always sets the uploader's avatar_file_id, so choosing a picture for a group changed your own avatar on that server too. I reproduced it on main with a throwaway server and three guests. After picking the picture, the uploader's users.avatar_file_id was the group icon's file id.

What changed

  • POST /api/uploads/group-icon runs the same code as the avatar route. That handler moved into storeAvatarImage(purpose), so it's the same size limit, SVG sanitising, sharp re-encode and background resize for big animated files. The group path doesn't write the user row, and it replies { fileId, processing }.
  • It's gated on send_direct_messages, which is what dm:group:create and dm:group:update already check. The avatar route stays on upload_avatar_image.
  • The file row still records uploaded_by_server_user_id (A file token read any file, private channels and DMs included (GRYT-921) #183). So the uploader can read it and dm:group:update accepts it. The check from Older files nobody is recorded as uploading stay readable (GRYT-921) #184 is untouched.
  • The media sweep and unreferencedAmong now keep files a group uses as its icon.

Please look at

  • The media sweep. Until now a group picture only survived the sweep because it was also somebody's avatar. If you changed your avatar later, the group's picture got deleted 30 minutes on. With the new route it'd be deleted every time, so the sweep change has to land with it.
  • The permission. I went with send_direct_messages to match the group handlers. If group pictures should also need upload_avatar_image, that's a one-line change.
  • uploads.ts is mostly the handler moving into a function. git diff -w shows the real change.
  • A picture that's uploaded and then abandoned (dialog cancelled) has nothing pointing at it now, so the sweep removes it after the grace period. Before, it just stayed on as your avatar.

Tests

  • groupIconUpload.test.ts goes through the real router with filesystem storage. Raster and SVG uploads leave the avatar alone, a non-image is refused, the avatar route still sets the avatar, and the sweep keeps the file once a group wears it. I mutation-checked it: putting the avatar write back fails three tests, and dropping the sweep change fails one.
  • uploadAuthOrder.test.ts lists the new route.
  • uploadStorage.test.ts read the attachment route's source up to the next uploadsRouter.post(, which is now past the moved handler. It stops at the route's own closing line instead.
  • yarn test, yarn test:examples, yarn build, eslint and the comment check pass locally.

The client needs a server release with this route before group pictures work again. It won't fall back to the avatar route. Details are in the client PR.

Left for later

Webhook avatars look like they have the same sweep gap, and the webhook settings tab reads file_id from an upload reply that sends fileId. I found that by reading the code and haven't checked it. It's GRYT-1183.

🤖 Generated with Claude Code

Group pictures went through POST /api/uploads/avatar, which always writes
the uploader's avatar_file_id. So picking a picture for a group also made
it your avatar on that server.

There's a separate POST /api/uploads/group-icon now. It runs the same
pipeline as the avatar route (size limit, SVG sanitising, sharp, the
animated resize) but never touches the user row, and it answers
{ fileId, processing }. It's gated on send_direct_messages, which is what
dm:group:create and dm:group:update check.

The media sweep only kept files that a message or a user avatar pointed
at. Group pictures got away with that because they were also somebody's
avatar. Once they aren't, the sweep deletes them 30 minutes after upload,
so it keeps anything a group wears as its icon too.

Co-Authored-By: Claude Opus 5 <[email protected]>
@sivert-io
sivert-io merged commit db592f9 into main Sep 15, 2026
3 checks passed
@sivert-io
sivert-io deleted the claude/GRYT-1182-group-picture-not-avatar branch September 15, 2026 07:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant