Conversations page, and know what is unread
/api/v1/conversations answered one page, never unread, its read endpoint did nothing, and DELETE was missing; each conversation cost a query per member. Now each conversation keeps its newest post (DmGroup.LastPostId, set as posts arrive, learnt once by migration _013) and pages by it as Mastodon does, and each persona's ConversationState holds what it read and what it took off its list: - unread when someone else wrote last, after what the persona read; - read marks it so, and writing in a conversation reads it; - DELETE takes it off the list until a newer message brings it back. The list reads its states, newest posts, members and accounts in a few queries per page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
ea5e607779
commit
17922f4dfd
11 files changed
+214
-27
No files matched your search
@@ -146,6 +146,43 @@ namespace PrivaPub.Tests.Http
|
||||
Assert.DoesNotContain(answer, (await stranger.Client.Get($"/api/v1/accounts/{bob.Id}/statuses")).Ok().Ids);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Conversations_page_by_their_newest_message_and_know_what_is_unread_read_and_removed()
|
||||
{
|
||||
var alice = await _host.Mastodon("alice");
|
||||
var bob = await _host.Mastodon("bob");
|
||||
var carol = await _host.Mastodon("carol");
|
||||
await bob.Status($"@{alice.UserName} first", ("visibility", "direct"));
|
||||
var fromCarol = (await carol.Status($"@{alice.UserName} second", ("visibility", "direct"))).Text("id");
|
||||
// writing in a conversation reads it
|
||||
await alice.Status($"@{bob.UserName} third", ("visibility", "direct"));
|
||||
async Task<JsonArray> List() => (await alice.Client.Get("/api/v1/conversations")).Ok().Array;
|
||||
|
||||
var all = await List();
|
||||
Assert.Equal(2, all.Count);
|
||||
var (withBob, withCarol) = (all[0].Text("id"), all[1].Text("id"));
|
||||
Assert.Equal(new[] { bob.Id }, all[0]!["accounts"]!.AsArray().Select(a => a.Text("id")));
|
||||
Assert.False(all[0].Flag("unread"));
|
||||
Assert.True(all[1].Flag("unread"));
|
||||
Assert.Equal(fromCarol, all[1]!["last_status"].Text("id"));
|
||||
|
||||
var page = (await alice.Client.Get("/api/v1/conversations?limit=1")).Ok();
|
||||
Assert.Equal(withBob, Assert.Single(page.Array).Text("id"));
|
||||
Assert.Equal(withCarol, Assert.Single((await alice.Client.Get(page.Link("next"))).Ok().Array).Text("id"));
|
||||
|
||||
Assert.False((await alice.Client.Post($"/api/v1/conversations/{withCarol}/read")).Ok().Body.Flag("unread"));
|
||||
Assert.False((await List()).Single(c => c.Text("id") == withCarol).Flag("unread"));
|
||||
|
||||
// off the list until a newer message, which brings it back unread; carol's own list never changed
|
||||
(await alice.Client.Delete($"/api/v1/conversations/{withCarol}")).Ok();
|
||||
Assert.Equal(new[] { withBob }, (await List()).Select(c => c.Text("id")));
|
||||
Assert.Equal(withCarol, Assert.Single((await carol.Client.Get("/api/v1/conversations")).Ok().Array).Text("id"));
|
||||
var again = (await carol.Status($"@{alice.UserName} are you there?", ("visibility", "direct"))).Text("id");
|
||||
var back = (await List())[0];
|
||||
Assert.Equal((withCarol, again, true), (back.Text("id"), back!["last_status"].Text("id"), back.Flag("unread")));
|
||||
Assert.Equal(HttpStatusCode.NotFound, (await bob.Client.Delete($"/api/v1/conversations/{withCarol}")).Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Markers_are_saved_versioned_and_read_back_per_timeline()
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user