feat(backend): global notices

This commit is contained in:
sam 2025-04-06 15:32:44 +02:00
parent 22be49976a
commit b07f4b75c0
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
19 changed files with 1247 additions and 8 deletions

View file

@ -13,20 +13,23 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
using System.Text.RegularExpressions;
using Foxnouns.Backend.Database.Models;
using Foxnouns.Backend.Dto;
using Foxnouns.Backend.Services.Caching;
using Foxnouns.Backend.Utils;
using Microsoft.AspNetCore.Mvc;
namespace Foxnouns.Backend.Controllers;
[Route("/api/v2/meta")]
public partial class MetaController(Config config) : ApiControllerBase
public partial class MetaController(Config config, NoticeCacheService noticeCache)
: ApiControllerBase
{
private const string Repository = "https://codeberg.org/pronounscc/pronouns.cc";
[HttpGet]
[ProducesResponseType<MetaResponse>(StatusCodes.Status200OK)]
public IActionResult GetMeta() =>
public async Task<IActionResult> GetMeta(CancellationToken ct = default) =>
Ok(
new MetaResponse(
Repository,
@ -45,10 +48,14 @@ public partial class MetaController(Config config) : ApiControllerBase
ValidationUtils.MaxCustomPreferences,
AuthUtils.MaxAuthMethodsPerType,
FlagsController.MaxFlagCount
)
),
Notice: NoticeResponse(await noticeCache.GetAsync(ct))
)
);
private static MetaNoticeResponse? NoticeResponse(Notice? notice) =>
notice == null ? null : new MetaNoticeResponse(notice.Id, notice.Message);
[HttpGet("page/{page}")]
public async Task<IActionResult> GetStaticPageAsync(string page, CancellationToken ct = default)
{
@ -71,7 +78,7 @@ public partial class MetaController(Config config) : ApiControllerBase
[HttpGet("/api/v2/coffee")]
public IActionResult BrewCoffee() =>
Problem("Sorry, I'm a teapot!", statusCode: StatusCodes.Status418ImATeapot);
StatusCode(StatusCodes.Status418ImATeapot, "Sorry, I'm a teapot!");
[GeneratedRegex(@"^[a-z\-_]+$")]
private static partial Regex PageRegex();

View file

@ -0,0 +1,77 @@
using Foxnouns.Backend.Database;
using Foxnouns.Backend.Database.Models;
using Foxnouns.Backend.Dto;
using Foxnouns.Backend.Middleware;
using Foxnouns.Backend.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace Foxnouns.Backend.Controllers.Moderation;
[Route("/api/v2/notices")]
[Authorize("user.moderation")]
[Limit(RequireModerator = true)]
public class NoticesController(
DatabaseContext db,
UserRendererService userRenderer,
ISnowflakeGenerator snowflakeGenerator,
IClock clock
) : ApiControllerBase
{
[HttpGet]
public async Task<IActionResult> GetNoticesAsync(CancellationToken ct = default)
{
List<Notice> notices = await db
.Notices.Include(n => n.Author)
.OrderByDescending(n => n.Id)
.ToListAsync(ct);
return Ok(notices.Select(RenderNotice));
}
[HttpPost]
public async Task<IActionResult> CreateNoticeAsync(CreateNoticeRequest req)
{
Instant now = clock.GetCurrentInstant();
if (req.StartTime < now)
{
throw new ApiError.BadRequest(
"Start time cannot be in the past",
"start_time",
req.StartTime
);
}
if (req.EndTime < now)
{
throw new ApiError.BadRequest(
"End time cannot be in the past",
"end_time",
req.EndTime
);
}
var notice = new Notice
{
Id = snowflakeGenerator.GenerateSnowflake(),
Message = req.Message,
StartTime = req.StartTime ?? clock.GetCurrentInstant(),
EndTime = req.EndTime,
Author = CurrentUser!,
};
db.Add(notice);
await db.SaveChangesAsync();
return Ok(RenderNotice(notice));
}
private NoticeResponse RenderNotice(Notice notice) =>
new(
notice.Id,
notice.Message,
notice.StartTime,
notice.EndTime,
userRenderer.RenderPartialUser(notice.Author)
);
}

View file

@ -281,6 +281,8 @@ public class UsersController(
if (req.HasProperty(nameof(req.DarkMode)))
user.Settings.DarkMode = req.DarkMode;
if (req.HasProperty(nameof(req.LastReadNotice)))
user.Settings.LastReadNotice = req.LastReadNotice;
user.LastActive = clock.GetCurrentInstant();
db.Update(user);