Repository navigation
Show fediverse replies to entries as comments - #494
Conversation
Replies from Mastodon and other fediverse servers were dropped: the inbox only understood Follow, Undo and Delete. A public Note or Article in reply to one of a journal's entries is now stored and shown with the entry's comments, when federation is on for the site and the journal, the entry is publicly visible and takes comments, and the signed sender wrote it. Replies live in a new activitypub_reply table (V1_30) rather than in comments: comments.uid is a local user that many places dereference, its text columns are latin1 and its id is a SMALLINT, so remote replies there would break local-user code, lose emoji and could use up local comment ids. The reply's ActivityPub id is unique, so a repeated delivery stores one row; rows go with their entry by a cascading foreign key. - RemoteReplyService takes Create, Update and Delete of replies from InboxService; only the author may change or remove a reply. Deleting a remote actor, signed or gone, also removes its replies. - Content is cleaned with a jsoup Safelist down to text, paragraphs, line breaks and http(s) links with rel="nofollow ugc noopener", and capped. Author name, @user@host and profile URL come from the actor document; no remote pictures are shown. - Replies appear on the entry page, in comment counts and in the /api/comment listing (marked remote). The entry's owner can delete one through DELETE /api/comment/remote/{id}; nobody else can. - The comment notification mail moved into CommentMailService so replies reuse it; its entry link now uses the baseuri setting's value instead of the Settings object's toString. - Account deletion removes replies to the account's entries. Co-Authored-By: Claude Opus 5.5 <[email protected]>
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
Reviewer's GuideThe PR adds end-to-end support for displaying fediverse replies as comments by validating and sanitizing ActivityPub replies into dedicated storage, exposing them through pages and APIs with owner moderation controls, handling edits/deletes and actor/account cleanup, and reusing centralized comment notification mail. Sequence diagram for storing a fediverse replysequenceDiagram
participant Fediverse as Fediverse server
participant InboxService
participant RemoteReplyService
participant SafeHttpFetcher
participant RemoteReplyRepository
participant CommentMailService
Fediverse->>InboxService: Create
InboxService->>RemoteReplyService: create(user, activity, sender)
RemoteReplyService->>RemoteReplyService: validate reply and entry policy
RemoteReplyService->>SafeHttpFetcher: signedGet(actor URL, maxBytes)
SafeHttpFetcher-->>RemoteReplyService: actor document
RemoteReplyService->>RemoteReplyService: clean(content)
RemoteReplyService->>RemoteReplyRepository: saveAndFlush(reply)
RemoteReplyService->>CommentMailService: commentAdded(entry, author, subject, body)
CommentMailService-->>Fediverse: HANDLED
alt actor fetch temporarily fails
RemoteReplyService-->>Fediverse: RETRY
else validation fails or reply is not eligible
RemoteReplyService-->>Fediverse: IGNORED
end
Sequence diagram for displaying and deleting a remote replysequenceDiagram
actor Owner
participant CommentController
participant RemoteReplyRepository
participant EntryRepository
participant UsersController
UsersController->>RemoteReplyRepository: findByEntryIdOrderByPublishedAscIdAsc(entryId)
UsersController-->>Owner: Render remote replies with author profile and handle
Owner->>CommentController: DELETE /api/comment/remote/{id}
CommentController->>RemoteReplyRepository: findById(id)
CommentController->>EntryRepository: findById(reply.entryId)
CommentController->>RemoteReplyRepository: delete(reply)
CommentController-->>Owner: deleted reply id
Entity relationship diagram for stored fediverse replieserDiagram
ENTRY ||--o{ ACTIVITYPUB_REPLY : receives
ENTRY {
int id PK
}
ACTIVITYPUB_REPLY {
int id PK
int entry_id FK
string object_id UK
string actor_id
string author_name
string author_handle
string author_url
text content
datetime published
}
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
|
Overall Grade |
Security Reliability Complexity Hygiene |
Code Review Summary
| Analyzer | Status | Updated (UTC) | Details |
|---|---|---|---|
| PHP | Nov 2, 2026 7:39p.m. | Review ↗ | |
| Java | Nov 2, 2026 7:39p.m. | Review ↗ | |
| Shell | Nov 2, 2026 7:39p.m. | Review ↗ | |
| JavaScript | Nov 2, 2026 7:39p.m. | Review ↗ |
Important
AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.
CodeQL flagged Integer.parseInt in RemoteReplyService as a possible uncaught NumberFormatException. The nine-digit pattern before it rules that out, but a catch costs nothing and matches WebFingerController. DeepSource reported two "incomplete assertion" issues in InboxControllerTests on verify(httpSignatureService).verify(...): a complete Mockito check of a mocked method that is itself named verify. They are marked with skipcq rather than rewritten. Co-Authored-By: Claude Opus 5.5 <[email protected]>
jj.js hadn't changed since 2017, before DeepSource was set up. Adding a function to it made DeepSource report all of its old issues (jQuery not defined, var, alert) as introduced, failing the JavaScript check. deleteRemoteComment now lives in the entry page's own script block, and jj.js is as it was. confirmDelete still comes from jj.js, which loads before anything can be clicked. Co-Authored-By: Claude Opus 5.5 <[email protected]>
DeepSource failed the Java check on two complexity warnings. create now hands the acceptance checks, the object id check and the save to helpers of their own. The comment count link, copied into three page layouts, is one helper that also counts fediverse replies. formatEntry was already far over the limit before this branch, and the remote replies are listed inside it, so it is marked with skipcq rather than split up here. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Both were over the cyclomatic complexity limit on master. This branch only replaced their comment-count switches with appendCommentsLink, so DeepSource now reports them as introduced and fails the check. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Both branches dispatched Create, Update and Delete to their own service. Replies to our entries are now tried first; whatever RemoteReplyService ignores goes on to RemotePostService, which already skips replies to our entries. HANDLED and RETRY from the replies stop there. A deleted or gone actor loses its followers, replies and follows alike. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Replies from Mastodon and other fediverse servers to a JustJournal entry now show as comments on it. Until now the inbox dropped them.
What is accepted
A
Createof a Note or Article is stored when all of these hold:inReplyTois one of our entry object ids, for an entry owned by the inbox's user;attributedTois the signed sender;Anything else gets the 202 that other ignored activities get. Replies to replies are ignored.
@user@hostand profile URL come from the sender's actor document, fetched through the SSRF-safe fetcher. The URL must be http(s). If the fetch fails temporarily, the inbox answers 503 so the sender retries.rel="nofollow ugc noopener". Replies over 10,000 characters become escaped plain text and are cut. No remote avatars or images are shown.UpdateorDeleteit. A signed Delete of the actor removes all their replies.Where they show
GET /api/comment?entryId=, withremote,authorNameandauthorUrl.DELETE /api/comment/remote/{id}). Anyone else gets 403.CommentMailService. That also fixes the entry link in the mail, which used to print theSettingsobject rather than the base URL.Storage
They go in a new table,
activitypub_reply(V1_30), not incomments. Three things aboutcommentsruled it out:uidmust be a local user, and much code callsgetUser()on comments;bodyandsubjectare latin1, so emoji would fail to insert;SMALLINT UNSIGNED, so remote spam could use up local comment ids.The new table is utf8mb4. Its rows are deleted with their entry.
Open questions
CommentController.putlets any logged-in user edit any local comment by id.comments.summary) are dropped.Testing
RemoteReplyServiceTests(34),InboxServiceRepliesTests(5),CommentMailServiceTests(2). Extended: comment, users, account, inbox and actor controller tests.InboxServiceandInboxControllerTests. Whichever merges second will need a small conflict fix.🤖 Generated with Claude Code
Summary by Sourcery
Show validated fediverse replies as manageable comments throughout entry pages and comment APIs.
New Features:
Bug Fixes:
Enhancements:
Build:
Tests: