- Added new endpoints for creating and managing extension requests, allowing users to request billing period extensions. - Implemented admin approval processes for extension requests via Telegram, including inline buttons for approval and rejection. - Introduced a gifting feature for admins to grant additional billing days directly to users without a request. - Updated the support ticket model to accommodate extension requests and their associated properties. - Enhanced the Telegram notifier to inform admins of new extension requests and notify users of approval or rejection. - Updated frontend components to support the new extension request and gifting functionalities, including user interfaces for managing these features. - Revised API documentation to reflect the new endpoints and their usage in the billing context.
119 lines
4.0 KiB
C#
119 lines
4.0 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using PnvPanel.Application.Auth;
|
|
using PnvPanel.Application.Common.Interfaces;
|
|
using PnvPanel.Application.Common.Messaging;
|
|
using PnvPanel.Application.Common.Models;
|
|
using PnvPanel.Domain.Support;
|
|
|
|
namespace PnvPanel.Application.Support.CreateRoleRequest;
|
|
|
|
public sealed class CreateRoleRequestTicketCommandHandler(
|
|
IAppDbContext dbContext,
|
|
IRoleService roleService,
|
|
IRealtimeNotifier notifier,
|
|
ITelegramNotifier telegramNotifier,
|
|
ICurrentUser currentUser
|
|
) : ICommandHandler<CreateRoleRequestTicketCommand, Result<TicketDetailDto>>
|
|
{
|
|
// Совпадает со значением Infrastructure.Identity.RoleNames.Admin — Application не может ссылаться
|
|
// на Infrastructure (направление зависимостей), поэтому системное имя роли продублировано здесь.
|
|
private const string AdminRoleName = "admin";
|
|
|
|
public async Task<Result<TicketDetailDto>> Handle(
|
|
CreateRoleRequestTicketCommand command,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
if (currentUser.UserId is not { } userId)
|
|
return Result.Failure<TicketDetailDto>(AuthErrors.Unauthorized);
|
|
|
|
var hasPending = await dbContext.SupportTickets.AnyAsync(
|
|
t =>
|
|
t.UserId == userId
|
|
&& t.Type == TicketType.RoleRequest
|
|
&& t.Status == TicketStatus.Open,
|
|
cancellationToken
|
|
);
|
|
if (hasPending)
|
|
return Result.Failure<TicketDetailDto>(SupportErrors.RoleRequestAlreadyPending);
|
|
|
|
SupportTicket ticket;
|
|
string? requestedRoleName = null;
|
|
|
|
if (command.ExistingRoleId is { } roleId)
|
|
{
|
|
var roles = await roleService.ListRolesAsync(cancellationToken);
|
|
var role = roles.FirstOrDefault(r => r.Id == roleId);
|
|
if (role is null)
|
|
return Result.Failure<TicketDetailDto>(SupportErrors.RoleNotFound);
|
|
|
|
if (role.Name.Equals(AdminRoleName, StringComparison.OrdinalIgnoreCase))
|
|
return Result.Failure<TicketDetailDto>(SupportErrors.CannotRequestAdminRole);
|
|
|
|
ticket = SupportTicket.CreateRoleRequestForExistingRole(userId, roleId);
|
|
requestedRoleName = role.Name;
|
|
}
|
|
else
|
|
{
|
|
ticket = SupportTicket.CreateRoleRequestForNewRole(
|
|
userId,
|
|
command.NewRoleName!,
|
|
command.NewRoleMaxConfigs!.Value,
|
|
command.NewRoleMaxIpLimit!.Value
|
|
);
|
|
}
|
|
|
|
dbContext.SupportTickets.Add(ticket);
|
|
|
|
var comment = TicketComment.Create(ticket.Id, userId, command.Justification);
|
|
dbContext.TicketComments.Add(comment);
|
|
|
|
var userName = currentUser.UserName ?? userId.ToString();
|
|
var roleDescription =
|
|
requestedRoleName
|
|
?? $"новая роль «{command.NewRoleName}» (конфигов: {command.NewRoleMaxConfigs}, IP: {command.NewRoleMaxIpLimit})";
|
|
|
|
await notifier.NotifyTicketCreatedAsync(
|
|
ticket.Id,
|
|
userId,
|
|
userName,
|
|
ticket.Type,
|
|
cancellationToken
|
|
);
|
|
await telegramNotifier.NotifyAdminsRoleRequestCreatedAsync(
|
|
ticket.Id,
|
|
userName,
|
|
roleDescription,
|
|
command.Justification,
|
|
cancellationToken
|
|
);
|
|
|
|
var commentDto = new TicketCommentDto(
|
|
comment.Id,
|
|
userId,
|
|
userName,
|
|
comment.Body,
|
|
comment.CreatedAt,
|
|
[]
|
|
);
|
|
|
|
var dto = new TicketDetailDto(
|
|
ticket.Id,
|
|
ticket.UserId,
|
|
userName,
|
|
ticket.Type,
|
|
ticket.Status,
|
|
ticket.RequestedRoleId,
|
|
requestedRoleName,
|
|
ticket.ProposedRoleName,
|
|
ticket.ProposedMaxConfigs,
|
|
ticket.ProposedMaxIpLimit,
|
|
ticket.RequestedDays,
|
|
ticket.CreatedAt,
|
|
[commentDto]
|
|
);
|
|
|
|
return Result.Success(dto);
|
|
}
|
|
}
|