Правки админки, вход

This commit is contained in:
Халимов Рустам
2026-03-31 23:55:38 +03:00
parent bce8f92101
commit 2c6d6f831f
13 changed files with 264 additions and 51 deletions

View File

@@ -0,0 +1,68 @@
using BCrypt.Net;
using Knot.Contracts.Auth.Application.Abstractions;
using Knot.Contracts.Auth.Domain;
using Knot.Modules.Admin.Application.Admin.DTOs;
using Knot.Shared.Kernel;
using MediatR;
namespace Knot.Modules.Admin.Application.Admin.Commands.CreateUser;
public record CreateUserCommand(
string Username,
string Password,
string DisplayName,
string? Email = null,
string? Bio = null
) : ICommand<AdminUserDto>;
internal sealed class CreateUserCommandHandler : ICommandHandler<CreateUserCommand, AdminUserDto>
{
private readonly IUserRepository _userRepository;
private readonly IAuthUnitOfWork _unitOfWork;
public CreateUserCommandHandler(IUserRepository userRepository, IAuthUnitOfWork unitOfWork)
{
_userRepository = userRepository;
_unitOfWork = unitOfWork;
}
public async Task<Result<AdminUserDto>> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
// Проверка уникальности username
if (!await _userRepository.IsUsernameUniqueAsync(request.Username, cancellationToken))
{
return Result.Failure<AdminUserDto>(new Error("Admin.CreateUser.UsernameNotUnique", "Username is already taken"));
}
// Хеширование пароля
string passwordHash = BCrypt.Net.BCrypt.HashPassword(request.Password);
// Создание контракта пользователя
var userContract = new UserContract
{
Id = Guid.NewGuid(),
Username = request.Username,
PasswordHash = passwordHash,
DisplayName = request.DisplayName,
Email = request.Email,
Bio = request.Bio,
CreatedAt = DateTime.UtcNow,
IsBanned = false,
IsOnline = false
};
_unitOfWork.Add(userContract);
await _unitOfWork.SaveChangesAsync(cancellationToken);
return Result.Success(new AdminUserDto(
userContract.Id,
userContract.Username,
userContract.DisplayName,
userContract.Email,
userContract.Avatar,
userContract.CreatedAt,
userContract.IsOnline,
userContract.LastSeen ?? DateTime.UtcNow,
userContract.IsBanned));
}
}

View File

@@ -18,7 +18,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="BCrypt.Net-Next" Version="4.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.4" />
</ItemGroup>
</Project>

View File

@@ -1,7 +1,9 @@
using System.Text.Json.Serialization;
using Knot.Contracts.Conversations.Abstractions;
using Knot.Contracts.Settings.Application.Abstractions;
using Knot.Contracts.Settings.Application.DTOs;
using Knot.Modules.Admin.Application.Admin.Commands;
using Knot.Modules.Admin.Application.Admin.Commands.CreateUser;
using Knot.Modules.Admin.Application.Admin.Commands.TestKlipy;
using Knot.Modules.Admin.Application.Admin.Queries;
using MediatR;
@@ -56,6 +58,12 @@ public static class AdminEndpoints
return Results.Ok(result.Value);
});
group.MapPost("users", async ([FromBody] CreateUserCommand command, ISender sender, CancellationToken ct) =>
{
var result = await sender.Send(command, ct);
return result.IsSuccess ? Results.Created($"/api/admin/users/{result.Value.Id}", result.Value) : Results.BadRequest(new { error = result.Error.Description });
});
group.MapPost("users/{userId:guid}/reset-password", async ([FromRoute] Guid userId, [FromBody] ResetPasswordRequest dto, ISender sender, CancellationToken ct) =>
{
var result = await sender.Send(new ResetUserPasswordCommand(userId, dto.NewPassword), ct);
@@ -106,6 +114,12 @@ public static class AdminEndpoints
return result.IsSuccess ? Results.Ok(result.Value) : Results.NotFound("User not found");
});
group.MapDelete("users/{userId:guid}", async ([FromRoute] Guid userId, IUserDeleterService userDeleter, CancellationToken ct) =>
{
var result = await userDeleter.DeleteUserAsync(userId, ct);
return result.IsSuccess ? Results.Ok() : Results.BadRequest(new { error = result.Error.Description });
});
group.MapGet("clean/dry-run", async (ISender sender, CancellationToken ct) =>
{
var result = await sender.Send(new CleanDryRunQuery(), ct);

View File

@@ -43,6 +43,7 @@ public static class DependencyInjection
services.AddScoped<Knot.Contracts.Messaging.Application.Abstractions.IMessageNotifier, Knot.Modules.Conversations.Infrastructure.SignalR.MessageNotifier>();
services.AddScoped<ConversationsAbstractions.IUserStatusService, Knot.Modules.Conversations.Infrastructure.Services.UserStatusService>();
services.AddScoped<Knot.Contracts.Conversations.Abstractions.IUserStatusService, Knot.Modules.Conversations.Infrastructure.Services.UserStatusService>();
services.AddScoped<Knot.Contracts.Conversations.Abstractions.IUserDeleterService, Knot.Modules.Conversations.Infrastructure.Services.UserDeleterService>();
return services;
}
}

