Fedibook Code Review 1 — Follow-up

I have worked through parts of the findings in code review 1. Here is a follow up report.

Reviewed against the current fix/review-1 branch (2026-04-05, updated 2026-04-05). This document goes through every item in the original review and records its current status.


Priority List Items

✅ 1. Mixed-language error messages

All Danish user-facing strings have been replaced with English. The fix covered:

  • service/post.go — 3 strings (post content, image count)
  • service/friendship.go — 3 strings (user not found, self-request, duplicate request) + 1 notification message
  • service/group.go — 1 string (member not found)
  • handler/post.go — 1 "Konflikt" title + Danish body
  • handler/friendship.go — 1 "Konflikt" title

The only remaining Danish text is in service/fbimport.go (Facebook export category keys), which is correct — those are data values, not error messages.

✅ 2. AP actor URL duplication

activitypub/urls.go provides UserActorURL, GroupActorURL, UserKeyID, GroupKeyID, and friends. These are now used consistently in handler/activitypub.go, handler/user.go, service/ap.go, service/ap_group.go, and internal/worker/handlers.go.

One remaining inline construction: handler/webfinger.go still assembles actor URLs by hand in two places (lines 69 and 124): "https://" + instanceDomain + "/ap/users/" + profile.Username. The rest of the codebase uses the helpers.

✅ 3. Worker main.go decomposition

All 12 task handler implementations have been moved to internal/worker/handlers.go. The Handlers struct holds the shared dependencies; each handler is a method with the asynq.HandlerFunc signature, so registration is a clean mux.HandleFunc(type, h.MethodName) call. cmd/worker/main.go is now 106 lines (down from ~900) and contains only startup, wiring, and the mux registrations.

✅ 4. Middleware error format

internal/middleware/problem.go was created with a local writeProblem function that sets Content-Type: application/problem+json and encodes a proper RFC 7807 struct. All five http.Error() calls in jwt.go and the one in ratelimit.go were replaced with calls to this helper.

✅ 5. fediverse_handle display bug

postResponse (handler/post.go:586) and commentResponse (handler/post.go:607) now call fediverseHandle(username, isLocal, instanceDomain) instead of the inline "@" + username + "@" + domain formula. Remote users (whose username is already stored as user@domain) now produce the correct @user@domain handle.


Consistency

✅ Mixed-language error messages

See priority item 1 above.

✅ Auth middleware uses a different error format

See priority item 4 above.

⬜ Inconsistent limit parsing

Not addressed. Two issues remain:

  • handler/user.go Search (lines 204–208): uses a bare strconv.Atoi(l) with no upper-bound check. parseCursorParams in handler/post.go:662 enforces 0 < limit <= 100; this handler does not.
  • handler/friendship.go List (lines 51–52) and ListRequests (lines 85–86): call _, limit := parseCursorParams(r) but then re-read the after cursor manually with r.URL.Query().Get("after"). The after value from parseCursorParams is discarded.

⬜ Group actor document is a raw map

Not addressed. handler/activitypub.go:GroupActor still builds the actor document as map[string]any (lines 277–303), while WebFingerHandler.GetActor uses the typed activitypub.Actor struct. The activitypub.Actor struct also still lacks an Image field for cover photos.

NodeInfo ignores registration mode

Fixed. APHandler now has an openRegistrations bool field set at construction time. cmd/api/main.go passes cfg.RegistrationMode == "open" so instances in "approval" or "closed" mode correctly advertise false.


Complexity

✅ In-function regexp compilation

Fixed. A package-level var slugifyRE = regexp.MustCompile("[^a-z0-9]+") was added alongside the existing handleRE in service/group.go. slugifyHandle now calls slugifyRE.ReplaceAllString(...) instead of compiling on every invocation.

✅ Worker main.go decomposition

See priority item 3 above.

actorURLsToMentions vs actorURLsToMentionMaps duplication

