Files
SocialPub/PrivaPub/Controllers/ClientToServer/ModeratorController.cs
T

124 lines
4.6 KiB
C#
Raw Normal View History

2026-10-01 12:13:57 +02:00
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Entities;
using PrivaPub.ClientModels;
using PrivaPub.ClientModels.Admin;
using PrivaPub.Domain.Instance;
using PrivaPub.Domain.Moderation;
2026-10-01 12:13:57 +02:00
using PrivaPub.Extensions;
using PrivaPub.Models;
2026-10-01 12:13:57 +02:00
using PrivaPub.Models.Social;
using PrivaPub.Models.User;
2023-02-18 08:52:17 +01:00
2023-02-19 00:43:43 +01:00
namespace PrivaPub.Controllers.ClientToServer
2023-02-18 08:52:17 +01:00
{
[ApiController,
2026-10-01 12:13:57 +02:00
Route("clientapi/moderator"),
Authorize(Policy = Policies.IsModerator)]
2023-02-18 08:52:17 +01:00
public class ModeratorController : ControllerBase
{
const int MaxLogPage = 100;
readonly IInstanceSettings _settings;
readonly IModerationLog _log;
public ModeratorController(IInstanceSettings settings, IModerationLog log)
{
_settings = settings;
_log = log;
}
2026-10-01 12:13:57 +02:00
[HttpGet, Route("/clientapi/moderator/reports")]
public async Task<IActionResult> Reports([FromQuery] bool resolved, CancellationToken token)
{
var rules = _settings.Current.Rules.ToDictionary(r => r.Id, r => r.Text ?? string.Empty);
return Ok((await DB.Default.Find<Report>().Match(r => r.IsResolved == resolved).Sort(r => r.ID, Order.Descending).Limit(100).ExecuteAsync(token))
.Select(r => new ViewReport
2026-10-01 12:13:57 +02:00
{
ID = r.ID,
2026-10-01 12:13:57 +02:00
Reporter = r.ReporterAvatarId != default ? "local" : r.ReporterActorURI,
TargetAccountId = r.TargetAccountId,
TargetActorURI = r.TargetActorURI,
PostIds = r.PostIds,
ObjectURIs = r.ObjectURIs,
Category = r.Category,
RuleIds = r.RuleIds ?? new(),
Rules = (r.RuleIds ?? new()).Select(id => rules.GetValueOrDefault(id, string.Empty)).ToList(),
Comment = r.Comment,
Forwarded = r.Forwarded,
IsResolved = r.IsResolved,
CreatedAt = r.CreatedAt,
2026-10-07 21:10:58 +02:00
ResolvedAt = r.ResolvedAt,
RemoteResolvedAt = r.RemoteResolvedAt,
RemoteResolvedBy = r.RemoteResolvedBy
2026-10-01 12:13:57 +02:00
}));
}
2026-10-01 12:13:57 +02:00
[HttpPost, Route("/clientapi/moderator/reports/{id}/resolve")]
public async Task<IActionResult> Resolve(string id, CancellationToken token)
{
var by = User.GetUserId();
2026-10-01 12:13:57 +02:00
var result = await DB.Default.Update<Report>().MatchID(id)
.Modify(r => r.IsResolved, true)
.Modify(r => r.ResolvedAt, DateTime.UtcNow)
.Modify(r => r.ResolvedBy, by)
2026-10-01 12:13:57 +02:00
.ExecuteAsync(token);
if (result.MatchedCount == 0)
return NotFound();
var report = await DB.Default.Find<Report>().MatchID(id).ExecuteFirstAsync(token);
await _log.Write(by, AdminActions.ReportResolve, AdminTargets.Report, id, report?.Category, reportId: id, token: token);
return Ok();
}
// The staff's log, newest first, a page at a time (before: the last id of the page before). Administrators read all
// of it; a moderator who isn't one reads what isn't about a login, and who acted only when it was the command line or
// themselves.
[HttpGet, Route("/clientapi/moderator/log")]
public async Task<IActionResult> Log([FromQuery] string before, [FromQuery] int limit = 50, CancellationToken token = default)
{
var me = await DB.Default.Find<RootUser>().MatchID(User.GetUserId()).ExecuteFirstAsync(token);
if (me is not { IsBanned: false, DeletedAt: null } || !me.Policies.Contains(Policies.IsModerator))
return Forbid();
var admin = me.Policies.Contains(Policies.IsAdmin);
limit = Math.Clamp(limit, 1, MaxLogPage);
var query = DB.Default.Find<AdminAction>();
if (!string.IsNullOrEmpty(before))
{
if (!MongoDB.Bson.ObjectId.TryParse(before, out _))
return BadRequest();
query.Match(f => f.Lt(a => a.ID, before));
}
if (!admin)
query.Match(a => a.TargetKind != AdminTargets.Root);
var actions = await query.Sort(a => a.ID, Order.Descending).Limit(limit).ExecuteAsync(token);
var actorIds = actions.Select(a => a.ByRootId).Where(id => id != default && id != AdminAction.Cli).Distinct().ToList();
var names = admin && actorIds.Count > 0
? (await DB.Default.Find<RootUser>().Match(u => actorIds.Contains(u.ID)).Project(u => u.Include(x => x.UserName)).ExecuteAsync(token))
.ToDictionary(u => u.ID, u => u.UserName)
: new Dictionary<string, string>();
return Ok(new ViewAdminActionsPage
{
Items = actions.Select(a => new ViewAdminAction
{
Id = a.ID,
At = a.At,
By = a.ByRootId == AdminAction.Cli ? AdminAction.Cli
: a.ByRootId == default ? default
: admin ? names.GetValueOrDefault(a.ByRootId, a.ByRootId)
: a.ByRootId == me.ID ? me.UserName
: default,
ByYou = a.ByRootId == me.ID,
Action = a.Action,
TargetKind = a.TargetKind,
TargetId = a.TargetId,
TargetLabel = a.TargetLabel,
ReportId = a.ReportId,
Note = a.Note
}).ToList(),
Next = actions.Count == limit ? actions[^1].ID : default
});
2026-10-01 12:13:57 +02:00
}
2023-02-18 08:52:17 +01:00
}
}