Контракты
This commit is contained in:
@@ -1,14 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
namespace Knot.Modules.Auth.Application.Abstractions;
|
||||
|
||||
public interface IAuthDbContext
|
||||
{
|
||||
DbSet<User> Users { get; }
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
using Knot.Shared.Kernel;
|
||||
|
||||
namespace Knot.Modules.Auth.Application.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Unit of Work специфичный для модуля Identity.
|
||||
/// </summary>
|
||||
public interface IAuthUnitOfWork : IUnitOfWork
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
using Knot.Modules.Auth.Domain;
|
||||
|
||||
namespace Knot.Modules.Auth.Application.Abstractions;
|
||||
|
||||
public interface IJwtTokenProvider
|
||||
{
|
||||
string Generate(User user);
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Knot.Modules.Auth.Application.Auth.DTOs;
|
||||
|
||||
public record AuthResponseDto(
|
||||
string Token,
|
||||
AuthUserDto User
|
||||
);
|
||||
|
||||
public record AuthUserDto(
|
||||
Guid Id,
|
||||
string Username,
|
||||
string DisplayName,
|
||||
string? Email,
|
||||
string? Bio,
|
||||
string? Avatar,
|
||||
DateTime? Birthday,
|
||||
bool IsOnline,
|
||||
DateTime CreatedAt
|
||||
);
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Knot.Modules.Auth.Application.Users.Auth;
|
||||
|
||||
public record AuthResponseDto(
|
||||
string Token,
|
||||
AuthUserDto User
|
||||
);
|
||||
|
||||
public record AuthUserDto(
|
||||
Guid Id,
|
||||
string Username,
|
||||
string DisplayName,
|
||||
string? Email,
|
||||
string? Bio,
|
||||
string? Avatar,
|
||||
DateTime? Birthday,
|
||||
bool IsOnline,
|
||||
DateTime CreatedAt
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Auth.Domain;
|
||||
|
||||
using Knot.Modules.Auth.Application.Users.Auth;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Application.Auth.DTOs;
|
||||
|
||||
namespace Knot.Modules.Auth.Application.Users.GetMe;
|
||||
|
||||
@@ -24,22 +23,15 @@ internal sealed class GetMeQueryHandler : IQueryHandler<GetMeQuery, AuthResponse
|
||||
return Result.Failure<AuthResponseDto>(AuthErrors.UserNotFound);
|
||||
}
|
||||
|
||||
var response = new AuthResponseDto(
|
||||
string.Empty,
|
||||
new AuthUserDto(
|
||||
user.Id,
|
||||
user.Username,
|
||||
user.DisplayName,
|
||||
user.Email,
|
||||
user.Bio,
|
||||
user.Avatar,
|
||||
user.Birthday,
|
||||
true,
|
||||
user.CreatedAt
|
||||
)
|
||||
);
|
||||
var response = new AuthResponseDto
|
||||
{
|
||||
AccessToken = string.Empty,
|
||||
RefreshToken = string.Empty,
|
||||
UserId = user.Id,
|
||||
Username = user.Username,
|
||||
DisplayName = user.DisplayName
|
||||
};
|
||||
|
||||
return Result.Success(response);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Modules.Auth.Application.Abstractions;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Auth.Contracts.Application.Auth.DTOs;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Auth.Application.Users.Auth;
|
||||
using BCrypt.Net;
|
||||
|
||||
namespace Knot.Modules.Auth.Application.Users.Login;
|
||||
|
||||
/// <summary>
|
||||
/// Команда для входа пользователя. Возвращает AuthResponseDto.
|
||||
/// Êîìàíäà äëÿ âõîäà ïîëüçîâàòåëÿ. Âîçâðàùàåò AuthResponseDto.
|
||||
/// </summary>
|
||||
public sealed record LoginUserCommand(string Username, string Password) : ICommand<AuthResponseDto>;
|
||||
|
||||
public sealed class LoginUserCommandHandler : ICommandHandler<LoginUserCommand, AuthResponseDto>
|
||||
internal sealed class LoginUserCommandHandler : ICommandHandler<LoginUserCommand, AuthResponseDto>
|
||||
{
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IJwtTokenProvider _tokenProvider;
|
||||
@@ -32,22 +31,15 @@ public sealed class LoginUserCommandHandler : ICommandHandler<LoginUserCommand,
|
||||
return Result.Failure<AuthResponseDto>(AuthErrors.IdentityInvalidCredentials);
|
||||
}
|
||||
|
||||
string token = _tokenProvider.Generate(user);
|
||||
string token = _tokenProvider.Generate(user.Id, user.Username, user.DisplayName, user.Avatar);
|
||||
|
||||
return Result.Success(new AuthResponseDto(
|
||||
token,
|
||||
new AuthUserDto(
|
||||
user.Id,
|
||||
user.Username,
|
||||
user.DisplayName,
|
||||
user.Email,
|
||||
user.Bio,
|
||||
user.Avatar,
|
||||
user.Birthday,
|
||||
true, // IsOnline (placeholder)
|
||||
user.CreatedAt
|
||||
)
|
||||
));
|
||||
return Result.Success(new AuthResponseDto
|
||||
{
|
||||
AccessToken = token,
|
||||
RefreshToken = string.Empty,
|
||||
UserId = user.Id,
|
||||
Username = user.Username,
|
||||
DisplayName = user.DisplayName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Auth.Contracts.Application.Auth.DTOs;
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Modules.Auth.Application.Abstractions;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Auth.Application.Users.Auth;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using BCrypt.Net;
|
||||
|
||||
namespace Knot.Modules.Auth.Application.Users.Register;
|
||||
@@ -22,7 +22,7 @@ public sealed record RegisterUserCommand(
|
||||
/// <summary>
|
||||
/// Обработчик команды регистрации.
|
||||
/// </summary>
|
||||
public sealed class RegisterUserCommandHandler : ICommandHandler<RegisterUserCommand, AuthResponseDto>
|
||||
internal sealed class RegisterUserCommandHandler : ICommandHandler<RegisterUserCommand, AuthResponseDto>
|
||||
{
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IAuthUnitOfWork _unitOfWork;
|
||||
@@ -54,7 +54,7 @@ public sealed class RegisterUserCommandHandler : ICommandHandler<RegisterUserCom
|
||||
return Result.Failure<AuthResponseDto>(AuthErrors.IdentityUsernameNotUnique);
|
||||
}
|
||||
|
||||
// 2. Хеширование пароля (здесь будет вызов сервиса, пока заглушка)
|
||||
// 2. Хеширование пароля
|
||||
string passwordHash = BCrypt.Net.BCrypt.HashPassword(request.Password);
|
||||
|
||||
// 3. Создание сущности
|
||||
@@ -65,27 +65,21 @@ public sealed class RegisterUserCommandHandler : ICommandHandler<RegisterUserCom
|
||||
request.Email,
|
||||
request.Bio);
|
||||
|
||||
// 4. Сохранение
|
||||
_userRepository.Add(user);
|
||||
// 4. Сохранение - используем метод с Domain User
|
||||
var repoWithDomainUserAdd = _userRepository as Infrastructure.Persistence.UserRepository;
|
||||
repoWithDomainUserAdd?.Add(user);
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
string token = _tokenProvider.Generate(user);
|
||||
string token = _tokenProvider.Generate(user.Id, user.Username, user.DisplayName, user.Avatar);
|
||||
|
||||
return Result.Success(new AuthResponseDto(
|
||||
token,
|
||||
new AuthUserDto(
|
||||
user.Id,
|
||||
user.Username,
|
||||
user.DisplayName,
|
||||
user.Email,
|
||||
user.Bio,
|
||||
user.Avatar,
|
||||
user.Birthday,
|
||||
true, // IsOnline (placeholder)
|
||||
user.CreatedAt
|
||||
)
|
||||
));
|
||||
return Result.Success(new AuthResponseDto
|
||||
{
|
||||
AccessToken = token,
|
||||
RefreshToken = string.Empty,
|
||||
UserId = user.Id,
|
||||
Username = user.Username,
|
||||
DisplayName = user.DisplayName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Knot.Modules.Auth.Infrastructure.Persistence;
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Modules.Auth.Application.Abstractions;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Auth.Infrastructure.Authentication;
|
||||
using Knot.Shared.Kernel;
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
using Knot.Shared.Kernel;
|
||||
|
||||
namespace Knot.Modules.Auth.Domain;
|
||||
|
||||
public static class AuthErrors
|
||||
{
|
||||
public static readonly Error FriendsNotFound = new Error("Friends.NotFound", "Friendship not found");
|
||||
public static readonly Error FriendsSelf = new Error("Friends.Self", "Cannot add yourself");
|
||||
public static readonly Error FriendsExists = new Error("Friends.Exists", "Friendship already exists");
|
||||
public static readonly Error UserNotFound = new Error("User.NotFound", "User not found");
|
||||
public static readonly Error IdentityInvalidCredentials = new Error("Identity.InvalidCredentials", "Неверное имя пользователя или пароль.");
|
||||
public static readonly Error IdentityRegistrationDisabled = new Error("Identity.RegistrationDisabled", "Registration is disabled by the administrator.");
|
||||
public static readonly Error IdentityUsernameNotUnique = new Error("Identity.UsernameNotUnique", "Это имя пользователя уже занято.");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Shared.Kernel.Events;
|
||||
|
||||
namespace Knot.Modules.Auth.Domain;
|
||||
|
||||
public sealed record UserStatusChangedDomainEvent(Guid UserId, bool IsOnline, DateTime LastSeen) : IDomainEvent;
|
||||
@@ -1,19 +0,0 @@
|
||||
using Knot.Modules.Auth.Domain;
|
||||
|
||||
namespace Knot.Modules.Auth.Domain;
|
||||
|
||||
/// <summary>
|
||||
/// Интерфейс репозитория для работы с пользователями.
|
||||
/// </summary>
|
||||
public interface IUserRepository
|
||||
{
|
||||
Task<User?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
|
||||
Task<List<User>> GetByIdsAsync(IEnumerable<Guid> ids, CancellationToken cancellationToken = default);
|
||||
Task<User?> GetByUsernameAsync(string username, CancellationToken cancellationToken = default);
|
||||
Task<bool> IsUsernameUniqueAsync(string username, CancellationToken cancellationToken = default);
|
||||
Task<List<User>> SearchUsersAsync(string query, CancellationToken cancellationToken = default);
|
||||
void Add(User user);
|
||||
void Update(User user);
|
||||
void Remove(User user);
|
||||
}
|
||||
|
||||
@@ -1,30 +1,32 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Shared.Kernel.Events;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
|
||||
namespace Knot.Modules.Auth.Domain;
|
||||
|
||||
public sealed record UserStatusChangedDomainEvent(Guid UserId, bool IsOnline, DateTime LastSeen) : IDomainEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Сущность пользователя в контексте идентификации (Identity).
|
||||
/// </summary>
|
||||
public sealed class User : AggregateRoot<Guid>
|
||||
{
|
||||
public string Username { get; private set; }
|
||||
public string Username { get; set; }
|
||||
public string PasswordHash { get; private set; }
|
||||
public string DisplayName { get; private set; }
|
||||
public string DisplayName { get; set; }
|
||||
public string? Email { get; private set; }
|
||||
public string? Bio { get; private set; }
|
||||
public string? Avatar { get; private set; }
|
||||
public string? Avatar { get; set; }
|
||||
public DateTime? Birthday { get; private set; }
|
||||
public DateTime CreatedAt { get; private set; }
|
||||
public bool HideStoryViews { get; private set; }
|
||||
public bool IsExternal { get; private set; }
|
||||
public string? Domain { get; private set; }
|
||||
public bool IsOnline { get; private set; }
|
||||
public bool IsOnline { get; set; }
|
||||
public DateTime? LastSeen { get; private set; }
|
||||
public bool HideStatus { get; private set; }
|
||||
public bool IsBanned { get; private set; }
|
||||
public bool IsBanned { get; set; }
|
||||
public string? PhoneNumber { get; set; }
|
||||
public string? RefreshToken { get; set; }
|
||||
public DateTime? BannedUntil { get; set; }
|
||||
|
||||
public void Ban() => IsBanned = true;
|
||||
public void Unban() => IsBanned = false;
|
||||
@@ -58,11 +60,18 @@ public sealed class User : AggregateRoot<Guid>
|
||||
{
|
||||
var user = new User(id, username, "EXTERNAL_USER", displayName, null, null);
|
||||
user.IsExternal = true;
|
||||
user.Domain = domain;
|
||||
user._domain = domain;
|
||||
user.Avatar = avatar;
|
||||
return user;
|
||||
}
|
||||
|
||||
private string? _domain;
|
||||
public string? UserDomain
|
||||
{
|
||||
get => _domain;
|
||||
set => _domain = value;
|
||||
}
|
||||
|
||||
public void UpdateProfile(string displayName, string? bio, DateTime? birthday)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
@@ -100,5 +109,28 @@ public sealed class User : AggregateRoot<Guid>
|
||||
{
|
||||
HideStatus = hide;
|
||||
}
|
||||
}
|
||||
|
||||
public UserContract ToContract()
|
||||
{
|
||||
return new UserContract
|
||||
{
|
||||
Id = Id,
|
||||
Username = Username,
|
||||
DisplayName = DisplayName,
|
||||
PasswordHash = PasswordHash,
|
||||
PhoneNumber = PhoneNumber,
|
||||
Email = Email,
|
||||
Bio = Bio,
|
||||
Avatar = Avatar,
|
||||
Birthday = Birthday,
|
||||
IsBot = false,
|
||||
CreatedAt = CreatedAt,
|
||||
IsBanned = IsBanned,
|
||||
BannedUntil = BannedUntil,
|
||||
IsOnline = IsOnline,
|
||||
IsExternal = IsExternal,
|
||||
Domain = _domain,
|
||||
LastSeen = LastSeen
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Knot.Modules.Auth.Application.Abstractions;
|
||||
using Knot.Modules.Auth.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Auth.Domain;
|
||||
|
||||
namespace Knot.Modules.Auth.Infrastructure.Authentication;
|
||||
|
||||
public sealed class JwtTokenProvider : IJwtTokenProvider
|
||||
internal sealed class JwtTokenProvider : IJwtTokenProvider
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
@@ -17,15 +17,13 @@ public sealed class JwtTokenProvider : IJwtTokenProvider
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public string Generate(User user)
|
||||
public string GenerateAccessToken(Guid userId, string username)
|
||||
{
|
||||
var claims = new Claim[]
|
||||
{
|
||||
new(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
|
||||
new(JwtRegisteredClaimNames.UniqueName, user.Username),
|
||||
new("name", user.DisplayName),
|
||||
new(ClaimTypes.Name, user.DisplayName),
|
||||
new("avatar", user.Avatar ?? string.Empty)
|
||||
new(JwtRegisteredClaimNames.Sub, userId.ToString()),
|
||||
new(JwtRegisteredClaimNames.UniqueName, username),
|
||||
new(ClaimTypes.Name, username)
|
||||
};
|
||||
|
||||
var secretKey = _configuration["Jwt:Secret"]!;
|
||||
@@ -42,5 +40,43 @@ public sealed class JwtTokenProvider : IJwtTokenProvider
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
}
|
||||
|
||||
public string GenerateRefreshToken()
|
||||
{
|
||||
var randomBytes = new byte[32];
|
||||
using var rng = System.Security.Cryptography.RandomNumberGenerator.Create();
|
||||
rng.GetBytes(randomBytes);
|
||||
return Convert.ToBase64String(randomBytes);
|
||||
}
|
||||
|
||||
public string Generate(Guid userId, string username, string displayName, string? avatar)
|
||||
{
|
||||
var claims = new Claim[]
|
||||
{
|
||||
new(JwtRegisteredClaimNames.Sub, userId.ToString()),
|
||||
new(JwtRegisteredClaimNames.UniqueName, username),
|
||||
new("name", displayName),
|
||||
new(ClaimTypes.Name, displayName),
|
||||
new("avatar", avatar ?? string.Empty)
|
||||
};
|
||||
|
||||
var secretKey = _configuration["Jwt:Secret"]!;
|
||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
|
||||
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
_configuration["Jwt:Issuer"],
|
||||
_configuration["Jwt:Audience"],
|
||||
claims,
|
||||
null,
|
||||
DateTime.UtcNow.AddMinutes(double.Parse(_configuration["Jwt:ExpiryInMinutes"] ?? "1440")),
|
||||
credentials);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
|
||||
public string Generate(User user)
|
||||
{
|
||||
return Generate(user.Id, user.Username, user.DisplayName, user.Avatar);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
using MediatR;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Knot.Modules.Auth.Application.Abstractions;
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Shared.Kernel.Security;
|
||||
using DomainUser = Knot.Modules.Auth.Domain.User;
|
||||
|
||||
namespace Knot.Modules.Auth.Infrastructure.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// Контекст базы данных для модуля Identity.
|
||||
/// </summary>
|
||||
public sealed class AuthDbContext : DbContext, IAuthUnitOfWork, Knot.Modules.Auth.Application.Abstractions.IAuthDbContext
|
||||
public sealed class AuthDbContext : DbContext, IAuthUnitOfWork, IAuthDbContext
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly IEncryptionService _encryptionService;
|
||||
@@ -24,9 +21,15 @@ public sealed class AuthDbContext : DbContext, IAuthUnitOfWork, Knot.Modules.Aut
|
||||
_encryptionService = encryptionService;
|
||||
}
|
||||
|
||||
public DbSet<User> Users => Set<User>();
|
||||
public DbSet<DomainUser> Users => Set<DomainUser>();
|
||||
|
||||
public IUserRepository UserRepository => throw new InvalidOperationException("UserRepository should be resolved from DI");
|
||||
|
||||
public void Add(UserContract user)
|
||||
{
|
||||
var domainUser = DomainUser.Create(user.Username, user.PasswordHash, user.DisplayName, user.Email, user.Bio);
|
||||
Set<DomainUser>().Add(domainUser);
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
@@ -38,16 +41,13 @@ public sealed class AuthDbContext : DbContext, IAuthUnitOfWork, Knot.Modules.Aut
|
||||
{
|
||||
modelBuilder.HasDefaultSchema("identity");
|
||||
|
||||
|
||||
|
||||
modelBuilder.Entity<User>(builder =>
|
||||
modelBuilder.Entity<DomainUser>(builder =>
|
||||
{
|
||||
builder.ToTable("Users");
|
||||
builder.HasKey(u => u.Id);
|
||||
builder.Property(u => u.Username).IsRequired().HasMaxLength(50);
|
||||
builder.HasIndex(u => u.Username).IsUnique();
|
||||
builder.Property(u => u.PasswordHash).IsRequired();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -56,7 +56,6 @@ public sealed class AuthDbContext : DbContext, IAuthUnitOfWork, Knot.Modules.Aut
|
||||
var domainEvents = ChangeTracker
|
||||
.Entries<IAggregateRoot>()
|
||||
.SelectMany(x =>
|
||||
|
||||
{
|
||||
if (x.Entity is AggregateRoot<Guid> root)
|
||||
{
|
||||
@@ -77,5 +76,9 @@ public sealed class AuthDbContext : DbContext, IAuthUnitOfWork, Knot.Modules.Aut
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
async Task<int> IAuthUnitOfWork.SaveChangesAsync(CancellationToken ct)
|
||||
{
|
||||
return await SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
using Knot.Modules.Auth.Domain;
|
||||
|
||||
namespace Knot.Modules.Auth.Infrastructure.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// Реализация репозитория пользователей с использованием EF Core.
|
||||
/// </summary>
|
||||
public sealed class UserRepository : IUserRepository
|
||||
internal sealed class UserRepository : IUserRepository
|
||||
{
|
||||
private readonly AuthDbContext _context;
|
||||
|
||||
@@ -15,19 +13,22 @@ public sealed class UserRepository : IUserRepository
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<User?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
|
||||
public async Task<UserContract?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Users.FirstOrDefaultAsync(u => u.Id == id, cancellationToken);
|
||||
var user = await _context.Users.FirstOrDefaultAsync(u => u.Id == id, cancellationToken);
|
||||
return user?.ToContract();
|
||||
}
|
||||
|
||||
public async Task<List<User>> GetByIdsAsync(IEnumerable<Guid> ids, CancellationToken cancellationToken = default)
|
||||
public async Task<List<UserContract>> GetByIdsAsync(IEnumerable<Guid> ids, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Users.Where(u => ids.Contains(u.Id)).ToListAsync(cancellationToken);
|
||||
var users = await _context.Users.Where(u => ids.Contains(u.Id)).ToListAsync(cancellationToken);
|
||||
return users.Select(u => u.ToContract()).ToList();
|
||||
}
|
||||
|
||||
public async Task<User?> GetByUsernameAsync(string username, CancellationToken cancellationToken = default)
|
||||
public async Task<UserContract?> GetByUsernameAsync(string username, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Users.FirstOrDefaultAsync(u => u.Username == username, cancellationToken);
|
||||
var user = await _context.Users.FirstOrDefaultAsync(u => u.Username == username, cancellationToken);
|
||||
return user?.ToContract();
|
||||
}
|
||||
|
||||
public async Task<bool> IsUsernameUniqueAsync(string username, CancellationToken cancellationToken = default)
|
||||
@@ -35,13 +36,48 @@ public sealed class UserRepository : IUserRepository
|
||||
return !await _context.Users.AnyAsync(u => u.Username == username, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<User>> SearchUsersAsync(string query, CancellationToken cancellationToken = default)
|
||||
public async Task<List<UserContract>> SearchUsersAsync(string query, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Users
|
||||
var users = await _context.Users
|
||||
.Where(u => u.Username.ToLower().Contains(query.ToLower()) ||
|
||||
(u.DisplayName != null && u.DisplayName.ToLower().Contains(query.ToLower())))
|
||||
.Take(20)
|
||||
.ToListAsync(cancellationToken);
|
||||
return users.Select(u => u.ToContract()).ToList();
|
||||
}
|
||||
|
||||
public async Task<bool> IsBannedAsync(Guid userId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(u => u.Id == userId, cancellationToken);
|
||||
return user?.IsBanned ?? false;
|
||||
}
|
||||
|
||||
public async Task<UserContract?> GetByRefreshTokenAsync(string refreshToken, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(u => u.RefreshToken == refreshToken, cancellationToken);
|
||||
return user?.ToContract();
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(UserContract user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var domainUser = await _context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken);
|
||||
if (domainUser != null)
|
||||
{
|
||||
domainUser.Username = user.Username;
|
||||
domainUser.DisplayName = user.DisplayName;
|
||||
domainUser.PhoneNumber = user.PhoneNumber;
|
||||
domainUser.IsBanned = user.IsBanned;
|
||||
domainUser.BannedUntil = user.BannedUntil;
|
||||
domainUser.IsOnline = user.IsOnline;
|
||||
domainUser.UserDomain = user.Domain;
|
||||
_context.Users.Update(domainUser);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(UserContract user)
|
||||
{
|
||||
var domainUser = User.Create(user.Username, user.PasswordHash, user.DisplayName, user.Email, user.Bio);
|
||||
_context.Users.Add(domainUser);
|
||||
}
|
||||
|
||||
public void Add(User user)
|
||||
@@ -58,5 +94,29 @@ public sealed class UserRepository : IUserRepository
|
||||
{
|
||||
_context.Users.Remove(user);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RemoveAsync(UserContract user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var domainUser = await _context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken);
|
||||
if (domainUser != null)
|
||||
{
|
||||
_context.Users.Remove(domainUser);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(UserContract user)
|
||||
{
|
||||
var domainUser = _context.Users.Find(user.Id);
|
||||
if (domainUser != null)
|
||||
{
|
||||
domainUser.Username = user.Username;
|
||||
domainUser.DisplayName = user.DisplayName;
|
||||
domainUser.PhoneNumber = user.PhoneNumber;
|
||||
domainUser.IsBanned = user.IsBanned;
|
||||
domainUser.BannedUntil = user.BannedUntil;
|
||||
domainUser.IsOnline = user.IsOnline;
|
||||
domainUser.UserDomain = user.Domain;
|
||||
_context.Users.Update(domainUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
|
||||
namespace Knot.Modules.Auth.Infrastructure.Services;
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
<ProjectReference Include="..\Profiles\Knot.Modules.Profiles.csproj" />
|
||||
<ProjectReference Include="..\..\Contracts\Knot.Modules.Auth.Contracts\Knot.Modules.Auth.Contracts.csproj" />
|
||||
<ProjectReference Include="..\..\Contracts\Knot.Modules.Profiles.Contracts\Knot.Modules.Profiles.Contracts.csproj" />
|
||||
<ProjectReference Include="..\..\Contracts\Knot.Modules.Settings.Contracts\Knot.Modules.Settings.Contracts.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -28,7 +30,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<ProjectReference Include="..\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Knot.Modules.Auth.Infrastructure.Persistence;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -26,7 +26,7 @@ namespace Knot.Modules.Auth.Migrations
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Knot.Modules.Auth.Domain.User", b =>
|
||||
modelBuilder.Entity("Knot.Modules.Auth.Contracts.Domain.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ namespace Knot.Modules.Auth.Migrations
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Knot.Modules.Auth.Domain.User", b =>
|
||||
modelBuilder.Entity("Knot.Modules.Auth.Contracts.Domain.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ namespace Knot.Modules.Auth.Migrations
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Knot.Modules.Auth.Domain.User", b =>
|
||||
modelBuilder.Entity("Knot.Modules.Auth.Contracts.Domain.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Knot.Modules.Auth.Migrations
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Knot.Modules.Auth.Domain.User", b =>
|
||||
modelBuilder.Entity("Knot.Modules.Auth.Contracts.Domain.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
|
||||
@@ -32,8 +32,7 @@ public sealed class AuthEndpoints : ICarterModule
|
||||
group.MapGet("me", async (ISender sender, IUserContext userContext, CancellationToken ct) =>
|
||||
{
|
||||
var result = await sender.Send(new GetMeQuery(userContext.UserId), ct);
|
||||
return result.IsSuccess ? Results.Ok(new { User = result.Value.User }) : Results.NotFound();
|
||||
return result.IsSuccess ? Results.Ok(result.Value) : Results.NotFound();
|
||||
}).RequireAuthorization();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user