[backend/masto-client] Add announcements (ISH-102)

This commit is contained in:
Laura Hausmann 2024-02-27 03:29:21 +01:00
parent cda90302e1
commit 4805aef7bf
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
3 changed files with 130 additions and 0 deletions

View file

@ -0,0 +1,103 @@
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Mime;
using Iceshrimp.Backend.Controllers.Mastodon.Attributes;
using Iceshrimp.Backend.Controllers.Mastodon.Schemas;
using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Helpers;
using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
using Iceshrimp.Backend.Core.Middleware;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Controllers.Mastodon;
[MastodonApiController]
[Route("/api/v1/announcements")]
[EnableCors("mastodon")]
[Authenticate]
[EnableRateLimiting("sliding")]
[Produces(MediaTypeNames.Application.Json)]
public class AnnouncementController(DatabaseContext db, MfmConverter mfmConverter) : ControllerBase
{
[HttpGet]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<AnnouncementEntity>))]
[SuppressMessage("ReSharper", "EntityFramework.UnsupportedServerSideFunctionCall", Justification = "Projectables")]
public async Task<IActionResult> GetAnnouncements([FromQuery(Name = "with_dismissed")] bool withDismissed)
{
var user = HttpContext.GetUserOrFail();
var announcements = db.Announcements.AsQueryable();
if (!withDismissed)
{
announcements = announcements.Where(p => p.IsReadBy(user));
}
var res = await announcements.OrderByDescending(p => p.UpdatedAt ?? p.CreatedAt)
.Select(p => new AnnouncementEntity
{
Id = p.Id,
PublishedAt = p.CreatedAt.ToStringIso8601Like(),
UpdatedAt = (p.UpdatedAt ?? p.CreatedAt).ToStringIso8601Like(),
IsRead = p.IsReadBy(user),
Content = $"""
**{p.Title}**
{p.Text}
""",
Mentions = new List<MentionEntity>(), //TODO
Emoji = new List<EmojiEntity>() //TODO
})
.ToListAsync();
await res.Select(async p => p.Content = await mfmConverter.ToHtmlAsync(p.Content, [], null)).AwaitAllAsync();
return Ok(res);
}
[HttpPost("{id}/dismiss")]
[Authorize("write:accounts")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(object))]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
[SuppressMessage("ReSharper", "EntityFramework.UnsupportedServerSideFunctionCall", Justification = "Projectables")]
public async Task<IActionResult> DismissAnnouncement(string id)
{
var user = HttpContext.GetUserOrFail();
var announcement = await db.Announcements.FirstOrDefaultAsync(p => p.Id == id) ??
throw GracefulException.RecordNotFound();
if (await db.Announcements.AnyAsync(p => p == announcement && !p.IsReadBy(user)))
{
var announcementRead = new AnnouncementRead
{
Id = IdHelpers.GenerateSlowflakeId(),
CreatedAt = DateTime.UtcNow,
Announcement = announcement,
User = user
};
await db.AnnouncementReads.AddAsync(announcementRead);
await db.SaveChangesAsync();
}
return Ok(new object());
}
[HttpPut("{id}/reactions/{name}")]
[Authorize("write:favourites")]
[ProducesResponseType(StatusCodes.Status501NotImplemented, Type = typeof(MastodonErrorResponse))]
public IActionResult ReactToAnnouncement(string id, string name) =>
throw new GracefulException(HttpStatusCode.NotImplemented,
"Iceshrimp.NET does not support this endpoint due to database schema differences to Mastodon");
[HttpDelete("{id}/reactions/{name}")]
[Authorize("write:favourites")]
[ProducesResponseType(StatusCodes.Status501NotImplemented, Type = typeof(MastodonErrorResponse))]
public IActionResult RemoveAnnouncementReaction(string id, string name) =>
throw new GracefulException(HttpStatusCode.NotImplemented,
"Iceshrimp.NET does not support this endpoint due to database schema differences to Mastodon");
}

View file

@ -0,0 +1,21 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
public class AnnouncementEntity
{
[J("id")] public required string Id { get; set; }
[J("content")] public required string Content { get; set; }
[J("published_at")] public required string PublishedAt { get; set; }
[J("updated_at")] public required string UpdatedAt { get; set; }
[J("read")] public required bool IsRead { get; set; }
[J("mentions")] public required List<MentionEntity> Mentions { get; set; }
[J("emojis")] public required List<EmojiEntity> Emoji { get; set; }
[J("statuses")] public List<object> Statuses = []; //FIXME
[J("reactions")] public List<object> Reactions = []; //FIXME
[J("tags")] public List<object> Tags = []; //FIXME
[J("published")] public bool Published => true;
[J("all_day")] public bool AllDay => false;
}

View file

@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using EntityFrameworkCore.Projectables;
using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Core.Database.Tables;
@ -39,4 +40,9 @@ public class Announcement
[InverseProperty(nameof(AnnouncementRead.Announcement))]
public virtual ICollection<AnnouncementRead> AnnouncementReads { get; set; } = new List<AnnouncementRead>();
[NotMapped] [Projectable] public virtual IEnumerable<User> ReadBy => AnnouncementReads.Select(p => p.User);
[Projectable]
public bool IsReadBy(User user) => ReadBy.Contains(user);
}