[backend/masto-client] Add announcements (ISH-102)
This commit is contained in:
parent
cda90302e1
commit
4805aef7bf
3 changed files with 130 additions and 0 deletions
103
Iceshrimp.Backend/Controllers/Mastodon/AnnouncementController.cs
Normal file
103
Iceshrimp.Backend/Controllers/Mastodon/AnnouncementController.cs
Normal 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");
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
Loading…
Add table
Reference in a new issue