One Post model for every visibility, read from notes the way peers write them
Post gains what federation and the Mastodon API need: Visibility (Public, Unlisted, FollowersOnly, Direct, Circle, LocalGeo), the author's account id, to/cc, the Create's id, url, context, quote, InReplyToURI and the parent's author, a separate SpoilerText next to the title, language, mentions, hashtags, remote attachments (alt text, blurhash, focus, size), reply/favourite/reblog counters, revisions, EditedAt and DeletedAt. Direct messages are Posts with Visibility Direct and a ConversationId; migration _005 copies DmPost rows across with their ids and fills the new fields of existing posts. DmPost is left in place so a rollback still sees the old messages. Inbound: - NoteParser reads Note, Article, Page, Question and media types: content, then contentMap, then _misskey_content; summary as the spoiler and name as the title; Mention and Hashtag tags; attachments; a PeerTube-style list attribution prefers the person over the channel; quote URIs. - Addressing classifies like Mastodon, finding followers-only by the author's own followers URL (now stored on ForeignAvatar), not by a "/followers" suffix. - Create keeps a post when a local persona is addressed or mentioned, when it replies to a local post (the parent's reply count goes up) or when a community it follows is addressed; an unsolicited public post is not stored. Update keeps the previous version as a revision. The outbox and object endpoints serve only Public and Unlisted posts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
This commit is contained in:
1 parent
796f06acf4
commit
e9529310fe
21 files changed
+777
-192
No files matched your search
@@ -0,0 +1,67 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
using PrivaPub.Models.Post;
|
||||
using PrivaPub.Models.User;
|
||||
|
||||
namespace PrivaPub.Infrastructure.Data.Migrations
|
||||
{
|
||||
public class _005_direct_posts_join_posts : IMigration
|
||||
{
|
||||
public async Task UpgradeAsync()
|
||||
{
|
||||
foreach (var dm in await DB.Default.Find<DmPost>().ExecuteAsync())
|
||||
{
|
||||
if (await DB.Default.Find<Post>().Match(p => p.ID == dm.ID || p.ObjectURI == dm.ObjectURI).ExecuteAnyAsync())
|
||||
continue;
|
||||
await DB.Default.SaveAsync(new Post
|
||||
{
|
||||
ID = dm.ID,
|
||||
GroupUserId = dm.GroupUserId,
|
||||
AuthorAccountId = dm.IsFederatedCopy ? await ForeignAccountId(dm.ActorURI) : dm.GroupUserId,
|
||||
ConversationId = dm.GroupId,
|
||||
Visibility = PostVisibility.Direct,
|
||||
AnsweringToPostId = Local(dm.AnsweringToPostId),
|
||||
InReplyToURI = Remote(dm.AnsweringToPostId),
|
||||
Title = dm.Title,
|
||||
Text = dm.Text,
|
||||
ContentHtml = dm.ContentHtml,
|
||||
ContentFormat = dm.ContentFormat,
|
||||
Media = dm.Media,
|
||||
HasContentWarning = dm.HasContentWarning,
|
||||
IsFederatedCopy = dm.IsFederatedCopy,
|
||||
ObjectURI = dm.ObjectURI,
|
||||
ActorURI = dm.ActorURI,
|
||||
Url = dm.ObjectURI,
|
||||
CreationDate = dm.CreationDate,
|
||||
UpdateDate = dm.UpdateDate
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var post in await DB.Default.Find<Post>().Match(p => p.Visibility != PostVisibility.Direct).ExecuteAsync())
|
||||
{
|
||||
var parent = Local(post.AnsweringToPostId) is { } parentId
|
||||
? await DB.Default.Find<Post>().OneAsync(parentId)
|
||||
: Remote(post.AnsweringToPostId) is { } parentUri
|
||||
? await DB.Default.Find<Post>().Match(p => p.ObjectURI == parentUri).ExecuteFirstAsync()
|
||||
: default;
|
||||
await DB.Default.Update<Post>().MatchID(post.ID)
|
||||
.Modify(p => p.Visibility, post.IsLocalOnly ? PostVisibility.Circle : PostVisibility.Public)
|
||||
.Modify(p => p.AuthorAccountId, post.IsFederatedCopy ? await ForeignAccountId(post.ActorURI) : post.GroupUserId)
|
||||
.Modify(p => p.AnsweringToPostId, parent?.ID)
|
||||
.Modify(p => p.InReplyToURI, parent?.ObjectURI ?? Remote(post.AnsweringToPostId))
|
||||
.Modify(p => p.InReplyToAccountId, parent?.AuthorAccountId ?? parent?.GroupUserId)
|
||||
.Modify(p => p.Url, post.Url ?? post.ObjectURI)
|
||||
.ExecuteAsync();
|
||||
}
|
||||
}
|
||||
|
||||
static string Local(string reference) =>
|
||||
string.IsNullOrEmpty(reference) || reference.Contains("://") ? default : reference;
|
||||
|
||||
static string Remote(string reference) =>
|
||||
reference != default && reference.Contains("://") ? reference : default;
|
||||
|
||||
static async Task<string> ForeignAccountId(string actorUri) =>
|
||||
(await DB.Default.Find<ForeignAvatar>().Match(a => a.ActorURI == actorUri).ExecuteFirstAsync())?.ID;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user