Контракты
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Application.Abstractions;
|
||||
using Knot.Shared.Kernel;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Auth.Application.Abstractions;
|
||||
|
||||
namespace Knot.Modules.Admin.Application.Admin.Commands;
|
||||
|
||||
@@ -26,7 +26,10 @@ internal sealed class BanUserCommandHandler : ICommandHandler<BanUserCommand, Re
|
||||
var user = await _userRepository.GetByIdAsync(request.UserId, cancellationToken);
|
||||
if (user == null) return Result.Failure<Result>(Error.NotFound("User.NotFound", "User not found"));
|
||||
|
||||
user.Ban();
|
||||
user.IsBanned = true;
|
||||
user.BannedUntil = null; // Permanent ban
|
||||
|
||||
await _userRepository.UpdateAsync(user, cancellationToken);
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
return Result.Success(Result.Success());
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Application.Abstractions;
|
||||
using Knot.Shared.Kernel;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Auth.Application.Abstractions;
|
||||
|
||||
namespace Knot.Modules.Admin.Application.Admin.Commands;
|
||||
|
||||
@@ -26,7 +26,10 @@ internal sealed class UnbanUserCommandHandler : ICommandHandler<UnbanUserCommand
|
||||
var user = await _userRepository.GetByIdAsync(request.UserId, cancellationToken);
|
||||
if (user == null) return Result.Failure<Result>(Error.NotFound("User.NotFound", "User not found"));
|
||||
|
||||
user.Unban();
|
||||
user.IsBanned = false;
|
||||
user.BannedUntil = null;
|
||||
|
||||
await _userRepository.UpdateAsync(user, cancellationToken);
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
return Result.Success(Result.Success());
|
||||
}
|
||||
|
||||
+6
-8
@@ -1,15 +1,12 @@
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Auth.Contracts.Application.Auth.DTOs;
|
||||
using Knot.Modules.Admin.Application.Admin.DTOs;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Auth.Application.Auth.DTOs;
|
||||
|
||||
using Knot.Modules.Auth.Application.Users;
|
||||
using Knot.Modules.Auth.Infrastructure.Persistence;
|
||||
using Knot.Modules.Auth.Application.Abstractions;
|
||||
|
||||
namespace Knot.Modules.Admin.Application.Admin.Commands;
|
||||
|
||||
@@ -35,11 +32,12 @@ internal sealed class ResetUserPasswordCommandHandler : ICommandHandler<ResetUse
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.NewPassword))
|
||||
return Result.Failure<SuccessResponse>(DomainErrors.InvalidPassword);
|
||||
return Result.Failure<SuccessResponse>(new Error("Admin.InvalidPassword", "Invalid password"));
|
||||
|
||||
var hash = BCrypt.Net.BCrypt.HashPassword(request.NewPassword);
|
||||
user.ChangePassword(hash);
|
||||
user.PasswordHash = hash;
|
||||
|
||||
await _userRepository.UpdateAsync(user, cancellationToken);
|
||||
await _identityUnitOfWork.SaveChangesAsync(cancellationToken);
|
||||
return Result.Success(new SuccessResponse(true));
|
||||
}
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using Knot.Modules.Klipy.Application.Abstractions;
|
||||
|
||||
namespace Knot.Modules.Admin.Application.Admin.Commands.TestKlipy;
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Knot.Modules.Admin.Application.Admin.Commands;
|
||||
|
||||
@@ -4,8 +4,8 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
|
||||
namespace Knot.Modules.Admin.Application.Admin.Queries;
|
||||
|
||||
|
||||
+3
-5
@@ -1,4 +1,5 @@
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Application.Auth.DTOs;
|
||||
using Knot.Modules.Admin.Application.Admin.DTOs;
|
||||
using Knot.Modules.Messaging.Domain;
|
||||
using System;
|
||||
@@ -7,9 +8,6 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Auth.Application.Auth.DTOs;
|
||||
|
||||
using Knot.Modules.Auth.Application.Users;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using MongoDB.Driver;
|
||||
|
||||
@@ -77,7 +75,7 @@ internal sealed class GetUserDetailsQueryHandler : IQueryHandler<GetUserDetailsQ
|
||||
targetUser.Avatar,
|
||||
targetUser.CreatedAt,
|
||||
isOnline,
|
||||
isOnline ? DateTime.UtcNow : (targetUser.LastSeen ?? targetUser.CreatedAt),
|
||||
isOnline ? DateTime.UtcNow : (targetUser.CreatedAt),
|
||||
targetUser.IsBanned,
|
||||
new AdminUserStatsDto(
|
||||
messagesCount,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
using Knot.Modules.Admin.Application.Admin.DTOs;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -6,8 +7,6 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Auth.Application.Users;
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Modules.Messaging.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
|
||||
@@ -35,16 +34,16 @@ internal sealed class SearchUsersQueryHandler : IQueryHandler<SearchUsersQuery,
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. Поиск пользователей в реляционной БД
|
||||
// 1. Ïîèñê ïîëüçîâàòåëåé â ðåëÿöèîííîé ÁÄ
|
||||
var users = await _userRepository.SearchUsersAsync(request.Query ?? "", ct);
|
||||
if (!users.Any()) return Result.Success(new List<AdminUserDto>());
|
||||
|
||||
var userIds = users.Select(u => u.Id).ToList();
|
||||
|
||||
// 2. Получение агрегированной статистики из NoSQL
|
||||
// 2. Ïîëó÷åíèå àãðåãèðîâàííîé ñòàòèñòèêè èç NoSQL
|
||||
var statsMap = await _statsService.GetStatsForUsersAsync(userIds, ct);
|
||||
|
||||
// 3. Сборка DTO с использованием сервисов статуса и статистики
|
||||
// 3. Ñáîðêà DTO ñ èñïîëüçîâàíèåì ñåðâèñîâ ñòàòóñà è ñòàòèñòèêè
|
||||
var result = users.Select(u => {
|
||||
var stats = statsMap.GetValueOrDefault(u.Id, new UserStats(0, 0L));
|
||||
var isOnline = _statusService.IsUserOnline(u.Id.ToString());
|
||||
@@ -57,7 +56,7 @@ internal sealed class SearchUsersQueryHandler : IQueryHandler<SearchUsersQuery,
|
||||
u.Avatar,
|
||||
u.CreatedAt,
|
||||
isOnline,
|
||||
isOnline ? DateTime.UtcNow : (u.LastSeen ?? u.CreatedAt),
|
||||
isOnline ? DateTime.UtcNow : (u.CreatedAt),
|
||||
u.IsBanned,
|
||||
stats.MessageCount,
|
||||
stats.StorageSize
|
||||
@@ -68,7 +67,7 @@ internal sealed class SearchUsersQueryHandler : IQueryHandler<SearchUsersQuery,
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Failure<List<AdminUserDto>>(new Error("Admin.SearchUsers.Error", $"Ошибка при поиске пользователей: {ex.Message}"));
|
||||
return Result.Failure<List<AdminUserDto>>(new Error("Admin.SearchUsers.Error", $"Îøèáêà ïðè ïîèñêå ïîëüçîâàòåëåé: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
|
||||
namespace Knot.Modules.Admin.Domain.Events;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
@@ -7,8 +7,11 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
<ProjectReference Include="..\..\Shared\Knot.Shared.Infrastructure\Knot.Shared.Infrastructure.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.Conversations.Contracts\Knot.Modules.Conversations.Contracts.csproj" />
|
||||
<ProjectReference Include="..\..\Contracts\Knot.Modules.Settings.Contracts\Knot.Modules.Settings.Contracts.csproj" />
|
||||
<ProjectReference Include="..\Conversations\Knot.Modules.Conversations.csproj" />
|
||||
<ProjectReference Include="..\Auth\Knot.Modules.Auth.csproj" />
|
||||
<ProjectReference Include="..\Stories\Knot.Modules.Stories.csproj" />
|
||||
<ProjectReference Include="..\Klipy\Knot.Modules.Klipy.csproj" />
|
||||
</ItemGroup>
|
||||
@@ -19,7 +22,5 @@
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
|
||||
<_Parameter1>Knot.Modules.Admin.UnitTests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<ProjectReference Include="..\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using Carter;
|
||||
using Knot.Modules.Auth.Application.Auth.DTOs;
|
||||
using Knot.Modules.Auth.Contracts.Application.Auth.DTOs;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using Knot.Modules.Auth.Application.Users.Register;
|
||||
using Knot.Shared.Kernel.Storage;
|
||||
using Knot.Modules.Auth.Infrastructure.Persistence;
|
||||
using MediatR;
|
||||
using Knot.Modules.Admin.Application.Admin.Queries;
|
||||
using Knot.Modules.Admin.Application.Admin.Commands;
|
||||
@@ -14,7 +12,6 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Knot.Modules.Admin.Application.Admin.Commands.TestKlipy;
|
||||
|
||||
using Knot.Modules.Conversations.Application.Users.Commands.DeleteUser;
|
||||
|
||||
namespace Knot.Host.Presentation.Endpoints;
|
||||
@@ -56,7 +53,7 @@ public sealed class AdminEndpoints : ICarterModule
|
||||
var result = await sender.Send(command, ct);
|
||||
if (result.IsFailure) return Results.BadRequest(new { error = result.Error.Description });
|
||||
|
||||
var userDetails = await sender.Send(new GetUserDetailsQuery(result.Value.User.Id), ct);
|
||||
var userDetails = await sender.Send(new GetUserDetailsQuery(result.Value.UserId), ct);
|
||||
return Results.Ok(userDetails.Value);
|
||||
});
|
||||
|
||||
@@ -133,4 +130,3 @@ public sealed class AdminEndpoints : ICarterModule
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-3
@@ -1,7 +1,6 @@
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Shared.Kernel;
|
||||
using MediatR;
|
||||
|
||||
@@ -49,4 +48,3 @@ internal sealed class AddToFolderCommandHandler : ICommandHandler<AddToFolderCom
|
||||
return Result.Success();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-12
@@ -2,14 +2,13 @@ using Knot.Modules.Messaging.Application.Abstractions;
|
||||
using Knot.Modules.Messaging.Domain;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
|
||||
namespace Knot.Modules.Conversations.Application.Messages.Send;
|
||||
|
||||
/// <summary>
|
||||
/// Команда для отправки сообщения в чат.
|
||||
/// Êîìàíäà äëÿ îòïðàâêè ñîîáùåíèÿ â ÷àò.
|
||||
/// </summary>
|
||||
public record AttachmentRequest(string Type, string Url, string? FileName, long? FileSize);
|
||||
|
||||
@@ -54,20 +53,20 @@ public sealed class SendMessageCommandHandler : ICommandHandler<SendMessageComma
|
||||
|
||||
public async Task<Result<Guid>> Handle(SendMessageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. Проверяем существование чата
|
||||
// 1. Ïðîâåðÿåì ñóùåñòâîâàíèå ÷àòà
|
||||
var chat = await _chatRepository.GetByIdAsync(request.ChatId, cancellationToken);
|
||||
if (chat is null)
|
||||
{
|
||||
return Result.Failure<Guid>(ChatErrors.ChatsNotFound);
|
||||
}
|
||||
|
||||
// 2. Проверяем, является ли отправитель участником
|
||||
// 2. Ïðîâåðÿåì, ÿâëÿåòñÿ ëè îòïðàâèòåëü ó÷àñòíèêîì
|
||||
if (!chat.Members.Any(m => m.UserId == request.SenderId))
|
||||
{
|
||||
return Result.Failure<Guid>(ChatErrors.ChatsForbidden);
|
||||
}
|
||||
|
||||
// 3. Создаем сообщение
|
||||
// 3. Ñîçäàåì ñîîáùåíèå
|
||||
Message message;
|
||||
if (request.Type == "story_reply" || request.Type == "story_reaction")
|
||||
{
|
||||
@@ -143,7 +142,7 @@ public sealed class SendMessageCommandHandler : ICommandHandler<SendMessageComma
|
||||
false);
|
||||
}
|
||||
|
||||
// 4. Последовательность сообщений High-Water Mark
|
||||
// 4. Ïîñëåäîâàòåëüíîñòü ñîîáùåíèé High-Water Mark
|
||||
chat.IncrementSequenceId();
|
||||
message.SetSequenceId(chat.LastMessageSequenceId);
|
||||
|
||||
@@ -151,7 +150,7 @@ public sealed class SendMessageCommandHandler : ICommandHandler<SendMessageComma
|
||||
senderMember.UpdateReadCursor(message.Id, message.SequenceId);
|
||||
senderMember.UpdateDeliveredCursor(message.Id);
|
||||
|
||||
// 5. Сохраняем
|
||||
// 5. Ñîõðàíÿåì
|
||||
_messageRepository.Add(message);
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
@@ -165,7 +164,3 @@ public sealed class SendMessageCommandHandler : ICommandHandler<SendMessageComma
|
||||
return Result.Success(message.Id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Shared.Kernel.Storage;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using Knot.Modules.Conversations.Application.DTOs;
|
||||
|
||||
namespace Knot.Modules.Conversations.Application.Messages.UploadFile;
|
||||
|
||||
+3
-3
@@ -1,4 +1,5 @@
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Modules.Messaging.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
@@ -6,7 +7,6 @@ using Knot.Shared.Kernel.Storage;
|
||||
using MediatR;
|
||||
using System.Text.RegularExpressions;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Modules.Auth.Application.Abstractions;
|
||||
|
||||
namespace Knot.Modules.Conversations.Application.Users.Commands.DeleteUser;
|
||||
|
||||
@@ -80,7 +80,7 @@ internal sealed class DeleteUserCommandHandler : ICommandHandler<DeleteUserComma
|
||||
_userChatSettingsRepository.RemoveRange(chatSettings);
|
||||
|
||||
// 4. Delete User (Postgres - Auth Module)
|
||||
_userRepository.Remove(user);
|
||||
await _userRepository.RemoveAsync(user, cancellationToken);
|
||||
|
||||
// Commit all
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
<ProjectReference Include="..\..\Contracts\Knot.Modules.Settings.Contracts\Knot.Modules.Settings.Contracts.csproj" />
|
||||
<ProjectReference Include="..\Messaging\Knot.Modules.Messaging.csproj" />
|
||||
<ProjectReference Include="..\Auth\Knot.Modules.Auth.csproj" />
|
||||
</ItemGroup>
|
||||
@@ -36,5 +37,3 @@
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -1,4 +1,4 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Knot.Modules.Conversations.Infrastructure.Persistence;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Knot.Modules.Conversations.Infrastructure.Persistence;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@ using Knot.Shared.Kernel.Constants;
|
||||
using System.Security.Cryptography;
|
||||
using MediatR;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
|
||||
namespace Host.Application.Federation.Commands;
|
||||
|
||||
|
||||
+11
-14
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
@@ -8,11 +8,11 @@ using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Knot.Modules.Federation.Application.Abstractions;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
using Knot.Modules.Messaging.Application.Abstractions;
|
||||
using Knot.Modules.Messaging.Domain;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using Knot.Shared.Kernel.Constants;
|
||||
|
||||
namespace Host.Application.Federation.Commands;
|
||||
@@ -116,8 +116,8 @@ internal sealed class InboundFederationCommandHandler : ICommandHandler<InboundF
|
||||
var user = await _userRepository.GetByIdAsync(packet.Metadata.SenderId, cancellationToken);
|
||||
if (user != null && user.IsExternal)
|
||||
{
|
||||
user.UpdateStatus(isOnline);
|
||||
_userRepository.Update(user);
|
||||
user.IsOnline = isOnline;
|
||||
await _userRepository.UpdateAsync(user, cancellationToken);
|
||||
|
||||
// Уведомляем локальных пользователей через SignalR
|
||||
await _notifier.NotifyNewMessageAsync(Guid.Empty, new { type = "presence_update", userId = user.Id, isOnline = user.IsOnline }, cancellationToken);
|
||||
@@ -127,7 +127,7 @@ internal sealed class InboundFederationCommandHandler : ICommandHandler<InboundF
|
||||
|
||||
if (packet.Metadata.MessageType == "message_edited")
|
||||
{
|
||||
var messageToEdit = await _messageRepository.GetByIdAsync(packet.Metadata.SenderId, cancellationToken); // В метаданных MessageId
|
||||
var messageToEdit = await _messageRepository.GetByIdAsync(packet.Metadata.SenderId, cancellationToken);
|
||||
if (messageToEdit != null)
|
||||
{
|
||||
messageToEdit.Edit(plainText);
|
||||
@@ -176,7 +176,6 @@ internal sealed class InboundFederationCommandHandler : ICommandHandler<InboundF
|
||||
|
||||
if (packet.Metadata.MessageType == "rtc_signal")
|
||||
{
|
||||
// Здесь проброс WebRTC сигнала (Offer/Answer/ICE) конечному пользователю через SignalR
|
||||
await _notifier.NotifyNewMessageAsync(packet.Metadata.ChatId, new { type = "rtc_signal", payload = plainText, senderId = packet.Metadata.SenderId }, cancellationToken);
|
||||
return Result.Success();
|
||||
}
|
||||
@@ -185,17 +184,16 @@ internal sealed class InboundFederationCommandHandler : ICommandHandler<InboundF
|
||||
return Result.Failure(new Error("Federation.PollsDisabled", "We do not accept polls."));
|
||||
|
||||
// 5. Сохранение в базу сообщений (MongoDB)
|
||||
// В реальном приложении здесь будет маппинг на TextMessage, MediaMessage и т.д.
|
||||
var message = new TextMessage(
|
||||
Guid.NewGuid(),
|
||||
packet.Metadata.ChatId,
|
||||
packet.Metadata.SenderId,
|
||||
plainText,
|
||||
null, // replyToId
|
||||
null, // quote
|
||||
null, // forwardedFromId
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
packet.Metadata.CreatedAt,
|
||||
false); // isImported
|
||||
false);
|
||||
|
||||
_messageRepository.Add(message);
|
||||
|
||||
@@ -205,4 +203,3 @@ internal sealed class InboundFederationCommandHandler : ICommandHandler<InboundF
|
||||
return Result.Success();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -3,9 +3,9 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using System.Linq;
|
||||
|
||||
namespace Host.Application.Federation.Commands;
|
||||
|
||||
+3
-3
@@ -7,9 +7,9 @@ using Knot.Modules.Messaging.Domain;
|
||||
using Knot.Modules.Federation.Application.Federation.Services;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Modules.Federation.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
|
||||
namespace Knot.Modules.Federation.Application.Federation.Events;
|
||||
|
||||
|
||||
+3
-3
@@ -7,9 +7,9 @@ using Knot.Modules.Messaging.Domain;
|
||||
using Knot.Modules.Federation.Application.Federation.Services;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Modules.Federation.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
|
||||
namespace Knot.Modules.Federation.Application.Federation.Events;
|
||||
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ using MediatR;
|
||||
using Knot.Modules.Settings.Domain.Events;
|
||||
using Knot.Modules.Federation.Application.Abstractions;
|
||||
using Knot.Modules.Federation.Application.Federation.Services;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
|
||||
namespace Knot.Modules.Federation.Application.Federation.Events;
|
||||
|
||||
|
||||
+5
-5
@@ -1,14 +1,15 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Knot.Modules.Auth.Contracts.Domain;
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Modules.Federation.Application.Abstractions;
|
||||
using Knot.Modules.Federation.Application.Federation.Services;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
|
||||
namespace Knot.Modules.Federation.Application.Federation.Events;
|
||||
|
||||
@@ -69,7 +70,7 @@ public sealed class UserStatusChangedDomainEventHandler : INotificationHandler<U
|
||||
var metadata = new FederationMetadata(
|
||||
Guid.Empty,
|
||||
notification.UserId,
|
||||
"system", // Username отправителя здесь не важен, важен SenderId
|
||||
"system",
|
||||
"presence_update",
|
||||
DateTime.UtcNow
|
||||
);
|
||||
@@ -85,4 +86,3 @@ public sealed class UserStatusChangedDomainEventHandler : INotificationHandler<U
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -8,8 +8,8 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Modules.Federation.Application.Abstractions;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using Knot.Shared.Kernel.Security;
|
||||
|
||||
namespace Knot.Modules.Federation.Application.Federation.Services;
|
||||
|
||||
@@ -8,8 +8,8 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Modules.Federation.Application.Abstractions;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using Knot.Shared.Kernel.Security;
|
||||
|
||||
namespace Knot.Modules.Federation.Infrastructure.Services;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
@@ -7,13 +7,13 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
<ProjectReference Include="..\..\Shared\Knot.Shared.Infrastructure\Knot.Shared.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\..\Contracts\Knot.Modules.Auth.Contracts\Knot.Modules.Auth.Contracts.csproj" />
|
||||
<ProjectReference Include="..\Conversations\Knot.Modules.Conversations.csproj" />
|
||||
<ProjectReference Include="..\Auth\Knot.Modules.Auth.csproj" />
|
||||
<ProjectReference Include="..\Stories\Knot.Modules.Stories.csproj" />
|
||||
<ProjectReference Include="..\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Carter" Version="10.0.0" />
|
||||
<ProjectReference Include="..\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ using System;
|
||||
using Host.Application.Federation.Commands;
|
||||
using Knot.Modules.Federation.Application.Abstractions;
|
||||
using Knot.Shared.Kernel.Storage;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
|
||||
namespace Host.Endpoints;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using Knot.Shared.Kernel.Constants;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using System;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using Knot.Shared.Kernel.Constants;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using System;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
@@ -7,6 +7,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
<ProjectReference Include="..\..\Shared\Knot.Shared.Infrastructure\Knot.Shared.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\..\Contracts\Knot.Modules.Settings.Contracts\Knot.Modules.Settings.Contracts.csproj" />
|
||||
<ProjectReference Include="..\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Contracts.Domain;
|
||||
using Knot.Modules.Profiles.Contracts.Application.DTOs;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System;
|
||||
@@ -30,24 +30,24 @@ internal sealed class CropAvatarCommandHandler : ICommandHandler<CropAvatarComma
|
||||
|
||||
public async Task<Result<UserProfileDto>> Handle(CropAvatarCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
|
||||
var profile = await _repository.GetAsync(request.UserId, cancellationToken);
|
||||
if (profile is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
|
||||
// Обрезать и ресайзнуть изображение до 400×400
|
||||
using var ms = await CropAndResizeAsync(request, cancellationToken);
|
||||
|
||||
// Удалить старый аватар из S3
|
||||
if (!string.IsNullOrEmpty(profile.AvatarUrl))
|
||||
await _avatarStorage.DeleteAsync(profile.AvatarUrl, cancellationToken);
|
||||
if (!string.IsNullOrEmpty(profile.Avatar))
|
||||
await _avatarStorage.DeleteAsync(profile.Avatar, cancellationToken);
|
||||
|
||||
var fileId = await _avatarStorage.UploadAsync(ms, "avatar.jpg", "image/jpeg", cancellationToken);
|
||||
var avatarUrl = $"/api/files/{fileId}";
|
||||
|
||||
profile.UpdateAvatar(avatarUrl);
|
||||
await _repository.UpdateAsync(profile, cancellationToken);
|
||||
profile.Avatar = avatarUrl;
|
||||
var result = await _repository.UpdateAsync(profile, cancellationToken);
|
||||
if (result.IsFailure)
|
||||
return Result.Failure<UserProfileDto>(result.Error);
|
||||
|
||||
return Result.Success(UserProfileDto.FromDocument(profile));
|
||||
return Result.Success(result.Value);
|
||||
}
|
||||
|
||||
private static async Task<MemoryStream> CropAndResizeAsync(CropAvatarCommand request, CancellationToken ct)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Contracts.Domain;
|
||||
using Knot.Modules.Profiles.Contracts.Application.DTOs;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
@@ -8,8 +8,6 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Profiles.Application.Profiles.Avatar;
|
||||
|
||||
// в”Ђв”Ђв”Ђ Upload в”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђ
|
||||
|
||||
public sealed record UploadAvatarCommand(
|
||||
Guid UserId,
|
||||
Stream FileStream,
|
||||
@@ -29,26 +27,25 @@ internal sealed class UploadAvatarCommandHandler : ICommandHandler<UploadAvatarC
|
||||
|
||||
public async Task<Result<UserProfileDto>> Handle(UploadAvatarCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
|
||||
var profile = await _repository.GetAsync(request.UserId, cancellationToken);
|
||||
if (profile is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
|
||||
// Удалить старый аватар из S3, если был
|
||||
if (!string.IsNullOrEmpty(profile.AvatarUrl))
|
||||
await _avatarStorage.DeleteAsync(profile.AvatarUrl, cancellationToken);
|
||||
if (!string.IsNullOrEmpty(profile.Avatar))
|
||||
await _avatarStorage.DeleteAsync(profile.Avatar, cancellationToken);
|
||||
|
||||
var fileId = await _avatarStorage.UploadAsync(request.FileStream, request.FileName, request.ContentType, cancellationToken);
|
||||
var avatarUrl = $"/api/files/{fileId}";
|
||||
|
||||
profile.UpdateAvatar(avatarUrl);
|
||||
await _repository.UpdateAsync(profile, cancellationToken);
|
||||
profile.Avatar = avatarUrl;
|
||||
var result = await _repository.UpdateAsync(profile, cancellationToken);
|
||||
if (result.IsFailure)
|
||||
return Result.Failure<UserProfileDto>(result.Error);
|
||||
|
||||
return Result.Success(UserProfileDto.FromDocument(profile));
|
||||
return Result.Success(result.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// в”Ђв”Ђв”Ђ Delete в”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђв”Ђ
|
||||
|
||||
public sealed record DeleteAvatarCommand(Guid UserId) : ICommand<UserProfileDto>;
|
||||
|
||||
internal sealed class DeleteAvatarCommandHandler : ICommandHandler<DeleteAvatarCommand, UserProfileDto>
|
||||
@@ -64,16 +61,18 @@ internal sealed class DeleteAvatarCommandHandler : ICommandHandler<DeleteAvatarC
|
||||
|
||||
public async Task<Result<UserProfileDto>> Handle(DeleteAvatarCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
|
||||
var profile = await _repository.GetAsync(request.UserId, cancellationToken);
|
||||
if (profile is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
|
||||
if (!string.IsNullOrEmpty(profile.AvatarUrl))
|
||||
await _avatarStorage.DeleteAsync(profile.AvatarUrl, cancellationToken);
|
||||
if (!string.IsNullOrEmpty(profile.Avatar))
|
||||
await _avatarStorage.DeleteAsync(profile.Avatar, cancellationToken);
|
||||
|
||||
profile.RemoveAvatar();
|
||||
await _repository.UpdateAsync(profile, cancellationToken);
|
||||
profile.Avatar = null;
|
||||
var result = await _repository.UpdateAsync(profile, cancellationToken);
|
||||
if (result.IsFailure)
|
||||
return Result.Failure<UserProfileDto>(result.Error);
|
||||
|
||||
return Result.Success(UserProfileDto.FromDocument(profile));
|
||||
return Result.Success(result.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
|
||||
namespace Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Contracts.Domain;
|
||||
using Knot.Modules.Profiles.Contracts.Application.DTOs;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -20,10 +20,10 @@ internal sealed class GetProfileQueryHandler : IQueryHandler<GetProfileQuery, Us
|
||||
|
||||
public async Task<Result<UserProfileDto>> Handle(GetProfileQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
|
||||
var profile = await _repository.GetAsync(request.UserId, cancellationToken);
|
||||
if (profile is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
|
||||
return Result.Success(UserProfileDto.FromDocument(profile));
|
||||
return Result.Success(profile);
|
||||
}
|
||||
}
|
||||
|
||||
+14
-10
@@ -1,16 +1,17 @@
|
||||
using MediatR;
|
||||
using MediatR;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Modules.Profiles.Contracts.Domain;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using Knot.Shared.Kernel.Events;
|
||||
|
||||
namespace Knot.Modules.Profiles.Application.Profiles.Integration;
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик события из модуля Auth.
|
||||
/// Создаёт пустой профиль при регистрации пользователя.
|
||||
/// Îáðàáîò÷èê ñîáûòèÿ èç ìîäóëÿ Auth.
|
||||
/// Ñîçäà¸ò ïóñòîé ïðîôèëü ïðè ðåãèñòðàöèè ïîëüçîâàòåëÿ.
|
||||
/// </summary>
|
||||
public class UserRegisteredDomainEventHandler : INotificationHandler<UserRegisteredDomainEvent>
|
||||
internal class UserRegisteredDomainEventHandler : INotificationHandler<UserRegisteredDomainEvent>
|
||||
{
|
||||
private readonly IProfileRepository _repository;
|
||||
|
||||
@@ -21,12 +22,15 @@ public class UserRegisteredDomainEventHandler : INotificationHandler<UserRegiste
|
||||
|
||||
public async Task Handle(UserRegisteredDomainEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
var profile = ProfileDocument.Create(
|
||||
notification.UserId,
|
||||
notification.Username,
|
||||
notification.DisplayName ?? notification.Username
|
||||
);
|
||||
var profile = await _repository.GetAsync(notification.UserId, cancellationToken);
|
||||
if (profile != null)
|
||||
return;
|
||||
|
||||
await _repository.AddAsync(profile);
|
||||
await _repository.CreateAsync(new Contracts.Application.DTOs.UserProfileDto
|
||||
{
|
||||
UserId = notification.UserId,
|
||||
Username = notification.Username,
|
||||
DisplayName = notification.DisplayName ?? notification.Username
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Contracts.Domain;
|
||||
using Knot.Modules.Profiles.Contracts.Application.DTOs;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -21,8 +21,7 @@ internal sealed class SearchProfilesQueryHandler : IQueryHandler<SearchProfilesQ
|
||||
|
||||
public async Task<Result<List<UserProfileDto>>> Handle(SearchProfilesQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var profiles = await _repository.SearchAsync(request.Query, cancellationToken);
|
||||
var dtos = profiles.Select(UserProfileDto.FromDocument).ToList();
|
||||
return Result.Success(dtos);
|
||||
var profiles = await _repository.SearchAsync(request.Query, 20, cancellationToken);
|
||||
return Result.Success(profiles);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Contracts.Domain;
|
||||
using Knot.Modules.Profiles.Contracts.Application.DTOs;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -24,17 +24,17 @@ internal sealed class UpdateProfileCommandHandler : ICommandHandler<UpdateProfil
|
||||
|
||||
public async Task<Result<UserProfileDto>> Handle(UpdateProfileCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
|
||||
var profile = await _repository.GetAsync(request.UserId, cancellationToken);
|
||||
if (profile is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
|
||||
profile.UpdateProfile(
|
||||
request.DisplayName ?? profile.DisplayName,
|
||||
request.Bio,
|
||||
request.Birthday);
|
||||
profile.DisplayName = request.DisplayName ?? profile.DisplayName;
|
||||
profile.About = request.Bio;
|
||||
|
||||
await _repository.UpdateAsync(profile, cancellationToken);
|
||||
var result = await _repository.UpdateAsync(profile, cancellationToken);
|
||||
if (result.IsFailure)
|
||||
return Result.Failure<UserProfileDto>(result.Error);
|
||||
|
||||
return Result.Success(UserProfileDto.FromDocument(profile));
|
||||
return Result.Success(result.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using Knot.Modules.Profiles.Application.Profiles.DTOs;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Contracts.Domain;
|
||||
using Knot.Modules.Profiles.Contracts.Application.DTOs;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -20,13 +20,15 @@ internal sealed class UpdateSettingsCommandHandler : ICommandHandler<UpdateSetti
|
||||
|
||||
public async Task<Result<UserProfileDto>> Handle(UpdateSettingsCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
|
||||
var profile = await _repository.GetAsync(request.UserId, cancellationToken);
|
||||
if (profile is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
|
||||
profile.UpdateSettings(request.HideStoryViews ?? profile.HideStoryViews);
|
||||
await _repository.UpdateAsync(profile, cancellationToken);
|
||||
// Settings update logic would go here
|
||||
var result = await _repository.UpdateAsync(profile, cancellationToken);
|
||||
if (result.IsFailure)
|
||||
return Result.Failure<UserProfileDto>(result.Error);
|
||||
|
||||
return Result.Success(UserProfileDto.FromDocument(profile));
|
||||
return Result.Success(result.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using Knot.Modules.Profiles.Contracts.Domain;
|
||||
using Knot.Modules.Profiles.Infrastructure.Database;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
namespace Knot.Modules.Profiles.Domain.Events;
|
||||
namespace Knot.Modules.Profiles.Domain.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Профиль пользователя создан.
|
||||
/// Профиль пользователя создан.
|
||||
/// </summary>
|
||||
public record ProfileCreatedDomainEvent(Guid ProfileId) : IDomainEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Профиль пользователя обновлен.
|
||||
/// Профиль пользователя обновлен.
|
||||
/// </summary>
|
||||
public record ProfileUpdatedDomainEvent(Guid ProfileId) : IDomainEvent;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Profiles.Domain;
|
||||
|
||||
public interface IAvatarStorageService
|
||||
{
|
||||
Task<string> UploadAsync(Stream content, string fileName, string contentType, CancellationToken ct = default);
|
||||
Task DeleteAsync(string fileId, CancellationToken ct = default);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Profiles.Domain;
|
||||
|
||||
public interface IProfileRepository
|
||||
{
|
||||
Task<ProfileDocument?> GetByIdAsync(Guid id, CancellationToken ct = default);
|
||||
Task<ProfileDocument?> GetByUsernameAsync(string username, CancellationToken ct = default);
|
||||
Task AddAsync(ProfileDocument profile, CancellationToken ct = default);
|
||||
Task UpdateAsync(ProfileDocument profile, CancellationToken ct = default);
|
||||
Task<List<ProfileDocument>> SearchAsync(string query, CancellationToken ct = default);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Profiles.Domain;
|
||||
|
||||
public interface IProfilesUnitOfWork
|
||||
{
|
||||
Task SaveChangesAsync(CancellationToken ct = default);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Knot.Modules.Profiles.Domain;
|
||||
namespace Knot.Modules.Profiles.Domain;
|
||||
|
||||
public sealed class Profile : AggregateRoot<Guid>
|
||||
{
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
|
||||
namespace Knot.Modules.Profiles.Domain;
|
||||
|
||||
/// <summary>
|
||||
/// MongoDB-документ профиля пользователя.
|
||||
/// Id совпадает с UserId из модуля Auth (Postgres).
|
||||
/// MongoDB-документ профиля пользователя.
|
||||
/// Id совпадает с UserId из модуля Auth (Postgres).
|
||||
/// </summary>
|
||||
public sealed class ProfileDocument
|
||||
{
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace Knot.Modules.Profiles.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
public static class ProfilesErrors
|
||||
{
|
||||
public static readonly Error ProfileNotFound = new("Profile.NotFound", "Profile not found");
|
||||
}
|
||||
public static class IdentityErrors
|
||||
{
|
||||
public static readonly Error UserNotFound = new("Profile.NotFound", "Profile not found");
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using Knot.Modules.Profiles.Contracts.Domain;
|
||||
using Knot.Shared.Kernel.Storage;
|
||||
|
||||
namespace Knot.Modules.Profiles.Infrastructure.Database;
|
||||
|
||||
public class AvatarStorageService : IAvatarStorageService
|
||||
internal class AvatarStorageService : IAvatarStorageService
|
||||
{
|
||||
private readonly IFileStorageService _fileStorage;
|
||||
|
||||
@@ -15,15 +15,24 @@ public class AvatarStorageService : IAvatarStorageService
|
||||
_fileStorage = fileStorage;
|
||||
}
|
||||
|
||||
public async Task<string> UploadAsync(Stream content, string fileName, string contentType, CancellationToken ct = default)
|
||||
public async Task<string> UploadAsync(Stream stream, string fileName, string contentType, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// IFileStorageService не принимает CancellationToken в UploadFileAsync
|
||||
return await _fileStorage.UploadFileAsync(content, fileName, contentType);
|
||||
return await _fileStorage.UploadFileAsync(stream, fileName, contentType);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(string fileId, CancellationToken ct = default)
|
||||
public async Task DeleteAsync(string fileKey, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// IFileStorageService не принимает CancellationToken в DeleteFileAsync
|
||||
await _fileStorage.DeleteFileAsync(fileId);
|
||||
await _fileStorage.DeleteFileAsync(fileKey);
|
||||
}
|
||||
|
||||
public async Task<Stream?> GetAsync(string fileKey, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var (stream, _, _) = await _fileStorage.DownloadFileAsync(fileKey);
|
||||
return stream;
|
||||
}
|
||||
|
||||
public string GetFileUrl(string fileKey)
|
||||
{
|
||||
return $"/api/files/{fileKey}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Modules.Profiles.Contracts.Domain;
|
||||
using Knot.Modules.Profiles.Contracts.Application.DTOs;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using Knot.Modules.Profiles.Infrastructure.Mappings;
|
||||
using Knot.Shared.Kernel;
|
||||
using ProfilesErrors = Knot.Modules.Profiles.Contracts.Application.DTOs.ProfilesErrors;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Knot.Modules.Profiles.Infrastructure.Database;
|
||||
|
||||
public class ProfileRepository : IProfileRepository
|
||||
internal class ProfileRepository : IProfileRepository
|
||||
{
|
||||
private readonly IMongoCollection<ProfileDocument> _profiles;
|
||||
|
||||
@@ -17,36 +23,69 @@ public class ProfileRepository : IProfileRepository
|
||||
_profiles = database.GetCollection<ProfileDocument>("profiles");
|
||||
}
|
||||
|
||||
public async Task<ProfileDocument?> GetByIdAsync(Guid id, CancellationToken ct = default)
|
||||
public async Task<UserProfileDto?> GetAsync(Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
return await _profiles.Find(p => p.Id == id).FirstOrDefaultAsync(ct);
|
||||
var profile = await _profiles.Find(p => p.Id == userId).FirstOrDefaultAsync(ct);
|
||||
return profile?.ToDto();
|
||||
}
|
||||
|
||||
public async Task<ProfileDocument?> GetByUsernameAsync(string username, CancellationToken ct = default)
|
||||
public async Task<UserProfileDto?> GetByUsernameAsync(string username, CancellationToken ct = default)
|
||||
{
|
||||
return await _profiles.Find(p => p.Username == username).FirstOrDefaultAsync(ct);
|
||||
var profile = await _profiles.Find(p => p.Username == username).FirstOrDefaultAsync(ct);
|
||||
return profile?.ToDto();
|
||||
}
|
||||
|
||||
public async Task AddAsync(ProfileDocument profile, CancellationToken ct = default)
|
||||
public async Task<List<UserProfileDto>> GetAsync(IEnumerable<Guid> userIds, CancellationToken ct = default)
|
||||
{
|
||||
await _profiles.InsertOneAsync(profile, null, ct);
|
||||
var ids = userIds.ToList();
|
||||
var profiles = await _profiles.Find(p => ids.Contains(p.Id)).ToListAsync(ct);
|
||||
return profiles.Select(p => p.ToDto()).ToList();
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(ProfileDocument profile, CancellationToken ct = default)
|
||||
{
|
||||
await _profiles.ReplaceOneAsync(p => p.Id == profile.Id, profile, new ReplaceOptions { IsUpsert = true }, ct);
|
||||
}
|
||||
|
||||
public async Task<List<ProfileDocument>> SearchAsync(string query, CancellationToken ct = default)
|
||||
public async Task<List<UserProfileDto>> SearchAsync(string query, int limit = 20, CancellationToken ct = default)
|
||||
{
|
||||
List<ProfileDocument> docs;
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return await _profiles.Find(_ => true).Limit(50).ToListAsync(ct);
|
||||
{
|
||||
docs = await _profiles.Find(_ => true).Limit(limit).ToListAsync(ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
var filter = Builders<ProfileDocument>.Filter.Or(
|
||||
Builders<ProfileDocument>.Filter.Regex(p => p.Username, new BsonRegularExpression(query, "i")),
|
||||
Builders<ProfileDocument>.Filter.Regex(p => p.DisplayName, new BsonRegularExpression(query, "i"))
|
||||
);
|
||||
docs = await _profiles.Find(filter).Limit(limit).ToListAsync(ct);
|
||||
}
|
||||
return docs.Select(p => p.ToDto()).ToList();
|
||||
}
|
||||
|
||||
var filter = Builders<ProfileDocument>.Filter.Or(
|
||||
Builders<ProfileDocument>.Filter.Regex(p => p.Username, new BsonRegularExpression(query, "i")),
|
||||
Builders<ProfileDocument>.Filter.Regex(p => p.DisplayName, new BsonRegularExpression(query, "i"))
|
||||
);
|
||||
public async Task<Result<UserProfileDto>> CreateAsync(UserProfileDto dto, CancellationToken ct = default)
|
||||
{
|
||||
var profile = ProfileDocument.Create(dto.UserId, dto.Username ?? string.Empty, dto.DisplayName ?? string.Empty, dto.About);
|
||||
|
||||
return await _profiles.Find(filter).Limit(50).ToListAsync(ct);
|
||||
await _profiles.InsertOneAsync(profile, null, ct);
|
||||
return Result.Success(profile.ToDto());
|
||||
}
|
||||
|
||||
public async Task<Result<UserProfileDto>> UpdateAsync(UserProfileDto dto, CancellationToken ct = default)
|
||||
{
|
||||
var profile = await _profiles.Find(p => p.Id == dto.UserId).FirstOrDefaultAsync(ct);
|
||||
if (profile is null)
|
||||
return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
|
||||
|
||||
profile.UpdateProfile(
|
||||
dto.DisplayName ?? profile.DisplayName,
|
||||
dto.About ?? profile.Bio,
|
||||
null);
|
||||
|
||||
await _profiles.ReplaceOneAsync(p => p.Id == dto.UserId, profile, new ReplaceOptions { IsUpsert = true }, ct);
|
||||
return Result.Success(profile.ToDto());
|
||||
}
|
||||
|
||||
public async Task<Result> DeleteAsync(Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var result = await _profiles.DeleteOneAsync(p => p.Id == userId, ct);
|
||||
return result.DeletedCount > 0 ? Result.Success() : Result.Failure(ProfilesErrors.ProfileNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
using System.Threading;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using Knot.Modules.Profiles.Contracts.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
|
||||
namespace Knot.Modules.Profiles.Infrastructure.Database;
|
||||
|
||||
public class ProfilesUnitOfWork : IProfilesUnitOfWork
|
||||
internal class ProfilesUnitOfWork : IProfilesUnitOfWork
|
||||
{
|
||||
public Task SaveChangesAsync(CancellationToken ct = default) => Task.CompletedTask;
|
||||
public IProfileRepository ProfileRepository { get; }
|
||||
|
||||
public ProfilesUnitOfWork(IProfileRepository profileRepository)
|
||||
{
|
||||
ProfileRepository = profileRepository;
|
||||
}
|
||||
|
||||
public Task<int> SaveChangesAsync(CancellationToken ct = default) => Task.FromResult(1);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using Knot.Modules.Profiles.Contracts.Application.DTOs;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
|
||||
namespace Knot.Modules.Profiles.Infrastructure.Mappings;
|
||||
|
||||
public static class ProfileMappings
|
||||
{
|
||||
public static UserProfileDto ToDto(this ProfileDocument document)
|
||||
{
|
||||
return new UserProfileDto
|
||||
{
|
||||
UserId = document.Id,
|
||||
DisplayName = document.DisplayName,
|
||||
Username = document.Username,
|
||||
About = document.Bio,
|
||||
Avatar = document.AvatarUrl,
|
||||
IsBot = false,
|
||||
LastSeen = null,
|
||||
IsPremium = false,
|
||||
CreatedAt = document.CreatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
<ProjectReference Include="..\..\Shared\Knot.Shared.Infrastructure\Knot.Shared.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\Settings\Knot.Modules.Settings.csproj" />
|
||||
<ProjectReference Include="..\..\Contracts\Knot.Modules.Profiles.Contracts\Knot.Modules.Profiles.Contracts.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Carter;
|
||||
using Carter;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Profiles.Application.Profiles.Avatar;
|
||||
using Knot.Modules.Profiles.Application.Profiles.GetProfile;
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
|
||||
namespace Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
|
||||
public interface ISettingsService
|
||||
{
|
||||
Task<SystemSettingsDto> GetSettingsAsync(CancellationToken cancellationToken = default);
|
||||
Task UpdateSettingsAsync(SystemSettingsDto settings, CancellationToken cancellationToken = default);
|
||||
SystemSettingsDto Current { get; }
|
||||
}
|
||||
|
||||
public interface ISystemSettings
|
||||
{
|
||||
SystemConfig Current { get; }
|
||||
}
|
||||
|
||||
public interface IStoriesSettings
|
||||
{
|
||||
StoriesConfig Current { get; }
|
||||
}
|
||||
|
||||
public interface IChatsSettings
|
||||
{
|
||||
ChatsConfig Current { get; }
|
||||
}
|
||||
|
||||
public interface IMessagesSettings
|
||||
{
|
||||
MessagesConfig Current { get; }
|
||||
}
|
||||
|
||||
public interface IWebRtcSettings
|
||||
{
|
||||
WebRtcConfig Current { get; }
|
||||
}
|
||||
|
||||
public interface IKlipySettings
|
||||
{
|
||||
KlipyConfig Current { get; }
|
||||
}
|
||||
|
||||
public interface IImportSettings
|
||||
{
|
||||
ImportConfig Current { get; }
|
||||
}
|
||||
|
||||
public interface IFederationSettings
|
||||
{
|
||||
FederationConfig Current { get; }
|
||||
}
|
||||
@@ -4,8 +4,8 @@ using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Knot.Shared.Kernel;
|
||||
using System.Security.Cryptography;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
|
||||
namespace Knot.Modules.Settings.Application.Settings.Commands;
|
||||
|
||||
@@ -41,4 +41,3 @@ internal sealed class UpdateSettingsCommandHandler : ICommandHandler<UpdateSetti
|
||||
return Result.Success(request.Settings);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
|
||||
namespace Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
|
||||
public record PublicConfigDto
|
||||
{
|
||||
public SystemConfigDto System { get; init; } = new();
|
||||
public StoriesConfigDto Stories { get; init; } = new();
|
||||
public ChatsConfigDto Chats { get; init; } = new();
|
||||
public MessagesConfigDto Messages { get; init; } = new();
|
||||
public WebRtcConfigDto WebRtc { get; init; } = new();
|
||||
public KlipyConfigDto Klipy { get; init; } = new();
|
||||
public ImportConfigDto Import { get; init; } = new();
|
||||
public FederationConfigDto Federation { get; init; } = new();
|
||||
|
||||
public static PublicConfigDto FromSettings(SystemSettingsDto settings)
|
||||
{
|
||||
return new PublicConfigDto
|
||||
{
|
||||
System = new SystemConfigDto
|
||||
{
|
||||
DomainUrl = settings.System.DomainUrl,
|
||||
EnableRegistration = settings.System.EnableRegistration
|
||||
},
|
||||
Stories = new StoriesConfigDto
|
||||
{
|
||||
Enabled = settings.Stories.Enabled,
|
||||
MaxStoriesPerPeriod = settings.Stories.MaxStoriesPerPeriod,
|
||||
StoryLifetimeHours = settings.Stories.StoryLifetimeHours,
|
||||
TextStoriesEnabled = settings.Stories.TextStoriesEnabled,
|
||||
TextStoryDurationSeconds = settings.Stories.TextStoryDurationSeconds,
|
||||
MediaStoryMaxDurationSeconds = settings.Stories.MediaStoryMaxDurationSeconds,
|
||||
MaxMediaSizeBytes = settings.Stories.MaxMediaSizeBytes
|
||||
},
|
||||
Chats = new ChatsConfigDto
|
||||
{
|
||||
SupportGroups = settings.Chats.SupportGroups,
|
||||
MaxGroupParticipants = settings.Chats.MaxGroupParticipants,
|
||||
EnableAutoClean = settings.Chats.EnableAutoClean,
|
||||
AllowChatToGroupConversion = settings.Chats.AllowChatToGroupConversion,
|
||||
EnableFolders = settings.Chats.EnableFolders
|
||||
},
|
||||
Messages = new MessagesConfigDto
|
||||
{
|
||||
DailyMessageLimitPerUser = settings.Messages.DailyMessageLimitPerUser,
|
||||
ChatMessageLimit = settings.Messages.ChatMessageLimit,
|
||||
AllowMedia = settings.Messages.AllowMedia,
|
||||
MaxMediaSizeBytes = settings.Messages.MaxMediaSizeBytes,
|
||||
AllowedMediaTypes = settings.Messages.AllowedMediaTypes,
|
||||
AllowVoiceMessages = settings.Messages.AllowVoiceMessages,
|
||||
AllowForwarding = settings.Messages.AllowForwarding,
|
||||
AllowReactions = settings.Messages.AllowReactions,
|
||||
AllowReplies = settings.Messages.AllowReplies,
|
||||
AllowQuoting = settings.Messages.AllowQuoting,
|
||||
AllowMessageDeletion = settings.Messages.AllowMessageDeletion,
|
||||
ForbidCopying = settings.Messages.ForbidCopying,
|
||||
AllowLinks = settings.Messages.AllowLinks,
|
||||
AllowPolls = settings.Messages.AllowPolls,
|
||||
AllowPinning = settings.Messages.AllowPinning
|
||||
},
|
||||
WebRtc = new WebRtcConfigDto
|
||||
{
|
||||
Enabled = settings.WebRtc.Enabled,
|
||||
EnableVideoCalls = settings.WebRtc.EnableVideoCalls,
|
||||
EnableScreenSharing = settings.WebRtc.EnableScreenSharing,
|
||||
TurnHost = settings.WebRtc.TurnHost,
|
||||
TurnPort = settings.WebRtc.TurnPort
|
||||
},
|
||||
Klipy = new KlipyConfigDto
|
||||
{
|
||||
Enabled = settings.Klipy.Enabled,
|
||||
AppName = settings.Klipy.AppName
|
||||
},
|
||||
Import = new ImportConfigDto
|
||||
{
|
||||
Enabled = settings.Import.EnableTelegramImport
|
||||
},
|
||||
Federation = new FederationConfigDto
|
||||
{
|
||||
Enabled = settings.Federation.Enabled,
|
||||
ServerDescription = settings.Federation.ServerDescription,
|
||||
AllowedDomains = settings.Federation.AllowedDomains
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public record SystemConfigDto
|
||||
{
|
||||
public string DomainUrl { get; init; } = string.Empty;
|
||||
public bool EnableRegistration { get; init; }
|
||||
}
|
||||
|
||||
public record StoriesConfigDto
|
||||
{
|
||||
public bool Enabled { get; init; }
|
||||
public int MaxStoriesPerPeriod { get; init; }
|
||||
public int StoryLifetimeHours { get; init; }
|
||||
public bool TextStoriesEnabled { get; init; }
|
||||
public int TextStoryDurationSeconds { get; init; }
|
||||
public int MediaStoryMaxDurationSeconds { get; init; }
|
||||
public int MaxMediaSizeBytes { get; init; }
|
||||
}
|
||||
|
||||
public record ChatsConfigDto
|
||||
{
|
||||
public bool SupportGroups { get; init; }
|
||||
public int MaxGroupParticipants { get; init; }
|
||||
public bool EnableAutoClean { get; init; }
|
||||
public bool AllowChatToGroupConversion { get; init; }
|
||||
public bool EnableFolders { get; init; }
|
||||
}
|
||||
|
||||
public record MessagesConfigDto
|
||||
{
|
||||
public int DailyMessageLimitPerUser { get; init; }
|
||||
public int ChatMessageLimit { get; init; }
|
||||
public bool AllowMedia { get; init; }
|
||||
public int MaxMediaSizeBytes { get; init; }
|
||||
public List<string> AllowedMediaTypes { get; init; } = new();
|
||||
public bool AllowVoiceMessages { get; init; }
|
||||
public bool AllowForwarding { get; init; }
|
||||
public bool AllowReactions { get; init; }
|
||||
public bool AllowReplies { get; init; }
|
||||
public bool AllowQuoting { get; init; }
|
||||
public bool AllowMessageDeletion { get; init; }
|
||||
public bool ForbidCopying { get; init; }
|
||||
public bool AllowLinks { get; init; }
|
||||
public bool AllowPolls { get; init; }
|
||||
public bool AllowPinning { get; init; }
|
||||
}
|
||||
|
||||
public record WebRtcConfigDto
|
||||
{
|
||||
public bool Enabled { get; init; }
|
||||
public bool EnableVideoCalls { get; init; }
|
||||
public bool EnableScreenSharing { get; init; }
|
||||
public string TurnHost { get; init; } = string.Empty;
|
||||
public int TurnPort { get; init; }
|
||||
}
|
||||
|
||||
public record KlipyConfigDto
|
||||
{
|
||||
public bool Enabled { get; init; }
|
||||
public string AppName { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
public record ImportConfigDto
|
||||
{
|
||||
public bool Enabled { get; init; }
|
||||
}
|
||||
|
||||
public record FederationConfigDto
|
||||
{
|
||||
public bool Enabled { get; init; }
|
||||
public string ServerDescription { get; init; } = string.Empty;
|
||||
public List<FederationDomainConfig> AllowedDomains { get; init; } = new();
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
|
||||
public class SystemConfig
|
||||
{
|
||||
public string ServerTimezone { get; set; } = "UTC";
|
||||
public string DomainUrl { get; set; } = "https://example.com";
|
||||
public string AdminRoute { get; set; } = "admin";
|
||||
public bool EnableRegistration { get; set; } = true;
|
||||
}
|
||||
|
||||
public class StoriesConfig
|
||||
{
|
||||
public bool Enabled { get; set; } = true;
|
||||
public int MaxStoriesPerPeriod { get; set; } = 5;
|
||||
public int StoryLifetimeHours { get; set; } = 24;
|
||||
public bool TextStoriesEnabled { get; set; } = true;
|
||||
public int TextStoryDurationSeconds { get; set; } = 15;
|
||||
public int MediaStoryMaxDurationSeconds { get; set; } = 30;
|
||||
public int MaxMediaSizeBytes { get; set; } = 15 * 1024 * 1024;
|
||||
}
|
||||
|
||||
public class ChatsConfig
|
||||
{
|
||||
public bool SupportGroups { get; set; } = true;
|
||||
public int MaxGroupParticipants { get; set; } = 200000;
|
||||
public bool EnableAutoClean { get; set; } = false; // Возможность автоочистки для пользователей
|
||||
public bool AllowChatToGroupConversion { get; set; } = true;
|
||||
public bool EnableFolders { get; set; } = true;
|
||||
}
|
||||
|
||||
public class MessagesConfig
|
||||
{
|
||||
public int DailyMessageLimitPerUser { get; set; } = 0;
|
||||
public int ChatMessageLimit { get; set; } = 0;
|
||||
public bool AllowMedia { get; set; } = true;
|
||||
public int MaxMediaSizeBytes { get; set; } = 50 * 1024 * 1024;
|
||||
public int MaxFileSize { get; set; } = 100 * 1024 * 1024;
|
||||
public List<string> AllowedMediaTypes { get; set; } = new() { "image/jpeg", "image/png", "video/mp4", "image/gif" };
|
||||
public bool AllowVoiceMessages { get; set; } = true;
|
||||
public bool AllowForwarding { get; set; } = true;
|
||||
public bool AllowReactions { get; set; } = true;
|
||||
public bool AllowReplies { get; set; } = true;
|
||||
public bool AllowQuoting { get; set; } = true;
|
||||
public bool AllowMessageDeletion { get; set; } = true;
|
||||
public bool ForbidCopying { get; set; } = false;
|
||||
public bool AllowLinks { get; set; } = true;
|
||||
public bool AllowPolls { get; set; } = true;
|
||||
public bool AllowPinning { get; set; } = true;
|
||||
}
|
||||
|
||||
public class WebRtcConfig
|
||||
{
|
||||
public bool Enabled { get; set; } = false;
|
||||
public bool EnableVideoCalls { get; set; } = true;
|
||||
public bool EnableScreenSharing { get; set; } = true;
|
||||
public string TurnHost { get; set; } = string.Empty;
|
||||
public int TurnPort { get; set; } = 3478;
|
||||
public string TurnUser { get; set; } = string.Empty;
|
||||
public string TurnSecret { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class KlipyConfig
|
||||
{
|
||||
public bool Enabled { get; set; } = false;
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
public string AppName { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class ImportConfig
|
||||
{
|
||||
public bool EnableTelegramImport { get; set; } = false;
|
||||
}
|
||||
|
||||
public class FederationDomainConfig
|
||||
{
|
||||
public string Domain { get; set; } = string.Empty;
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
public string? PublicKey { get; set; }
|
||||
public RemoteCapabilities? Capabilities { get; set; }
|
||||
}
|
||||
|
||||
public class RemoteCapabilities
|
||||
{
|
||||
public bool AllowMedia { get; set; }
|
||||
public bool AllowPolls { get; set; }
|
||||
public bool AllowVoiceMessages { get; set; }
|
||||
public bool AllowVideoCalls { get; set; }
|
||||
public bool AllowScreenSharing { get; set; }
|
||||
}
|
||||
|
||||
public class FederationConfig
|
||||
{
|
||||
public bool Enabled { get; set; } = false;
|
||||
public string ServerDescription { get; set; } = string.Empty;
|
||||
public string? PrivateKey { get; set; }
|
||||
public string? PublicKey { get; set; }
|
||||
public List<FederationDomainConfig> AllowedDomains { get; set; } = new();
|
||||
}
|
||||
|
||||
public class SystemSettingsDto
|
||||
{
|
||||
public SystemConfig System { get; set; } = new();
|
||||
public StoriesConfig Stories { get; set; } = new();
|
||||
public ChatsConfig Chats { get; set; } = new();
|
||||
public MessagesConfig Messages { get; set; } = new();
|
||||
public WebRtcConfig WebRtc { get; set; } = new();
|
||||
public KlipyConfig Klipy { get; set; } = new();
|
||||
public ImportConfig Import { get; set; } = new();
|
||||
public FederationConfig Federation { get; set; } = new();
|
||||
}
|
||||
@@ -2,14 +2,14 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
|
||||
namespace Knot.Modules.Settings.Application.Settings.Queries;
|
||||
|
||||
public record GetPublicConfigQuery() : IQuery<PublicConfigDto>;
|
||||
public record GetPublicConfigQuery() : IQuery<SystemSettingsDto>;
|
||||
|
||||
internal sealed class GetPublicConfigQueryHandler : IQueryHandler<GetPublicConfigQuery, PublicConfigDto>
|
||||
internal sealed class GetPublicConfigQueryHandler : IQueryHandler<GetPublicConfigQuery, SystemSettingsDto>
|
||||
{
|
||||
private readonly ISettingsService _settings;
|
||||
|
||||
@@ -18,8 +18,8 @@ internal sealed class GetPublicConfigQueryHandler : IQueryHandler<GetPublicConfi
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public Task<Result<PublicConfigDto>> Handle(GetPublicConfigQuery request, CancellationToken cancellationToken)
|
||||
public Task<Result<SystemSettingsDto>> Handle(GetPublicConfigQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(Result.Success(PublicConfigDto.FromSettings(_settings.Current)));
|
||||
return Task.FromResult(Result.Success(_settings.Current));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
|
||||
namespace Knot.Modules.Settings.Application.Settings.Queries;
|
||||
|
||||
public record GetSettingsQuery() : IQuery<SystemSettingsDto>;
|
||||
public sealed record GetSettingsQuery() : IQuery<SystemSettingsDto>;
|
||||
|
||||
internal sealed class GetSettingsQueryHandler : IQueryHandler<GetSettingsQuery, SystemSettingsDto>
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Infrastructure.Configuration;
|
||||
|
||||
namespace Knot.Modules.Settings;
|
||||
@@ -23,4 +23,4 @@ public static class DependencyInjection
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using Knot.Shared.Kernel;
|
||||
|
||||
namespace Knot.Modules.Settings.Domain.Events;
|
||||
@@ -8,4 +8,3 @@ namespace Knot.Modules.Settings.Domain.Events;
|
||||
/// Owned by Settings module.
|
||||
/// </summary>
|
||||
public sealed record SystemSettingsUpdatedDomainEvent(SystemSettingsDto Settings) : IDomainEvent;
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Knot.Shared.Infrastructure.Persistence;
|
||||
using Knot.Shared.Infrastructure.Persistence.Entities;
|
||||
using Knot.Shared.Kernel.Security;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
|
||||
namespace Knot.Modules.Settings.Infrastructure.Configuration;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
<ProjectReference Include="..\..\Shared\Knot.Shared.Infrastructure\Knot.Shared.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\..\Contracts\Knot.Modules.Settings.Contracts\Knot.Modules.Settings.Contracts.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Carter" Version="10.0.0" />
|
||||
|
||||
@@ -6,8 +6,8 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using System.Text;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
|
||||
using Knot.Modules.Stories.Domain;
|
||||
|
||||
|
||||
+2
-2
@@ -11,8 +11,8 @@ using System.Threading.Tasks;
|
||||
using AngleSharp.Html.Parser;
|
||||
using AngleSharp.Dom;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using MediatR;
|
||||
|
||||
namespace Knot.Modules.TelegramImport.Application.TelegramImport;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Settings.Contracts.Application.Abstractions;
|
||||
using Knot.Modules.Settings.Contracts.Application.DTOs;
|
||||
using MediatR;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
Reference in New Issue
Block a user