View File

@@ -0,0 +1,17 @@
using Knot.Modules.Conversations.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace Knot.Modules.Conversations.Infrastructure;
[DbContext(typeof(ChatsDbContext))]
public class ChatsDbContextFactory : IDesignTimeDbContextFactory<ChatsDbContext>
{
public ChatsDbContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<ChatsDbContext>();
optionsBuilder.UseNpgsql("Host=localhost;Database=knot_db;Username=knot;Password=knot_pass");
return new ChatsDbContext(optionsBuilder.Options);
}
}

View File

@@ -18,11 +18,16 @@ namespace Knot.Modules.Conversations.Infrastructure.Persistence;
public sealed class ChatsDbContext : DbContext, Knot.Modules.Conversations.Application.Abstractions.IChatsUnitOfWork, Knot.Contracts.Conversations.Infrastructure.Persistence.IChatsDbContext
{
private readonly IMediator _mediator;
private readonly IEncryptionService _encryptionService;
private readonly IMediator? _mediator;
private readonly IEncryptionService? _encryptionService;
public ChatsDbContext(DbContextOptions<ChatsDbContext> options)
: base(options)
{
}
public ChatsDbContext(DbContextOptions<ChatsDbContext> options, IMediator mediator, IEncryptionService encryptionService)
: base(options)
: this(options)
{
_mediator = mediator;
_encryptionService = encryptionService;
@@ -99,9 +104,12 @@ public sealed class ChatsDbContext : DbContext, Knot.Modules.Conversations.Appli
int result = await base.SaveChangesAsync(cancellationToken);
foreach (var domainEvent in domainEvents)
if (_mediator != null)
{
await _mediator.Publish(domainEvent, cancellationToken);
foreach (var domainEvent in domainEvents)
{
await _mediator.Publish(domainEvent, cancellationToken);
}
}
return result;

View File

@@ -0,0 +1,21 @@
using Knot.Contracts.Conversations.Abstractions;
using Knot.Modules.Conversations.Application.Users.Commands.DeleteUser;
using Knot.Shared.Kernel;
using MediatR;
namespace Knot.Modules.Conversations.Infrastructure.Services;
public sealed class UserDeleterService : IUserDeleterService
{
private readonly ISender _sender;
public UserDeleterService(ISender sender)
{
_sender = sender;
}
public async Task<Result> DeleteUserAsync(Guid userId, CancellationToken cancellationToken)
{
return await _sender.Send(new DeleteUserCommand(userId), cancellationToken);
}
}

View File

@@ -19,6 +19,10 @@
<PackageReference Include="HtmlAgilityPack" Version="1.12.4" />
<PackageReference Include="MediatR" Version="12.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.4" />

View File

@@ -1,4 +1,4 @@
// <auto-generated />
// <auto-generated />
using System;
using Knot.Modules.Conversations.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;