feat(release): Add emoji reactions support for Releases#38415
feat(release): Add emoji reactions support for Releases#38415SudhanshuMatrix wants to merge 5 commits into
Conversation
bircni
left a comment
There was a problem hiding this comment.
Findings
1. services/repository/delete.go & services/release/release.go:364 — Reactions are orphaned when a release or repo is deleted
DeleteReleaseByID deletes the release row and its attachments but never deletes release_reaction rows. Likewise, the repo-deletion bean list in services/repository/delete.go:150-175 cleans up &repo_model.Release{RepoID: repoID} but has no entry for release reactions — and can't trivially, since ReleaseReaction has no repo_id column (only release_id).
Result: every deleted release/repo leaks orphaned reaction rows forever.
Fix: add a cleanup step. In DeleteReleaseByID, delete reactions by release_id; for repo deletion, delete via a subquery/join on the repo's releases before the releases themselves are removed, e.g.:
sess.In("release_id", builder.Select("id").From("release").Where(builder.Eq{"repo_id": repoID})).Delete(&repo_model.ReleaseReaction{})A regression test asserting reactions are gone after DeleteReleaseByID would lock this in.
2. routers/web/repo/release.go:719-728 — Benign "already reacted" / forbidden errors return HTTP 500
reaction, err := release_service.CreateReleaseReaction(ctx, ctx.Doer, rel, form.Content)
if err != nil {
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.ServerError("ChangeReleaseReaction", err)
return
}
ctx.ServerError("CreateReleaseReaction", err) // <-- everything else is a 500
return
}Compare ChangeIssueReaction (routers/web/repo/issue.go), which treats ErrForbiddenIssueReaction/ErrBlockedUser as errors but logs-and-continues on the "already exists" case rather than 500ing. Here, ErrReleaseReactionAlreadyExist (easily triggered by a stale tab / double-click / concurrent request) and ErrForbiddenReleaseReaction (crafted content) both surface as a 500. Mirror the issue handler: return early only for forbidden/blocked, log.Info + break for the duplicate case.
3. routers/web/repo/release.go:99-105 — N+1 query loading reaction users
getReleaseInfos batch-loads all reactions in one query (good), but then loops per release calling relReactions.LoadUsers(...), issuing one user query per release. For a releases page with N releases this is N user queries. Minor, but the batching intent is undercut — consider collecting all user IDs across releases and loading once, or noting this as acceptable given typical page sizes.
4. models/repo/release_reaction.go (whole file) — ~330 lines largely duplicated from models/issues/reaction.go
LoadUser, RemapExternalUser, getUserIDs, valuesUser, newMigrationOriginalUser, GroupByType, HasUser, GetFirstUsers, GetMoreUserCount, and the find/create/delete logic are near-verbatim copies of the issue reaction code. newMigrationOriginalUser and valuesUser in particular already exist elsewhere. This is a maintainability concern — the two implementations will drift. Worth a maintainer discussion on whether reactions can share a generic implementation (parameterized by the owning entity) rather than copy-paste per entity type.
5. templates/repo/release/list.tmpl:39-44 & reactions.tmpl — Two reaction "add" buttons when reactions exist
The list template places add_reaction (smiley) in the top-right header, and the bottom reactions.tmpl also renders add_reaction at the end of the reaction list ({{if AllowedReactions}}). So once a release has reactions, users see two smiley pickers. For issue comments the top picker lives in a hidden dropdown, so it doesn't visibly duplicate; here both are inline. Confirm this is the intended UX, or omit the header button (or the bottom one) to avoid redundancy.
Minor / nits
models/repo/release_reaction.go:229LoadUsersdoesIn("id", userIDs)with a possibly-emptyuserIDsslice (all external authors) — same copied pattern as issues, generally safe, just flagging.- The
react/unreacthandler correctly re-fetches all reactions unfiltered for the fragment render — good, consistent with the issue flow. - Migration copyright year
2026andAssisted-byconventions look consistent with repo standards;NOT NULL DEFAULT(0)unique index matches the issue-reaction schema. - Tests are solid for the happy paths (create/duplicate/forbidden/find/delete/group). Consider adding a test for the blocked-user path in
services/releaseand the deletion-cleanup once #1 is fixed.
just a quick review with claude
|
Also, I think it is a really bad idea to duplicate the entire reactions logic. |
delvh
left a comment
There was a problem hiding this comment.
Let's not duplicate the reactions logic
|
I implemented release reactions as a separate The That said, I'd appreciate your feedback. Would you prefer a single polymorphic |
|
A new table is better and some logics could be shared? |
…ic reaction table - Modified the existing `reaction` table to add a `release_id` column, updating the composite unique index `s` accordingly. - Moved and unified all query, creation, deletion, formatting, and user loading logic for reactions into `models/repo/reaction.go`. - Refactored `models/issues/reaction.go` to type-alias `issues.Reaction` and `issues.ReactionList` to `repo.Reaction` and `repo.ReactionList`, maintaining complete backwards compatibility. - Cleaned up routers and services to use the unified model and queries. - Removed the separate `release_reaction` table, saving duplicate logic and code size.
Description
This pull request implements the emoji reactions feature for Gitea Releases, bringing feature parity with GitHub. Community members can now react to software releases using standard emojis (👍, 👎, 😄, 🎉, etc.) to show appreciation and give feedback.
Fixes #34268
Screenshots
Before
After
Key Changes
ReleaseReactionmodel, supporting database queries, creation, deletion, and user association for release-specific reactions. Added a migration (v343) to create the database table.services/release./releases/:id/reactions/:actionendpoints inrouters/weband addedChangeReleaseReactionhandler to handle interactive reactions.margin: 1.25rem 0 0.5rem;) to keep reactions clean, separated, and aligned flush to the left.initCompReactionSelectoron the releases page.Testing & Verification
models/repo/release_reaction_test.goandservices/release/release_test.go.