Communities follow FEP-1b12, circles federate to their members only
Communities: - a post addressed to a community (to, cc or audience) is accepted according to its posting policy - followers, anyone, or moderators - and GroupDistributor announces the whole activity with `audience` to the community's followers, plus the object for new posts so Mastodon shows them; updates and deletes of community content are announced too; - top-level posts are Pages with a name (the title, or a headline from the text); /flock counts members, /wardens lists moderators; - a Mastodon client posts into a community by mentioning it, or into a remote group, which sets `audience`; - an Announce of an activity from a remote group a persona follows (Lemmy) is followed through: the object is fetched from its own origin, kept with its AudienceURI, and fanned out to the group's local followers; updates are applied in place and deletes checked against the origin. Circles stop being local-only: an undiscoverable Group actor whose follows are all requests the owner approves; posts addressed to the circle and its /flock and delivered to members' own inboxes, never announced, never public; a remote member's post into the circle is accepted from members only. SignedFetchAuthorizer serves circle posts and collections only to a signed request from a member or a member server's instance actor - 404 for anyone else. Circles never surface in search, lookups, mentions, account ids or profile pages. Federation:SecureMode requires a valid signature on every GET under /peasants except the instance actor. Group forms take a posting policy. 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
0ccbcd558f
commit
a5d9a89445
32 files changed
+652
-58
No files matched your search
@@ -116,6 +116,7 @@ namespace PrivaPub.Services
|
||||
PrivateKey = privateKey,
|
||||
PublicKey = publicKey,
|
||||
Kind = form.IsCommunity ? GroupKind.Community : GroupKind.Circle,
|
||||
PostingPolicy = Policy(form.PostingPolicy) ?? PostingPolicy.Followers,
|
||||
IsDiscoverable = form.IsDiscoverable,
|
||||
ManuallyApprovesMembers = form.ManuallyApprovesMembers,
|
||||
OwnerAvatarId = form.AvatarId,
|
||||
@@ -157,6 +158,8 @@ namespace PrivaPub.Services
|
||||
group.Description = form.Description;
|
||||
if (form.IsDiscoverable.HasValue)
|
||||
group.IsDiscoverable = form.IsDiscoverable.Value;
|
||||
if (Policy(form.PostingPolicy) is { } policy)
|
||||
group.PostingPolicy = policy;
|
||||
if (form.ManuallyApprovesMembers.HasValue)
|
||||
group.ManuallyApprovesMembers = form.ManuallyApprovesMembers.Value;
|
||||
if (form.RemoveInvitationPassword)
|
||||
@@ -316,6 +319,14 @@ namespace PrivaPub.Services
|
||||
!string.IsNullOrEmpty(rootUserId) && !string.IsNullOrEmpty(avatarId)
|
||||
&& await _dbEntities.RootToAvatars.Match(ra => ra.RootId == rootUserId && ra.AvatarId == avatarId).ExecuteAnyAsync(token);
|
||||
|
||||
static PostingPolicy? Policy(string value) => value?.ToLowerInvariant() switch
|
||||
{
|
||||
"anyone" => PostingPolicy.Anyone,
|
||||
"moderators" => PostingPolicy.Moderators,
|
||||
"followers" => PostingPolicy.Followers,
|
||||
_ => (PostingPolicy?)null
|
||||
};
|
||||
|
||||
static string NewInvitationCode() => $"{Guid.NewGuid():N}{Guid.NewGuid():N}";
|
||||
|
||||
ViewGroup ToView(GroupEntity group, string avatarId)
|
||||
@@ -331,6 +342,7 @@ namespace PrivaPub.Services
|
||||
Url = actor.Uri,
|
||||
Handle = actor.Handle,
|
||||
IsCommunity = group.Kind == GroupKind.Community,
|
||||
PostingPolicy = group.PostingPolicy.ToString().ToLowerInvariant(),
|
||||
IsDiscoverable = group.IsDiscoverable,
|
||||
ManuallyApprovesMembers = group.ManuallyApprovesMembers,
|
||||
IsOwner = group.OwnerAvatarId == avatarId,
|
||||
|
||||
Reference in new issue
Block a user