Build / Build (push) Successful in 51s
Starts the completion program (SocialPub docs/ROADMAP.md, "Owner decisions on finishing everything", 2026-10-17). - The localizer's namespace is decePubClient.Services; collAnon is gone from the repo. - Dead code: PagesBase, VConstants, SettingsOptions, ViewLanguage, unused APIs routes, InstanceService.Self, RequestStats.LastOf/AverageMs, PostAnon/DeleteTotallyAnon, unused AppStatusService methods and their main.js helpers (the app badge stays for Web Push), ExtensionMethods/SUtility leftovers (Markdig and BlazorZXingJs packages go), unused PublicCacheData/PageSettings/AppConfiguration fields, commented-out code. - The client log in IndexedDB, which nothing ever read, goes with the whole IndexedDB layer (DnetIndexedDb, IStorage): logs go to the browser console; main.js deletes the old "data" database once. - The solution names the real ClientModels project. Unused vendor assets (fontawesome, solid, open-iconic, rxjs) go. - robots.txt disallows everything; manifest.json and index.html say decePub; the service worker's cache name follows each publish's assets version, so old caches are purged. - MessageType is Visibility (member names kept: they are resx keys); ExpandMessage.razor is Thread.razor (same route). - i18n: dead keys removed, Italian "camera" is "punto di vista", DInputBase/DCalendar validation messages come from ErrorMessages, DataTransfer's spinner has its label. - CLAUDE.md follows: client storage, JS interop, PWA, the D fields' messages. Verified: dotnet build decePubClient.csproj and decePubClient.sln -c Release (0 errors, no new warnings); a Pasture publish served by the pasture's decepub peer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WpVCoDeM1qUTRp4fXQdfof
97 lines
5.1 KiB
C#
97 lines
5.1 KiB
C#
using System.Net;
|
|
|
|
using decePubClient.Extensions;
|
|
using decePubClient.Models;
|
|
|
|
using PrivaPub.ClientModels;
|
|
using PrivaPub.ClientModels.Group;
|
|
|
|
namespace decePubClient.Services
|
|
{
|
|
/// <summary>The persona's communities and circles (/clientapi/group): made, joined by invitation, edited and left;
|
|
/// for their owners and moderators, who is in them and who asks to join.</summary>
|
|
public class GroupService(IHttpService httpService, ILoggingService logger, CoalescingStringLocalizer localizer)
|
|
{
|
|
/// <summary>The persona's groups, communities first; empty on failure.</summary>
|
|
public async Task<List<ViewGroup>> List(string personaId, CancellationToken cancellationToken = default)
|
|
{
|
|
if (personaId is null)
|
|
return [];
|
|
|
|
using var response = await httpService.Get(APIs.ClientApi.GET_Groups, [$"avatarId={Uri.EscapeDataString(personaId)}"],
|
|
cancellationToken: cancellationToken);
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
var result = await response.Content.ReadWebResult(response.StatusCode, localizer, cancellationToken);
|
|
if (result.StatusCode is not (int)HttpStatusCode.Gone)
|
|
await logger.ProcessWarning(result.ToString(localizer), $"{nameof(GroupService)}.{nameof(List)}", cancellationToken);
|
|
return [];
|
|
}
|
|
|
|
var groups = await response.DefaultReadFromJsonAsync<List<ViewGroup>>(cancellationToken) ?? [];
|
|
return [.. groups.OrderByDescending(g => g.IsCommunity).ThenBy(g => g.Name ?? g.UserName)];
|
|
}
|
|
|
|
/// <summary>Makes a community or a circle owned by the persona; Data is the ViewGroup.</summary>
|
|
public Task<WebResult> Insert(InsertGroupForm form, CancellationToken cancellationToken = default) =>
|
|
GroupResult(APIs.ClientApi.POST_InsertGroup, form, cancellationToken);
|
|
|
|
/// <summary>Changes a group the persona runs; Data is the ViewGroup.</summary>
|
|
public Task<WebResult> Update(UpdateGroupForm form, CancellationToken cancellationToken = default) =>
|
|
GroupResult(APIs.ClientApi.POST_UpdateGroup, form, cancellationToken);
|
|
|
|
/// <summary>Joins a group by its invitation (and its password, when it has one); Data is the ViewGroup.</summary>
|
|
public Task<WebResult> Join(JoinGroupForm form, CancellationToken cancellationToken = default) =>
|
|
GroupResult(APIs.ClientApi.POST_JoinGroup, form, cancellationToken);
|
|
|
|
public Task<WebResult> Leave(string personaId, string groupId, CancellationToken cancellationToken = default) =>
|
|
Membership(APIs.ClientApi.POST_LeaveGroup, personaId, groupId, null, cancellationToken);
|
|
|
|
/// <summary>Lets in someone who asked to join a group the persona runs.</summary>
|
|
public Task<WebResult> Approve(string personaId, string groupId, string actorUri, CancellationToken cancellationToken = default) =>
|
|
Membership(APIs.ClientApi.POST_ApproveGroupMember, personaId, groupId, actorUri, cancellationToken);
|
|
|
|
/// <summary>Declines a request to join; the asker's server is told.</summary>
|
|
public Task<WebResult> Reject(string personaId, string groupId, string actorUri, CancellationToken cancellationToken = default) =>
|
|
Membership(APIs.ClientApi.POST_RejectGroupMember, personaId, groupId, actorUri, cancellationToken);
|
|
|
|
/// <summary>Takes a member out of a group the persona runs.</summary>
|
|
public Task<WebResult> Remove(string personaId, string groupId, string actorUri, CancellationToken cancellationToken = default) =>
|
|
Membership(APIs.ClientApi.POST_RemoveGroupMember, personaId, groupId, actorUri, cancellationToken);
|
|
|
|
/// <summary>Who is in a group the persona runs, and who asks to join; empty on failure.</summary>
|
|
public async Task<List<ViewGroupMember>> Members(string personaId, string groupId, CancellationToken cancellationToken = default)
|
|
{
|
|
using var response = await httpService.Get(APIs.ClientApi.GET_GroupMembers,
|
|
[$"avatarId={Uri.EscapeDataString(personaId)}", $"groupId={Uri.EscapeDataString(groupId)}"], cancellationToken: cancellationToken);
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
var result = await response.Content.ReadWebResult(response.StatusCode, localizer, cancellationToken);
|
|
await logger.ProcessWarning(result.ToString(localizer), $"{nameof(GroupService)}.{nameof(Members)}", cancellationToken);
|
|
return [];
|
|
}
|
|
|
|
return await response.DefaultReadFromJsonAsync<List<ViewGroupMember>>(cancellationToken) ?? [];
|
|
}
|
|
|
|
async Task<WebResult> GroupResult(string route, object form, CancellationToken cancellationToken)
|
|
{
|
|
using var response = await httpService.Post(route, form, cancellationToken: cancellationToken);
|
|
if (!response.IsSuccessStatusCode)
|
|
return await response.Content.ReadWebResult(response.StatusCode, localizer, cancellationToken);
|
|
|
|
return new WebResult { Data = await response.DefaultReadFromJsonAsync<ViewGroup>(cancellationToken) };
|
|
}
|
|
|
|
async Task<WebResult> Membership(string route, string personaId, string groupId, string actorUri, CancellationToken cancellationToken)
|
|
{
|
|
using var response = await httpService.Post(route, new GroupMembershipForm { AvatarId = personaId, GroupId = groupId, MemberActorURI = actorUri },
|
|
cancellationToken: cancellationToken);
|
|
if (!response.IsSuccessStatusCode)
|
|
return await response.Content.ReadWebResult(response.StatusCode, localizer, cancellationToken);
|
|
|
|
return new WebResult();
|
|
}
|
|
}
|
|
}
|