Files
PnvPanel/backend/src/PnvPanel.Application/Common/Interfaces/IIdentityService.cs
T
Leonid Pershin e19860ba46
CI / Backend (build + test) (push) Successful in 1m30s
CI / Frontend (lint + typecheck + build) (push) Successful in 33s
Implement billing status notification and enhance user management integration
- 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.
2026-07-19 23:22:57 +03:00

180 lines
7.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
);
}