Files
SocialPub/PrivaPub/Api/Mastodon/Controllers/AnnouncementsController.cs
T

98 lines
4.3 KiB
C#
Raw Normal View History

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Entities;
using PrivaPub.Api.Mastodon.Infrastructure;
using PrivaPub.Domain.Social;
using PrivaPub.Models.Social;
namespace PrivaPub.Api.Mastodon.Controllers
{
// Mastodon's announcements: the administrators' (Domain/Social/Announcements.cs), each persona's dismissals and
// reactions its own. Anonymous readers get the list without `read` or `me`.
public class AnnouncementsController : MastodonController
{
readonly IAnnouncements _announcements;
public AnnouncementsController(IAnnouncements announcements)
{
_announcements = announcements;
}
[HttpGet("/api/v1/announcements"), Scope("read", requiresUser: false), AllowAnonymous]
public async Task<IActionResult> List(CancellationToken token) =>
Json(await Map(await _announcements.Shown(token), MyId, token));
[HttpPost("/api/v1/announcements/{id}/dismiss"), Scope("write:accounts")]
public async Task<IActionResult> Dismiss(string id, CancellationToken token) =>
await _announcements.Dismiss(id, MyId, token) ? Json(new { }) : NotFoundError();
[HttpPut("/api/v1/announcements/{id}/reactions/{name}"), Scope("write:favourites")]
public Task<IActionResult> React(string id, string name, CancellationToken token) => Reaction(id, name, add: true, token);
[HttpDelete("/api/v1/announcements/{id}/reactions/{name}"), Scope("write:favourites")]
public Task<IActionResult> Unreact(string id, string name, CancellationToken token) => Reaction(id, name, add: false, token);
async Task<IActionResult> Reaction(string id, string name, bool add, CancellationToken token)
{
var (status, error) = await _announcements.React(id, MyId, name, add, token);
return error == default ? Json(new { }) : Error(status, error);
}
/// <summary>Mastodon's Announcement entities, as the viewer (a persona, or nobody) sees them.</summary>
public static async Task<List<Dictionary<string, object>>> Map(IReadOnlyList<Announcement> announcements, string viewerId, CancellationToken token)
{
var ids = announcements.Select(a => a.ID).ToList();
if (ids.Count == 0)
return new List<Dictionary<string, object>>();
var reactions = await DB.Default.Find<AnnouncementReaction>().Match(r => ids.Contains(r.AnnouncementId)).ExecuteAsync(token);
var read = viewerId == default
? new HashSet<string>()
: (await DB.Default.Find<AnnouncementRead>().Match(r => r.AvatarId == viewerId && ids.Contains(r.AnnouncementId)).ExecuteAsync(token))
.Select(r => r.AnnouncementId).ToHashSet();
return announcements.Select(a =>
{
var entity = new Dictionary<string, object>
{
["id"] = a.ID,
["content"] = a.Html ?? string.Empty,
["starts_at"] = a.StartsAt == default ? default : MastodonJson.Time(a.StartsAt.Value),
["ends_at"] = a.EndsAt == default ? default : MastodonJson.Time(a.EndsAt.Value),
["all_day"] = a.AllDay,
["published_at"] = MastodonJson.Time(a.PublishedAt ?? a.CreatedAt),
["updated_at"] = MastodonJson.Time(a.UpdatedAt),
["mentions"] = Array.Empty<object>(),
["statuses"] = Array.Empty<object>(),
["tags"] = Array.Empty<object>(),
["emojis"] = Array.Empty<object>(),
["reactions"] = reactions.Where(r => r.AnnouncementId == a.ID)
.GroupBy(r => r.Name)
.OrderBy(g => g.Min(r => r.CreatedAt))
.Select(g => Reaction(g.Key, g.Count(), viewerId == default ? default : g.Any(r => r.AvatarId == viewerId)))
.ToList()
};
if (viewerId != default)
entity["read"] = read.Contains(a.ID);
return entity;
}).ToList();
}
// a unicode emoji's reaction: no url (custom emoji come later); `me` only for a persona
static Dictionary<string, object> Reaction(string name, int count, bool? me)
{
var reaction = new Dictionary<string, object> { ["name"] = name, ["count"] = count };
if (me is { } mine)
reaction["me"] = mine;
return reaction;
}
/// <summary>The `announcement.reaction` event's payload: the emoji, how many chose it now, and on which.</summary>
public static async Task<Dictionary<string, object>> ReactionEvent(string announcementId, string name, CancellationToken token) => new()
{
["name"] = name,
["count"] = await DB.Default.CountAsync<AnnouncementReaction>(r => r.AnnouncementId == announcementId && r.Name == name, token),
["announcement_id"] = announcementId
};
}
}