Not addressed. Both functions still exist with equivalent logic but different return types:

  • actorURLsToMentions in internal/worker/handlers.go:795 returns []activitypub.Mention
  • actorURLsToMentionMaps in service/ap_group.go:1335 returns []map[string]string

The underlying divergence — that ap_group.go builds AP payloads as map[string]any while the worker uses typed structs — is what forces the duplication.

⬜ Opaque group membership status encoding

Not addressed. repository/group.go still concatenates role || '_' || status in SQL (line 64) to produce strings like "member_pending" and "admin_invited". The service layer still decodes these with a switch statement. A typo in either the SQL or the switch silently falls through to "none".


Structure

✅ Duplicate constructors

Fixed. NewPostHandler and NewGroupHandler (the short forms, neither of which had any call sites) have been deleted. Only NewPostHandlerWithUploadDir and NewGroupHandlerWithUploadDir remain, which is what cmd/api/main.go already used.

✅ HTTP signature verification is duplicated

Fixed. A readAndVerifyBody(w, r, logTag string) ([]byte, bool) method was extracted on APHandler. Both Inbox and GroupInbox now delegate to it, replacing the 10-line duplicated block in each.

⬜ Setter methods for optional dependencies

Not addressed. SetAPMemberInviter, SetGroupResolver, SetGroupFinder, SetGroupJoiner, SetLocalInviter in handler/group.go, handler/webfinger.go, service/ap_group.go, and service/channel.go still assign without validating. A missing call produces a nil-pointer panic at the first relevant request, not a startup error.

UserIDFromContext wrapper

Not addressed. handler/auth.go:267–270 still re-exports middleware.UserIDFromContext as a handler-package function. All handler files call the wrapper rather than the original directly.


Dead Code and Duplication

summaryMap vs userSummaryResponse

Not addressed. Both functions exist in the handler package and produce structurally identical map[string]any objects for a user summary:

  • summaryMap(u *domain.UserSummary)handler/friendship.go:306
  • userSummaryResponse(u *domain.User, instanceDomain string)handler/user.go:294

fediverse_handle display bug

See priority item 5 above.

⚠️ AP actor URL constructed in five places

Partially addressed. The activitypub URL helpers are used in most places. The remaining exception is handler/webfinger.go, which still inlines the URL construction at two points (lines 69 and 124). All other files (handler/activitypub.go, handler/user.go, service/ap.go, service/ap_group.go, internal/worker/handlers.go) use activitypub.UserActorURL and activitypub.GroupActorURL.

processGroupAnnounce legacy path

Not addressed. service/ap.go:775 still contains the handler for the old Announce{Create{Note}} format. The comment notes it is kept for in-flight activities. No deprecation window or removal ticket exists.


ActivityPub Specifics

⚠️ Scattered AP URL construction

Same status as the dead-code item above — mostly resolved, with handler/webfinger.go as the remaining outlier.

⬜ Fragile fallback URL in ServeNote

Not addressed. handler/activitypub.go:ServeNote still reconstructs the actor URL from the username when post.Author.APID is nil, using the Mastodon-specific /users/ path segment. The correct fix is to ensure UpsertRemoteUser always populates ap_id, removing the need for the fallback entirely.

openRegistrations hardcoded

Same as the NodeInfo consistency item above — fixed.


Summary

Status Count Items
✅ Fixed 9 Mixed-language strings, AP actor URL (mostly), Worker decomposition, Middleware format, fediverse_handle bug, NodeInfo registration, Regexp compilation, Duplicate constructors, HTTP sig duplication
⚠️ Partial 2 AP actor URL (webfinger.go still inlines), processGroupAnnounce (no removal plan)
⬜ Outstanding 9 Limit parsing, Group actor map, actorURLsToMentions duplication, Group membership encoding, Setter nil guards, UserIDFromContext wrapper, summaryMap duplication, ServeNote fallback URL, Legacy announce path

All five priority-list items and four further quick wins are resolved. No correctness bugs remain among the outstanding items — they are all structural or consistency concerns.