Build / Build (push) Successful in 1m3s
A singleton ServerSettings (title, descriptions, contact, thumbnail, privacy policy, terms, rules, relays, media overrides, PublishActivity), cached by InstanceSettings and read by the instance API, NodeInfo, the instance actor, the web pages and the mailer; its defaults are the old literals, so nothing changes until an administrator edits. /clientapi/admin/settings (and /thumbnail), /announcements, /relays for administrators; /clientapi/moderator/log, the staff's log (AdminAction, written by domain blocks, backups, settings and the CLI; no IP, never a root paired with its personas). The Mastodon API's rules, extended description, privacy policy, terms of service, domain blocks (Published, Obfuscate), announcements (dismiss, reactions, streaming) and activity (only when published). Reports carry rule ids. Media overrides clamped to hard caps. Relays named by the settings reconcile next to the configured ones. Verified: interop.sh instance accounts relay with mastodon, relay, aoderelay and mailpit, 119 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WpVCoDeM1qUTRp4fXQdfof
94 lines
3.6 KiB
C#
94 lines
3.6 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Localization;
|
|
|
|
using MongoDB.Entities;
|
|
|
|
using PrivaPub.ClientModels;
|
|
using PrivaPub.ClientModels.Admin;
|
|
using PrivaPub.Domain.Moderation;
|
|
using PrivaPub.Extensions;
|
|
using PrivaPub.Federation.Moderation;
|
|
using PrivaPub.Models;
|
|
using PrivaPub.Models.Federation;
|
|
using PrivaPub.Resources;
|
|
|
|
namespace PrivaPub.Controllers.ClientToServer
|
|
{
|
|
[ApiController,
|
|
Route("clientapi/admin/domainblocks"),
|
|
Authorize(Policy = Policies.IsAdmin)]
|
|
public class DomainBlockController : ControllerBase
|
|
{
|
|
readonly IDomainBlocks _domainBlocks;
|
|
readonly IStringLocalizer _localizer;
|
|
readonly Domain.Media.IMediaProxy _proxy;
|
|
readonly IModerationLog _log;
|
|
|
|
public DomainBlockController(IDomainBlocks domainBlocks, IStringLocalizer<GenericRes> localizer, Domain.Media.IMediaProxy proxy, IModerationLog log)
|
|
{
|
|
_domainBlocks = domainBlocks;
|
|
_localizer = localizer;
|
|
_proxy = proxy;
|
|
_log = log;
|
|
}
|
|
|
|
[HttpGet, Route("/clientapi/admin/domainblocks/list")]
|
|
public async Task<IActionResult> List(CancellationToken token) =>
|
|
Ok((await DB.Default.Find<DomainBlock>().Sort(b => b.Domain, Order.Ascending).ExecuteAsync(token)).Select(ToView).ToList());
|
|
|
|
[HttpPost, Route("/clientapi/admin/domainblocks/insert")]
|
|
public async Task<IActionResult> Insert(DomainBlockForm form, CancellationToken token)
|
|
{
|
|
var domain = DomainBlocks.Normalise(form.Domain);
|
|
if (!ModelState.IsValid || Uri.CheckHostName(domain) != UriHostNameType.Dns)
|
|
return BadRequest(new WebResult().Invalidate(_localizer["Invalid model."]));
|
|
|
|
var block = await DB.Default.UpdateAndGet<DomainBlock>()
|
|
.Match(b => b.Domain == domain)
|
|
.Modify(b => b.Domain, domain)
|
|
.Modify(b => b.Severity, form.Suspend ? DomainBlockSeverity.Suspend : DomainBlockSeverity.Silence)
|
|
.Modify(b => b.RejectMedia, form.RejectMedia)
|
|
.Modify(b => b.PublicComment, form.PublicComment)
|
|
.Modify(b => b.PrivateComment, form.PrivateComment)
|
|
.Modify(b => b.Published, form.Published)
|
|
.Modify(b => b.Obfuscate, form.Obfuscate)
|
|
.Modify(b => b.SetOnInsert(x => x.CreatedAt, DateTime.UtcNow))
|
|
.Option(o => o.IsUpsert = true)
|
|
.ExecuteAsync(token);
|
|
await _domainBlocks.Reload(token);
|
|
// what the media proxy holds of it goes with the block
|
|
if (block.Severity == DomainBlockSeverity.Suspend || block.RejectMedia)
|
|
_proxy.Purge(domain);
|
|
await _log.Write(User.GetUserId(), AdminActions.DomainBlockInsert, AdminTargets.Domain, domain, domain,
|
|
note: string.Join(", ", new[] { block.Severity.ToString().ToLowerInvariant(), block.RejectMedia ? "media rejected" : default,
|
|
block.Published ? (block.Obfuscate ? "published, obfuscated" : "published") : default }.Where(n => n != default)), token: token);
|
|
return Ok(ToView(block));
|
|
}
|
|
|
|
[HttpPost, Route("/clientapi/admin/domainblocks/delete")]
|
|
public async Task<IActionResult> Delete([FromQuery] string domain, CancellationToken token)
|
|
{
|
|
domain = DomainBlocks.Normalise(domain);
|
|
var deleted = await DB.Default.DeleteAsync<DomainBlock>(b => b.Domain == domain);
|
|
await _domainBlocks.Reload(token);
|
|
if (deleted.DeletedCount > 0)
|
|
await _log.Write(User.GetUserId(), AdminActions.DomainBlockDelete, AdminTargets.Domain, domain, domain, token: token);
|
|
return Ok();
|
|
}
|
|
|
|
static ViewDomainBlock ToView(DomainBlock block) => new()
|
|
{
|
|
Id = block.ID,
|
|
Domain = block.Domain,
|
|
Severity = block.Severity.ToString(),
|
|
RejectMedia = block.RejectMedia,
|
|
PublicComment = block.PublicComment,
|
|
PrivateComment = block.PrivateComment,
|
|
Published = block.Published,
|
|
Obfuscate = block.Obfuscate,
|
|
CreatedAt = block.CreatedAt
|
|
};
|
|
}
|
|
}
|