Files

178 lines
7.8 KiB
C#
Raw Permalink Normal View History

using Microsoft.AspNetCore.Mvc;
using MongoDB.Driver;
using MongoDB.Entities;
using PrivaPub.Api.Mastodon.Infrastructure;
using PrivaPub.Api.Mastodon.Mappers;
using PrivaPub.Domain.Social;
using PrivaPub.Models.Social;
using PrivaPub.StaticServices;
namespace PrivaPub.Api.Mastodon.Controllers
{
// Mastodon's notification policy and requests (4.3): what the persona's notifications from strangers, new accounts,
// unasked private mentions and silenced accounts become, and the requests that gather the filtered ones, one per
// account, let in or dismissed
public class NotificationRequestsController : MastodonController
{
readonly MastodonMapper _mapper;
readonly DbEntities _dbEntities;
public NotificationRequestsController(MastodonMapper mapper, DbEntities dbEntities)
{
_mapper = mapper;
_dbEntities = dbEntities;
}
static string Name(NotificationAction action) => action switch
{
NotificationAction.Filter => "filter",
NotificationAction.Drop => "drop",
_ => "accept"
};
static NotificationAction? Parse(string value) => value?.ToLowerInvariant() switch
{
"accept" => NotificationAction.Accept,
"filter" => NotificationAction.Filter,
"drop" => NotificationAction.Drop,
_ => null
};
async Task<object> Summary(CancellationToken token) => new
{
pending_requests_count = await DB.Default.CountAsync<NotificationRequest>(r => r.AvatarId == MyId, token),
pending_notifications_count = await DB.Default.CountAsync<Notification>(n => n.AvatarId == MyId && n.Filtered, token)
};
async Task<object> V2(NotificationPolicy policy, CancellationToken token) => new
{
for_not_following = Name(policy.ForNotFollowing),
for_not_followers = Name(policy.ForNotFollowers),
for_new_accounts = Name(policy.ForNewAccounts),
for_private_mentions = Name(policy.ForPrivateMentions),
for_limited_accounts = Name(policy.ForLimitedAccounts),
summary = await Summary(token)
};
// the first version spoke of filtering only: dropped is filtered for it
async Task<object> V1(NotificationPolicy policy, CancellationToken token) => new
{
filter_not_following = policy.ForNotFollowing != NotificationAction.Accept,
filter_not_followers = policy.ForNotFollowers != NotificationAction.Accept,
filter_new_accounts = policy.ForNewAccounts != NotificationAction.Accept,
filter_private_mentions = policy.ForPrivateMentions != NotificationAction.Accept,
summary = await Summary(token)
};
[HttpGet("/api/v2/notifications/policy"), Scope("read:notifications")]
public async Task<IActionResult> Policy(CancellationToken token) => Json(await V2(await NotificationPolicies.Of(MyId, token), token));
[HttpPatch("/api/v2/notifications/policy"), Scope("write:notifications")]
public async Task<IActionResult> SetPolicy(CancellationToken token)
{
var policy = await NotificationPolicies.Of(MyId, token);
policy.ForNotFollowing = Parse(Params.Get("for_not_following")) ?? policy.ForNotFollowing;
policy.ForNotFollowers = Parse(Params.Get("for_not_followers")) ?? policy.ForNotFollowers;
policy.ForNewAccounts = Parse(Params.Get("for_new_accounts")) ?? policy.ForNewAccounts;
policy.ForPrivateMentions = Parse(Params.Get("for_private_mentions")) ?? policy.ForPrivateMentions;
policy.ForLimitedAccounts = Parse(Params.Get("for_limited_accounts")) ?? policy.ForLimitedAccounts;
await NotificationPolicies.Save(policy, token);
return Json(await V2(policy, token));
}
[HttpGet("/api/v1/notifications/policy"), Scope("read:notifications")]
public async Task<IActionResult> PolicyV1(CancellationToken token) => Json(await V1(await NotificationPolicies.Of(MyId, token), token));
[HttpPut("/api/v1/notifications/policy"), HttpPatch("/api/v1/notifications/policy"), Scope("write:notifications")]
public async Task<IActionResult> SetPolicyV1(CancellationToken token)
{
var policy = await NotificationPolicies.Of(MyId, token);
static NotificationAction Filter(bool? filter, NotificationAction current) =>
filter switch { true => current == NotificationAction.Drop ? NotificationAction.Drop : NotificationAction.Filter, false => NotificationAction.Accept, _ => current };
policy.ForNotFollowing = Filter(Params.Bool("filter_not_following"), policy.ForNotFollowing);
policy.ForNotFollowers = Filter(Params.Bool("filter_not_followers"), policy.ForNotFollowers);
policy.ForNewAccounts = Filter(Params.Bool("filter_new_accounts"), policy.ForNewAccounts);
policy.ForPrivateMentions = Filter(Params.Bool("filter_private_mentions"), policy.ForPrivateMentions);
await NotificationPolicies.Save(policy, token);
return Json(await V1(policy, token));
}
async Task<object> Map(NotificationRequest request, CancellationToken token)
{
var account = await _mapper.Account(request.FromAccountId, token);
if (account == default)
return default;
var post = string.IsNullOrEmpty(request.LastPostId) ? default
: await _dbEntities.Posts.Match(p => p.ID == request.LastPostId && !p.DeletedAt.HasValue).ExecuteFirstAsync(token);
return new
{
id = request.ID,
created_at = MastodonJson.Time(request.CreatedAt),
updated_at = MastodonJson.Time(request.UpdatedAt),
notifications_count = request.NotificationsCount.ToString(System.Globalization.CultureInfo.InvariantCulture),
account,
last_status = post == default ? default : await _mapper.Status(post, MyId, token)
};
}
[HttpGet("/api/v1/notifications/requests"), Scope("read:notifications")]
public async Task<IActionResult> Requests(CancellationToken token)
{
var requests = await Page.From(Params, Limit(40, 80)).Fetch(DB.Default.Find<NotificationRequest>().Match(r => r.AvatarId == MyId), r => r.ID, token);
Link("/api/v1/notifications/requests", requests.LastOrDefault()?.ID, requests.FirstOrDefault()?.ID);
var mapped = new List<object>();
foreach (var request in requests)
if (await Map(request, token) is { } one)
mapped.Add(one);
return Json(mapped);
}
Task<NotificationRequest> Mine(string id, CancellationToken token) =>
DB.Default.Find<NotificationRequest>().Match(r => r.ID == id && r.AvatarId == MyId).ExecuteFirstAsync(token);
[HttpGet("/api/v1/notifications/requests/merged"), Scope("read:notifications")]
public IActionResult Merged() => Json(new { merged = true });
[HttpGet("/api/v1/notifications/requests/{id}"), Scope("read:notifications")]
public async Task<IActionResult> Request(string id, CancellationToken token) =>
await Mine(id, token) is { } request && await Map(request, token) is { } mapped ? Json(mapped) : NotFoundError();
[HttpPost("/api/v1/notifications/requests/{id}/accept"), Scope("write:notifications")]
public async Task<IActionResult> Accept(string id, CancellationToken token)
{
if (await Mine(id, token) is not { } request)
return NotFoundError();
await NotificationPolicies.Accept(request, token);
return Json(new { });
}
[HttpPost("/api/v1/notifications/requests/{id}/dismiss"), Scope("write:notifications")]
public async Task<IActionResult> Dismiss(string id, CancellationToken token)
{
if (await Mine(id, token) is not { } request)
return NotFoundError();
await NotificationPolicies.Dismiss(request, token);
return Json(new { });
}
[HttpPost("/api/v1/notifications/requests/accept"), Scope("write:notifications")]
public async Task<IActionResult> AcceptMany(CancellationToken token)
{
foreach (var id in Params.List("id").Take(100))
if (await Mine(id, token) is { } request)
await NotificationPolicies.Accept(request, token);
return Json(new { });
}
[HttpPost("/api/v1/notifications/requests/dismiss"), Scope("write:notifications")]
public async Task<IActionResult> DismissMany(CancellationToken token)
{
foreach (var id in Params.List("id").Take(100))
if (await Mine(id, token) is { } request)
await NotificationPolicies.Dismiss(request, token);
return Json(new { });
}
}
}