Структура, доп модули, федерация, документация
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Shared.Kernel.Configuration;
|
||||
using Knot.Shared.Kernel;
|
||||
using MediatR;
|
||||
|
||||
namespace Knot.Modules.Conversations.Application.Folders.Commands.AddToFolder;
|
||||
|
||||
public record AddToFolderCommand(Guid UserId, Guid ChatId, List<Guid> FolderIds) : ICommand;
|
||||
|
||||
internal sealed class AddToFolderCommandHandler : ICommandHandler<AddToFolderCommand>
|
||||
{
|
||||
private readonly IUserChatSettingsRepository _settingsRepository;
|
||||
private readonly IChatsUnitOfWork _unitOfWork;
|
||||
private readonly IChatsSettings _chatsSettings;
|
||||
|
||||
public AddToFolderCommandHandler(
|
||||
IUserChatSettingsRepository settingsRepository,
|
||||
IChatsUnitOfWork unitOfWork,
|
||||
IChatsSettings chatsSettings)
|
||||
{
|
||||
_settingsRepository = settingsRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
_chatsSettings = chatsSettings;
|
||||
}
|
||||
|
||||
public async Task<Result> Handle(AddToFolderCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_chatsSettings.Current.EnableFolders)
|
||||
{
|
||||
return Result.Failure(ChatErrors.FoldersDisabled);
|
||||
}
|
||||
|
||||
var settings = await _settingsRepository.GetAsync(request.UserId, request.ChatId, cancellationToken);
|
||||
|
||||
if (settings == null)
|
||||
{
|
||||
settings = UserChatSettings.Create(request.UserId, request.ChatId);
|
||||
_settingsRepository.Add(settings);
|
||||
}
|
||||
|
||||
foreach (var folderId in request.FolderIds)
|
||||
{
|
||||
settings.AddToFolder(folderId);
|
||||
}
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
return Result.Success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Shared.Kernel;
|
||||
using MediatR;
|
||||
|
||||
namespace Knot.Modules.Conversations.Application.Folders.Commands.RemoveFromFolder;
|
||||
|
||||
public record RemoveFromFolderCommand(Guid UserId, Guid ChatId, List<Guid> FolderIds) : ICommand;
|
||||
|
||||
internal sealed class RemoveFromFolderCommandHandler : ICommandHandler<RemoveFromFolderCommand>
|
||||
{
|
||||
private readonly IUserChatSettingsRepository _settingsRepository;
|
||||
private readonly IChatsUnitOfWork _unitOfWork;
|
||||
|
||||
public RemoveFromFolderCommandHandler(IUserChatSettingsRepository settingsRepository, IChatsUnitOfWork unitOfWork)
|
||||
{
|
||||
_settingsRepository = settingsRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public async Task<Result> Handle(RemoveFromFolderCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var settings = await _settingsRepository.GetAsync(request.UserId, request.ChatId, cancellationToken);
|
||||
|
||||
if (settings == null)
|
||||
{
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
foreach (var folderId in request.FolderIds)
|
||||
{
|
||||
settings.RemoveFromFolder(folderId);
|
||||
}
|
||||
|
||||
_settingsRepository.Update(settings);
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
return Result.Success();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
using Knot.Modules.Messaging.Application.Abstractions;
|
||||
using Knot.Modules.Messaging.Domain;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using global::Knot.Modules.Conversations.Domain;
|
||||
using global::Knot.Modules.Conversations.Infrastructure.SignalR;
|
||||
using global::Knot.Shared.Kernel;
|
||||
using global::Knot.Modules.Conversations.Application.Abstractions;
|
||||
using MessagingMessageRepository = Knot.Modules.Messaging.Domain.IMessageRepository;
|
||||
|
||||
namespace Knot.Modules.Conversations.Application.Messages.Delete;
|
||||
|
||||
@@ -17,12 +16,12 @@ public sealed record DeleteMessagesCommand(
|
||||
|
||||
public sealed class DeleteMessagesCommandHandler : ICommandHandler<DeleteMessagesCommand>
|
||||
{
|
||||
private readonly IMessageRepository _messageRepository;
|
||||
private readonly MessagingMessageRepository _messageRepository;
|
||||
private readonly IChatsUnitOfWork _unitOfWork;
|
||||
private readonly IHubContext<ChatHub> _hubContext;
|
||||
|
||||
public DeleteMessagesCommandHandler(
|
||||
IMessageRepository messageRepository,
|
||||
MessagingMessageRepository messageRepository,
|
||||
IChatsUnitOfWork unitOfWork,
|
||||
IHubContext<ChatHub> hubContext)
|
||||
{
|
||||
@@ -67,7 +66,6 @@ public sealed class DeleteMessagesCommandHandler : ICommandHandler<DeleteMessage
|
||||
}
|
||||
else
|
||||
{
|
||||
// Уведомляем только самого пользователя (все его текущие сессии)
|
||||
await _hubContext.Clients.User(request.UserId.ToString()).SendAsync("messages_deleted", new
|
||||
{
|
||||
chatId = request.ChatId,
|
||||
@@ -79,5 +77,3 @@ public sealed class DeleteMessagesCommandHandler : ICommandHandler<DeleteMessage
|
||||
return global::Knot.Shared.Kernel.Result.Success();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using Knot.Modules.Messaging.Application.Abstractions;
|
||||
using Knot.Modules.Messaging.Domain;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Shared.Kernel.Configuration;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
|
||||
namespace Knot.Modules.Conversations.Application.Messages.Send;
|
||||
@@ -22,7 +23,11 @@ public sealed record SendMessageCommand(
|
||||
Guid? ForwardedFromId = null,
|
||||
Guid? StoryId = null,
|
||||
string? StoryMediaUrl = null,
|
||||
string? StoryMediaType = null) : ICommand<Guid>;
|
||||
string? StoryMediaType = null,
|
||||
List<string>? PollOptions = null,
|
||||
bool? PollIsAnonymous = null,
|
||||
bool? PollAllowMultipleAnswers = null,
|
||||
DateTime? PollExpiresAt = null) : ICommand<Guid>;
|
||||
|
||||
public sealed class SendMessageCommandHandler : ICommandHandler<SendMessageCommand, Guid>
|
||||
{
|
||||
@@ -30,17 +35,20 @@ public sealed class SendMessageCommandHandler : ICommandHandler<SendMessageComma
|
||||
private readonly IMessageRepository _messageRepository;
|
||||
private readonly IChatsUnitOfWork _unitOfWork;
|
||||
private readonly MediatR.IMediator _mediator;
|
||||
private readonly IMessagesSettings _messagesSettings;
|
||||
|
||||
public SendMessageCommandHandler(
|
||||
IChatRepository chatRepository,
|
||||
IMessageRepository messageRepository,
|
||||
IChatsUnitOfWork unitOfWork,
|
||||
MediatR.IMediator mediator)
|
||||
MediatR.IMediator mediator,
|
||||
IMessagesSettings messagesSettings)
|
||||
{
|
||||
_chatRepository = chatRepository;
|
||||
_messageRepository = messageRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
_mediator = mediator;
|
||||
_messagesSettings = messagesSettings;
|
||||
}
|
||||
|
||||
public async Task<Result<Guid>> Handle(SendMessageCommand request, CancellationToken cancellationToken)
|
||||
@@ -62,6 +70,8 @@ public sealed class SendMessageCommandHandler : ICommandHandler<SendMessageComma
|
||||
Message message;
|
||||
if (request.Type == "story_reply" || request.Type == "story_reaction")
|
||||
{
|
||||
if (!_messagesSettings.Current.AllowMedia) return Result.Failure<Guid>(ChatErrors.MediaDisabled);
|
||||
|
||||
var parsedStoryMediaType = Enum.TryParse<MediaType>(request.StoryMediaType, true, out var sTypeEnum) ? sTypeEnum : MediaType.Image;
|
||||
message = new StoryMessage(
|
||||
Guid.NewGuid(),
|
||||
@@ -78,6 +88,8 @@ public sealed class SendMessageCommandHandler : ICommandHandler<SendMessageComma
|
||||
}
|
||||
else if (request.Attachments != null && request.Attachments.Any())
|
||||
{
|
||||
if (!_messagesSettings.Current.AllowMedia) return Result.Failure<Guid>(ChatErrors.MediaDisabled);
|
||||
|
||||
var firstAtt = request.Attachments.First();
|
||||
var parsedType = Enum.TryParse<MediaType>(firstAtt.Type, true, out var mTypeEnum) ? mTypeEnum : MediaType.File;
|
||||
|
||||
@@ -98,6 +110,24 @@ public sealed class SendMessageCommandHandler : ICommandHandler<SendMessageComma
|
||||
((MediaMessage)message).AddMedia(pType, att.Url, att.FileName, att.FileSize);
|
||||
}
|
||||
}
|
||||
else if (request.Type == "poll")
|
||||
{
|
||||
if (!_messagesSettings.Current.AllowPolls) return Result.Failure<Guid>(ChatErrors.PollsDisabled);
|
||||
|
||||
message = new PollMessage(
|
||||
Guid.NewGuid(),
|
||||
request.ChatId,
|
||||
request.SenderId,
|
||||
request.Content ?? "Poll",
|
||||
request.PollOptions ?? new List<string>(),
|
||||
request.PollIsAnonymous ?? true,
|
||||
request.PollAllowMultipleAnswers ?? false,
|
||||
request.PollExpiresAt,
|
||||
request.ReplyToId,
|
||||
request.ForwardedFromId,
|
||||
DateTime.UtcNow,
|
||||
false);
|
||||
}
|
||||
else
|
||||
{
|
||||
message = new TextMessage(
|
||||
|
||||
@@ -16,9 +16,9 @@ public record UploadFileCommand(string FileName, string ContentType, long Length
|
||||
internal sealed class UploadFileCommandHandler : ICommandHandler<UploadFileCommand, UploadFileResponseDto>
|
||||
{
|
||||
private readonly IFileStorageService _fileStorage;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IMessagesSettings _settingsService;
|
||||
|
||||
public UploadFileCommandHandler(IFileStorageService fileStorage, ISettingsService settingsService)
|
||||
public UploadFileCommandHandler(IFileStorageService fileStorage, IMessagesSettings settingsService)
|
||||
{
|
||||
_fileStorage = fileStorage;
|
||||
_settingsService = settingsService;
|
||||
@@ -31,8 +31,10 @@ internal sealed class UploadFileCommandHandler : ICommandHandler<UploadFileComma
|
||||
return Result.Failure<UploadFileResponseDto>(ChatErrors.FileEmpty);
|
||||
}
|
||||
|
||||
var maxMb = _settingsService.Current.MaxFileSizeMb;
|
||||
if (request.Length > maxMb * 1024 * 1024)
|
||||
var maxBytes = _settingsService.Current.MaxMediaSizeBytes;
|
||||
var maxMb = maxBytes / (1024 * 1024);
|
||||
|
||||
if (request.Length > maxBytes)
|
||||
{
|
||||
return Result.Failure<UploadFileResponseDto>(ChatErrors.FileTooLarge(maxMb));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Modules.Messaging.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
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;
|
||||
|
||||
public record DeleteUserCommand(Guid UserId) : ICommand;
|
||||
|
||||
internal sealed class DeleteUserCommandHandler : ICommandHandler<DeleteUserCommand>
|
||||
{
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IUserChatSettingsRepository _userChatSettingsRepository;
|
||||
private readonly IUserFolderSettingsRepository _userFolderSettingsRepository;
|
||||
private readonly Knot.Modules.Messaging.Domain.IMessageRepository _messageRepository;
|
||||
private readonly IFileStorageService _fileStorage;
|
||||
private readonly IChatsUnitOfWork _unitOfWork;
|
||||
private readonly IAuthUnitOfWork _authUnitOfWork;
|
||||
|
||||
public DeleteUserCommandHandler(
|
||||
IUserRepository userRepository,
|
||||
IUserChatSettingsRepository userChatSettingsRepository,
|
||||
IUserFolderSettingsRepository userFolderSettingsRepository,
|
||||
Knot.Modules.Messaging.Domain.IMessageRepository messageRepository,
|
||||
IFileStorageService fileStorage,
|
||||
IChatsUnitOfWork unitOfWork,
|
||||
IAuthUnitOfWork authUnitOfWork)
|
||||
{
|
||||
_userRepository = userRepository;
|
||||
_userChatSettingsRepository = userChatSettingsRepository;
|
||||
_userFolderSettingsRepository = userFolderSettingsRepository;
|
||||
_messageRepository = messageRepository;
|
||||
_fileStorage = fileStorage;
|
||||
_unitOfWork = unitOfWork;
|
||||
_authUnitOfWork = authUnitOfWork;
|
||||
}
|
||||
|
||||
public async Task<Result> Handle(DeleteUserCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(request.UserId, cancellationToken);
|
||||
if (user == null) return Result.Failure(Error.NotFound("User.NotFound", "User not found"));
|
||||
|
||||
// 1. Delete Messages and their files in MongoDB
|
||||
var allMessages = await _messageRepository.GetAllMessagesAsync(cancellationToken);
|
||||
var userMessages = allMessages.Where(m => m.SenderId == request.UserId).ToList();
|
||||
|
||||
foreach (var msg in userMessages)
|
||||
{
|
||||
if (msg is MediaMessage mediaMsg)
|
||||
{
|
||||
foreach (var media in mediaMsg.Media)
|
||||
{
|
||||
bool isUsedElsewhere = allMessages.Any(m => m.Id != msg.Id &&
|
||||
m is MediaMessage mm && mm.Media.Any(ame => ame.Url == media.Url));
|
||||
|
||||
if (!isUsedElsewhere)
|
||||
{
|
||||
var fileId = ExtractFileId(media.Url);
|
||||
if (!string.IsNullOrEmpty(fileId))
|
||||
{
|
||||
await _fileStorage.DeleteFileAsync(fileId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _messageRepository.DeleteUserMessagesAsync(request.UserId, cancellationToken);
|
||||
|
||||
// 2. Delete Folder Settings (MongoDB)
|
||||
await _userFolderSettingsRepository.RemoveByUserIdAsync(request.UserId, cancellationToken);
|
||||
|
||||
// 3. Delete Chat Settings (Postgres)
|
||||
var chatSettings = await _userChatSettingsRepository.GetByUserIdAsync(request.UserId, cancellationToken);
|
||||
_userChatSettingsRepository.RemoveRange(chatSettings);
|
||||
|
||||
// 4. Delete User (Postgres - Auth Module)
|
||||
_userRepository.Remove(user);
|
||||
|
||||
// Commit all
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
await _authUnitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
private string? ExtractFileId(string url)
|
||||
{
|
||||
var match = Regex.Match(url, @"/([^/]+)$");
|
||||
return match.Success ? match.Groups[1].Value : null;
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,11 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Conversations.Infrastructure.Persistence;
|
||||
using Knot.Modules.Conversations.Infrastructure.Persistence.Mongo;
|
||||
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Modules.Messaging.Domain;
|
||||
using Knot.Modules.Messaging.Infrastructure.Persistence;
|
||||
|
||||
namespace Knot.Modules.Conversations;
|
||||
|
||||
@@ -25,12 +28,19 @@ public static class DependencyInjection
|
||||
options.UseNpgsql(connectionString));
|
||||
|
||||
// MongoDB Setup for Messages
|
||||
ConversationsMongoDbMapConfigurator.Configure();
|
||||
|
||||
var mongoConnectionString = configuration.GetConnectionString("MongoConnection") ?? "mongodb://localhost:27017";
|
||||
|
||||
// Registration
|
||||
services.AddScoped<IChatsUnitOfWork>(sp => sp.GetRequiredService<ChatsDbContext>());
|
||||
services.AddScoped<IChatRepository, ChatRepository>();
|
||||
services.AddScoped<IFolderRepository, FolderRepository>();
|
||||
services.AddScoped<IUserChatSettingsRepository, UserChatSettingsRepository>();
|
||||
services.AddScoped<IUserFolderSettingsRepository, UserFolderSettingsRepository>();
|
||||
|
||||
// Messaging Repository registration (might be redundant if already in Messaging module, but needed for specific commands in Conversations)
|
||||
services.AddScoped<IMessageRepository, MessageRepository>();
|
||||
|
||||
// MediatR
|
||||
services.AddMediatR(config =>
|
||||
|
||||
@@ -15,6 +15,9 @@ public static class ChatErrors
|
||||
public static readonly Error MessagesNotFound = new Error("Messages.NotFound", "Message not found.");
|
||||
public static readonly Error ChatsNotFound = new Error("Chats.NotFound", "Чат не найден.");
|
||||
public static readonly Error Unauthorized = new Error("Chats.Unauthorized", "Access denied");
|
||||
public static readonly Error FoldersDisabled = new Error("Folders.Disabled", "Folders feature is disabled by the administrator.");
|
||||
public static readonly Error PollsDisabled = new Error("Polls.Disabled", "Polls are disabled by the administrator.");
|
||||
public static readonly Error MediaDisabled = new Error("Media.Disabled", "Media messages are disabled by the administrator.");
|
||||
|
||||
public static Error ImportCreateChatFailed(string msg) => new Error("Import.CreateChatFailed", msg);
|
||||
public static Error FileTooLarge(int maxMb) => new Error("File.TooLarge", $"File exceeds the maximum allowed size of {maxMb}MB.");
|
||||
|
||||
108
backend/src/Modules/Conversations/Domain/Folder.cs
Normal file
108
backend/src/Modules/Conversations/Domain/Folder.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Knot.Shared.Kernel;
|
||||
|
||||
namespace Knot.Modules.Conversations.Domain;
|
||||
|
||||
/// <summary>
|
||||
/// Сущность папки для группировки чатов.
|
||||
/// </summary>
|
||||
public sealed class Folder : AggregateRoot<Guid>
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public string? Icon { get; private set; } // URL из хранилища
|
||||
public bool IsDefault { get; private set; }
|
||||
public FolderType Type { get; private set; }
|
||||
|
||||
public Folder(Guid id, string name, string? icon = null, bool isDefault = false, FolderType type = FolderType.Custom)
|
||||
: base(id)
|
||||
{
|
||||
Name = name;
|
||||
Icon = icon;
|
||||
IsDefault = isDefault;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public void Update(string name, string? icon)
|
||||
{
|
||||
if (IsDefault) throw new InvalidOperationException("Cannot rename default folders.");
|
||||
Name = name;
|
||||
Icon = icon;
|
||||
}
|
||||
}
|
||||
|
||||
public enum FolderType
|
||||
{
|
||||
All, // Все чаты
|
||||
New, // Новые (с непрочитанными)
|
||||
Muted, // Без звука
|
||||
Custom // Пользовательская
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Настройки конкретного чата для конкретного пользователя.
|
||||
/// Хранятся в PostgreSQL (связь User <-> Chat).
|
||||
/// </summary>
|
||||
public sealed class UserChatSettings : Entity<Guid>
|
||||
{
|
||||
public Guid UserId { get; private set; }
|
||||
public Guid ChatId { get; private set; }
|
||||
|
||||
// Список папок, в которые входит чат для этого пользователя
|
||||
private readonly List<Guid> _folderIds = new();
|
||||
public IReadOnlyCollection<Guid> FolderIds => _folderIds.AsReadOnly();
|
||||
|
||||
public bool IsMuted { get; private set; }
|
||||
|
||||
private UserChatSettings() : base(Guid.NewGuid()) { }
|
||||
|
||||
public UserChatSettings(Guid userId, Guid chatId) : base(Guid.NewGuid())
|
||||
{
|
||||
UserId = userId;
|
||||
ChatId = chatId;
|
||||
}
|
||||
|
||||
public static UserChatSettings Create(Guid userId, Guid chatId) => new(userId, chatId);
|
||||
|
||||
public void AddToFolder(Guid folderId)
|
||||
{
|
||||
if (!_folderIds.Contains(folderId)) _folderIds.Add(folderId);
|
||||
}
|
||||
|
||||
public void RemoveFromFolder(Guid folderId)
|
||||
{
|
||||
_folderIds.Remove(folderId);
|
||||
}
|
||||
|
||||
public void SetMute(bool isMuted) => IsMuted = isMuted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Глобальные настройки папок пользователя (скрытие дефолтных и т.д.).
|
||||
/// Будет храниться в MongoDB.
|
||||
/// </summary>
|
||||
public sealed class UserFolderSettings : AggregateRoot<Guid>
|
||||
{
|
||||
public Guid UserId { get; private set; }
|
||||
|
||||
// Список ID папок, которые пользователь скрыл (только для дефолтных)
|
||||
public List<Guid> HiddenDefaultFolderIds { get; private set; } = new();
|
||||
|
||||
// Список пользовательских папок (Guid созданных Folder)
|
||||
public List<Guid> CustomFolderIds { get; private set; } = new();
|
||||
|
||||
public UserFolderSettings(Guid userId) : base(Guid.NewGuid())
|
||||
{
|
||||
UserId = userId;
|
||||
}
|
||||
|
||||
public void HideFolder(Guid folderId)
|
||||
{
|
||||
if (!HiddenDefaultFolderIds.Contains(folderId)) HiddenDefaultFolderIds.Add(folderId);
|
||||
}
|
||||
|
||||
public void ShowFolder(Guid folderId)
|
||||
{
|
||||
HiddenDefaultFolderIds.Remove(folderId);
|
||||
}
|
||||
}
|
||||
@@ -12,3 +12,28 @@ public interface IChatRepository
|
||||
Task<List<Chat>> GetUserChatsAsync(Guid userId, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public interface IFolderRepository
|
||||
{
|
||||
void Add(Folder folder);
|
||||
void Update(Folder folder);
|
||||
void Remove(Folder folder);
|
||||
Task<Folder?> GetByIdAsync(Guid id, CancellationToken cancellationToken);
|
||||
Task<List<Folder>> GetUserFoldersAsync(Guid userId, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public interface IUserChatSettingsRepository
|
||||
{
|
||||
void Add(UserChatSettings settings);
|
||||
void Update(UserChatSettings settings);
|
||||
void Remove(UserChatSettings settings);
|
||||
void RemoveRange(IEnumerable<UserChatSettings> settings);
|
||||
Task<UserChatSettings?> GetAsync(Guid userId, Guid chatId, CancellationToken cancellationToken);
|
||||
Task<List<UserChatSettings>> GetByUserIdAsync(Guid userId, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public interface IUserFolderSettingsRepository
|
||||
{
|
||||
Task<UserFolderSettings?> GetByUserIdAsync(Guid userId, CancellationToken cancellationToken);
|
||||
Task UpdateAsync(UserFolderSettings settings, CancellationToken cancellationToken);
|
||||
Task RemoveByUserIdAsync(Guid userId, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -54,3 +54,59 @@ public sealed class ChatRepository : IChatRepository
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class FolderRepository : IFolderRepository
|
||||
{
|
||||
private readonly ChatsDbContext _dbContext;
|
||||
|
||||
public FolderRepository(ChatsDbContext dbContext)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
}
|
||||
|
||||
public void Add(Folder folder) => _dbContext.Folders.Add(folder);
|
||||
public void Update(Folder folder) => _dbContext.Folders.Update(folder);
|
||||
public void Remove(Folder folder) => _dbContext.Folders.Remove(folder);
|
||||
|
||||
public async Task<Folder?> GetByIdAsync(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _dbContext.Folders.FirstOrDefaultAsync(f => f.Id == id, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<Folder>> GetUserFoldersAsync(Guid userId, CancellationToken cancellationToken)
|
||||
{
|
||||
// В доменной модели Folder не имеет UserId напрямую (может быть общей сущностью),
|
||||
// но по логике "папки в настройках пользователя" можно фильтровать через настройки.
|
||||
// Пока возвращаем все папки, которыми владеет пользователь (если добавить UserId)
|
||||
// или все для упрощения первой итерации.
|
||||
return await _dbContext.Folders.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class UserChatSettingsRepository : IUserChatSettingsRepository
|
||||
{
|
||||
private readonly ChatsDbContext _dbContext;
|
||||
|
||||
public UserChatSettingsRepository(ChatsDbContext dbContext)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
}
|
||||
|
||||
public void Add(UserChatSettings settings) => _dbContext.UserChatSettings.Add(settings);
|
||||
public void Update(UserChatSettings settings) => _dbContext.UserChatSettings.Update(settings);
|
||||
public void Remove(UserChatSettings settings) => _dbContext.UserChatSettings.Remove(settings);
|
||||
public void RemoveRange(IEnumerable<UserChatSettings> settings) => _dbContext.UserChatSettings.RemoveRange(settings);
|
||||
|
||||
public async Task<UserChatSettings?> GetAsync(Guid userId, Guid chatId, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _dbContext.UserChatSettings
|
||||
.FirstOrDefaultAsync(s => s.UserId == userId && s.ChatId == chatId, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<UserChatSettings>> GetByUserIdAsync(Guid userId, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _dbContext.UserChatSettings
|
||||
.Where(s => s.UserId == userId)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,11 @@ using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Shared.Kernel.Security;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Conversations.Infrastructure.Persistence;
|
||||
|
||||
@@ -24,8 +29,8 @@ public sealed class ChatsDbContext : DbContext, IChatsUnitOfWork
|
||||
}
|
||||
|
||||
public DbSet<Chat> Chats => Set<Chat>();
|
||||
|
||||
|
||||
public DbSet<Folder> Folders => Set<Folder>();
|
||||
public DbSet<UserChatSettings> UserChatSettings => Set<UserChatSettings>();
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
@@ -43,7 +48,6 @@ public sealed class ChatsDbContext : DbContext, IChatsUnitOfWork
|
||||
builder.HasKey(c => c.Id);
|
||||
builder.Property(c => c.Type).HasConversion<string>();
|
||||
|
||||
|
||||
builder.OwnsMany(c => c.Members, mb =>
|
||||
{
|
||||
mb.ToTable("ChatMembers");
|
||||
@@ -53,15 +57,32 @@ public sealed class ChatsDbContext : DbContext, IChatsUnitOfWork
|
||||
}).Navigation(c => c.Members).UsePropertyAccessMode(PropertyAccessMode.Field);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Folder>(builder =>
|
||||
{
|
||||
builder.ToTable("Folders");
|
||||
builder.HasKey(f => f.Id);
|
||||
builder.Property(f => f.Type).HasConversion<string>();
|
||||
});
|
||||
|
||||
modelBuilder.Entity<UserChatSettings>(builder =>
|
||||
{
|
||||
builder.ToTable("UserChatSettings");
|
||||
builder.HasKey(s => s.Id);
|
||||
builder.HasIndex(s => new { s.UserId, s.ChatId }).IsUnique();
|
||||
|
||||
builder.Property(s => s.FolderIds)
|
||||
.HasConversion(
|
||||
v => string.Join(',', v),
|
||||
v => v.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(Guid.Parse).ToList()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. Получаем все события из агрегатов
|
||||
var domainEvents = ChangeTracker
|
||||
.Entries<IAggregateRoot>()
|
||||
.SelectMany(x =>
|
||||
|
||||
{
|
||||
if (x.Entity is AggregateRoot<Guid> root)
|
||||
{
|
||||
@@ -73,10 +94,8 @@ public sealed class ChatsDbContext : DbContext, IChatsUnitOfWork
|
||||
})
|
||||
.ToList();
|
||||
|
||||
// 2. Сохраняем изменения
|
||||
int result = await base.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 3. Публикуем события через MediatR
|
||||
foreach (var domainEvent in domainEvents)
|
||||
{
|
||||
await _mediator.Publish(domainEvent, cancellationToken);
|
||||
@@ -85,4 +104,3 @@ public sealed class ChatsDbContext : DbContext, IChatsUnitOfWork
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using MongoDB.Bson.Serialization;
|
||||
|
||||
namespace Knot.Modules.Conversations.Infrastructure.Persistence.Mongo;
|
||||
|
||||
public static class ConversationsMongoDbMapConfigurator
|
||||
{
|
||||
private static bool _initialized;
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
if (_initialized) return;
|
||||
|
||||
if (!BsonClassMap.IsClassMapRegistered(typeof(UserFolderSettings)))
|
||||
{
|
||||
BsonClassMap.RegisterClassMap<UserFolderSettings>(cm =>
|
||||
{
|
||||
cm.AutoMap();
|
||||
cm.SetDiscriminator("UserFolderSettings");
|
||||
});
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Knot.Modules.Conversations.Infrastructure.Persistence.Mongo;
|
||||
|
||||
public sealed class UserFolderSettingsRepository : IUserFolderSettingsRepository
|
||||
{
|
||||
private readonly IMongoCollection<UserFolderSettings> _collection;
|
||||
|
||||
public UserFolderSettingsRepository(IMongoDatabase database)
|
||||
{
|
||||
_collection = database.GetCollection<UserFolderSettings>("user_folder_settings");
|
||||
}
|
||||
|
||||
public async Task<UserFolderSettings?> GetByUserIdAsync(Guid userId, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _collection.Find(s => s.UserId == userId).FirstOrDefaultAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(UserFolderSettings settings, CancellationToken cancellationToken)
|
||||
{
|
||||
var options = new ReplaceOptions { IsUpsert = true };
|
||||
await _collection.ReplaceOneAsync(s => s.Id == settings.Id, settings, options, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task RemoveByUserIdAsync(Guid userId, CancellationToken cancellationToken)
|
||||
{
|
||||
await _collection.DeleteManyAsync(s => s.UserId == userId, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
<ProjectReference Include="..\Messaging\Knot.Modules.Messaging.csproj" />
|
||||
<ProjectReference Include="..\Auth\Knot.Modules.Auth.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -35,4 +36,3 @@
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
155
backend/src/Modules/Conversations/conversations_errors.json
Normal file
155
backend/src/Modules/Conversations/conversations_errors.json
Normal file
@@ -0,0 +1,155 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/sarif-1.0.0",
|
||||
"version": "1.0.0",
|
||||
"runs": [
|
||||
{
|
||||
"tool": {
|
||||
"name": "Компилятор Microsoft (R) Visual C#",
|
||||
"version": "5.0.0.0",
|
||||
"fileVersion": "5.0.0-1.25358.103 (75972a5b)",
|
||||
"semanticVersion": "5.0.0",
|
||||
"language": "ru-RU"
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"ruleId": "CS0234",
|
||||
"level": "error",
|
||||
"message": "Тип или имя пространства имен \"Auth\" не существует в пространстве имен \"Knot.Modules\" (возможно, отсутствует ссылка на сборку).",
|
||||
"locations": [
|
||||
{
|
||||
"resultFile": {
|
||||
"uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Conversations/Application/Users/Commands/DeleteUser/DeleteUserCommand.cs",
|
||||
"region": {
|
||||
"startLine": 1,
|
||||
"startColumn": 20,
|
||||
"endLine": 1,
|
||||
"endColumn": 24
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "CS0234",
|
||||
"level": "error",
|
||||
"message": "Тип или имя пространства имен \"Auth\" не существует в пространстве имен \"Knot.Modules\" (возможно, отсутствует ссылка на сборку).",
|
||||
"locations": [
|
||||
{
|
||||
"resultFile": {
|
||||
"uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Conversations/Application/Users/Commands/DeleteUser/DeleteUserCommand.cs",
|
||||
"region": {
|
||||
"startLine": 9,
|
||||
"startColumn": 20,
|
||||
"endLine": 9,
|
||||
"endColumn": 24
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "CS0246",
|
||||
"level": "error",
|
||||
"message": "Не удалось найти тип или имя пространства имен \"IUserRepository\" (возможно, отсутствует директива using или ссылка на сборку).",
|
||||
"locations": [
|
||||
{
|
||||
"resultFile": {
|
||||
"uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Conversations/Application/Users/Commands/DeleteUser/DeleteUserCommand.cs",
|
||||
"region": {
|
||||
"startLine": 17,
|
||||
"startColumn": 22,
|
||||
"endLine": 17,
|
||||
"endColumn": 37
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "CS0246",
|
||||
"level": "error",
|
||||
"message": "Не удалось найти тип или имя пространства имен \"IAuthUnitOfWork\" (возможно, отсутствует директива using или ссылка на сборку).",
|
||||
"locations": [
|
||||
{
|
||||
"resultFile": {
|
||||
"uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Conversations/Application/Users/Commands/DeleteUser/DeleteUserCommand.cs",
|
||||
"region": {
|
||||
"startLine": 23,
|
||||
"startColumn": 22,
|
||||
"endLine": 23,
|
||||
"endColumn": 37
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "CS0246",
|
||||
"level": "error",
|
||||
"message": "Не удалось найти тип или имя пространства имен \"IUserRepository\" (возможно, отсутствует директива using или ссылка на сборку).",
|
||||
"locations": [
|
||||
{
|
||||
"resultFile": {
|
||||
"uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Conversations/Application/Users/Commands/DeleteUser/DeleteUserCommand.cs",
|
||||
"region": {
|
||||
"startLine": 26,
|
||||
"startColumn": 9,
|
||||
"endLine": 26,
|
||||
"endColumn": 24
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "CS0246",
|
||||
"level": "error",
|
||||
"message": "Не удалось найти тип или имя пространства имен \"IAuthUnitOfWork\" (возможно, отсутствует директива using или ссылка на сборку).",
|
||||
"locations": [
|
||||
{
|
||||
"resultFile": {
|
||||
"uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Conversations/Application/Users/Commands/DeleteUser/DeleteUserCommand.cs",
|
||||
"region": {
|
||||
"startLine": 32,
|
||||
"startColumn": 9,
|
||||
"endLine": 32,
|
||||
"endColumn": 24
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"rules": {
|
||||
"CS0234": {
|
||||
"id": "CS0234",
|
||||
"defaultLevel": "error",
|
||||
"helpUri": "https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS0234)",
|
||||
"properties": {
|
||||
"category": "Compiler",
|
||||
"isEnabledByDefault": true,
|
||||
"tags": [
|
||||
"Compiler",
|
||||
"Telemetry",
|
||||
"NotConfigurable"
|
||||
]
|
||||
}
|
||||
},
|
||||
"CS0246": {
|
||||
"id": "CS0246",
|
||||
"defaultLevel": "error",
|
||||
"helpUri": "https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS0246)",
|
||||
"properties": {
|
||||
"category": "Compiler",
|
||||
"isEnabledByDefault": true,
|
||||
"tags": [
|
||||
"Compiler",
|
||||
"Telemetry",
|
||||
"NotConfigurable"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
245
backend/src/Modules/Conversations/errors.json
Normal file
245
backend/src/Modules/Conversations/errors.json
Normal file
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/sarif-1.0.0",
|
||||
"version": "1.0.0",
|
||||
"runs": [
|
||||
{
|
||||
"tool": {
|
||||
"name": "Компилятор Microsoft (R) Visual C#",
|
||||
"version": "5.0.0.0",
|
||||
"fileVersion": "5.0.0-1.25358.103 (75972a5b)",
|
||||
"semanticVersion": "5.0.0",
|
||||
"language": "ru-RU"
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"ruleId": "CS0234",
|
||||
"level": "error",
|
||||
"message": "Тип или имя пространства имен \"Auth\" не существует в пространстве имен \"Knot.Modules\" (возможно, отсутствует ссылка на сборку).",
|
||||
"locations": [
|
||||
{
|
||||
"resultFile": {
|
||||
"uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Conversations/Application/Users/Commands/DeleteUser/DeleteUserCommand.cs",
|
||||
"region": {
|
||||
"startLine": 1,
|
||||
"startColumn": 20,
|
||||
"endLine": 1,
|
||||
"endColumn": 24
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "CS0246",
|
||||
"level": "error",
|
||||
"message": "Не удалось найти тип или имя пространства имен \"IChatsUnitOfWork\" (возможно, отсутствует директива using или ссылка на сборку).",
|
||||
"locations": [
|
||||
{
|
||||
"resultFile": {
|
||||
"uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Conversations/Application/Folders/Commands/AddToFolder/AddToFolderCommand.cs",
|
||||
"region": {
|
||||
"startLine": 12,
|
||||
"startColumn": 22,
|
||||
"endLine": 12,
|
||||
"endColumn": 38
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "CS0246",
|
||||
"level": "error",
|
||||
"message": "Не удалось найти тип или имя пространства имен \"IChatsUnitOfWork\" (возможно, отсутствует директива using или ссылка на сборку).",
|
||||
"locations": [
|
||||
{
|
||||
"resultFile": {
|
||||
"uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Conversations/Application/Folders/Commands/AddToFolder/AddToFolderCommand.cs",
|
||||
"region": {
|
||||
"startLine": 14,
|
||||
"startColumn": 86,
|
||||
"endLine": 14,
|
||||
"endColumn": 102
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "CS0246",
|
||||
"level": "error",
|
||||
"message": "Не удалось найти тип или имя пространства имен \"IChatsUnitOfWork\" (возможно, отсутствует директива using или ссылка на сборку).",
|
||||
"locations": [
|
||||
{
|
||||
"resultFile": {
|
||||
"uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Conversations/Application/Folders/Commands/RemoveFromFolder/RemoveFromFolderCommand.cs",
|
||||
"region": {
|
||||
"startLine": 12,
|
||||
"startColumn": 22,
|
||||
"endLine": 12,
|
||||
"endColumn": 38
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "CS0246",
|
||||
"level": "error",
|
||||
"message": "Не удалось найти тип или имя пространства имен \"IChatsUnitOfWork\" (возможно, отсутствует директива using или ссылка на сборку).",
|
||||
"locations": [
|
||||
{
|
||||
"resultFile": {
|
||||
"uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Conversations/Application/Folders/Commands/RemoveFromFolder/RemoveFromFolderCommand.cs",
|
||||
"region": {
|
||||
"startLine": 14,
|
||||
"startColumn": 91,
|
||||
"endLine": 14,
|
||||
"endColumn": 107
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "CS0246",
|
||||
"level": "error",
|
||||
"message": "Не удалось найти тип или имя пространства имен \"IUserRepository\" (возможно, отсутствует директива using или ссылка на сборку).",
|
||||
"locations": [
|
||||
{
|
||||
"resultFile": {
|
||||
"uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Conversations/Application/Users/Commands/DeleteUser/DeleteUserCommand.cs",
|
||||
"region": {
|
||||
"startLine": 15,
|
||||
"startColumn": 22,
|
||||
"endLine": 15,
|
||||
"endColumn": 37
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "CS0246",
|
||||
"level": "error",
|
||||
"message": "Не удалось найти тип или имя пространства имен \"IChatsUnitOfWork\" (возможно, отсутствует директива using или ссылка на сборку).",
|
||||
"locations": [
|
||||
{
|
||||
"resultFile": {
|
||||
"uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Conversations/Application/Users/Commands/DeleteUser/DeleteUserCommand.cs",
|
||||
"region": {
|
||||
"startLine": 20,
|
||||
"startColumn": 22,
|
||||
"endLine": 20,
|
||||
"endColumn": 38
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "CS0234",
|
||||
"level": "error",
|
||||
"message": "Тип или имя пространства имен \"Auth\" не существует в пространстве имен \"Knot.Modules\" (возможно, отсутствует ссылка на сборку).",
|
||||
"locations": [
|
||||
{
|
||||
"resultFile": {
|
||||
"uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Conversations/Application/Users/Commands/DeleteUser/DeleteUserCommand.cs",
|
||||
"region": {
|
||||
"startLine": 21,
|
||||
"startColumn": 35,
|
||||
"endLine": 21,
|
||||
"endColumn": 39
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "CS0246",
|
||||
"level": "error",
|
||||
"message": "Не удалось найти тип или имя пространства имен \"IUserRepository\" (возможно, отсутствует директива using или ссылка на сборку).",
|
||||
"locations": [
|
||||
{
|
||||
"resultFile": {
|
||||
"uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Conversations/Application/Users/Commands/DeleteUser/DeleteUserCommand.cs",
|
||||
"region": {
|
||||
"startLine": 24,
|
||||
"startColumn": 9,
|
||||
"endLine": 24,
|
||||
"endColumn": 24
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "CS0246",
|
||||
"level": "error",
|
||||
"message": "Не удалось найти тип или имя пространства имен \"IChatsUnitOfWork\" (возможно, отсутствует директива using или ссылка на сборку).",
|
||||
"locations": [
|
||||
{
|
||||
"resultFile": {
|
||||
"uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Conversations/Application/Users/Commands/DeleteUser/DeleteUserCommand.cs",
|
||||
"region": {
|
||||
"startLine": 29,
|
||||
"startColumn": 9,
|
||||
"endLine": 29,
|
||||
"endColumn": 25
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "CS0234",
|
||||
"level": "error",
|
||||
"message": "Тип или имя пространства имен \"Auth\" не существует в пространстве имен \"Knot.Modules\" (возможно, отсутствует ссылка на сборку).",
|
||||
"locations": [
|
||||
{
|
||||
"resultFile": {
|
||||
"uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Conversations/Application/Users/Commands/DeleteUser/DeleteUserCommand.cs",
|
||||
"region": {
|
||||
"startLine": 30,
|
||||
"startColumn": 22,
|
||||
"endLine": 30,
|
||||
"endColumn": 26
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"rules": {
|
||||
"CS0234": {
|
||||
"id": "CS0234",
|
||||
"defaultLevel": "error",
|
||||
"helpUri": "https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS0234)",
|
||||
"properties": {
|
||||
"category": "Compiler",
|
||||
"isEnabledByDefault": true,
|
||||
"tags": [
|
||||
"Compiler",
|
||||
"Telemetry",
|
||||
"NotConfigurable"
|
||||
]
|
||||
}
|
||||
},
|
||||
"CS0246": {
|
||||
"id": "CS0246",
|
||||
"defaultLevel": "error",
|
||||
"helpUri": "https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS0246)",
|
||||
"properties": {
|
||||
"category": "Compiler",
|
||||
"isEnabledByDefault": true,
|
||||
"tags": [
|
||||
"Compiler",
|
||||
"Telemetry",
|
||||
"NotConfigurable"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user