A located post's author deletes it

A located post's card had no action at all, so whoever wrote one (with this device's position) could not take it back
from the app. The persona's own located card now has Delete, which asks first, without the line about other servers
(a located post never left PrivaPub), and goes through /clientapi/post/delete, since the Mastodon API looks past
located posts. The globe's end-to-end test deletes the post it writes this way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-07 02:14:53 +02:00
1 parent eade4e399a
commit e88f4afc45
5 files changed
+29 -5

No files matched your search

+1 -1
View File
@@ -285,7 +285,7 @@ supplies:
`/clientapi/post/nearby` and have no Mastodon actions), `StatusService` (publish, favourite, boost, bookmark, delete,
threads, mute, block, poll votes, emoji reactions through Pleroma's `/api/v1/pleroma/statuses/:id/reactions`, joining and
leaving remote events through `/api/privapub/v1/statuses/:id/join|leave`; located and group posts go through
`/clientapi/post/insert`), `AccountService` (profiles, relationships, follow,
`/clientapi/post/insert`, and a located post's author deletes it through `/clientapi/post/delete`, its card's one action), `AccountService` (profiles, relationships, follow,
block, mute, follow requests, `/api/v2/search`, whose handles and URLs PrivaPub resolves), `ListService` (the
persona's lists and their members; members are only accounts it follows, found with `accounts/search?following=true`),
`FilterService` (the persona's v2 filters, their keywords and filtered posts), `DiscoveryService` (hashtags and
+1
View File
@@ -49,6 +49,7 @@ namespace decePubClient;
[JsonSerializable(typeof(ViewPost))]
[JsonSerializable(typeof(List<ViewPost>))]
[JsonSerializable(typeof(InsertPostForm))]
[JsonSerializable(typeof(DeletePostForm))]
[JsonSerializable(typeof(ViewReport))]
[JsonSerializable(typeof(List<ViewReport>))]
[JsonSerializable(typeof(DomainBlockForm))]
+15 -4
View File
@@ -7,7 +7,7 @@
quoted post (Compact) has none, since it is not an item of the feed. A CDN-fronted server's post has no point, only
its CDN's group (data-cdn); data-post-at is when it was written, for the globe's Sun; data-uri is its ActivityPub id,
which the e2e tests (tests/e2e) find it by. The persona's filters apply where the page says the card is (FilterIn): a
hide filter leaves it out, a warn filter folds it behind the filter's title, a blur filter blurs its media. Located posts have no actions: PrivaPub keeps them off the Mastodon API. Nerd stats are on
hide filter leaves it out, a warn filter folds it behind the filter's title, a blur filter blurs its media. Located posts have no Mastodon actions (PrivaPub keeps them off the Mastodon API); their author deletes them through /clientapi. Nerd stats are on
the time, the author and its server's history, the place or the CDN, the media and the counts. *@
@if (!IsFilteredOut)
{
@@ -211,10 +211,20 @@
@if (Post.Located)
{
<p class="inline-flex gap-2 items-center text-xs opacity-70">
<div class="flex flex-wrap items-center gap-2 text-xs">
<p class="inline-flex gap-2 items-center opacity-70">
<i class="ion-md-locate"></i>
<span>@Localizer["{0} km from you, shown within {1} km", Number(Post.DistanceKm), Number(Post.RangeKm)]</span>
</p>
@* its author takes it back here: it has no other action *@
@if (IsOwn && !Compact)
{
<DButton Size="DSize.Xs" class="ml-auto" Disabled="IsDefaultDisabled || isActing" OnClick="Delete" data-located-delete>
<i aria-hidden="true" class="ion-md-trash text-error"></i>
<span>@Localizer["Delete"]</span>
</DButton>
}
</div>
}
@if (status.Privapub?.Approval is "pending" or "rejected")
@@ -949,11 +959,12 @@
async Task Delete()
{
if (!await Confirm.Ask(Localizer["Delete this post?"], Localizer["It goes from here, and the servers that have it are asked to delete it too."],
// a located post never left PrivaPub: no server is asked
if (!await Confirm.Ask(Localizer["Delete this post?"], Post.Located ? null : Localizer["It goes from here, and the servers that have it are asked to delete it too."],
Localizer["Delete"], true))
return;
isActing = true;
var result = await StatusService.Delete(Status.Id);
var result = Post.Located ? await StatusService.DeleteLocated(Status.Id, CascadingState.PersonaId) : await StatusService.Delete(Status.Id);
isActing = false;
if (result.IsInvalid)
{
+1
View File
@@ -33,6 +33,7 @@ public static class APIs
public const string GET_Posts = "clientapi/post/list";
public const string GET_Nearby = "clientapi/post/nearby";
public const string POST_InsertPost = "clientapi/post/insert";
public const string POST_DeletePost = "clientapi/post/delete";
public const string GET_ModeratorReports = "clientapi/moderator/reports";
public static string POST_M_ResolveReport(string reportId) => $"clientapi/moderator/reports/{Uri.EscapeDataString(reportId)}/resolve";
public const string GET_AdminDomainBlocks = "clientapi/admin/domainblocks/list";
+11
View File
@@ -228,6 +228,17 @@ namespace decePubClient.Services
return await StatusResult(response, cancellationToken);
}
/// <summary>Deletes the persona's own located post: through /clientapi, since the Mastodon API looks past located posts.</summary>
public async Task<WebResult> DeleteLocated(string postId, string personaId, CancellationToken cancellationToken = default)
{
using var response = await httpService.Post(APIs.ClientApi.POST_DeletePost, new DeletePostForm { AvatarId = personaId, PostId = postId },
cancellationToken: cancellationToken);
if (!response.IsSuccessStatusCode)
return await response.Content.ReadWebResult(response.StatusCode, localizer, cancellationToken);
return new WebResult();
}
public async Task<Status> Get(string statusId, CancellationToken cancellationToken = default)
{
using var response = await httpService.Get(APIs.Mastodon.GET_M_Status(statusId), cancellationToken: cancellationToken);