- Added `NotifyBillingStatusChangedAsync` method to `IRealtimeNotifier` for notifying clients about changes in billing status. - Updated `BillingConfigResumer` to call the new notification method after modifying billing configurations, ensuring users receive real-time updates. - Enhanced `ListUsersQueryHandler` to include a `BillingPendingReview` property in `UserSummaryDto`, indicating if a user has a pending payment request awaiting confirmation. - Refactored various command handlers to utilize `AdvisoryLock` for managing concurrent requests, preventing race conditions in billing operations. - Updated tests to cover new notification behaviors and ensure proper functionality in billing status management.
180 lines
7.3 KiB
C#
180 lines
7.3 KiB
C#
using PnvPanel.Application.Common.Models;
|
||
|
||
namespace PnvPanel.Application.Common.Interfaces;
|
||
|
||
public sealed record AuthenticatedUser(Guid Id, string UserName, string Role);
|
||
|
||
public sealed record CurrentUserProfile(
|
||
Guid Id,
|
||
string UserName,
|
||
Guid RoleId,
|
||
string Role,
|
||
bool IsActivated,
|
||
bool IsBlocked,
|
||
int MaxConfigs,
|
||
int MaxIpLimit,
|
||
string SubscriptionToken,
|
||
bool BillingEnabled,
|
||
DateTimeOffset? BillingPaidUntil,
|
||
bool BillingSuspended
|
||
);
|
||
|
||
/// <summary>Срез биллинг-полей пользователя — для фоновой джобы приостановки (см. BillingService),
|
||
/// не требует полного CurrentUserProfile (роль/квоты там не нужны).</summary>
|
||
public sealed record BillingUserDto(
|
||
Guid UserId,
|
||
DateTimeOffset? PaidUntil,
|
||
bool Suspended,
|
||
DateTimeOffset? LastWarnedForPaidUntil
|
||
);
|
||
|
||
public sealed record UserSummaryDto(
|
||
Guid Id,
|
||
string UserName,
|
||
string Role,
|
||
bool IsActivated,
|
||
bool IsBlocked,
|
||
DateTimeOffset? ActivatedAt,
|
||
bool BillingEnabled,
|
||
DateTimeOffset? BillingPaidUntil,
|
||
/// <summary>Есть Subscription-заявка на оплату в AwaitingConfirmation — конфиги не гасятся, пока
|
||
/// админ не решит (см. BillingService). Заполняется в ListUsersQueryHandler (не здесь — Identity
|
||
/// не должен знать про PaymentRequests), false по умолчанию для мест, не подгружающих это поле.</summary>
|
||
bool BillingPendingReview = false
|
||
);
|
||
|
||
public sealed record UserStatsDto(int Total, int Activated);
|
||
|
||
public sealed record TelegramLinkInfo(bool IsLinked, long? TelegramUserId, string? Username);
|
||
|
||
public interface IIdentityService
|
||
{
|
||
Task<Result<Guid>> CreateUserAsync(
|
||
string userName,
|
||
string password,
|
||
CancellationToken cancellationToken
|
||
);
|
||
|
||
Task<Result<AuthenticatedUser>> ValidateCredentialsAsync(
|
||
string userName,
|
||
string password,
|
||
CancellationToken cancellationToken
|
||
);
|
||
|
||
Task<CurrentUserProfile?> GetProfileAsync(Guid userId, CancellationToken cancellationToken);
|
||
|
||
Task<Result> ChangePasswordAsync(
|
||
Guid userId,
|
||
string currentPassword,
|
||
string newPassword,
|
||
CancellationToken cancellationToken
|
||
);
|
||
|
||
Task<Result> ChangeUserNameAsync(
|
||
Guid userId,
|
||
string newUserName,
|
||
CancellationToken cancellationToken
|
||
);
|
||
|
||
/// <summary>
|
||
/// Помечает пользователя активированным. Изменение не коммитится немедленно (в отличие от
|
||
/// CreateUserAsync/ChangePasswordAsync) — оно попадает в трекер того же DbContext и сохраняется
|
||
/// вместе с изменением ActivationRequest одной транзакцией через UnitOfWorkBehavior.
|
||
/// </summary>
|
||
Task<Result> ActivateUserAsync(
|
||
Guid userId,
|
||
Guid activatedBy,
|
||
CancellationToken cancellationToken
|
||
);
|
||
|
||
Task<IReadOnlyDictionary<Guid, string>> GetUserNamesAsync(
|
||
IReadOnlyCollection<Guid> userIds,
|
||
CancellationToken cancellationToken
|
||
);
|
||
|
||
/// <summary>Удаляет аккаунт (самоудаление). Конфиги должны быть отозваны заранее вызывающей стороной.</summary>
|
||
Task<Result> DeleteUserAsync(Guid userId, CancellationToken cancellationToken);
|
||
|
||
/// <summary>Для агрегированной подписки /sub/{userToken} (все активные конфиги пользователя).</summary>
|
||
Task<Guid?> FindUserIdBySubscriptionTokenAsync(
|
||
string token,
|
||
CancellationToken cancellationToken
|
||
);
|
||
|
||
/// <summary>Блокировка: вход запрещён (см. ValidateCredentialsAsync). Конфиги гасит вызывающая сторона.</summary>
|
||
Task<Result> BlockUserAsync(Guid userId, CancellationToken cancellationToken);
|
||
|
||
Task<Result> UnblockUserAsync(Guid userId, CancellationToken cancellationToken);
|
||
|
||
/// <summary>Сброс пароля админом — для пользователей без привязанного Telegram (M7).</summary>
|
||
Task<Result> ResetPasswordAsync(
|
||
Guid userId,
|
||
string newPassword,
|
||
CancellationToken cancellationToken
|
||
);
|
||
|
||
Task<PagedList<UserSummaryDto>> ListUsersAsync(
|
||
int page,
|
||
int pageSize,
|
||
string? search,
|
||
CancellationToken cancellationToken
|
||
);
|
||
|
||
Task<UserStatsDto> GetUserStatsAsync(CancellationToken cancellationToken);
|
||
|
||
Task<Result> LinkTelegramAsync(
|
||
Guid userId,
|
||
long telegramUserId,
|
||
string? telegramUsername,
|
||
CancellationToken cancellationToken
|
||
);
|
||
|
||
Task<Result> UnlinkTelegramAsync(Guid userId, CancellationToken cancellationToken);
|
||
|
||
Task<Guid?> FindUserIdByTelegramUserIdAsync(
|
||
long telegramUserId,
|
||
CancellationToken cancellationToken
|
||
);
|
||
|
||
Task<TelegramLinkInfo> GetTelegramLinkInfoAsync(
|
||
Guid userId,
|
||
CancellationToken cancellationToken
|
||
);
|
||
|
||
/// <summary>Telegram ID активных (активированных, не заблокированных) пользователей с привязкой —
|
||
/// для рассылки уведомлений вроде публикации новости.</summary>
|
||
Task<IReadOnlyCollection<long>> GetActivatedLinkedTelegramUserIdsAsync(
|
||
CancellationToken cancellationToken
|
||
);
|
||
|
||
/// <summary>Все Id пользователей, кроме указанного — для полного сброса панели (сохраняется
|
||
/// только текущий админ).</summary>
|
||
Task<IReadOnlyList<Guid>> ListAllUserIdsExceptAsync(
|
||
Guid exceptUserId,
|
||
CancellationToken cancellationToken
|
||
);
|
||
|
||
/// <summary>Пользователи с billing-ролью (role.BillingEnabled), не заблокированные — обход для
|
||
/// BillingService (приостановка за неуплату / предупреждения).</summary>
|
||
Task<IReadOnlyList<BillingUserDto>> ListBillingUsersAsync(CancellationToken cancellationToken);
|
||
|
||
/// <summary>Гасит конфиги за неуплату на уровне пользователя (BillingSuspended=true) — сами
|
||
/// конфиги гасит вызывающая сторона (см. BillingService, по образцу BlockUserCommandHandler).</summary>
|
||
Task<Result> SuspendBillingAsync(Guid userId, CancellationToken cancellationToken);
|
||
|
||
/// <summary>Продлевает оплаченный период, снимает приостановку и сбрасывает флаг "предупреждение
|
||
/// отправлено" (новый срок ещё не близко к истечению). Используется и при подтверждении оплаты,
|
||
/// и при первичной выдаче грейс-периода.</summary>
|
||
Task<Result> ExtendBillingPaidUntilAsync(
|
||
Guid userId,
|
||
DateTimeOffset paidUntil,
|
||
CancellationToken cancellationToken
|
||
);
|
||
|
||
Task<Result> MarkBillingWarningSentAsync(
|
||
Guid userId,
|
||
DateTimeOffset paidUntil,
|
||
CancellationToken cancellationToken
|
||
);
|
||
}
|