Refactor project files for improved readability and structure
CI / Backend (build + test) (push) Successful in 1m18s
CI / Frontend (lint + typecheck + build) (push) Successful in 31s

- Cleaned up whitespace in Directory.Build.props and Directory.Packages.props for consistency.
- Reformatted project file references in PnvPanel.Api.csproj for better clarity.
- Enhanced code readability in various endpoint files by adjusting line breaks and indentation.
- Standardized method signatures and improved formatting in ResultExtensions and multiple endpoint classes for better maintainability.
This commit is contained in:
Leonid Pershin
2026-07-14 07:24:13 +03:00
parent 9d5424bb9c
commit df137ca5a7
285 changed files with 6911 additions and 2063 deletions
@@ -4,24 +4,38 @@ namespace PnvPanel.Application.Auth;
public static class AuthErrors
{
public static readonly Error DuplicateUserName =
Error.Conflict("Auth.DuplicateUserName", "Пользователь с таким именем уже существует.");
public static readonly Error DuplicateUserName = Error.Conflict(
"Auth.DuplicateUserName",
"Пользователь с таким именем уже существует."
);
public static readonly Error InvalidCredentials =
Error.Unauthorized("Auth.InvalidCredentials", "Неверное имя пользователя или пароль.");
public static readonly Error InvalidCredentials = Error.Unauthorized(
"Auth.InvalidCredentials",
"Неверное имя пользователя или пароль."
);
public static readonly Error LockedOut =
Error.Unauthorized("Auth.LockedOut", "Слишком много неудачных попыток входа. Попробуйте позже.");
public static readonly Error LockedOut = Error.Unauthorized(
"Auth.LockedOut",
"Слишком много неудачных попыток входа. Попробуйте позже."
);
public static readonly Error InvalidRefreshToken =
Error.Unauthorized("Auth.InvalidRefreshToken", "Недействительный refresh-токен.");
public static readonly Error InvalidRefreshToken = Error.Unauthorized(
"Auth.InvalidRefreshToken",
"Недействительный refresh-токен."
);
public static readonly Error Unauthorized =
Error.Unauthorized("Auth.Unauthorized", "Требуется аутентификация.");
public static readonly Error Unauthorized = Error.Unauthorized(
"Auth.Unauthorized",
"Требуется аутентификация."
);
public static readonly Error UserBlocked =
Error.Forbidden("Auth.UserBlocked", "Аккаунт заблокирован администратором.");
public static readonly Error UserBlocked = Error.Forbidden(
"Auth.UserBlocked",
"Аккаунт заблокирован администратором."
);
public static readonly Error NotActivated =
Error.Forbidden("Auth.NotActivated", "Аккаунт не активирован — обратитесь к администратору.");
public static readonly Error NotActivated = Error.Forbidden(
"Auth.NotActivated",
"Аккаунт не активирован — обратитесь к администратору."
);
}
@@ -5,4 +5,5 @@ public sealed record AuthResult(
DateTimeOffset AccessTokenExpiresAt,
string RefreshToken,
DateTimeOffset RefreshTokenExpiresAt,
CurrentUserDto User);
CurrentUserDto User
);
@@ -3,4 +3,5 @@ using PnvPanel.Application.Common.Models;
namespace PnvPanel.Application.Auth.ChangePassword;
public sealed record ChangePasswordCommand(string CurrentPassword, string NewPassword) : ICommand<Result>;
public sealed record ChangePasswordCommand(string CurrentPassword, string NewPassword)
: ICommand<Result>;
@@ -4,14 +4,21 @@ using PnvPanel.Application.Common.Models;
namespace PnvPanel.Application.Auth.ChangePassword;
public sealed class ChangePasswordCommandHandler(IIdentityService identityService, ICurrentUser currentUser)
: ICommandHandler<ChangePasswordCommand, Result>
public sealed class ChangePasswordCommandHandler(
IIdentityService identityService,
ICurrentUser currentUser
) : ICommandHandler<ChangePasswordCommand, Result>
{
public Task<Result> Handle(ChangePasswordCommand command, CancellationToken cancellationToken)
{
if (currentUser.UserId is not { } userId)
return Task.FromResult(Result.Failure(AuthErrors.Unauthorized));
return identityService.ChangePasswordAsync(userId, command.CurrentPassword, command.NewPassword, cancellationToken);
return identityService.ChangePasswordAsync(
userId,
command.CurrentPassword,
command.NewPassword,
cancellationToken
);
}
}
@@ -4,8 +4,10 @@ using PnvPanel.Application.Common.Models;
namespace PnvPanel.Application.Auth.ChangeUserName;
public sealed class ChangeUserNameCommandHandler(IIdentityService identityService, ICurrentUser currentUser)
: ICommandHandler<ChangeUserNameCommand, Result>
public sealed class ChangeUserNameCommandHandler(
IIdentityService identityService,
ICurrentUser currentUser
) : ICommandHandler<ChangeUserNameCommand, Result>
{
public Task<Result> Handle(ChangeUserNameCommand command, CancellationToken cancellationToken)
{
@@ -1,3 +1,9 @@
namespace PnvPanel.Application.Auth;
public sealed record CurrentUserDto(Guid Id, string UserName, string Role, bool IsActivated, bool TelegramLinked);
public sealed record CurrentUserDto(
Guid Id,
string UserName,
string Role,
bool IsActivated,
bool TelegramLinked
);
@@ -6,27 +6,44 @@ using PnvPanel.Domain.Configs;
namespace PnvPanel.Application.Auth.DeleteMyAccount;
public sealed class DeleteMyAccountCommandHandler(IAppDbContext dbContext, IIdentityService identityService, IXuiPanelGateway gateway, ICurrentUser currentUser)
: ICommandHandler<DeleteMyAccountCommand, Result>
public sealed class DeleteMyAccountCommandHandler(
IAppDbContext dbContext,
IIdentityService identityService,
IXuiPanelGateway gateway,
ICurrentUser currentUser
) : ICommandHandler<DeleteMyAccountCommand, Result>
{
public async Task<Result> Handle(DeleteMyAccountCommand command, CancellationToken cancellationToken)
public async Task<Result> Handle(
DeleteMyAccountCommand command,
CancellationToken cancellationToken
)
{
if (currentUser.UserId is not { } userId)
return Result.Failure(AuthErrors.Unauthorized);
var configs = await dbContext.VpnConfigs
.Where(c => c.UserId == userId && c.Status != ConfigStatus.Revoked)
var configs = await dbContext
.VpnConfigs.Where(c => c.UserId == userId && c.Status != ConfigStatus.Revoked)
.ToListAsync(cancellationToken);
foreach (var config in configs)
{
var inbound = await dbContext.Inbounds.AsNoTracking().FirstOrDefaultAsync(i => i.Id == config.InboundId, cancellationToken);
var inbound = await dbContext
.Inbounds.AsNoTracking()
.FirstOrDefaultAsync(i => i.Id == config.InboundId, cancellationToken);
var node = inbound is null
? null
: await dbContext.Nodes.AsNoTracking().FirstOrDefaultAsync(n => n.Id == inbound.NodeId, cancellationToken);
: await dbContext
.Nodes.AsNoTracking()
.FirstOrDefaultAsync(n => n.Id == inbound.NodeId, cancellationToken);
if (inbound is not null && node is not null)
await gateway.RemoveClientAsync(node, inbound.RemoteInboundId, config.ClientExternalId, config.Protocol, cancellationToken);
await gateway.RemoveClientAsync(
node,
inbound.RemoteInboundId,
config.ClientExternalId,
config.Protocol,
cancellationToken
);
config.Revoke();
}
@@ -7,11 +7,19 @@ namespace PnvPanel.Application.Auth.Login;
public sealed class LoginCommandHandler(
IIdentityService identityService,
IJwtTokenService jwtTokenService,
IRefreshTokenService refreshTokenService) : ICommandHandler<LoginCommand, Result<AuthResult>>
IRefreshTokenService refreshTokenService
) : ICommandHandler<LoginCommand, Result<AuthResult>>
{
public async Task<Result<AuthResult>> Handle(LoginCommand command, CancellationToken cancellationToken)
public async Task<Result<AuthResult>> Handle(
LoginCommand command,
CancellationToken cancellationToken
)
{
var credentialsResult = await identityService.ValidateCredentialsAsync(command.UserName, command.Password, cancellationToken);
var credentialsResult = await identityService.ValidateCredentialsAsync(
command.UserName,
command.Password,
cancellationToken
);
if (!credentialsResult.IsSuccess)
return Result.Failure<AuthResult>(credentialsResult.Error);
@@ -23,14 +31,26 @@ public sealed class LoginCommandHandler(
var (accessToken, accessExpiresAt) = jwtTokenService.GenerateAccessToken(user);
var refreshToken = await refreshTokenService.IssueAsync(user.Id, cancellationToken);
var telegramInfo = await identityService.GetTelegramLinkInfoAsync(profile.Id, cancellationToken);
var dto = new CurrentUserDto(profile.Id, profile.UserName, profile.Role, profile.IsActivated, telegramInfo.IsLinked);
var telegramInfo = await identityService.GetTelegramLinkInfoAsync(
profile.Id,
cancellationToken
);
var dto = new CurrentUserDto(
profile.Id,
profile.UserName,
profile.Role,
profile.IsActivated,
telegramInfo.IsLinked
);
return Result.Success(new AuthResult(
accessToken,
accessExpiresAt,
refreshToken.RawToken,
refreshToken.ExpiresAt,
dto));
return Result.Success(
new AuthResult(
accessToken,
accessExpiresAt,
refreshToken.RawToken,
refreshToken.ExpiresAt,
dto
)
);
}
}
@@ -4,10 +4,15 @@ using PnvPanel.Application.Common.Models;
namespace PnvPanel.Application.Auth.Me;
public sealed class GetCurrentUserQueryHandler(IIdentityService identityService, ICurrentUser currentUser)
: IQueryHandler<GetCurrentUserQuery, Result<CurrentUserDto>>
public sealed class GetCurrentUserQueryHandler(
IIdentityService identityService,
ICurrentUser currentUser
) : IQueryHandler<GetCurrentUserQuery, Result<CurrentUserDto>>
{
public async Task<Result<CurrentUserDto>> Handle(GetCurrentUserQuery query, CancellationToken cancellationToken)
public async Task<Result<CurrentUserDto>> Handle(
GetCurrentUserQuery query,
CancellationToken cancellationToken
)
{
if (currentUser.UserId is not { } userId)
return Result.Failure<CurrentUserDto>(AuthErrors.Unauthorized);
@@ -16,8 +21,19 @@ public sealed class GetCurrentUserQueryHandler(IIdentityService identityService,
if (profile is null)
return Result.Failure<CurrentUserDto>(AuthErrors.Unauthorized);
var telegramInfo = await identityService.GetTelegramLinkInfoAsync(userId, cancellationToken);
var telegramInfo = await identityService.GetTelegramLinkInfoAsync(
userId,
cancellationToken
);
return Result.Success(new CurrentUserDto(profile.Id, profile.UserName, profile.Role, profile.IsActivated, telegramInfo.IsLinked));
return Result.Success(
new CurrentUserDto(
profile.Id,
profile.UserName,
profile.Role,
profile.IsActivated,
telegramInfo.IsLinked
)
);
}
}
@@ -7,29 +7,51 @@ namespace PnvPanel.Application.Auth.Refresh;
public sealed class RefreshCommandHandler(
IIdentityService identityService,
IJwtTokenService jwtTokenService,
IRefreshTokenService refreshTokenService) : ICommandHandler<RefreshCommand, Result<AuthResult>>
IRefreshTokenService refreshTokenService
) : ICommandHandler<RefreshCommand, Result<AuthResult>>
{
public async Task<Result<AuthResult>> Handle(RefreshCommand command, CancellationToken cancellationToken)
public async Task<Result<AuthResult>> Handle(
RefreshCommand command,
CancellationToken cancellationToken
)
{
var rotated = await refreshTokenService.RotateAsync(command.RawRefreshToken, cancellationToken);
var rotated = await refreshTokenService.RotateAsync(
command.RawRefreshToken,
cancellationToken
);
if (!rotated.IsSuccess)
return Result.Failure<AuthResult>(rotated.Error);
var profile = await identityService.GetProfileAsync(rotated.Value.UserId, cancellationToken);
var profile = await identityService.GetProfileAsync(
rotated.Value.UserId,
cancellationToken
);
if (profile is null)
return Result.Failure<AuthResult>(AuthErrors.InvalidRefreshToken);
var authUser = new AuthenticatedUser(profile.Id, profile.UserName, profile.Role);
var (accessToken, accessExpiresAt) = jwtTokenService.GenerateAccessToken(authUser);
var telegramInfo = await identityService.GetTelegramLinkInfoAsync(profile.Id, cancellationToken);
var dto = new CurrentUserDto(profile.Id, profile.UserName, profile.Role, profile.IsActivated, telegramInfo.IsLinked);
var telegramInfo = await identityService.GetTelegramLinkInfoAsync(
profile.Id,
cancellationToken
);
var dto = new CurrentUserDto(
profile.Id,
profile.UserName,
profile.Role,
profile.IsActivated,
telegramInfo.IsLinked
);
return Result.Success(new AuthResult(
accessToken,
accessExpiresAt,
rotated.Value.RawToken,
rotated.Value.ExpiresAt,
dto));
return Result.Success(
new AuthResult(
accessToken,
accessExpiresAt,
rotated.Value.RawToken,
rotated.Value.ExpiresAt,
dto
)
);
}
}
@@ -3,6 +3,7 @@ using PnvPanel.Application.Common.Models;
namespace PnvPanel.Application.Auth.Register;
public sealed record RegisterCommand(string UserName, string Password) : ICommand<Result<RegisterResult>>;
public sealed record RegisterCommand(string UserName, string Password)
: ICommand<Result<RegisterResult>>;
public sealed record RegisterResult(Guid Id, string UserName);
@@ -7,9 +7,16 @@ namespace PnvPanel.Application.Auth.Register;
public sealed class RegisterCommandHandler(IIdentityService identityService)
: ICommandHandler<RegisterCommand, Result<RegisterResult>>
{
public async Task<Result<RegisterResult>> Handle(RegisterCommand command, CancellationToken cancellationToken)
public async Task<Result<RegisterResult>> Handle(
RegisterCommand command,
CancellationToken cancellationToken
)
{
var result = await identityService.CreateUserAsync(command.UserName, command.Password, cancellationToken);
var result = await identityService.CreateUserAsync(
command.UserName,
command.Password,
cancellationToken
);
return result.IsSuccess
? Result.Success(new RegisterResult(result.Value, command.UserName))