Add news feature with CRUD operations and real-time notifications
CI / Backend (build + test) (push) Successful in 1m18s
CI / Frontend (lint + typecheck + build) (push) Successful in 32s

- Implemented news management functionality, allowing admins to create, read, update, and delete news posts.
- Introduced a new SignalR event for broadcasting news updates to all connected clients.
- Updated API documentation to include new endpoints for news management.
- Enhanced frontend with a dedicated news page and admin interface for managing news posts.
- Added necessary localization for news-related terms in both Russian and English.
This commit is contained in:
Leonid Pershin
2026-07-03 15:28:33 +03:00
parent bea2b5fcf7
commit b6637a1c03
47 changed files with 2523 additions and 9 deletions
@@ -0,0 +1,52 @@
using PnvPanel.Api.Common;
using PnvPanel.Application.Admin.News;
using PnvPanel.Application.Common.Messaging;
using PnvPanel.Application.Common.Models;
using PnvPanel.Application.News;
using PnvPanel.Infrastructure.Identity;
namespace PnvPanel.Api.Endpoints;
public static class AdminNewsEndpoints
{
public static IEndpointRouteBuilder MapAdminNewsEndpoints(this IEndpointRouteBuilder app)
{
var admin = app.MapGroup("/api/admin/news")
.WithTags("Admin.News")
.RequireAuthorization(policy => policy.RequireRole(RoleNames.Admin));
admin.MapGet("", ListAdminNews).Produces<PagedList<NewsPostDto>>();
admin.MapPost("", CreatePost).Produces<NewsPostDto>();
admin.MapPut("/{id:guid}", UpdatePost).Produces<NewsPostDto>();
admin.MapDelete("/{id:guid}", DeletePost).Produces(StatusCodes.Status204NoContent);
return app;
}
private static async Task<IResult> ListAdminNews(int page, int pageSize, ISender sender, CancellationToken cancellationToken)
{
var result = await sender.Send(new ListAdminNewsQuery(page, pageSize), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> CreatePost(CreatePostCommand command, ISender sender, CancellationToken cancellationToken)
{
var result = await sender.Send(command, cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> UpdatePost(Guid id, UpdatePostBody body, ISender sender, CancellationToken cancellationToken)
{
var command = new UpdatePostCommand(id, body.Title, body.Body);
var result = await sender.Send(command, cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> DeletePost(Guid id, ISender sender, CancellationToken cancellationToken)
{
var result = await sender.Send(new DeletePostCommand(id), cancellationToken);
return result.ToHttpResult();
}
}
public sealed record UpdatePostBody(string Title, string Body);
@@ -0,0 +1,24 @@
using PnvPanel.Api.Common;
using PnvPanel.Application.Common.Messaging;
using PnvPanel.Application.Common.Models;
using PnvPanel.Application.News;
namespace PnvPanel.Api.Endpoints;
public static class NewsEndpoints
{
public static IEndpointRouteBuilder MapNewsEndpoints(this IEndpointRouteBuilder app)
{
app.MapGet("/api/news", ListNews)
.WithTags("News")
.RequireAuthorization()
.Produces<PagedList<NewsPostDto>>();
return app;
}
private static async Task<IResult> ListNews(int page, int pageSize, ISender sender, CancellationToken cancellationToken)
{
var result = await sender.Send(new ListNewsQuery(page, pageSize), cancellationToken);
return result.ToHttpResult();
}
}
@@ -48,4 +48,12 @@ internal sealed class SignalRRealtimeNotifier(IHubContext<PanelHub> hubContext)
new { userId },
cancellationToken);
}
public Task NotifyNewsPublishedAsync(Guid postId, string title, DateTimeOffset createdAt, CancellationToken cancellationToken)
{
return hubContext.Clients.All.SendAsync(
"newsPublished",
new { id = postId, title, createdAt },
cancellationToken);
}
}
+2
View File
@@ -151,9 +151,11 @@ app.MapInboundEndpoints();
app.MapConfigEndpoints();
app.MapSubscriptionEndpoints();
app.MapAppEndpoints();
app.MapNewsEndpoints();
app.MapAdminUserEndpoints();
app.MapAdminStatsEndpoints();
app.MapAdminAppEndpoints();
app.MapAdminNewsEndpoints();
app.MapTelegramEndpoints();
app.MapHub<PanelHub>("/hubs/panel");