From 09e5cbaa76133eb7f65fe94b9dbb2ad085635377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A5=D0=B0=D0=BB=D0=B8=D0=BC=D0=BE=D0=B2=20=D0=A0=D1=83?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=BC?= Date: Mon, 30 Mar 2026 15:35:28 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Auth/Abstractions/IUserQueryService.cs | 14 ++++ .../Abstractions/IAuthDbContext.cs | 3 +- .../Application/Users/GetMe/GetMeQuery.cs | 6 -- .../Users/Login/LoginUserCommand.cs | 10 --- .../Users/Register/RegisterUserCommand.cs | 14 ---- .../Cleanup/Abstractions/ICleanupService.cs | 30 +++++++++ .../Cleanup/Knot.Contracts.Cleanup.csproj | 10 +++ .../Abstractions/IChatQueryService.cs | 18 +++++ .../Abstractions/IUserStatusService.cs | 6 ++ .../Abstractions/ISignalRNotifications.cs | 24 +++++++ .../Persistence/IChatsDbContext.cs | 20 ++++++ .../Knot.Contracts.Conversations.csproj | 4 ++ .../Klipy/Abstractions/IKlipyClient.cs | 6 ++ .../Abstractions/IMessageQueryService.cs | 14 ++++ .../Abstractions/IUserStatsService.cs | 13 ++++ .../Abstractions/IMessageRepository.cs | 1 + .../Abstractions/IUserStatsService.cs | 16 ----- .../Persistence/IMongoMessageCollection.cs | 26 ++++++++ .../Abstractions/IFriendshipRepository.cs | 12 ++++ .../Contracts/Relations/Domain/Friendship.cs | 11 ++++ .../Relations/Domain/FriendshipStatus.cs | 8 +++ .../Contracts/Relations/Domain/UserReplica.cs | 12 ++++ .../Abstractions/IStoryQueryService.cs | 13 ++++ .../Stories/Knot.Contracts.Stories.csproj | 1 + .../Modules/Admin/Knot.Modules.Admin.csproj | 12 ++-- .../Persistence/UserQueryService.cs | 39 +++++++++++ .../Abstractions/IUserDeleterService.cs | 11 ++++ .../Conversations/DependencyInjection.cs | 12 ++-- .../Persistence/ChatQueryService.cs | 25 +++++++ .../Persistence/ConversationsDbContext.cs | 21 +++--- .../Federation/Knot.Modules.Federation.csproj | 1 - .../Modules/Klipy/Knot.Modules.Klipy.csproj | 1 - .../Modules/Messaging/DependencyInjection.cs | 3 +- .../Persistence/MessageQueryService.cs | 37 +++++++++++ .../Persistence/MessageRepository.cs | 6 ++ .../Persistence/Mongo/UserStatsService.cs | 16 +++-- .../Messaging/Knot.Modules.Messaging.csproj | 4 +- .../Abstractions/FriendshipRepository.cs | 37 +++++++++++ .../Modules/Relations/DependencyInjection.cs | 6 +- .../Modules/Relations/Domain/Friendship.cs | 8 +-- .../Relations/Knot.Modules.Relations.csproj | 2 +- .../Storage/Knot.Modules.Storage.csproj | 2 +- .../Abstractions/IStoriesDbContext.cs | 21 ------ .../Abstractions/IStoryNotificationService.cs | 18 +++++ .../Abstractions/IUserRepository.cs | 3 +- .../Commands/ViewStory/ViewStoryCommand.cs | 52 +++++++-------- .../Queries/GetStories/GetStoriesQuery.cs | 44 ++++++------- .../GetUserStories/GetUserStoriesQuery.cs | 13 ++-- backend/src/Modules/Stories/GlobalUsings.cs | 1 - .../Persistence/Mongo/StoryQueryService.cs | 25 +++++++ .../StoryNotificationService.cs | 35 ++++++++++ .../Stories/Knot.Modules.Stories.csproj | 8 +-- .../TelegramImport/AnalyzeImportCommand.cs | 20 +++--- .../TelegramImport/TelegramImportErrors.cs | 7 ++ .../Background/TelegramImportWorker.cs | 66 +++++++++---------- .../Knot.Modules.TelegramImport.csproj | 11 +++- .../Modules/WebRtc/Knot.Modules.WebRtc.csproj | 5 +- 57 files changed, 631 insertions(+), 233 deletions(-) create mode 100644 backend/src/Contracts/Auth/Abstractions/IUserQueryService.cs delete mode 100644 backend/src/Contracts/Auth/Application/Users/GetMe/GetMeQuery.cs delete mode 100644 backend/src/Contracts/Auth/Application/Users/Login/LoginUserCommand.cs delete mode 100644 backend/src/Contracts/Auth/Application/Users/Register/RegisterUserCommand.cs create mode 100644 backend/src/Contracts/Cleanup/Abstractions/ICleanupService.cs create mode 100644 backend/src/Contracts/Cleanup/Knot.Contracts.Cleanup.csproj create mode 100644 backend/src/Contracts/Conversations/Abstractions/IChatQueryService.cs create mode 100644 backend/src/Contracts/Conversations/Abstractions/IUserStatusService.cs create mode 100644 backend/src/Contracts/Conversations/Application/Abstractions/ISignalRNotifications.cs create mode 100644 backend/src/Contracts/Conversations/Infrastructure/Persistence/IChatsDbContext.cs create mode 100644 backend/src/Contracts/Klipy/Abstractions/IKlipyClient.cs create mode 100644 backend/src/Contracts/Messaging/Abstractions/IMessageQueryService.cs create mode 100644 backend/src/Contracts/Messaging/Abstractions/IUserStatsService.cs delete mode 100644 backend/src/Contracts/Messaging/Application/Abstractions/IUserStatsService.cs create mode 100644 backend/src/Contracts/Messaging/Infrastructure/Persistence/IMongoMessageCollection.cs create mode 100644 backend/src/Contracts/Relations/Application/Abstractions/IFriendshipRepository.cs create mode 100644 backend/src/Contracts/Relations/Domain/Friendship.cs create mode 100644 backend/src/Contracts/Relations/Domain/FriendshipStatus.cs create mode 100644 backend/src/Contracts/Relations/Domain/UserReplica.cs create mode 100644 backend/src/Contracts/Stories/Abstractions/IStoryQueryService.cs create mode 100644 backend/src/Modules/Auth/Infrastructure/Persistence/UserQueryService.cs create mode 100644 backend/src/Modules/Conversations/Application/Abstractions/IUserDeleterService.cs create mode 100644 backend/src/Modules/Conversations/Infrastructure/Persistence/ChatQueryService.cs create mode 100644 backend/src/Modules/Messaging/Infrastructure/Persistence/MessageQueryService.cs create mode 100644 backend/src/Modules/Relations/Application/Abstractions/FriendshipRepository.cs delete mode 100644 backend/src/Modules/Stories/Application/Abstractions/IStoriesDbContext.cs create mode 100644 backend/src/Modules/Stories/Application/Abstractions/IStoryNotificationService.cs create mode 100644 backend/src/Modules/Stories/Infrastructure/Persistence/Mongo/StoryQueryService.cs create mode 100644 backend/src/Modules/Stories/Infrastructure/StoryNotificationService.cs create mode 100644 backend/src/Modules/TelegramImport/Application/TelegramImport/TelegramImportErrors.cs diff --git a/backend/src/Contracts/Auth/Abstractions/IUserQueryService.cs b/backend/src/Contracts/Auth/Abstractions/IUserQueryService.cs new file mode 100644 index 0000000..be86941 --- /dev/null +++ b/backend/src/Contracts/Auth/Abstractions/IUserQueryService.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Knot.Contracts.Auth.Abstractions; + +public interface IUserQueryService +{ + Task> GetAllUsersAsync(CancellationToken cancellationToken); + Task> SearchUsersAsync(string query, CancellationToken cancellationToken); +} + +public record UserInfo(Guid Id, string? Avatar, string? UserName, string? DisplayName, DateTime CreatedAt, string? PhoneNumber, string? Email); diff --git a/backend/src/Contracts/Auth/Application/Abstractions/IAuthDbContext.cs b/backend/src/Contracts/Auth/Application/Abstractions/IAuthDbContext.cs index 3969a1e..241e741 100644 --- a/backend/src/Contracts/Auth/Application/Abstractions/IAuthDbContext.cs +++ b/backend/src/Contracts/Auth/Application/Abstractions/IAuthDbContext.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore; +using Knot.Contracts.Auth.Domain; +using Microsoft.EntityFrameworkCore; namespace Knot.Contracts.Auth.Application.Abstractions; diff --git a/backend/src/Contracts/Auth/Application/Users/GetMe/GetMeQuery.cs b/backend/src/Contracts/Auth/Application/Users/GetMe/GetMeQuery.cs deleted file mode 100644 index d5e059b..0000000 --- a/backend/src/Contracts/Auth/Application/Users/GetMe/GetMeQuery.cs +++ /dev/null @@ -1,6 +0,0 @@ -using Knot.Contracts.Auth.Domain; -using MediatR; - -namespace Knot.Contracts.Auth.Application.Users.GetMe; - -public record GetMeQuery() : IRequest; diff --git a/backend/src/Contracts/Auth/Application/Users/Login/LoginUserCommand.cs b/backend/src/Contracts/Auth/Application/Users/Login/LoginUserCommand.cs deleted file mode 100644 index e251e87..0000000 --- a/backend/src/Contracts/Auth/Application/Users/Login/LoginUserCommand.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Knot.Contracts.Auth.Application.Auth.DTOs; -using Knot.Shared.Kernel; -using MediatR; - -namespace Knot.Contracts.Auth.Application.Users.Login; - -public record LoginUserCommand( - string Username, - string Password -) : IRequest>; diff --git a/backend/src/Contracts/Auth/Application/Users/Register/RegisterUserCommand.cs b/backend/src/Contracts/Auth/Application/Users/Register/RegisterUserCommand.cs deleted file mode 100644 index 4114a4c..0000000 --- a/backend/src/Contracts/Auth/Application/Users/Register/RegisterUserCommand.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Knot.Contracts.Auth.Application.Auth.DTOs; -using Knot.Contracts.Auth.Domain; -using Knot.Shared.Kernel; -using MediatR; - -namespace Knot.Contracts.Auth.Application.Users.Register; - -public record RegisterUserCommand( - string Username, - string Password, - string DisplayName, - string? Email, - string? Bio -) : IRequest>; diff --git a/backend/src/Contracts/Cleanup/Abstractions/ICleanupService.cs b/backend/src/Contracts/Cleanup/Abstractions/ICleanupService.cs new file mode 100644 index 0000000..7bee5b7 --- /dev/null +++ b/backend/src/Contracts/Cleanup/Abstractions/ICleanupService.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Knot.Contracts.Cleanup.Abstractions; + +public interface ICleanupService +{ + Task GetCleanupDataAsync(CancellationToken cancellationToken); + Task CleanOrphanMessagesAsync(List messageIds, CancellationToken cancellationToken); + Task CleanOrphanFilesAsync(List fileIds, CancellationToken cancellationToken); +} + +public record CleanupData( + List Chats, + List Messages, + List Stories, + List Users, + List Files); + +public record ChatCleanupInfo(Guid Id, string? Avatar); + +public record MessageCleanupInfo(Guid Id, Guid ChatId, bool IsDeleted, string? MediaUrl); + +public record StoryCleanupInfo(Guid Id, string? MediaUrl); + +public record UserCleanupInfo(Guid Id, string? Avatar); + +public record FileCleanupInfo(string FileId); diff --git a/backend/src/Contracts/Cleanup/Knot.Contracts.Cleanup.csproj b/backend/src/Contracts/Cleanup/Knot.Contracts.Cleanup.csproj new file mode 100644 index 0000000..34e1efd --- /dev/null +++ b/backend/src/Contracts/Cleanup/Knot.Contracts.Cleanup.csproj @@ -0,0 +1,10 @@ + + + net10.0 + enable + enable + + + + + diff --git a/backend/src/Contracts/Conversations/Abstractions/IChatQueryService.cs b/backend/src/Contracts/Conversations/Abstractions/IChatQueryService.cs new file mode 100644 index 0000000..a809f32 --- /dev/null +++ b/backend/src/Contracts/Conversations/Abstractions/IChatQueryService.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Knot.Contracts.Conversations.Abstractions; + +public interface IChatQueryService +{ + Task> GetAllChatsAsync(CancellationToken cancellationToken); +} + +public interface IUserDeleterService +{ + Task DeleteUserAsync(Guid userId, CancellationToken cancellationToken); +} + +public record ChatInfo(Guid Id, string? Avatar); diff --git a/backend/src/Contracts/Conversations/Abstractions/IUserStatusService.cs b/backend/src/Contracts/Conversations/Abstractions/IUserStatusService.cs new file mode 100644 index 0000000..e5833c3 --- /dev/null +++ b/backend/src/Contracts/Conversations/Abstractions/IUserStatusService.cs @@ -0,0 +1,6 @@ +namespace Knot.Contracts.Conversations.Abstractions; + +public interface IUserStatusService +{ + bool IsUserOnline(string userId); +} diff --git a/backend/src/Contracts/Conversations/Application/Abstractions/ISignalRNotifications.cs b/backend/src/Contracts/Conversations/Application/Abstractions/ISignalRNotifications.cs new file mode 100644 index 0000000..d936dd1 --- /dev/null +++ b/backend/src/Contracts/Conversations/Application/Abstractions/ISignalRNotifications.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.SignalR; + +namespace Knot.Contracts.Conversations.Application.Abstractions; + +public interface IStoryNotificationsClient +{ + Task StoryViewed(Guid storyId, Guid userId); + Task StoryReactionAdded(Guid storyId, Guid userId, string reaction); + Task StoryReactionRemoved(Guid storyId, Guid userId); +} + +public interface IChatHubClient +{ + Task MessageReceived(object message); + Task MessageDeleted(Guid messageId); + Task MessageEdited(Guid messageId, object newContent); + Task ReactionAdded(Guid messageId, string reaction, Guid userId); + Task ReactionRemoved(Guid messageId, string reaction, Guid userId); + Task UserStatusChanged(Guid userId, bool isOnline); + Task ChatUpdated(object chat); + Task ChatDeleted(Guid chatId); + Task UserJoinedChat(Guid chatId, Guid userId); + Task UserLeftChat(Guid chatId, Guid userId); +} diff --git a/backend/src/Contracts/Conversations/Infrastructure/Persistence/IChatsDbContext.cs b/backend/src/Contracts/Conversations/Infrastructure/Persistence/IChatsDbContext.cs new file mode 100644 index 0000000..ccfba8b --- /dev/null +++ b/backend/src/Contracts/Conversations/Infrastructure/Persistence/IChatsDbContext.cs @@ -0,0 +1,20 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Knot.Contracts.Conversations.Application.Abstractions; +using Knot.Shared.Kernel; + +namespace Knot.Contracts.Conversations.Infrastructure.Persistence; + +public interface IChatsDbContext : IChatsUnitOfWork +{ + IQueryable Chats { get; } +} + +public class Chat : AggregateRoot +{ + public Chat() : base(Guid.NewGuid()) { } + + + public string? Avatar { get; set; } +} \ No newline at end of file diff --git a/backend/src/Contracts/Conversations/Knot.Contracts.Conversations.csproj b/backend/src/Contracts/Conversations/Knot.Contracts.Conversations.csproj index bed802d..39bed14 100644 --- a/backend/src/Contracts/Conversations/Knot.Contracts.Conversations.csproj +++ b/backend/src/Contracts/Conversations/Knot.Contracts.Conversations.csproj @@ -11,6 +11,10 @@ + + + + diff --git a/backend/src/Contracts/Klipy/Abstractions/IKlipyClient.cs b/backend/src/Contracts/Klipy/Abstractions/IKlipyClient.cs new file mode 100644 index 0000000..7880625 --- /dev/null +++ b/backend/src/Contracts/Klipy/Abstractions/IKlipyClient.cs @@ -0,0 +1,6 @@ +namespace Knot.Contracts.Klipy.Abstractions; + +public interface IKlipyClient +{ + Task TestConnectionAsync(CancellationToken cancellationToken = default); +} diff --git a/backend/src/Contracts/Messaging/Abstractions/IMessageQueryService.cs b/backend/src/Contracts/Messaging/Abstractions/IMessageQueryService.cs new file mode 100644 index 0000000..9888b6b --- /dev/null +++ b/backend/src/Contracts/Messaging/Abstractions/IMessageQueryService.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Knot.Contracts.Messaging.Abstractions; + +public interface IMessageQueryService +{ + Task> GetAllMessagesAsync(CancellationToken cancellationToken); + Task DeleteMessagesAsync(List messageIds, CancellationToken cancellationToken); +} + +public record MessageInfo(Guid Id, Guid ChatId, bool IsDeleted, string? MediaUrl); diff --git a/backend/src/Contracts/Messaging/Abstractions/IUserStatsService.cs b/backend/src/Contracts/Messaging/Abstractions/IUserStatsService.cs new file mode 100644 index 0000000..fcce633 --- /dev/null +++ b/backend/src/Contracts/Messaging/Abstractions/IUserStatsService.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Knot.Contracts.Messaging.Abstractions; + +public interface IUserStatsService +{ + Task> GetStatsForUsersAsync(List userIds, CancellationToken cancellationToken = default); +} + +public record UserStats(int MessageCount, long StorageSize); diff --git a/backend/src/Contracts/Messaging/Application/Abstractions/IMessageRepository.cs b/backend/src/Contracts/Messaging/Application/Abstractions/IMessageRepository.cs index 048a900..8feac1a 100644 --- a/backend/src/Contracts/Messaging/Application/Abstractions/IMessageRepository.cs +++ b/backend/src/Contracts/Messaging/Application/Abstractions/IMessageRepository.cs @@ -18,4 +18,5 @@ public interface IMessageRepository Task DeleteChatMessagesAsync(Guid chatId, CancellationToken cancellationToken); Task DeleteUserMessagesAsync(Guid userId, CancellationToken cancellationToken); Task RemoveAsync(Guid id, CancellationToken cancellationToken); + Task DeleteAsync(Message message, CancellationToken cancellationToken); } diff --git a/backend/src/Contracts/Messaging/Application/Abstractions/IUserStatsService.cs b/backend/src/Contracts/Messaging/Application/Abstractions/IUserStatsService.cs deleted file mode 100644 index 8e25410..0000000 --- a/backend/src/Contracts/Messaging/Application/Abstractions/IUserStatsService.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Knot.Contracts.Messaging.Application.Abstractions; - -public record UserStats(int MessageCount, long StorageSize); - -public interface IUserStatsService -{ - Task> GetStatsForUsersAsync(IEnumerable userIds, CancellationToken ct = default); - Task GetTotalStorageSizeAsync(CancellationToken ct = default); - Task GetCountOrphanedMessagesAsync(HashSet activeChatIds, CancellationToken ct = default); - Task GetOrphanedMediaSizeAsync(HashSet validFileIds, CancellationToken ct = default); -} diff --git a/backend/src/Contracts/Messaging/Infrastructure/Persistence/IMongoMessageCollection.cs b/backend/src/Contracts/Messaging/Infrastructure/Persistence/IMongoMessageCollection.cs new file mode 100644 index 0000000..be6ae95 --- /dev/null +++ b/backend/src/Contracts/Messaging/Infrastructure/Persistence/IMongoMessageCollection.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Knot.Contracts.Messaging.Infrastructure.Persistence; + +public interface IMongoMessageCollection +{ + Task> GetAllAsync(CancellationToken cancellationToken); + Task> GetOrphanedMessagesAsync(HashSet activeChatIds, CancellationToken cancellationToken); + Task DeleteManyAsync(List messageIds, CancellationToken cancellationToken); +} + +public class Message +{ + public Guid Id { get; set; } + public Guid ChatId { get; set; } + public bool IsDeleted { get; set; } + public string? MediaUrl { get; set; } + public List? Media { get; set; } +} + +public class Media +{ + public string Url { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/backend/src/Contracts/Relations/Application/Abstractions/IFriendshipRepository.cs b/backend/src/Contracts/Relations/Application/Abstractions/IFriendshipRepository.cs new file mode 100644 index 0000000..8ebf229 --- /dev/null +++ b/backend/src/Contracts/Relations/Application/Abstractions/IFriendshipRepository.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Knot.Contracts.Relations.Domain; + +namespace Knot.Contracts.Relations.Application.Abstractions; + +public interface IFriendshipRepository +{ + Task> GetAcceptedFriendshipsAsync(Guid userId, CancellationToken ct = default); +} diff --git a/backend/src/Contracts/Relations/Domain/Friendship.cs b/backend/src/Contracts/Relations/Domain/Friendship.cs new file mode 100644 index 0000000..bb84471 --- /dev/null +++ b/backend/src/Contracts/Relations/Domain/Friendship.cs @@ -0,0 +1,11 @@ +namespace Knot.Contracts.Relations.Domain; + +using System; + +public record Friendship( + Guid Id, + Guid UserId, + Guid FriendId, + FriendshipStatus Status, + DateTime CreatedAt +); diff --git a/backend/src/Contracts/Relations/Domain/FriendshipStatus.cs b/backend/src/Contracts/Relations/Domain/FriendshipStatus.cs new file mode 100644 index 0000000..d1c43c1 --- /dev/null +++ b/backend/src/Contracts/Relations/Domain/FriendshipStatus.cs @@ -0,0 +1,8 @@ +namespace Knot.Contracts.Relations.Domain; + +public enum FriendshipStatus +{ + Pending, + Accepted, + Declined +} diff --git a/backend/src/Contracts/Relations/Domain/UserReplica.cs b/backend/src/Contracts/Relations/Domain/UserReplica.cs new file mode 100644 index 0000000..2d80d0a --- /dev/null +++ b/backend/src/Contracts/Relations/Domain/UserReplica.cs @@ -0,0 +1,12 @@ +namespace Knot.Contracts.Relations.Domain; + +using System; + +public record UserReplica( + Guid Id, + string Username, + string DisplayName, + string Avatar, + bool IsExternal, + string? Domain +); diff --git a/backend/src/Contracts/Stories/Abstractions/IStoryQueryService.cs b/backend/src/Contracts/Stories/Abstractions/IStoryQueryService.cs new file mode 100644 index 0000000..e5cce7c --- /dev/null +++ b/backend/src/Contracts/Stories/Abstractions/IStoryQueryService.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Knot.Contracts.Stories.Abstractions; + +public interface IStoryQueryService +{ + Task> GetAllStoriesAsync(CancellationToken cancellationToken); +} + +public record StoryInfo(Guid Id, string? MediaUrl); diff --git a/backend/src/Contracts/Stories/Knot.Contracts.Stories.csproj b/backend/src/Contracts/Stories/Knot.Contracts.Stories.csproj index 4bd9c60..0170d3e 100644 --- a/backend/src/Contracts/Stories/Knot.Contracts.Stories.csproj +++ b/backend/src/Contracts/Stories/Knot.Contracts.Stories.csproj @@ -13,6 +13,7 @@ + diff --git a/backend/src/Modules/Admin/Knot.Modules.Admin.csproj b/backend/src/Modules/Admin/Knot.Modules.Admin.csproj index edc21f5..534ad08 100644 --- a/backend/src/Modules/Admin/Knot.Modules.Admin.csproj +++ b/backend/src/Modules/Admin/Knot.Modules.Admin.csproj @@ -14,12 +14,12 @@ - - - - - - + + + + + + diff --git a/backend/src/Modules/Auth/Infrastructure/Persistence/UserQueryService.cs b/backend/src/Modules/Auth/Infrastructure/Persistence/UserQueryService.cs new file mode 100644 index 0000000..05309ad --- /dev/null +++ b/backend/src/Modules/Auth/Infrastructure/Persistence/UserQueryService.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Knot.Contracts.Auth.Abstractions; +using Knot.Modules.Auth.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; + +namespace Knot.Modules.Auth.Infrastructure.Persistence; + +public sealed class UserQueryService : IUserQueryService +{ + private readonly AuthDbContext _context; + + public UserQueryService(AuthDbContext context) + { + _context = context; + } + + public async Task> GetAllUsersAsync(CancellationToken cancellationToken) + { + var users = await _context.Users.AsNoTracking().ToListAsync(cancellationToken); + return users.Select(u => new UserInfo(u.Id, u.Avatar, u.Username, u.DisplayName, u.CreatedAt, u.PhoneNumber, u.Email)).ToList(); + } + + public async Task> SearchUsersAsync(string query, CancellationToken cancellationToken) + { + var users = await _context.Users + .AsNoTracking() + .Where(u => u.Username != null && u.Username.Contains(query) || + u.DisplayName != null && u.DisplayName.Contains(query) || + u.Email != null && u.Email.Contains(query) || + u.PhoneNumber != null && u.PhoneNumber.Contains(query)) + .Take(50) + .ToListAsync(cancellationToken); + return users.Select(u => new UserInfo(u.Id, u.Avatar, u.Username, u.DisplayName, u.CreatedAt, u.PhoneNumber, u.Email)).ToList(); + } +} diff --git a/backend/src/Modules/Conversations/Application/Abstractions/IUserDeleterService.cs b/backend/src/Modules/Conversations/Application/Abstractions/IUserDeleterService.cs new file mode 100644 index 0000000..5565e80 --- /dev/null +++ b/backend/src/Modules/Conversations/Application/Abstractions/IUserDeleterService.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Knot.Shared.Kernel; + +namespace Knot.Modules.Conversations.Application.Abstractions; + +public interface IUserDeleterService +{ + Task DeleteUserAsync(Guid userId, CancellationToken cancellationToken); +} diff --git a/backend/src/Modules/Conversations/DependencyInjection.cs b/backend/src/Modules/Conversations/DependencyInjection.cs index 80569f4..9753682 100644 --- a/backend/src/Modules/Conversations/DependencyInjection.cs +++ b/backend/src/Modules/Conversations/DependencyInjection.cs @@ -1,3 +1,4 @@ +using Knot.Contracts.Conversations.Application.Abstractions; using Knot.Contracts.Messaging.Application.Abstractions; using Knot.Contracts.Messaging.Domain; using Knot.Modules.Conversations.Application.Abstractions; @@ -8,6 +9,7 @@ using Knot.Shared.Kernel; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using ConversationsAbstractions = Knot.Modules.Conversations.Application.Abstractions; namespace Knot.Modules.Conversations; @@ -22,28 +24,22 @@ public static class DependencyInjection services.AddDbContext(options => options.UseNpgsql(connectionString)); - // MongoDB Setup for Messages ConversationsMongoDbMapConfigurator.Configure(); var mongoConnectionString = configuration.GetConnectionString("MongoConnection") ?? "mongodb://localhost:27017"; - // Registration - services.AddScoped(sp => sp.GetRequiredService()); + services.AddScoped(sp => sp.GetRequiredService()); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); - // Messaging Repository - registered in Messaging module - // services.AddScoped(); - - // MediatR services.AddMediatR(config => config.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly)); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); return services; } } diff --git a/backend/src/Modules/Conversations/Infrastructure/Persistence/ChatQueryService.cs b/backend/src/Modules/Conversations/Infrastructure/Persistence/ChatQueryService.cs new file mode 100644 index 0000000..80524ae --- /dev/null +++ b/backend/src/Modules/Conversations/Infrastructure/Persistence/ChatQueryService.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Knot.Contracts.Conversations.Abstractions; +using Microsoft.EntityFrameworkCore; + +namespace Knot.Modules.Conversations.Infrastructure.Persistence; + +public sealed class ChatQueryService : IChatQueryService +{ + private readonly ChatsDbContext _context; + + public ChatQueryService(ChatsDbContext context) + { + _context = context; + } + + public async Task> GetAllChatsAsync(CancellationToken cancellationToken) + { + var chats = await _context.Chats.AsNoTracking().ToListAsync(cancellationToken); + return chats.Select(c => new ChatInfo(c.Id, c.Avatar)).ToList(); + } +} diff --git a/backend/src/Modules/Conversations/Infrastructure/Persistence/ConversationsDbContext.cs b/backend/src/Modules/Conversations/Infrastructure/Persistence/ConversationsDbContext.cs index 4b73e1c..e1f4782 100644 --- a/backend/src/Modules/Conversations/Infrastructure/Persistence/ConversationsDbContext.cs +++ b/backend/src/Modules/Conversations/Infrastructure/Persistence/ConversationsDbContext.cs @@ -1,22 +1,23 @@ -using MediatR; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Diagnostics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Knot.Contracts.Conversations.Application.Abstractions; 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; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; namespace Knot.Modules.Conversations.Infrastructure.Persistence; /// /// Контекст базы данных для модуля чатов. /// -public sealed class ChatsDbContext : DbContext, IChatsUnitOfWork +public sealed class ChatsDbContext : DbContext, Knot.Modules.Conversations.Application.Abstractions.IChatsUnitOfWork { private readonly IMediator _mediator; private readonly IEncryptionService _encryptionService; @@ -69,7 +70,7 @@ public sealed class ChatsDbContext : DbContext, IChatsUnitOfWork 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), diff --git a/backend/src/Modules/Federation/Knot.Modules.Federation.csproj b/backend/src/Modules/Federation/Knot.Modules.Federation.csproj index 239451a..3302ede 100644 --- a/backend/src/Modules/Federation/Knot.Modules.Federation.csproj +++ b/backend/src/Modules/Federation/Knot.Modules.Federation.csproj @@ -12,7 +12,6 @@ - diff --git a/backend/src/Modules/Klipy/Knot.Modules.Klipy.csproj b/backend/src/Modules/Klipy/Knot.Modules.Klipy.csproj index 08cddbb..c585e72 100644 --- a/backend/src/Modules/Klipy/Knot.Modules.Klipy.csproj +++ b/backend/src/Modules/Klipy/Knot.Modules.Klipy.csproj @@ -8,7 +8,6 @@ - diff --git a/backend/src/Modules/Messaging/DependencyInjection.cs b/backend/src/Modules/Messaging/DependencyInjection.cs index 3614037..d03aa2c 100644 --- a/backend/src/Modules/Messaging/DependencyInjection.cs +++ b/backend/src/Modules/Messaging/DependencyInjection.cs @@ -1,5 +1,6 @@ using Knot.Contracts.Messaging.Application.Abstractions; using Knot.Contracts.Messaging.Domain; +using Knot.Modules.Messaging.Application.Abstractions; using Knot.Modules.Messaging.Infrastructure.Persistence; using Knot.Modules.Messaging.Infrastructure.Persistence.Mongo; using Knot.Shared.Kernel; @@ -24,12 +25,10 @@ public static class DependencyInjection services.AddScoped(sp => sp.GetRequiredService().GetDatabase("forkmessager_chats")); - // Registration services.AddScoped(); services.AddScoped(); services.AddScoped(); - // MediatR services.AddMediatR(config => config.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly)); diff --git a/backend/src/Modules/Messaging/Infrastructure/Persistence/MessageQueryService.cs b/backend/src/Modules/Messaging/Infrastructure/Persistence/MessageQueryService.cs new file mode 100644 index 0000000..7a772eb --- /dev/null +++ b/backend/src/Modules/Messaging/Infrastructure/Persistence/MessageQueryService.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Knot.Contracts.Messaging.Abstractions; +using Knot.Contracts.Messaging.Domain; +using MongoDB.Driver; + +namespace Knot.Modules.Messaging.Infrastructure.Persistence; + +public sealed class MessageQueryService : IMessageQueryService +{ + private readonly IMongoCollection _messages; + + public MessageQueryService(IMongoDatabase database) + { + _messages = database.GetCollection("messages"); + } + + public async Task> GetAllMessagesAsync(CancellationToken cancellationToken) + { + var messages = await _messages.Find(_ => true).ToListAsync(cancellationToken); + return messages.Select(m => new MessageInfo( + m.Id, + m.ChatId, + m.State.HasFlag(MessageState.IsDeleted), + m is MediaMessage mm && mm.Media.Any() ? mm.Media.First().Url : null + )).ToList(); + } + + public async Task DeleteMessagesAsync(List messageIds, CancellationToken cancellationToken) + { + var filter = Builders.Filter.In(m => m.Id, messageIds); + await _messages.DeleteManyAsync(filter, cancellationToken); + } +} diff --git a/backend/src/Modules/Messaging/Infrastructure/Persistence/MessageRepository.cs b/backend/src/Modules/Messaging/Infrastructure/Persistence/MessageRepository.cs index 91bdcdc..3a40eea 100644 --- a/backend/src/Modules/Messaging/Infrastructure/Persistence/MessageRepository.cs +++ b/backend/src/Modules/Messaging/Infrastructure/Persistence/MessageRepository.cs @@ -153,6 +153,12 @@ public sealed class MessageRepository : IMessageRepository var filter = Builders.Filter.Eq(m => m.Id, id); await _messages.DeleteOneAsync(filter, cancellationToken); } + + public async Task DeleteAsync(Message message, CancellationToken cancellationToken) + { + var filter = Builders.Filter.Eq(m => m.Id, message.Id); + await _messages.DeleteOneAsync(filter, cancellationToken); + } } diff --git a/backend/src/Modules/Messaging/Infrastructure/Persistence/Mongo/UserStatsService.cs b/backend/src/Modules/Messaging/Infrastructure/Persistence/Mongo/UserStatsService.cs index 1a311e3..332dc6e 100644 --- a/backend/src/Modules/Messaging/Infrastructure/Persistence/Mongo/UserStatsService.cs +++ b/backend/src/Modules/Messaging/Infrastructure/Persistence/Mongo/UserStatsService.cs @@ -3,14 +3,16 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Knot.Contracts.Messaging.Application.Abstractions; +using Knot.Contracts.Messaging.Abstractions; using Knot.Contracts.Messaging.Domain; +using Knot.Modules.Messaging.Application.Abstractions; using MongoDB.Bson; using MongoDB.Driver; +using ModuleUserStatsService = Knot.Modules.Messaging.Application.Abstractions.IUserStatsService; namespace Knot.Modules.Messaging.Infrastructure.Persistence.Mongo; -public sealed class UserStatsService : IUserStatsService +public sealed class UserStatsService : ModuleUserStatsService { private readonly IMongoCollection _messages; @@ -19,13 +21,13 @@ public sealed class UserStatsService : IUserStatsService _messages = database.GetCollection("messages"); } - public async Task> GetStatsForUsersAsync(IEnumerable userIds, CancellationToken ct = default) + public async Task> GetStatsForUsersAsync(IEnumerable userIds, CancellationToken ct = default) { - var userGuidList = userIds.ToList(); - if (!userGuidList.Any()) return new Dictionary(); + var userIdList = userIds.ToList(); + if (!userIdList.Any()) return new Dictionary(); var stats = await _messages.Aggregate() - .Match(Builders.Filter.In(m => m.SenderId, userGuidList)) + .Match(Builders.Filter.In(m => m.SenderId, userIdList)) .Group(new BsonDocument { { "_id", "$SenderId" }, { "Count", new BsonDocument("$sum", 1) }, @@ -35,7 +37,7 @@ public sealed class UserStatsService : IUserStatsService return stats.ToDictionary( doc => doc["_id"].AsGuid, - doc => new UserStats( + doc => new Knot.Modules.Messaging.Application.Abstractions.UserStats( doc["Count"].AsInt32, doc.Contains("MediaSize") && !doc["MediaSize"].IsBsonNull ? (long)(doc["MediaSize"].IsInt64 ? doc["MediaSize"].AsInt64 : doc["MediaSize"].AsInt32) : 0L ) diff --git a/backend/src/Modules/Messaging/Knot.Modules.Messaging.csproj b/backend/src/Modules/Messaging/Knot.Modules.Messaging.csproj index 28a6606..eaa5799 100644 --- a/backend/src/Modules/Messaging/Knot.Modules.Messaging.csproj +++ b/backend/src/Modules/Messaging/Knot.Modules.Messaging.csproj @@ -3,13 +3,13 @@ + - + - diff --git a/backend/src/Modules/Relations/Application/Abstractions/FriendshipRepository.cs b/backend/src/Modules/Relations/Application/Abstractions/FriendshipRepository.cs new file mode 100644 index 0000000..c28d180 --- /dev/null +++ b/backend/src/Modules/Relations/Application/Abstractions/FriendshipRepository.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Knot.Contracts.Relations.Application.Abstractions; +using Knot.Contracts.Relations.Domain; +using Knot.Modules.Relations.Domain; +using Knot.Modules.Relations.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; + +namespace Knot.Modules.Relations.Application.Abstractions; + +internal sealed class FriendshipRepository : IFriendshipRepository +{ + private readonly RelationsDbContext _context; + + public FriendshipRepository(RelationsDbContext context) + { + _context = context; + } + + public async Task> GetAcceptedFriendshipsAsync(Guid userId, CancellationToken ct = default) + { + var friendships = await _context.Set() + .Where(f => f.Status == Knot.Contracts.Relations.Domain.FriendshipStatus.Accepted && (f.UserId == userId || f.FriendId == userId)) + .ToListAsync(ct); + + return friendships.Select(f => new Knot.Contracts.Relations.Domain.Friendship( + f.Id, + f.UserId, + f.FriendId, + f.Status, + f.CreatedAt + )).ToList(); + } +} diff --git a/backend/src/Modules/Relations/DependencyInjection.cs b/backend/src/Modules/Relations/DependencyInjection.cs index 6c1b3e3..915efbd 100644 --- a/backend/src/Modules/Relations/DependencyInjection.cs +++ b/backend/src/Modules/Relations/DependencyInjection.cs @@ -1,7 +1,9 @@ +using Knot.Contracts.Relations.Application.Abstractions; +using Knot.Modules.Relations.Application.Abstractions; +using Knot.Modules.Relations.Infrastructure.Persistence; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Knot.Modules.Relations.Infrastructure.Persistence; namespace Knot.Modules.Relations; @@ -13,6 +15,8 @@ public static class DependencyInjection services.AddDbContext(options => options.UseNpgsql(connectionString)); + services.AddScoped(); + services.AddMediatR(config => config.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly)); diff --git a/backend/src/Modules/Relations/Domain/Friendship.cs b/backend/src/Modules/Relations/Domain/Friendship.cs index b053d30..52531a8 100644 --- a/backend/src/Modules/Relations/Domain/Friendship.cs +++ b/backend/src/Modules/Relations/Domain/Friendship.cs @@ -1,14 +1,8 @@ +using Knot.Contracts.Relations.Domain; using Knot.Shared.Kernel; namespace Knot.Modules.Relations.Domain; -public enum FriendshipStatus -{ - Pending, - Accepted, - Declined -} - public sealed class Friendship : Entity { public Guid UserId { get; private set; } diff --git a/backend/src/Modules/Relations/Knot.Modules.Relations.csproj b/backend/src/Modules/Relations/Knot.Modules.Relations.csproj index 67e2845..4bbafee 100644 --- a/backend/src/Modules/Relations/Knot.Modules.Relations.csproj +++ b/backend/src/Modules/Relations/Knot.Modules.Relations.csproj @@ -7,6 +7,7 @@ + @@ -21,7 +22,6 @@ - diff --git a/backend/src/Modules/Storage/Knot.Modules.Storage.csproj b/backend/src/Modules/Storage/Knot.Modules.Storage.csproj index a00fe29..bd98b1f 100644 --- a/backend/src/Modules/Storage/Knot.Modules.Storage.csproj +++ b/backend/src/Modules/Storage/Knot.Modules.Storage.csproj @@ -17,7 +17,7 @@ - + diff --git a/backend/src/Modules/Stories/Application/Abstractions/IStoriesDbContext.cs b/backend/src/Modules/Stories/Application/Abstractions/IStoriesDbContext.cs deleted file mode 100644 index 777b5f5..0000000 --- a/backend/src/Modules/Stories/Application/Abstractions/IStoriesDbContext.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using Knot.Modules.Stories.Domain; -using Knot.Modules.Relations.Domain; - -namespace Knot.Modules.Stories.Application.Abstractions; - -public interface IStoriesDbContext -{ - DbSet Stories { get; } - DbSet Friendships { get; } - DbSet StoryViewers { get; } - DbSet Set() where TEntity : class; - Task SaveChangesAsync(CancellationToken cancellationToken); -} - -public interface IStoriesUnitOfWork -{ - Task SaveChangesAsync(CancellationToken cancellationToken = default); -} diff --git a/backend/src/Modules/Stories/Application/Abstractions/IStoryNotificationService.cs b/backend/src/Modules/Stories/Application/Abstractions/IStoryNotificationService.cs new file mode 100644 index 0000000..a89b4d3 --- /dev/null +++ b/backend/src/Modules/Stories/Application/Abstractions/IStoryNotificationService.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Knot.Modules.Stories.Application.Abstractions; + +public interface IStoryNotificationService +{ + Task NotifyStoryViewedAsync( + Guid storyId, + Guid userId, + string? username, + string? displayName, + string? avatar, + int newViewsCount, + Guid storyOwnerId, + CancellationToken ct = default); +} diff --git a/backend/src/Modules/Stories/Application/Abstractions/IUserRepository.cs b/backend/src/Modules/Stories/Application/Abstractions/IUserRepository.cs index 382f251..98cc4a8 100644 --- a/backend/src/Modules/Stories/Application/Abstractions/IUserRepository.cs +++ b/backend/src/Modules/Stories/Application/Abstractions/IUserRepository.cs @@ -1,12 +1,11 @@ using System; using System.Threading; using System.Threading.Tasks; -using Knot.Modules.Stories.Domain; namespace Knot.Modules.Stories.Application.Abstractions; public interface IUserRepository { Task ExistsAsync(Guid id, CancellationToken cancellationToken = default); - Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); + Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); } diff --git a/backend/src/Modules/Stories/Application/Stories/Commands/ViewStory/ViewStoryCommand.cs b/backend/src/Modules/Stories/Application/Stories/Commands/ViewStory/ViewStoryCommand.cs index 347737e..fe84a28 100644 --- a/backend/src/Modules/Stories/Application/Stories/Commands/ViewStory/ViewStoryCommand.cs +++ b/backend/src/Modules/Stories/Application/Stories/Commands/ViewStory/ViewStoryCommand.cs @@ -1,21 +1,15 @@ -using Knot.Modules.Stories.Application.Stories; using System; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediatR; -using Knot.Shared.Kernel; -using Knot.Modules.Stories.Domain; - -using Microsoft.EntityFrameworkCore; -using Microsoft.AspNetCore.SignalR; -using Knot.Modules.Conversations.Infrastructure.SignalR; - - +using Knot.Modules.Stories.Application.Abstractions; using Knot.Modules.Stories.Application.DTOs; +using Knot.Modules.Stories.Application.Stories; +using Knot.Modules.Stories.Domain; using Knot.Modules.Stories.Infrastructure.Database; - - +using Knot.Shared.Kernel; +using MediatR; +using Microsoft.EntityFrameworkCore; namespace Knot.Modules.Stories.Application.Stories.Commands.ViewStory; @@ -26,18 +20,18 @@ internal sealed class ViewStoryCommandHandler : ICommandHandler _hubContext; + private readonly IStoryNotificationService _notificationService; public ViewStoryCommandHandler( - StoriesDbContext context, + StoriesDbContext context, Knot.Modules.Stories.Application.Abstractions.IStoryRepository storyRepository, - IUserRepository userRepository, - IHubContext hubContext) + IUserRepository userRepository, + IStoryNotificationService notificationService) { _context = context; _storyRepository = storyRepository; _userRepository = userRepository; - _hubContext = hubContext; + _notificationService = notificationService; } public async Task> Handle(ViewStoryCommand request, CancellationToken cancellationToken) @@ -60,22 +54,20 @@ internal sealed class ViewStoryCommandHandler : ICommandHandler>> Handle(GetStoriesQuery request, CancellationToken cancellationToken) { - var friendships = await _context.Set() - .Where(f => f.Status == FriendshipStatus.Accepted && (f.UserId == request.UserId || f.FriendId == request.UserId)) - .ToListAsync(cancellationToken); - + var friendships = await _friendshipRepository.GetAcceptedFriendshipsAsync(request.UserId, cancellationToken); + var friendIds = friendships.Select(f => f.UserId == request.UserId ? f.FriendId : f.UserId).ToList(); friendIds.Add(request.UserId); @@ -89,14 +89,14 @@ internal sealed class GetStoriesQueryHandler : IQueryHandler + return Result.Success(result.OrderBy(r => + { + if (r.User.Id == request.UserId) { - if (r.User.Id == request.UserId) - { - return 0; - } - return 1; - }).ToList()); + return 0; + } + return 1; + }).ToList()); } } diff --git a/backend/src/Modules/Stories/Application/Stories/Queries/GetUserStories/GetUserStoriesQuery.cs b/backend/src/Modules/Stories/Application/Stories/Queries/GetUserStories/GetUserStoriesQuery.cs index 60346b7..0125200 100644 --- a/backend/src/Modules/Stories/Application/Stories/Queries/GetUserStories/GetUserStoriesQuery.cs +++ b/backend/src/Modules/Stories/Application/Stories/Queries/GetUserStories/GetUserStoriesQuery.cs @@ -2,15 +2,12 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; -using MediatR; -using Knot.Shared.Kernel; using Knot.Modules.Stories.Application.Abstractions; -using Knot.Modules.Stories.Infrastructure.Database; -using Microsoft.EntityFrameworkCore; - - - using Knot.Modules.Stories.Application.DTOs; +using Knot.Modules.Stories.Infrastructure.Database; +using Knot.Shared.Kernel; +using MediatR; +using Microsoft.EntityFrameworkCore; @@ -69,7 +66,7 @@ internal sealed class GetUserStoriesQueryHandler : IQueryHandler> GetFederatedStories(Knot.Modules.Relations.Domain.UserReplica user, Guid currentUserId, CancellationToken ct) + private async Task> GetFederatedStories(Knot.Contracts.Relations.Domain.UserReplica user, Guid currentUserId, CancellationToken ct) { // В будущем: Реальный вызов через HttpClient к удаленному домену // return await _federationGateway.FetchStoriesAsync(user.Domain, user.Id); diff --git a/backend/src/Modules/Stories/GlobalUsings.cs b/backend/src/Modules/Stories/GlobalUsings.cs index 0d33ed6..f196cf0 100644 --- a/backend/src/Modules/Stories/GlobalUsings.cs +++ b/backend/src/Modules/Stories/GlobalUsings.cs @@ -1,3 +1,2 @@ global using Knot.Modules.Stories.Application.Abstractions; global using Knot.Modules.Stories.Domain; -global using Knot.Modules.Relations.Domain; diff --git a/backend/src/Modules/Stories/Infrastructure/Persistence/Mongo/StoryQueryService.cs b/backend/src/Modules/Stories/Infrastructure/Persistence/Mongo/StoryQueryService.cs new file mode 100644 index 0000000..df1cd99 --- /dev/null +++ b/backend/src/Modules/Stories/Infrastructure/Persistence/Mongo/StoryQueryService.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Knot.Contracts.Stories.Abstractions; +using Knot.Modules.Stories.Domain; +using MongoDB.Driver; + +namespace Knot.Modules.Stories.Infrastructure.Persistence.Mongo; + +public sealed class StoryQueryService : IStoryQueryService +{ + private readonly IMongoCollection _stories; + + public StoryQueryService(IMongoDatabase database) + { + _stories = database.GetCollection("stories"); + } + + public async Task> GetAllStoriesAsync(CancellationToken cancellationToken) + { + var stories = await _stories.Find(_ => true).ToListAsync(cancellationToken); + return stories.Select(s => new StoryInfo(s.Id, s.MediaUrl)).ToList(); + } +} diff --git a/backend/src/Modules/Stories/Infrastructure/StoryNotificationService.cs b/backend/src/Modules/Stories/Infrastructure/StoryNotificationService.cs new file mode 100644 index 0000000..843cd12 --- /dev/null +++ b/backend/src/Modules/Stories/Infrastructure/StoryNotificationService.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace Knot.Modules.Stories.Application.Abstractions; + +internal sealed class StoryNotificationService : IStoryNotificationService +{ + private readonly ILogger _logger; + + public StoryNotificationService(ILogger logger) + { + _logger = logger; + } + + public Task NotifyStoryViewedAsync( + Guid storyId, + Guid userId, + string? username, + string? displayName, + string? avatar, + int newViewsCount, + Guid storyOwnerId, + CancellationToken ct = default) + { + // TODO: Реализовать отправку через SignalR + // Пока заглушка - в будущем использовать IHubContext + _logger.LogInformation( + "Story {StoryId} viewed by user {UserId}. New views count: {ViewsCount}", + storyId, userId, newViewsCount); + + return Task.CompletedTask; + } +} diff --git a/backend/src/Modules/Stories/Knot.Modules.Stories.csproj b/backend/src/Modules/Stories/Knot.Modules.Stories.csproj index 2f94e79..2851337 100644 --- a/backend/src/Modules/Stories/Knot.Modules.Stories.csproj +++ b/backend/src/Modules/Stories/Knot.Modules.Stories.csproj @@ -7,10 +7,11 @@ + + + + - - - @@ -24,7 +25,6 @@ - diff --git a/backend/src/Modules/TelegramImport/Application/TelegramImport/AnalyzeImportCommand.cs b/backend/src/Modules/TelegramImport/Application/TelegramImport/AnalyzeImportCommand.cs index 7c510f2..da971b1 100644 --- a/backend/src/Modules/TelegramImport/Application/TelegramImport/AnalyzeImportCommand.cs +++ b/backend/src/Modules/TelegramImport/Application/TelegramImport/AnalyzeImportCommand.cs @@ -1,5 +1,3 @@ -using Knot.Modules.Conversations.Domain; -using Knot.Modules.Messaging.Domain; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -8,11 +6,11 @@ using System.IO.Compression; using System.Linq; using System.Threading; using System.Threading.Tasks; -using AngleSharp.Html.Parser; using AngleSharp.Dom; -using Knot.Shared.Kernel; +using AngleSharp.Html.Parser; using Knot.Contracts.Settings.Application.Abstractions; using Knot.Contracts.Settings.Application.DTOs; +using Knot.Shared.Kernel; using MediatR; namespace Knot.Modules.TelegramImport.Application.TelegramImport; @@ -25,8 +23,8 @@ public static class TelegramImportState public record ImportConflictDto(string Type, string Message, bool Blocked); public record AnalyzeImportResponseDto( - Guid Token, - List Names, + Guid Token, + List Names, List Conflicts); public record AnalyzeImportCommand(Stream FileStream, string FileName) : ICommand; @@ -43,12 +41,12 @@ internal sealed class AnalyzeImportCommandHandler : ICommandHandler(ChatErrors.FileEmpty); + return Result.Failure(TelegramImportErrors.FileEmpty); } if (!request.FileName.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) { - return Result.Failure(ChatErrors.FileInvalidExtension); + return Result.Failure(TelegramImportErrors.FileInvalidExtension); } var token = Guid.NewGuid(); @@ -104,12 +102,12 @@ internal sealed class AnalyzeImportCommandHandler : ICommandHandler(); - if (!settings.Messages.AllowMedia) + if (!settings.Messages.AllowMedia) conflicts.Add(new ImportConflictDto("Media", "Медиафайлы (фото/видео) отключены на сервере. Они не будут импортированы.", true)); - + if (!settings.Messages.AllowVoiceMessages) conflicts.Add(new ImportConflictDto("Voice", "Голосовые сообщения запрещены администратором. Будут пропущены.", true)); - + if (!settings.Messages.AllowPolls) conflicts.Add(new ImportConflictDto("Polls", "Опросы не поддерживаются текущими настройками сервера.", true)); diff --git a/backend/src/Modules/TelegramImport/Application/TelegramImport/TelegramImportErrors.cs b/backend/src/Modules/TelegramImport/Application/TelegramImport/TelegramImportErrors.cs new file mode 100644 index 0000000..2153d7a --- /dev/null +++ b/backend/src/Modules/TelegramImport/Application/TelegramImport/TelegramImportErrors.cs @@ -0,0 +1,7 @@ +namespace Knot.Modules.TelegramImport.Application.TelegramImport; + +public static class TelegramImportErrors +{ + public static readonly Knot.Shared.Kernel.Error FileEmpty = new("File.Empty", "No file uploaded"); + public static readonly Knot.Shared.Kernel.Error FileInvalidExtension = new("File.InvalidExtension", "Only .zip files are supported"); +} diff --git a/backend/src/Modules/TelegramImport/Infrastructure/Background/TelegramImportWorker.cs b/backend/src/Modules/TelegramImport/Infrastructure/Background/TelegramImportWorker.cs index 1e8d87d..58d8eb3 100644 --- a/backend/src/Modules/TelegramImport/Infrastructure/Background/TelegramImportWorker.cs +++ b/backend/src/Modules/TelegramImport/Infrastructure/Background/TelegramImportWorker.cs @@ -4,17 +4,17 @@ using System.IO.Compression; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Knot.Contracts.Conversations.Application.Abstractions; +using Knot.Contracts.Conversations.Domain; +using Knot.Contracts.Messaging.Application.Abstractions; +using Knot.Contracts.Messaging.Domain; +using Knot.Modules.TelegramImport.Application.Abstractions; +using Knot.Modules.TelegramImport.Application.TelegramImport; +using Knot.Modules.TelegramImport.Infrastructure.Background; +using Knot.Shared.Kernel.Storage; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Knot.Modules.TelegramImport.Application.Abstractions; -using Knot.Modules.TelegramImport.Application.TelegramImport; -using Knot.Modules.Messaging.Application.Abstractions; -using Knot.Modules.Conversations.Application.Abstractions; -using Knot.Modules.Messaging.Domain; -using Knot.Modules.Conversations.Domain; -using Knot.Shared.Kernel.Storage; -using Knot.Modules.TelegramImport.Infrastructure.Background; namespace Knot.Modules.TelegramImport.Infrastructure.Background; @@ -34,7 +34,7 @@ public class TelegramImportWorker : BackgroundService protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("Telegram Import Worker started."); - + // В реальном проекте здесь будет чтение из Channels или RabbitMQ // Для примера оставим заглушку цикла while (!stoppingToken.IsCancellationRequested) @@ -55,33 +55,33 @@ public class TelegramImportWorker : BackgroundService var jobInfo = new ImportJobInfo { JobId = jobId, Status = ImportJobStatus.Processing }; _jobStore.AddOrUpdate(jobInfo); - try + try { - using var archive = ZipFile.OpenRead(zipPath); - var entries = archive.Entries.Where(e => e.Name.StartsWith("messages") && e.Name.EndsWith(".html")).ToList(); + using var archive = ZipFile.OpenRead(zipPath); + var entries = archive.Entries.Where(e => e.Name.StartsWith("messages") && e.Name.EndsWith(".html")).ToList(); - // 1. Создание чата (уже было в оригинале, но здесь в фоне) - Guid targetChatId = Guid.NewGuid(); // Упростим логику для демонстрации рефакторинга + // 1. Создание чата (уже было в оригинале, но здесь в фоне) + Guid targetChatId = Guid.NewGuid(); // Упростим логику для демонстрации рефакторинга - foreach (var entry in entries) - { - using var stream = entry.Open(); - var messages = await parser.ParseMessagesAsync(stream, "", ct); - - foreach (var m in messages) - { - Guid senderGuid = request.Mapping.TryGetValue(m.SenderName ?? "", out var sid) ? sid : request.CurrentUserId; - - var textMsg = new TextMessage(Guid.NewGuid(), targetChatId, senderGuid, m.Content, null, null, null, m.CreatedAt, true); - msgRepo.Add(textMsg); - - jobInfo.ProcessedMessages++; - _jobStore.AddOrUpdate(jobInfo); - } - await uow.SaveChangesAsync(ct); - } - - jobInfo.Status = ImportJobStatus.Completed; + foreach (var entry in entries) + { + using var stream = entry.Open(); + var messages = await parser.ParseMessagesAsync(stream, "", ct); + + foreach (var m in messages) + { + Guid senderGuid = request.Mapping.TryGetValue(m.SenderName ?? "", out var sid) ? sid : request.CurrentUserId; + + var textMsg = new TextMessage(Guid.NewGuid(), targetChatId, senderGuid, m.Content, null, null, null, m.CreatedAt, true); + msgRepo.Add(textMsg); + + jobInfo.ProcessedMessages++; + _jobStore.AddOrUpdate(jobInfo); + } + await uow.SaveChangesAsync(ct); + } + + jobInfo.Status = ImportJobStatus.Completed; } catch (Exception ex) { diff --git a/backend/src/Modules/TelegramImport/Knot.Modules.TelegramImport.csproj b/backend/src/Modules/TelegramImport/Knot.Modules.TelegramImport.csproj index ea627ed..5ee3c84 100644 --- a/backend/src/Modules/TelegramImport/Knot.Modules.TelegramImport.csproj +++ b/backend/src/Modules/TelegramImport/Knot.Modules.TelegramImport.csproj @@ -2,9 +2,14 @@ - - - + + + + + + + + diff --git a/backend/src/Modules/WebRtc/Knot.Modules.WebRtc.csproj b/backend/src/Modules/WebRtc/Knot.Modules.WebRtc.csproj index 279cb24..bff30b3 100644 --- a/backend/src/Modules/WebRtc/Knot.Modules.WebRtc.csproj +++ b/backend/src/Modules/WebRtc/Knot.Modules.WebRtc.csproj @@ -7,14 +7,11 @@ - - - + -