From 11f2b232a3b20dc7dba763f244f2bded244ce7d5 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: Fri, 27 Mar 2026 15:45:34 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=B1=D0=BE=D1=80=D0=BA=D0=B0=20=D0=B1?= =?UTF-8?q?=D1=8D=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/Knot.sln | 550 +++++++++++++----- ...Knot.Modules.Admin.IntegrationTests.csproj | 26 + .../Knot.Modules.Admin.UnitTests.csproj | 8 +- .../Knot.Modules.Settings.UnitTests.csproj | 6 +- .../ResetUserPasswordCommandHandlerTests.cs | 2 +- ...Knot.Modules.Auth.IntegrationTests.csproj} | 5 +- .../Knot.Modules.Auth.UnitTests.csproj | 5 +- .../LoginUserCommandHandlerTests.cs | 0 .../Chats/CreateChatCommandHandlerTests.cs | 0 .../Chats/GetChatsQueryHandlerTests.cs | 0 ...GetOrCreateFavoritesCommandHandlerTests.cs | 0 ...not.Modules.Conversations.UnitTests.csproj | 8 +- .../SendMessageCommandHandlerTests.cs | 9 +- ...Modules.Federation.IntegrationTests.csproj | 23 + .../Knot.Modules.Federation.UnitTests.csproj | 8 +- .../Knot.Modules.Klipy.UnitTests.csproj | 8 +- .../CreatePersonalChatCommandHandlerTests.cs | 42 -- ....Modules.Messaging.IntegrationTests.csproj | 23 + .../Knot.Modules.Messaging.UnitTests.csproj | 4 +- ....Modules.Relations.IntegrationTests.csproj | 23 + .../Knot.Modules.Relations.UnitTests.csproj | 8 +- .../BaseIntegrationTest.cs | 88 +++ .../Knot.IntegrationTests.Shared.csproj | 38 ++ .../Knot.Modules.Storage.UnitTests.csproj | 8 +- ...ot.Modules.Stories.IntegrationTests.csproj | 26 + .../Stories/IntegrationTests/StoriesTests.cs | 34 ++ .../CreateStoryCommandHandlerTests.cs | 76 +++ .../DeleteStoryCommandHandlerTests.cs | 76 +++ .../Knot.Modules.Stories.UnitTests.csproj | 8 +- ...ot.Modules.TelegramImport.UnitTests.csproj | 8 +- .../Knot.Modules.Profiles.UnitTests.csproj | 30 + .../UpdateProfileCommandHandlerTests.cs | 13 +- .../Knot.Modules.WebRtc.UnitTests.csproj | 8 +- .../src/Docs/profiles_module_documentation.md | 105 ++++ .../src/Docs/settings_module_documentation.md | 129 ++++ backend/src/Host/Host.csproj | 2 + backend/src/Host/Program.cs | 14 +- .../Commands/CleanRun/CleanRunCommand.cs | 4 +- .../ResetUserPasswordCommand.cs | 4 +- .../TestKlipy/TestKlipyConnectionCommand.cs | 7 +- .../UpdateSettings/UpdateSettingsCommand.cs | 9 +- .../Queries/CleanDryRun/CleanDryRunQuery.cs | 4 +- .../GetDashboardStatsQuery.cs | 4 +- .../Queries/GetSettings/GetSettingsQuery.cs | 8 +- .../GetUserDetails/GetUserDetailsQuery.cs | 4 +- .../Queries/SearchUsers/SearchUsersQuery.cs | 4 +- .../SystemSettingsUpdatedDomainEvent.cs | 4 +- .../Modules/Admin/Knot.Modules.Admin.csproj | 4 +- .../Presentation/Endpoints/AdminEndpoints.cs | 12 +- .../Register/RegisterUserCommandHandler.cs | 4 +- backend/src/Modules/Auth/Domain/User.cs | 5 +- .../src/Modules/Auth/Knot.Modules.Auth.csproj | 6 +- .../AddToFolder/AddToFolderCommand.cs | 4 +- .../Send/SendMessageCommandHandler.cs | 4 +- .../Messages/UploadFile/UploadFileCommand.cs | 4 +- .../Knot.Modules.Conversations.csproj | 8 +- .../Commands/HandshakeFederationCommand.cs | 5 +- .../Commands/InboundFederationCommand.cs | 4 +- .../Federation/Commands/ResolveUserCommand.cs | 4 +- .../Events/FederatedMessageActionsHandler.cs | 4 +- .../Events/MessageSentDomainEventHandler.cs | 4 +- ...SystemSettingsUpdatedDomainEventHandler.cs | 7 +- .../UserStatusChangedDomainEventHandler.cs | 4 +- .../Services/FederationPacketService.cs | 4 +- .../Services/FederationGateway.cs | 4 +- .../Federation/Knot.Modules.Federation.csproj | 4 +- .../Endpoints/FederationEndpoints.cs | 5 +- .../Klipy/Queries/GetTrendingGifsQuery.cs | 4 +- .../Klipy/Queries/SearchGifsQuery.cs | 4 +- .../Modules/Klipy/Knot.Modules.Klipy.csproj | 4 +- .../Messaging/Knot.Modules.Messaging.csproj | 7 +- .../Abstractions/IAvatarStorageService.cs | 22 + .../Abstractions/IProfileRepository.cs | 10 +- .../Abstractions/IProfilesUnitOfWork.cs | 4 +- .../Application/Profiles/Avatar/CropAvatar.cs | 96 +-- .../Profiles/Avatar/DeleteAvatar.cs | 44 -- .../Profiles/Avatar/UploadAvatar.cs | 93 ++- .../Profiles/DTOs/ProfilesRequests.cs | 6 + .../Profiles/DTOs/UpdateProfileRequest.cs | 7 - .../Profiles/DTOs/UpdateSettingsRequest.cs | 5 - .../Application/Profiles/DTOs/UserDto.cs | 14 - .../Profiles/DTOs/UserProfileDto.cs | 27 +- .../Application/Profiles/GetUser/GetUser.cs | 37 +- .../UserRegisteredDomainEventHandler.cs | 38 ++ .../Profiles/Search/SearchUsers.cs | 36 +- .../Profiles/UpdateProfile/UpdateProfile.cs | 47 +- .../Profiles/UpdateSettings/UpdateSettings.cs | 41 +- .../Profiles/Application/Profiles/UserDto.cs | 14 - .../Application/Profiles/UserProfileDto.cs | 12 - .../Modules/Profiles/DependencyInjection.cs | 31 + .../Domain/Events/ProfileDomainEvents.cs | 14 + .../Profiles/Domain/ProfileDocument.cs | 69 +++ .../Database/AvatarStorageService.cs | 33 ++ .../Database/ProfileRepository.cs | 48 ++ .../Database/ProfilesUnitOfWork.cs | 12 + .../Profiles/Knot.Modules.Profiles.csproj | 14 +- .../Abstractions/IRelationsDbContext.cs | 1 - .../Application/Contacts/ContactDto.cs | 41 ++ .../Application/Contacts/GetContacts.cs | 14 +- .../Application/Contacts/RemoveContact.cs | 3 - .../Modules/Relations/DependencyInjection.cs | 21 + .../Relations/Knot.Modules.Relations.csproj | 7 +- ...iendsEndpoints.cs => ContactsEndpoints.cs} | 5 +- backend/src/Modules/Relations/rel_err.json | 69 +++ .../Abstractions}/ISettingsService.cs | 3 +- .../Commands/UpdateSettingsCommand.cs | 44 ++ .../DTOs/PublicConfigDto.cs | 10 +- .../Settings/DTOs}/SystemSettingsDto.cs | 32 +- .../Queries/GetPublicConfigQuery.cs | 17 +- .../Settings/Queries/GetSettingsQuery.cs | 26 + .../Modules/Settings/DependencyInjection.cs | 25 +- .../SystemSettingsUpdatedDomainEvent.cs | 11 + .../Configuration/SettingsService.cs | 22 +- .../Settings/Knot.Modules.Settings.csproj | 9 +- .../Endpoints/SettingsEndpoints.cs | 25 - .../Storage/Knot.Modules.Storage.csproj | 3 + .../Presentation/Endpoints/FilesEndpoints.cs | 4 +- .../CreateStory/CreateStoryCommand.cs | 6 +- .../DeleteStory/DeleteStoryCommand.cs | 17 +- .../Commands/ViewStory/ViewStoryCommand.cs | 6 +- .../Queries/GetStories/GetStoriesQuery.cs | 3 +- .../GetStoryViewers/GetStoryViewersQuery.cs | 6 +- .../GetUserStories/GetUserStoriesQuery.cs | 3 +- backend/src/Modules/Stories/AssemblyInfo.cs | 5 + .../Stories/Knot.Modules.Stories.csproj | 8 +- .../Endpoints/StoriesEndpoints.cs | 16 +- .../TelegramImport/AnalyzeImportCommand.cs | 4 +- .../TelegramImport/DependencyInjection.cs | 11 + .../Knot.Modules.TelegramImport.csproj | 4 +- .../WebRtc/Queries/GetIceServersQuery.cs | 5 +- .../Modules/WebRtc/Knot.Modules.WebRtc.csproj | 4 +- .../DependencyInjection.cs | 13 - .../Knot.Shared.Infrastructure.csproj | 2 + .../Events/IdentityEvents.cs | 15 + .../Knot.Shared.Kernel.csproj | 1 + 135 files changed, 2162 insertions(+), 725 deletions(-) create mode 100644 backend/Tests/Admin/IntegrationTests/Knot.Modules.Admin.IntegrationTests.csproj rename backend/Tests/{Knot.Modules.Admin.UnitTests => Admin/UnitTests}/Knot.Modules.Admin.UnitTests.csproj (69%) rename backend/Tests/{Knot.Modules.Settings.UnitTests => Admin/UnitTests}/Knot.Modules.Settings.UnitTests.csproj (76%) rename backend/Tests/{Knot.Modules.Admin.UnitTests => Admin/UnitTests}/ResetUserPasswordCommandHandlerTests.cs (97%) rename backend/Tests/{Knot.Modules.Profiles.UnitTests/Knot.Modules.Profiles.UnitTests.csproj => Auth/IntegrationTests/Knot.Modules.Auth.IntegrationTests.csproj} (81%) rename backend/Tests/{Knot.Modules.Auth.UnitTests => Auth/UnitTests}/Knot.Modules.Auth.UnitTests.csproj (85%) rename backend/Tests/{Knot.Modules.Auth.UnitTests => Auth/UnitTests}/LoginUserCommandHandlerTests.cs (100%) rename backend/Tests/{Knot.Modules.Conversations.UnitTests => Conversations/UnitTests}/Chats/CreateChatCommandHandlerTests.cs (100%) rename backend/Tests/{Knot.Modules.Conversations.UnitTests => Conversations/UnitTests}/Chats/GetChatsQueryHandlerTests.cs (100%) rename backend/Tests/{Knot.Modules.Conversations.UnitTests => Conversations/UnitTests}/GetOrCreateFavoritesCommandHandlerTests.cs (100%) rename backend/Tests/{Knot.Modules.Conversations.UnitTests => Conversations/UnitTests}/Knot.Modules.Conversations.UnitTests.csproj (72%) rename backend/Tests/{Knot.Modules.Conversations.UnitTests => Conversations/UnitTests}/Messages/SendMessageCommandHandlerTests.cs (87%) create mode 100644 backend/Tests/Federation/IntegrationTests/Knot.Modules.Federation.IntegrationTests.csproj rename backend/Tests/{Knot.Modules.Federation.UnitTests => Federation/UnitTests}/Knot.Modules.Federation.UnitTests.csproj (69%) rename backend/Tests/{Knot.Modules.Klipy.UnitTests => Klipy/UnitTests}/Knot.Modules.Klipy.UnitTests.csproj (69%) delete mode 100644 backend/Tests/Knot.Modules.Conversations.UnitTests/CreatePersonalChatCommandHandlerTests.cs create mode 100644 backend/Tests/Messaging/IntegrationTests/Knot.Modules.Messaging.IntegrationTests.csproj rename backend/Tests/{Knot.Modules.Messaging.UnitTests => Messaging/UnitTests}/Knot.Modules.Messaging.UnitTests.csproj (83%) create mode 100644 backend/Tests/Relations/IntegrationTests/Knot.Modules.Relations.IntegrationTests.csproj rename backend/Tests/{Knot.Modules.Relations.UnitTests => Relations/UnitTests}/Knot.Modules.Relations.UnitTests.csproj (69%) create mode 100644 backend/Tests/Shared/IntegrationTests.Shared/BaseIntegrationTest.cs create mode 100644 backend/Tests/Shared/IntegrationTests.Shared/Knot.IntegrationTests.Shared.csproj rename backend/Tests/{Knot.Modules.Storage.UnitTests => Storage/UnitTests}/Knot.Modules.Storage.UnitTests.csproj (69%) create mode 100644 backend/Tests/Stories/IntegrationTests/Knot.Modules.Stories.IntegrationTests.csproj create mode 100644 backend/Tests/Stories/IntegrationTests/StoriesTests.cs create mode 100644 backend/Tests/Stories/UnitTests/CreateStoryCommandHandlerTests.cs create mode 100644 backend/Tests/Stories/UnitTests/DeleteStoryCommandHandlerTests.cs rename backend/Tests/{Knot.Modules.Stories.UnitTests => Stories/UnitTests}/Knot.Modules.Stories.UnitTests.csproj (69%) rename backend/Tests/{Knot.Modules.TelegramImport.UnitTests => TelegramImport/UnitTests}/Knot.Modules.TelegramImport.UnitTests.csproj (68%) create mode 100644 backend/Tests/Users/UnitTests/Knot.Modules.Profiles.UnitTests.csproj rename backend/Tests/{Knot.Modules.Profiles.UnitTests => Users/UnitTests}/UpdateProfileCommandHandlerTests.cs (74%) rename backend/Tests/{Knot.Modules.WebRtc.UnitTests => WebRtc/UnitTests}/Knot.Modules.WebRtc.UnitTests.csproj (69%) create mode 100644 backend/src/Docs/profiles_module_documentation.md create mode 100644 backend/src/Docs/settings_module_documentation.md create mode 100644 backend/src/Modules/Profiles/Application/Abstractions/IAvatarStorageService.cs delete mode 100644 backend/src/Modules/Profiles/Application/Profiles/Avatar/DeleteAvatar.cs create mode 100644 backend/src/Modules/Profiles/Application/Profiles/DTOs/ProfilesRequests.cs delete mode 100644 backend/src/Modules/Profiles/Application/Profiles/DTOs/UpdateProfileRequest.cs delete mode 100644 backend/src/Modules/Profiles/Application/Profiles/DTOs/UpdateSettingsRequest.cs delete mode 100644 backend/src/Modules/Profiles/Application/Profiles/DTOs/UserDto.cs create mode 100644 backend/src/Modules/Profiles/Application/Profiles/Integration/UserRegisteredDomainEventHandler.cs delete mode 100644 backend/src/Modules/Profiles/Application/Profiles/UserDto.cs delete mode 100644 backend/src/Modules/Profiles/Application/Profiles/UserProfileDto.cs create mode 100644 backend/src/Modules/Profiles/DependencyInjection.cs create mode 100644 backend/src/Modules/Profiles/Domain/Events/ProfileDomainEvents.cs create mode 100644 backend/src/Modules/Profiles/Domain/ProfileDocument.cs create mode 100644 backend/src/Modules/Profiles/Infrastructure/Database/AvatarStorageService.cs create mode 100644 backend/src/Modules/Profiles/Infrastructure/Database/ProfileRepository.cs create mode 100644 backend/src/Modules/Profiles/Infrastructure/Database/ProfilesUnitOfWork.cs create mode 100644 backend/src/Modules/Relations/Application/Contacts/ContactDto.cs create mode 100644 backend/src/Modules/Relations/DependencyInjection.cs rename backend/src/Modules/Relations/Presentation/Endpoints/{FriendsEndpoints.cs => ContactsEndpoints.cs} (95%) create mode 100644 backend/src/Modules/Relations/rel_err.json rename backend/src/{Shared/Knot.Shared.Kernel/Configuration => Modules/Settings/Application/Settings/Abstractions}/ISettingsService.cs (88%) create mode 100644 backend/src/Modules/Settings/Application/Settings/Commands/UpdateSettingsCommand.cs rename backend/src/Modules/Settings/Application/{Config => Settings}/DTOs/PublicConfigDto.cs (96%) rename backend/src/{Shared/Knot.Shared.Kernel/Configuration => Modules/Settings/Application/Settings/DTOs}/SystemSettingsDto.cs (82%) rename backend/src/Modules/Settings/Application/{Config => Settings}/Queries/GetPublicConfigQuery.cs (66%) create mode 100644 backend/src/Modules/Settings/Application/Settings/Queries/GetSettingsQuery.cs create mode 100644 backend/src/Modules/Settings/Domain/Events/SystemSettingsUpdatedDomainEvent.cs rename backend/src/{Shared/Knot.Shared.Infrastructure => Modules/Settings/Infrastructure}/Configuration/SettingsService.cs (88%) delete mode 100644 backend/src/Modules/Settings/Presentation/Endpoints/SettingsEndpoints.cs create mode 100644 backend/src/Modules/Stories/AssemblyInfo.cs create mode 100644 backend/src/Modules/TelegramImport/DependencyInjection.cs create mode 100644 backend/src/Shared/Knot.Shared.Kernel/Events/IdentityEvents.cs diff --git a/backend/Knot.sln b/backend/Knot.sln index 9597f75..60ebd47 100644 --- a/backend/Knot.sln +++ b/backend/Knot.sln @@ -13,8 +13,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Shared.Infrastructure" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Profiles", "Profiles", "{CA5186C2-289A-9324-4152-9805A73A9773}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Profiles", "src\Modules\Profiles\Knot.Modules.Profiles.csproj", "{CD14770B-8BCD-4D93-AD2C-581ED030E209}" @@ -35,8 +33,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Auth", "Auth", "{E9D55F35-C EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Auth", "src\Modules\Auth\Knot.Modules.Auth.csproj", "{CF08AE9C-B337-47BA-B273-4B8E5610D7BD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Auth.UnitTests", "tests\Knot.Modules.Auth.UnitTests\Knot.Modules.Auth.UnitTests.csproj", "{BA37DF67-98D3-4399-BD68-9679396C1547}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Messaging", "Messaging", "{AE120A0E-5815-2D46-946C-DA544028AD88}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Messaging", "src\Modules\Messaging\Knot.Modules.Messaging.csproj", "{C0B5496E-F083-413E-90AE-31282A0ACCA5}" @@ -49,44 +45,132 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Conversations", "Conversati EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Conversations", "src\Modules\Conversations\Knot.Modules.Conversations.csproj", "{F3EFD119-44F7-46BF-A03C-060F30A1A1E0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Admin.UnitTests", "Tests\Knot.Modules.Admin.UnitTests\Knot.Modules.Admin.UnitTests.csproj", "{489D5878-3945-4F8E-AD13-E5499A18DFCC}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Admin", "Admin", "{E9E1155A-A339-766C-C185-6698FF423EBC}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Admin", "src\Modules\Admin\Knot.Modules.Admin.csproj", "{FFC12440-F128-48F5-9BE5-E5263930F876}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Federation.UnitTests", "Tests\Knot.Modules.Federation.UnitTests\Knot.Modules.Federation.UnitTests.csproj", "{7A402C99-2E66-4EBE-A257-0D7923A96DFD}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Federation", "Federation", "{837D2CF4-7AEA-BF76-6362-47ECD7058EEC}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Federation", "src\Modules\Federation\Knot.Modules.Federation.csproj", "{716E089D-0661-4D39-813B-063DF9A4C0E4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Klipy.UnitTests", "Tests\Knot.Modules.Klipy.UnitTests\Knot.Modules.Klipy.UnitTests.csproj", "{43E6E19C-39EC-49C8-91EE-49586A4B9D03}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Klipy", "Klipy", "{4BC86D3F-8D7B-ACB9-EA51-9374BC9D421A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Klipy", "src\Modules\Klipy\Knot.Modules.Klipy.csproj", "{2498C58C-53B0-4DF2-B4EA-BA69B35A7488}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Stories.UnitTests", "Tests\Knot.Modules.Stories.UnitTests\Knot.Modules.Stories.UnitTests.csproj", "{4394178B-1ADD-478E-BF73-574B31C11292}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Relations.UnitTests", "Tests\Knot.Modules.Relations.UnitTests\Knot.Modules.Relations.UnitTests.csproj", "{2F2C7E01-5791-4A6D-9A80-C71CAD846FA8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Settings.UnitTests", "Tests\Knot.Modules.Settings.UnitTests\Knot.Modules.Settings.UnitTests.csproj", "{76706BA9-11E0-463A-AF8E-18DFBD9E95A2}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Settings", "Settings", "{0587C5BB-3015-204D-19B6-EB01E03E48B6}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Settings", "src\Modules\Settings\Knot.Modules.Settings.csproj", "{AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Storage.UnitTests", "Tests\Knot.Modules.Storage.UnitTests\Knot.Modules.Storage.UnitTests.csproj", "{C11BFE93-017D-4538-A61F-A8D98F8D2D75}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.TelegramImport.UnitTests", "Tests\Knot.Modules.TelegramImport.UnitTests\Knot.Modules.TelegramImport.UnitTests.csproj", "{38276FFF-05D8-4005-8863-018CEE833D66}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.WebRtc.UnitTests", "Tests\Knot.Modules.WebRtc.UnitTests\Knot.Modules.WebRtc.UnitTests.csproj", "{32AC7906-B197-4628-9C47-9337BB5A53E2}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebRtc", "WebRtc", "{DD6FAF9E-10A0-95E7-0C06-698A6F70AFEE}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.WebRtc", "src\Modules\WebRtc\Knot.Modules.WebRtc.csproj", "{03BE4EB1-FD38-445A-9A4B-7B481560E9A2}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{D0D6B8B0-AC35-B3FA-0B96-A9B0E4A3253E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IntegrationTests.Shared", "IntegrationTests.Shared", "{21C6533F-5B99-F63D-D6A0-0D1E74554637}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.IntegrationTests.Shared", "Tests\Shared\IntegrationTests.Shared\Knot.IntegrationTests.Shared.csproj", "{2BDD861B-44BC-463B-88C4-E8E2B516A7EE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Host", "src\Host\Host.csproj", "{98ABB710-9106-4E5C-AA04-D19D5E92EDC1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Stories", "Stories", "{407BCCB7-2AB0-5429-FB5F-A9376121CF06}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{E7E14295-D412-4B43-44D5-0AAD5D27712E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Stories.UnitTests", "Tests\Stories\UnitTests\Knot.Modules.Stories.UnitTests.csproj", "{8865807E-C577-4476-BE33-0EBB4E07CD8B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IntegrationTests", "IntegrationTests", "{8CFDC985-5E1F-E566-CA4D-28D1B5AD1B06}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Stories.IntegrationTests", "Tests\Stories\IntegrationTests\Knot.Modules.Stories.IntegrationTests.csproj", "{028AF0DA-6D64-435F-B1CA-C14829892C4F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Admin", "Admin", "{EDEF9ADB-D556-6B20-2E4B-8F977F5C545A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{AA22B6BC-789B-946B-F9BD-06D6D86B3211}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Admin.UnitTests", "Tests\Admin\UnitTests\Knot.Modules.Admin.UnitTests.csproj", "{9DB38617-9738-45C3-A101-8B04869CB07F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IntegrationTests", "IntegrationTests", "{1A80A494-6197-707A-BB12-E5CAFEB6DBC3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Admin.IntegrationTests", "Tests\Admin\IntegrationTests\Knot.Modules.Admin.IntegrationTests.csproj", "{ABC9A8FF-0332-4987-A2E8-63108394DA26}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Auth", "Auth", "{EDFE54F1-08A0-5241-003B-94DECB51237C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{50896B77-11EA-ADED-5FA2-195E6FABE81B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Auth.UnitTests", "Tests\Auth\UnitTests\Knot.Modules.Auth.UnitTests.csproj", "{DD4F5E02-4366-4CA3-8CF1-941C21C27B6E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IntegrationTests", "IntegrationTests", "{0BF9C624-18C3-4CFE-0DEF-3D1D0C280507}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Auth.IntegrationTests", "Tests\Auth\IntegrationTests\Knot.Modules.Auth.IntegrationTests.csproj", "{776C6198-A229-4C2C-B19E-3F2744BD5279}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Federation", "Federation", "{036B8169-510F-3971-AD3F-66559BF086AA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{92885E6D-C52A-2FB4-A33D-9A8FE23A87BF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Federation.UnitTests", "Tests\Federation\UnitTests\Knot.Modules.Federation.UnitTests.csproj", "{EA780C77-735F-4FA7-8AE9-78413C928BA6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IntegrationTests", "IntegrationTests", "{3794ADC3-BA35-663C-885F-C144081FBEA1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Federation.IntegrationTests", "Tests\Federation\IntegrationTests\Knot.Modules.Federation.IntegrationTests.csproj", "{60326D0F-D9C7-4B8D-B664-BC8DE239AD31}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Messaging", "Messaging", "{3AB41C38-05DB-BFCC-9BA0-9BC97041A508}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{43109C86-4551-F94F-E9C3-994B9C735653}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Messaging.UnitTests", "Tests\Messaging\UnitTests\Knot.Modules.Messaging.UnitTests.csproj", "{635D6C93-D9E2-46C2-9294-5C0713CBA245}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IntegrationTests", "IntegrationTests", "{FE27F79D-3631-2D96-056A-69F8E94CD8E3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Messaging.IntegrationTests", "Tests\Messaging\IntegrationTests\Knot.Modules.Messaging.IntegrationTests.csproj", "{0C37AA6D-37DB-439B-97D5-9E799DDDC831}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Relations", "Relations", "{F29503A5-E7F1-B611-4026-76E83B89B0AB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{8D009ABD-C820-B7D4-2412-0990C1960013}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Relations.UnitTests", "Tests\Relations\UnitTests\Knot.Modules.Relations.UnitTests.csproj", "{89593AC0-B761-400D-8213-F6E728980F59}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IntegrationTests", "IntegrationTests", "{7C677DC5-D0E9-6A09-3DB0-042075A9E677}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Relations.IntegrationTests", "Tests\Relations\IntegrationTests\Knot.Modules.Relations.IntegrationTests.csproj", "{312F4C56-7865-47FD-8170-E6555DBC4884}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Conversations", "Conversations", "{382D38B3-4D74-95AB-947E-DC7B53CBCF32}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{489DA9C1-7DF6-4B43-A5A6-01AD261B3AC8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Conversations.UnitTests", "Tests\Conversations\UnitTests\Knot.Modules.Conversations.UnitTests.csproj", "{5B428B36-6857-47BA-8DE1-A8207A2DF1E8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TelegramImport", "TelegramImport", "{A3D04C92-6F74-537C-D0F4-BD2C8CD7687D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{2BF544BD-4498-3DD9-D97E-96A22189C7D8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.TelegramImport.UnitTests", "Tests\TelegramImport\UnitTests\Knot.Modules.TelegramImport.UnitTests.csproj", "{568561C3-C4D9-4783-8103-B68C94E2F8D1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebRtc", "WebRtc", "{CCF0649B-70C0-BDF5-F1BD-87A1DBED8C5D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{4FFF0ACB-E93F-597A-5968-6C89F4BB5AC7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.WebRtc.UnitTests", "Tests\WebRtc\UnitTests\Knot.Modules.WebRtc.UnitTests.csproj", "{65A65ABB-1F74-471C-84C5-E9CBA6F27B74}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Klipy", "Klipy", "{46A5ACB5-176C-983D-5434-922CD2C33C4A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{43930E3C-6ABA-C329-E65B-CFA08664DBAF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Klipy.UnitTests", "Tests\Klipy\UnitTests\Knot.Modules.Klipy.UnitTests.csproj", "{71B7CB00-B0B9-4FEB-9E9F-E8B47FAD5408}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Storage", "Storage", "{B6FD6E61-6054-1C88-3299-57EC8B98D824}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{93CD11E9-EE4D-82DD-DD63-4D36831D0976}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Storage.UnitTests", "Tests\Storage\UnitTests\Knot.Modules.Storage.UnitTests.csproj", "{418BE048-D472-40C0-917F-060A9E781B82}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Users", "Users", "{553D81D4-666B-BE26-75F9-480CB214ACEE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{3A48F700-9635-2216-D291-85337CE5D869}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Profiles.UnitTests", "Tests\Users\UnitTests\Knot.Modules.Profiles.UnitTests.csproj", "{6BD8DC02-2270-4276-952E-E34E973A1237}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -181,18 +265,6 @@ Global {CF08AE9C-B337-47BA-B273-4B8E5610D7BD}.Release|x64.Build.0 = Release|Any CPU {CF08AE9C-B337-47BA-B273-4B8E5610D7BD}.Release|x86.ActiveCfg = Release|Any CPU {CF08AE9C-B337-47BA-B273-4B8E5610D7BD}.Release|x86.Build.0 = Release|Any CPU - {BA37DF67-98D3-4399-BD68-9679396C1547}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BA37DF67-98D3-4399-BD68-9679396C1547}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BA37DF67-98D3-4399-BD68-9679396C1547}.Debug|x64.ActiveCfg = Debug|Any CPU - {BA37DF67-98D3-4399-BD68-9679396C1547}.Debug|x64.Build.0 = Debug|Any CPU - {BA37DF67-98D3-4399-BD68-9679396C1547}.Debug|x86.ActiveCfg = Debug|Any CPU - {BA37DF67-98D3-4399-BD68-9679396C1547}.Debug|x86.Build.0 = Debug|Any CPU - {BA37DF67-98D3-4399-BD68-9679396C1547}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BA37DF67-98D3-4399-BD68-9679396C1547}.Release|Any CPU.Build.0 = Release|Any CPU - {BA37DF67-98D3-4399-BD68-9679396C1547}.Release|x64.ActiveCfg = Release|Any CPU - {BA37DF67-98D3-4399-BD68-9679396C1547}.Release|x64.Build.0 = Release|Any CPU - {BA37DF67-98D3-4399-BD68-9679396C1547}.Release|x86.ActiveCfg = Release|Any CPU - {BA37DF67-98D3-4399-BD68-9679396C1547}.Release|x86.Build.0 = Release|Any CPU {C0B5496E-F083-413E-90AE-31282A0ACCA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C0B5496E-F083-413E-90AE-31282A0ACCA5}.Debug|Any CPU.Build.0 = Debug|Any CPU {C0B5496E-F083-413E-90AE-31282A0ACCA5}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -229,18 +301,6 @@ Global {F3EFD119-44F7-46BF-A03C-060F30A1A1E0}.Release|x64.Build.0 = Release|Any CPU {F3EFD119-44F7-46BF-A03C-060F30A1A1E0}.Release|x86.ActiveCfg = Release|Any CPU {F3EFD119-44F7-46BF-A03C-060F30A1A1E0}.Release|x86.Build.0 = Release|Any CPU - {489D5878-3945-4F8E-AD13-E5499A18DFCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {489D5878-3945-4F8E-AD13-E5499A18DFCC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {489D5878-3945-4F8E-AD13-E5499A18DFCC}.Debug|x64.ActiveCfg = Debug|Any CPU - {489D5878-3945-4F8E-AD13-E5499A18DFCC}.Debug|x64.Build.0 = Debug|Any CPU - {489D5878-3945-4F8E-AD13-E5499A18DFCC}.Debug|x86.ActiveCfg = Debug|Any CPU - {489D5878-3945-4F8E-AD13-E5499A18DFCC}.Debug|x86.Build.0 = Debug|Any CPU - {489D5878-3945-4F8E-AD13-E5499A18DFCC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {489D5878-3945-4F8E-AD13-E5499A18DFCC}.Release|Any CPU.Build.0 = Release|Any CPU - {489D5878-3945-4F8E-AD13-E5499A18DFCC}.Release|x64.ActiveCfg = Release|Any CPU - {489D5878-3945-4F8E-AD13-E5499A18DFCC}.Release|x64.Build.0 = Release|Any CPU - {489D5878-3945-4F8E-AD13-E5499A18DFCC}.Release|x86.ActiveCfg = Release|Any CPU - {489D5878-3945-4F8E-AD13-E5499A18DFCC}.Release|x86.Build.0 = Release|Any CPU {FFC12440-F128-48F5-9BE5-E5263930F876}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FFC12440-F128-48F5-9BE5-E5263930F876}.Debug|Any CPU.Build.0 = Debug|Any CPU {FFC12440-F128-48F5-9BE5-E5263930F876}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -253,18 +313,6 @@ Global {FFC12440-F128-48F5-9BE5-E5263930F876}.Release|x64.Build.0 = Release|Any CPU {FFC12440-F128-48F5-9BE5-E5263930F876}.Release|x86.ActiveCfg = Release|Any CPU {FFC12440-F128-48F5-9BE5-E5263930F876}.Release|x86.Build.0 = Release|Any CPU - {7A402C99-2E66-4EBE-A257-0D7923A96DFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7A402C99-2E66-4EBE-A257-0D7923A96DFD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7A402C99-2E66-4EBE-A257-0D7923A96DFD}.Debug|x64.ActiveCfg = Debug|Any CPU - {7A402C99-2E66-4EBE-A257-0D7923A96DFD}.Debug|x64.Build.0 = Debug|Any CPU - {7A402C99-2E66-4EBE-A257-0D7923A96DFD}.Debug|x86.ActiveCfg = Debug|Any CPU - {7A402C99-2E66-4EBE-A257-0D7923A96DFD}.Debug|x86.Build.0 = Debug|Any CPU - {7A402C99-2E66-4EBE-A257-0D7923A96DFD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7A402C99-2E66-4EBE-A257-0D7923A96DFD}.Release|Any CPU.Build.0 = Release|Any CPU - {7A402C99-2E66-4EBE-A257-0D7923A96DFD}.Release|x64.ActiveCfg = Release|Any CPU - {7A402C99-2E66-4EBE-A257-0D7923A96DFD}.Release|x64.Build.0 = Release|Any CPU - {7A402C99-2E66-4EBE-A257-0D7923A96DFD}.Release|x86.ActiveCfg = Release|Any CPU - {7A402C99-2E66-4EBE-A257-0D7923A96DFD}.Release|x86.Build.0 = Release|Any CPU {716E089D-0661-4D39-813B-063DF9A4C0E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {716E089D-0661-4D39-813B-063DF9A4C0E4}.Debug|Any CPU.Build.0 = Debug|Any CPU {716E089D-0661-4D39-813B-063DF9A4C0E4}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -277,18 +325,6 @@ Global {716E089D-0661-4D39-813B-063DF9A4C0E4}.Release|x64.Build.0 = Release|Any CPU {716E089D-0661-4D39-813B-063DF9A4C0E4}.Release|x86.ActiveCfg = Release|Any CPU {716E089D-0661-4D39-813B-063DF9A4C0E4}.Release|x86.Build.0 = Release|Any CPU - {43E6E19C-39EC-49C8-91EE-49586A4B9D03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {43E6E19C-39EC-49C8-91EE-49586A4B9D03}.Debug|Any CPU.Build.0 = Debug|Any CPU - {43E6E19C-39EC-49C8-91EE-49586A4B9D03}.Debug|x64.ActiveCfg = Debug|Any CPU - {43E6E19C-39EC-49C8-91EE-49586A4B9D03}.Debug|x64.Build.0 = Debug|Any CPU - {43E6E19C-39EC-49C8-91EE-49586A4B9D03}.Debug|x86.ActiveCfg = Debug|Any CPU - {43E6E19C-39EC-49C8-91EE-49586A4B9D03}.Debug|x86.Build.0 = Debug|Any CPU - {43E6E19C-39EC-49C8-91EE-49586A4B9D03}.Release|Any CPU.ActiveCfg = Release|Any CPU - {43E6E19C-39EC-49C8-91EE-49586A4B9D03}.Release|Any CPU.Build.0 = Release|Any CPU - {43E6E19C-39EC-49C8-91EE-49586A4B9D03}.Release|x64.ActiveCfg = Release|Any CPU - {43E6E19C-39EC-49C8-91EE-49586A4B9D03}.Release|x64.Build.0 = Release|Any CPU - {43E6E19C-39EC-49C8-91EE-49586A4B9D03}.Release|x86.ActiveCfg = Release|Any CPU - {43E6E19C-39EC-49C8-91EE-49586A4B9D03}.Release|x86.Build.0 = Release|Any CPU {2498C58C-53B0-4DF2-B4EA-BA69B35A7488}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2498C58C-53B0-4DF2-B4EA-BA69B35A7488}.Debug|Any CPU.Build.0 = Debug|Any CPU {2498C58C-53B0-4DF2-B4EA-BA69B35A7488}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -301,42 +337,6 @@ Global {2498C58C-53B0-4DF2-B4EA-BA69B35A7488}.Release|x64.Build.0 = Release|Any CPU {2498C58C-53B0-4DF2-B4EA-BA69B35A7488}.Release|x86.ActiveCfg = Release|Any CPU {2498C58C-53B0-4DF2-B4EA-BA69B35A7488}.Release|x86.Build.0 = Release|Any CPU - {4394178B-1ADD-478E-BF73-574B31C11292}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4394178B-1ADD-478E-BF73-574B31C11292}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4394178B-1ADD-478E-BF73-574B31C11292}.Debug|x64.ActiveCfg = Debug|Any CPU - {4394178B-1ADD-478E-BF73-574B31C11292}.Debug|x64.Build.0 = Debug|Any CPU - {4394178B-1ADD-478E-BF73-574B31C11292}.Debug|x86.ActiveCfg = Debug|Any CPU - {4394178B-1ADD-478E-BF73-574B31C11292}.Debug|x86.Build.0 = Debug|Any CPU - {4394178B-1ADD-478E-BF73-574B31C11292}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4394178B-1ADD-478E-BF73-574B31C11292}.Release|Any CPU.Build.0 = Release|Any CPU - {4394178B-1ADD-478E-BF73-574B31C11292}.Release|x64.ActiveCfg = Release|Any CPU - {4394178B-1ADD-478E-BF73-574B31C11292}.Release|x64.Build.0 = Release|Any CPU - {4394178B-1ADD-478E-BF73-574B31C11292}.Release|x86.ActiveCfg = Release|Any CPU - {4394178B-1ADD-478E-BF73-574B31C11292}.Release|x86.Build.0 = Release|Any CPU - {2F2C7E01-5791-4A6D-9A80-C71CAD846FA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2F2C7E01-5791-4A6D-9A80-C71CAD846FA8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2F2C7E01-5791-4A6D-9A80-C71CAD846FA8}.Debug|x64.ActiveCfg = Debug|Any CPU - {2F2C7E01-5791-4A6D-9A80-C71CAD846FA8}.Debug|x64.Build.0 = Debug|Any CPU - {2F2C7E01-5791-4A6D-9A80-C71CAD846FA8}.Debug|x86.ActiveCfg = Debug|Any CPU - {2F2C7E01-5791-4A6D-9A80-C71CAD846FA8}.Debug|x86.Build.0 = Debug|Any CPU - {2F2C7E01-5791-4A6D-9A80-C71CAD846FA8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2F2C7E01-5791-4A6D-9A80-C71CAD846FA8}.Release|Any CPU.Build.0 = Release|Any CPU - {2F2C7E01-5791-4A6D-9A80-C71CAD846FA8}.Release|x64.ActiveCfg = Release|Any CPU - {2F2C7E01-5791-4A6D-9A80-C71CAD846FA8}.Release|x64.Build.0 = Release|Any CPU - {2F2C7E01-5791-4A6D-9A80-C71CAD846FA8}.Release|x86.ActiveCfg = Release|Any CPU - {2F2C7E01-5791-4A6D-9A80-C71CAD846FA8}.Release|x86.Build.0 = Release|Any CPU - {76706BA9-11E0-463A-AF8E-18DFBD9E95A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {76706BA9-11E0-463A-AF8E-18DFBD9E95A2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {76706BA9-11E0-463A-AF8E-18DFBD9E95A2}.Debug|x64.ActiveCfg = Debug|Any CPU - {76706BA9-11E0-463A-AF8E-18DFBD9E95A2}.Debug|x64.Build.0 = Debug|Any CPU - {76706BA9-11E0-463A-AF8E-18DFBD9E95A2}.Debug|x86.ActiveCfg = Debug|Any CPU - {76706BA9-11E0-463A-AF8E-18DFBD9E95A2}.Debug|x86.Build.0 = Debug|Any CPU - {76706BA9-11E0-463A-AF8E-18DFBD9E95A2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {76706BA9-11E0-463A-AF8E-18DFBD9E95A2}.Release|Any CPU.Build.0 = Release|Any CPU - {76706BA9-11E0-463A-AF8E-18DFBD9E95A2}.Release|x64.ActiveCfg = Release|Any CPU - {76706BA9-11E0-463A-AF8E-18DFBD9E95A2}.Release|x64.Build.0 = Release|Any CPU - {76706BA9-11E0-463A-AF8E-18DFBD9E95A2}.Release|x86.ActiveCfg = Release|Any CPU - {76706BA9-11E0-463A-AF8E-18DFBD9E95A2}.Release|x86.Build.0 = Release|Any CPU {AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}.Debug|Any CPU.Build.0 = Debug|Any CPU {AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -349,42 +349,6 @@ Global {AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}.Release|x64.Build.0 = Release|Any CPU {AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}.Release|x86.ActiveCfg = Release|Any CPU {AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}.Release|x86.Build.0 = Release|Any CPU - {C11BFE93-017D-4538-A61F-A8D98F8D2D75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C11BFE93-017D-4538-A61F-A8D98F8D2D75}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C11BFE93-017D-4538-A61F-A8D98F8D2D75}.Debug|x64.ActiveCfg = Debug|Any CPU - {C11BFE93-017D-4538-A61F-A8D98F8D2D75}.Debug|x64.Build.0 = Debug|Any CPU - {C11BFE93-017D-4538-A61F-A8D98F8D2D75}.Debug|x86.ActiveCfg = Debug|Any CPU - {C11BFE93-017D-4538-A61F-A8D98F8D2D75}.Debug|x86.Build.0 = Debug|Any CPU - {C11BFE93-017D-4538-A61F-A8D98F8D2D75}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C11BFE93-017D-4538-A61F-A8D98F8D2D75}.Release|Any CPU.Build.0 = Release|Any CPU - {C11BFE93-017D-4538-A61F-A8D98F8D2D75}.Release|x64.ActiveCfg = Release|Any CPU - {C11BFE93-017D-4538-A61F-A8D98F8D2D75}.Release|x64.Build.0 = Release|Any CPU - {C11BFE93-017D-4538-A61F-A8D98F8D2D75}.Release|x86.ActiveCfg = Release|Any CPU - {C11BFE93-017D-4538-A61F-A8D98F8D2D75}.Release|x86.Build.0 = Release|Any CPU - {38276FFF-05D8-4005-8863-018CEE833D66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {38276FFF-05D8-4005-8863-018CEE833D66}.Debug|Any CPU.Build.0 = Debug|Any CPU - {38276FFF-05D8-4005-8863-018CEE833D66}.Debug|x64.ActiveCfg = Debug|Any CPU - {38276FFF-05D8-4005-8863-018CEE833D66}.Debug|x64.Build.0 = Debug|Any CPU - {38276FFF-05D8-4005-8863-018CEE833D66}.Debug|x86.ActiveCfg = Debug|Any CPU - {38276FFF-05D8-4005-8863-018CEE833D66}.Debug|x86.Build.0 = Debug|Any CPU - {38276FFF-05D8-4005-8863-018CEE833D66}.Release|Any CPU.ActiveCfg = Release|Any CPU - {38276FFF-05D8-4005-8863-018CEE833D66}.Release|Any CPU.Build.0 = Release|Any CPU - {38276FFF-05D8-4005-8863-018CEE833D66}.Release|x64.ActiveCfg = Release|Any CPU - {38276FFF-05D8-4005-8863-018CEE833D66}.Release|x64.Build.0 = Release|Any CPU - {38276FFF-05D8-4005-8863-018CEE833D66}.Release|x86.ActiveCfg = Release|Any CPU - {38276FFF-05D8-4005-8863-018CEE833D66}.Release|x86.Build.0 = Release|Any CPU - {32AC7906-B197-4628-9C47-9337BB5A53E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {32AC7906-B197-4628-9C47-9337BB5A53E2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {32AC7906-B197-4628-9C47-9337BB5A53E2}.Debug|x64.ActiveCfg = Debug|Any CPU - {32AC7906-B197-4628-9C47-9337BB5A53E2}.Debug|x64.Build.0 = Debug|Any CPU - {32AC7906-B197-4628-9C47-9337BB5A53E2}.Debug|x86.ActiveCfg = Debug|Any CPU - {32AC7906-B197-4628-9C47-9337BB5A53E2}.Debug|x86.Build.0 = Debug|Any CPU - {32AC7906-B197-4628-9C47-9337BB5A53E2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {32AC7906-B197-4628-9C47-9337BB5A53E2}.Release|Any CPU.Build.0 = Release|Any CPU - {32AC7906-B197-4628-9C47-9337BB5A53E2}.Release|x64.ActiveCfg = Release|Any CPU - {32AC7906-B197-4628-9C47-9337BB5A53E2}.Release|x64.Build.0 = Release|Any CPU - {32AC7906-B197-4628-9C47-9337BB5A53E2}.Release|x86.ActiveCfg = Release|Any CPU - {32AC7906-B197-4628-9C47-9337BB5A53E2}.Release|x86.Build.0 = Release|Any CPU {03BE4EB1-FD38-445A-9A4B-7B481560E9A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {03BE4EB1-FD38-445A-9A4B-7B481560E9A2}.Debug|Any CPU.Build.0 = Debug|Any CPU {03BE4EB1-FD38-445A-9A4B-7B481560E9A2}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -397,6 +361,246 @@ Global {03BE4EB1-FD38-445A-9A4B-7B481560E9A2}.Release|x64.Build.0 = Release|Any CPU {03BE4EB1-FD38-445A-9A4B-7B481560E9A2}.Release|x86.ActiveCfg = Release|Any CPU {03BE4EB1-FD38-445A-9A4B-7B481560E9A2}.Release|x86.Build.0 = Release|Any CPU + {2BDD861B-44BC-463B-88C4-E8E2B516A7EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2BDD861B-44BC-463B-88C4-E8E2B516A7EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2BDD861B-44BC-463B-88C4-E8E2B516A7EE}.Debug|x64.ActiveCfg = Debug|Any CPU + {2BDD861B-44BC-463B-88C4-E8E2B516A7EE}.Debug|x64.Build.0 = Debug|Any CPU + {2BDD861B-44BC-463B-88C4-E8E2B516A7EE}.Debug|x86.ActiveCfg = Debug|Any CPU + {2BDD861B-44BC-463B-88C4-E8E2B516A7EE}.Debug|x86.Build.0 = Debug|Any CPU + {2BDD861B-44BC-463B-88C4-E8E2B516A7EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2BDD861B-44BC-463B-88C4-E8E2B516A7EE}.Release|Any CPU.Build.0 = Release|Any CPU + {2BDD861B-44BC-463B-88C4-E8E2B516A7EE}.Release|x64.ActiveCfg = Release|Any CPU + {2BDD861B-44BC-463B-88C4-E8E2B516A7EE}.Release|x64.Build.0 = Release|Any CPU + {2BDD861B-44BC-463B-88C4-E8E2B516A7EE}.Release|x86.ActiveCfg = Release|Any CPU + {2BDD861B-44BC-463B-88C4-E8E2B516A7EE}.Release|x86.Build.0 = Release|Any CPU + {98ABB710-9106-4E5C-AA04-D19D5E92EDC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98ABB710-9106-4E5C-AA04-D19D5E92EDC1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98ABB710-9106-4E5C-AA04-D19D5E92EDC1}.Debug|x64.ActiveCfg = Debug|Any CPU + {98ABB710-9106-4E5C-AA04-D19D5E92EDC1}.Debug|x64.Build.0 = Debug|Any CPU + {98ABB710-9106-4E5C-AA04-D19D5E92EDC1}.Debug|x86.ActiveCfg = Debug|Any CPU + {98ABB710-9106-4E5C-AA04-D19D5E92EDC1}.Debug|x86.Build.0 = Debug|Any CPU + {98ABB710-9106-4E5C-AA04-D19D5E92EDC1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {98ABB710-9106-4E5C-AA04-D19D5E92EDC1}.Release|Any CPU.Build.0 = Release|Any CPU + {98ABB710-9106-4E5C-AA04-D19D5E92EDC1}.Release|x64.ActiveCfg = Release|Any CPU + {98ABB710-9106-4E5C-AA04-D19D5E92EDC1}.Release|x64.Build.0 = Release|Any CPU + {98ABB710-9106-4E5C-AA04-D19D5E92EDC1}.Release|x86.ActiveCfg = Release|Any CPU + {98ABB710-9106-4E5C-AA04-D19D5E92EDC1}.Release|x86.Build.0 = Release|Any CPU + {8865807E-C577-4476-BE33-0EBB4E07CD8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8865807E-C577-4476-BE33-0EBB4E07CD8B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8865807E-C577-4476-BE33-0EBB4E07CD8B}.Debug|x64.ActiveCfg = Debug|Any CPU + {8865807E-C577-4476-BE33-0EBB4E07CD8B}.Debug|x64.Build.0 = Debug|Any CPU + {8865807E-C577-4476-BE33-0EBB4E07CD8B}.Debug|x86.ActiveCfg = Debug|Any CPU + {8865807E-C577-4476-BE33-0EBB4E07CD8B}.Debug|x86.Build.0 = Debug|Any CPU + {8865807E-C577-4476-BE33-0EBB4E07CD8B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8865807E-C577-4476-BE33-0EBB4E07CD8B}.Release|Any CPU.Build.0 = Release|Any CPU + {8865807E-C577-4476-BE33-0EBB4E07CD8B}.Release|x64.ActiveCfg = Release|Any CPU + {8865807E-C577-4476-BE33-0EBB4E07CD8B}.Release|x64.Build.0 = Release|Any CPU + {8865807E-C577-4476-BE33-0EBB4E07CD8B}.Release|x86.ActiveCfg = Release|Any CPU + {8865807E-C577-4476-BE33-0EBB4E07CD8B}.Release|x86.Build.0 = Release|Any CPU + {028AF0DA-6D64-435F-B1CA-C14829892C4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {028AF0DA-6D64-435F-B1CA-C14829892C4F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {028AF0DA-6D64-435F-B1CA-C14829892C4F}.Debug|x64.ActiveCfg = Debug|Any CPU + {028AF0DA-6D64-435F-B1CA-C14829892C4F}.Debug|x64.Build.0 = Debug|Any CPU + {028AF0DA-6D64-435F-B1CA-C14829892C4F}.Debug|x86.ActiveCfg = Debug|Any CPU + {028AF0DA-6D64-435F-B1CA-C14829892C4F}.Debug|x86.Build.0 = Debug|Any CPU + {028AF0DA-6D64-435F-B1CA-C14829892C4F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {028AF0DA-6D64-435F-B1CA-C14829892C4F}.Release|Any CPU.Build.0 = Release|Any CPU + {028AF0DA-6D64-435F-B1CA-C14829892C4F}.Release|x64.ActiveCfg = Release|Any CPU + {028AF0DA-6D64-435F-B1CA-C14829892C4F}.Release|x64.Build.0 = Release|Any CPU + {028AF0DA-6D64-435F-B1CA-C14829892C4F}.Release|x86.ActiveCfg = Release|Any CPU + {028AF0DA-6D64-435F-B1CA-C14829892C4F}.Release|x86.Build.0 = Release|Any CPU + {9DB38617-9738-45C3-A101-8B04869CB07F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9DB38617-9738-45C3-A101-8B04869CB07F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9DB38617-9738-45C3-A101-8B04869CB07F}.Debug|x64.ActiveCfg = Debug|Any CPU + {9DB38617-9738-45C3-A101-8B04869CB07F}.Debug|x64.Build.0 = Debug|Any CPU + {9DB38617-9738-45C3-A101-8B04869CB07F}.Debug|x86.ActiveCfg = Debug|Any CPU + {9DB38617-9738-45C3-A101-8B04869CB07F}.Debug|x86.Build.0 = Debug|Any CPU + {9DB38617-9738-45C3-A101-8B04869CB07F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9DB38617-9738-45C3-A101-8B04869CB07F}.Release|Any CPU.Build.0 = Release|Any CPU + {9DB38617-9738-45C3-A101-8B04869CB07F}.Release|x64.ActiveCfg = Release|Any CPU + {9DB38617-9738-45C3-A101-8B04869CB07F}.Release|x64.Build.0 = Release|Any CPU + {9DB38617-9738-45C3-A101-8B04869CB07F}.Release|x86.ActiveCfg = Release|Any CPU + {9DB38617-9738-45C3-A101-8B04869CB07F}.Release|x86.Build.0 = Release|Any CPU + {ABC9A8FF-0332-4987-A2E8-63108394DA26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABC9A8FF-0332-4987-A2E8-63108394DA26}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABC9A8FF-0332-4987-A2E8-63108394DA26}.Debug|x64.ActiveCfg = Debug|Any CPU + {ABC9A8FF-0332-4987-A2E8-63108394DA26}.Debug|x64.Build.0 = Debug|Any CPU + {ABC9A8FF-0332-4987-A2E8-63108394DA26}.Debug|x86.ActiveCfg = Debug|Any CPU + {ABC9A8FF-0332-4987-A2E8-63108394DA26}.Debug|x86.Build.0 = Debug|Any CPU + {ABC9A8FF-0332-4987-A2E8-63108394DA26}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ABC9A8FF-0332-4987-A2E8-63108394DA26}.Release|Any CPU.Build.0 = Release|Any CPU + {ABC9A8FF-0332-4987-A2E8-63108394DA26}.Release|x64.ActiveCfg = Release|Any CPU + {ABC9A8FF-0332-4987-A2E8-63108394DA26}.Release|x64.Build.0 = Release|Any CPU + {ABC9A8FF-0332-4987-A2E8-63108394DA26}.Release|x86.ActiveCfg = Release|Any CPU + {ABC9A8FF-0332-4987-A2E8-63108394DA26}.Release|x86.Build.0 = Release|Any CPU + {DD4F5E02-4366-4CA3-8CF1-941C21C27B6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD4F5E02-4366-4CA3-8CF1-941C21C27B6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD4F5E02-4366-4CA3-8CF1-941C21C27B6E}.Debug|x64.ActiveCfg = Debug|Any CPU + {DD4F5E02-4366-4CA3-8CF1-941C21C27B6E}.Debug|x64.Build.0 = Debug|Any CPU + {DD4F5E02-4366-4CA3-8CF1-941C21C27B6E}.Debug|x86.ActiveCfg = Debug|Any CPU + {DD4F5E02-4366-4CA3-8CF1-941C21C27B6E}.Debug|x86.Build.0 = Debug|Any CPU + {DD4F5E02-4366-4CA3-8CF1-941C21C27B6E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD4F5E02-4366-4CA3-8CF1-941C21C27B6E}.Release|Any CPU.Build.0 = Release|Any CPU + {DD4F5E02-4366-4CA3-8CF1-941C21C27B6E}.Release|x64.ActiveCfg = Release|Any CPU + {DD4F5E02-4366-4CA3-8CF1-941C21C27B6E}.Release|x64.Build.0 = Release|Any CPU + {DD4F5E02-4366-4CA3-8CF1-941C21C27B6E}.Release|x86.ActiveCfg = Release|Any CPU + {DD4F5E02-4366-4CA3-8CF1-941C21C27B6E}.Release|x86.Build.0 = Release|Any CPU + {776C6198-A229-4C2C-B19E-3F2744BD5279}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {776C6198-A229-4C2C-B19E-3F2744BD5279}.Debug|Any CPU.Build.0 = Debug|Any CPU + {776C6198-A229-4C2C-B19E-3F2744BD5279}.Debug|x64.ActiveCfg = Debug|Any CPU + {776C6198-A229-4C2C-B19E-3F2744BD5279}.Debug|x64.Build.0 = Debug|Any CPU + {776C6198-A229-4C2C-B19E-3F2744BD5279}.Debug|x86.ActiveCfg = Debug|Any CPU + {776C6198-A229-4C2C-B19E-3F2744BD5279}.Debug|x86.Build.0 = Debug|Any CPU + {776C6198-A229-4C2C-B19E-3F2744BD5279}.Release|Any CPU.ActiveCfg = Release|Any CPU + {776C6198-A229-4C2C-B19E-3F2744BD5279}.Release|Any CPU.Build.0 = Release|Any CPU + {776C6198-A229-4C2C-B19E-3F2744BD5279}.Release|x64.ActiveCfg = Release|Any CPU + {776C6198-A229-4C2C-B19E-3F2744BD5279}.Release|x64.Build.0 = Release|Any CPU + {776C6198-A229-4C2C-B19E-3F2744BD5279}.Release|x86.ActiveCfg = Release|Any CPU + {776C6198-A229-4C2C-B19E-3F2744BD5279}.Release|x86.Build.0 = Release|Any CPU + {EA780C77-735F-4FA7-8AE9-78413C928BA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA780C77-735F-4FA7-8AE9-78413C928BA6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA780C77-735F-4FA7-8AE9-78413C928BA6}.Debug|x64.ActiveCfg = Debug|Any CPU + {EA780C77-735F-4FA7-8AE9-78413C928BA6}.Debug|x64.Build.0 = Debug|Any CPU + {EA780C77-735F-4FA7-8AE9-78413C928BA6}.Debug|x86.ActiveCfg = Debug|Any CPU + {EA780C77-735F-4FA7-8AE9-78413C928BA6}.Debug|x86.Build.0 = Debug|Any CPU + {EA780C77-735F-4FA7-8AE9-78413C928BA6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA780C77-735F-4FA7-8AE9-78413C928BA6}.Release|Any CPU.Build.0 = Release|Any CPU + {EA780C77-735F-4FA7-8AE9-78413C928BA6}.Release|x64.ActiveCfg = Release|Any CPU + {EA780C77-735F-4FA7-8AE9-78413C928BA6}.Release|x64.Build.0 = Release|Any CPU + {EA780C77-735F-4FA7-8AE9-78413C928BA6}.Release|x86.ActiveCfg = Release|Any CPU + {EA780C77-735F-4FA7-8AE9-78413C928BA6}.Release|x86.Build.0 = Release|Any CPU + {60326D0F-D9C7-4B8D-B664-BC8DE239AD31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60326D0F-D9C7-4B8D-B664-BC8DE239AD31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60326D0F-D9C7-4B8D-B664-BC8DE239AD31}.Debug|x64.ActiveCfg = Debug|Any CPU + {60326D0F-D9C7-4B8D-B664-BC8DE239AD31}.Debug|x64.Build.0 = Debug|Any CPU + {60326D0F-D9C7-4B8D-B664-BC8DE239AD31}.Debug|x86.ActiveCfg = Debug|Any CPU + {60326D0F-D9C7-4B8D-B664-BC8DE239AD31}.Debug|x86.Build.0 = Debug|Any CPU + {60326D0F-D9C7-4B8D-B664-BC8DE239AD31}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60326D0F-D9C7-4B8D-B664-BC8DE239AD31}.Release|Any CPU.Build.0 = Release|Any CPU + {60326D0F-D9C7-4B8D-B664-BC8DE239AD31}.Release|x64.ActiveCfg = Release|Any CPU + {60326D0F-D9C7-4B8D-B664-BC8DE239AD31}.Release|x64.Build.0 = Release|Any CPU + {60326D0F-D9C7-4B8D-B664-BC8DE239AD31}.Release|x86.ActiveCfg = Release|Any CPU + {60326D0F-D9C7-4B8D-B664-BC8DE239AD31}.Release|x86.Build.0 = Release|Any CPU + {635D6C93-D9E2-46C2-9294-5C0713CBA245}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {635D6C93-D9E2-46C2-9294-5C0713CBA245}.Debug|Any CPU.Build.0 = Debug|Any CPU + {635D6C93-D9E2-46C2-9294-5C0713CBA245}.Debug|x64.ActiveCfg = Debug|Any CPU + {635D6C93-D9E2-46C2-9294-5C0713CBA245}.Debug|x64.Build.0 = Debug|Any CPU + {635D6C93-D9E2-46C2-9294-5C0713CBA245}.Debug|x86.ActiveCfg = Debug|Any CPU + {635D6C93-D9E2-46C2-9294-5C0713CBA245}.Debug|x86.Build.0 = Debug|Any CPU + {635D6C93-D9E2-46C2-9294-5C0713CBA245}.Release|Any CPU.ActiveCfg = Release|Any CPU + {635D6C93-D9E2-46C2-9294-5C0713CBA245}.Release|Any CPU.Build.0 = Release|Any CPU + {635D6C93-D9E2-46C2-9294-5C0713CBA245}.Release|x64.ActiveCfg = Release|Any CPU + {635D6C93-D9E2-46C2-9294-5C0713CBA245}.Release|x64.Build.0 = Release|Any CPU + {635D6C93-D9E2-46C2-9294-5C0713CBA245}.Release|x86.ActiveCfg = Release|Any CPU + {635D6C93-D9E2-46C2-9294-5C0713CBA245}.Release|x86.Build.0 = Release|Any CPU + {0C37AA6D-37DB-439B-97D5-9E799DDDC831}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C37AA6D-37DB-439B-97D5-9E799DDDC831}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C37AA6D-37DB-439B-97D5-9E799DDDC831}.Debug|x64.ActiveCfg = Debug|Any CPU + {0C37AA6D-37DB-439B-97D5-9E799DDDC831}.Debug|x64.Build.0 = Debug|Any CPU + {0C37AA6D-37DB-439B-97D5-9E799DDDC831}.Debug|x86.ActiveCfg = Debug|Any CPU + {0C37AA6D-37DB-439B-97D5-9E799DDDC831}.Debug|x86.Build.0 = Debug|Any CPU + {0C37AA6D-37DB-439B-97D5-9E799DDDC831}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C37AA6D-37DB-439B-97D5-9E799DDDC831}.Release|Any CPU.Build.0 = Release|Any CPU + {0C37AA6D-37DB-439B-97D5-9E799DDDC831}.Release|x64.ActiveCfg = Release|Any CPU + {0C37AA6D-37DB-439B-97D5-9E799DDDC831}.Release|x64.Build.0 = Release|Any CPU + {0C37AA6D-37DB-439B-97D5-9E799DDDC831}.Release|x86.ActiveCfg = Release|Any CPU + {0C37AA6D-37DB-439B-97D5-9E799DDDC831}.Release|x86.Build.0 = Release|Any CPU + {89593AC0-B761-400D-8213-F6E728980F59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89593AC0-B761-400D-8213-F6E728980F59}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89593AC0-B761-400D-8213-F6E728980F59}.Debug|x64.ActiveCfg = Debug|Any CPU + {89593AC0-B761-400D-8213-F6E728980F59}.Debug|x64.Build.0 = Debug|Any CPU + {89593AC0-B761-400D-8213-F6E728980F59}.Debug|x86.ActiveCfg = Debug|Any CPU + {89593AC0-B761-400D-8213-F6E728980F59}.Debug|x86.Build.0 = Debug|Any CPU + {89593AC0-B761-400D-8213-F6E728980F59}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89593AC0-B761-400D-8213-F6E728980F59}.Release|Any CPU.Build.0 = Release|Any CPU + {89593AC0-B761-400D-8213-F6E728980F59}.Release|x64.ActiveCfg = Release|Any CPU + {89593AC0-B761-400D-8213-F6E728980F59}.Release|x64.Build.0 = Release|Any CPU + {89593AC0-B761-400D-8213-F6E728980F59}.Release|x86.ActiveCfg = Release|Any CPU + {89593AC0-B761-400D-8213-F6E728980F59}.Release|x86.Build.0 = Release|Any CPU + {312F4C56-7865-47FD-8170-E6555DBC4884}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {312F4C56-7865-47FD-8170-E6555DBC4884}.Debug|Any CPU.Build.0 = Debug|Any CPU + {312F4C56-7865-47FD-8170-E6555DBC4884}.Debug|x64.ActiveCfg = Debug|Any CPU + {312F4C56-7865-47FD-8170-E6555DBC4884}.Debug|x64.Build.0 = Debug|Any CPU + {312F4C56-7865-47FD-8170-E6555DBC4884}.Debug|x86.ActiveCfg = Debug|Any CPU + {312F4C56-7865-47FD-8170-E6555DBC4884}.Debug|x86.Build.0 = Debug|Any CPU + {312F4C56-7865-47FD-8170-E6555DBC4884}.Release|Any CPU.ActiveCfg = Release|Any CPU + {312F4C56-7865-47FD-8170-E6555DBC4884}.Release|Any CPU.Build.0 = Release|Any CPU + {312F4C56-7865-47FD-8170-E6555DBC4884}.Release|x64.ActiveCfg = Release|Any CPU + {312F4C56-7865-47FD-8170-E6555DBC4884}.Release|x64.Build.0 = Release|Any CPU + {312F4C56-7865-47FD-8170-E6555DBC4884}.Release|x86.ActiveCfg = Release|Any CPU + {312F4C56-7865-47FD-8170-E6555DBC4884}.Release|x86.Build.0 = Release|Any CPU + {5B428B36-6857-47BA-8DE1-A8207A2DF1E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B428B36-6857-47BA-8DE1-A8207A2DF1E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B428B36-6857-47BA-8DE1-A8207A2DF1E8}.Debug|x64.ActiveCfg = Debug|Any CPU + {5B428B36-6857-47BA-8DE1-A8207A2DF1E8}.Debug|x64.Build.0 = Debug|Any CPU + {5B428B36-6857-47BA-8DE1-A8207A2DF1E8}.Debug|x86.ActiveCfg = Debug|Any CPU + {5B428B36-6857-47BA-8DE1-A8207A2DF1E8}.Debug|x86.Build.0 = Debug|Any CPU + {5B428B36-6857-47BA-8DE1-A8207A2DF1E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B428B36-6857-47BA-8DE1-A8207A2DF1E8}.Release|Any CPU.Build.0 = Release|Any CPU + {5B428B36-6857-47BA-8DE1-A8207A2DF1E8}.Release|x64.ActiveCfg = Release|Any CPU + {5B428B36-6857-47BA-8DE1-A8207A2DF1E8}.Release|x64.Build.0 = Release|Any CPU + {5B428B36-6857-47BA-8DE1-A8207A2DF1E8}.Release|x86.ActiveCfg = Release|Any CPU + {5B428B36-6857-47BA-8DE1-A8207A2DF1E8}.Release|x86.Build.0 = Release|Any CPU + {568561C3-C4D9-4783-8103-B68C94E2F8D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {568561C3-C4D9-4783-8103-B68C94E2F8D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {568561C3-C4D9-4783-8103-B68C94E2F8D1}.Debug|x64.ActiveCfg = Debug|Any CPU + {568561C3-C4D9-4783-8103-B68C94E2F8D1}.Debug|x64.Build.0 = Debug|Any CPU + {568561C3-C4D9-4783-8103-B68C94E2F8D1}.Debug|x86.ActiveCfg = Debug|Any CPU + {568561C3-C4D9-4783-8103-B68C94E2F8D1}.Debug|x86.Build.0 = Debug|Any CPU + {568561C3-C4D9-4783-8103-B68C94E2F8D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {568561C3-C4D9-4783-8103-B68C94E2F8D1}.Release|Any CPU.Build.0 = Release|Any CPU + {568561C3-C4D9-4783-8103-B68C94E2F8D1}.Release|x64.ActiveCfg = Release|Any CPU + {568561C3-C4D9-4783-8103-B68C94E2F8D1}.Release|x64.Build.0 = Release|Any CPU + {568561C3-C4D9-4783-8103-B68C94E2F8D1}.Release|x86.ActiveCfg = Release|Any CPU + {568561C3-C4D9-4783-8103-B68C94E2F8D1}.Release|x86.Build.0 = Release|Any CPU + {65A65ABB-1F74-471C-84C5-E9CBA6F27B74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65A65ABB-1F74-471C-84C5-E9CBA6F27B74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65A65ABB-1F74-471C-84C5-E9CBA6F27B74}.Debug|x64.ActiveCfg = Debug|Any CPU + {65A65ABB-1F74-471C-84C5-E9CBA6F27B74}.Debug|x64.Build.0 = Debug|Any CPU + {65A65ABB-1F74-471C-84C5-E9CBA6F27B74}.Debug|x86.ActiveCfg = Debug|Any CPU + {65A65ABB-1F74-471C-84C5-E9CBA6F27B74}.Debug|x86.Build.0 = Debug|Any CPU + {65A65ABB-1F74-471C-84C5-E9CBA6F27B74}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65A65ABB-1F74-471C-84C5-E9CBA6F27B74}.Release|Any CPU.Build.0 = Release|Any CPU + {65A65ABB-1F74-471C-84C5-E9CBA6F27B74}.Release|x64.ActiveCfg = Release|Any CPU + {65A65ABB-1F74-471C-84C5-E9CBA6F27B74}.Release|x64.Build.0 = Release|Any CPU + {65A65ABB-1F74-471C-84C5-E9CBA6F27B74}.Release|x86.ActiveCfg = Release|Any CPU + {65A65ABB-1F74-471C-84C5-E9CBA6F27B74}.Release|x86.Build.0 = Release|Any CPU + {71B7CB00-B0B9-4FEB-9E9F-E8B47FAD5408}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71B7CB00-B0B9-4FEB-9E9F-E8B47FAD5408}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71B7CB00-B0B9-4FEB-9E9F-E8B47FAD5408}.Debug|x64.ActiveCfg = Debug|Any CPU + {71B7CB00-B0B9-4FEB-9E9F-E8B47FAD5408}.Debug|x64.Build.0 = Debug|Any CPU + {71B7CB00-B0B9-4FEB-9E9F-E8B47FAD5408}.Debug|x86.ActiveCfg = Debug|Any CPU + {71B7CB00-B0B9-4FEB-9E9F-E8B47FAD5408}.Debug|x86.Build.0 = Debug|Any CPU + {71B7CB00-B0B9-4FEB-9E9F-E8B47FAD5408}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71B7CB00-B0B9-4FEB-9E9F-E8B47FAD5408}.Release|Any CPU.Build.0 = Release|Any CPU + {71B7CB00-B0B9-4FEB-9E9F-E8B47FAD5408}.Release|x64.ActiveCfg = Release|Any CPU + {71B7CB00-B0B9-4FEB-9E9F-E8B47FAD5408}.Release|x64.Build.0 = Release|Any CPU + {71B7CB00-B0B9-4FEB-9E9F-E8B47FAD5408}.Release|x86.ActiveCfg = Release|Any CPU + {71B7CB00-B0B9-4FEB-9E9F-E8B47FAD5408}.Release|x86.Build.0 = Release|Any CPU + {418BE048-D472-40C0-917F-060A9E781B82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {418BE048-D472-40C0-917F-060A9E781B82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {418BE048-D472-40C0-917F-060A9E781B82}.Debug|x64.ActiveCfg = Debug|Any CPU + {418BE048-D472-40C0-917F-060A9E781B82}.Debug|x64.Build.0 = Debug|Any CPU + {418BE048-D472-40C0-917F-060A9E781B82}.Debug|x86.ActiveCfg = Debug|Any CPU + {418BE048-D472-40C0-917F-060A9E781B82}.Debug|x86.Build.0 = Debug|Any CPU + {418BE048-D472-40C0-917F-060A9E781B82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {418BE048-D472-40C0-917F-060A9E781B82}.Release|Any CPU.Build.0 = Release|Any CPU + {418BE048-D472-40C0-917F-060A9E781B82}.Release|x64.ActiveCfg = Release|Any CPU + {418BE048-D472-40C0-917F-060A9E781B82}.Release|x64.Build.0 = Release|Any CPU + {418BE048-D472-40C0-917F-060A9E781B82}.Release|x86.ActiveCfg = Release|Any CPU + {418BE048-D472-40C0-917F-060A9E781B82}.Release|x86.Build.0 = Release|Any CPU + {6BD8DC02-2270-4276-952E-E34E973A1237}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6BD8DC02-2270-4276-952E-E34E973A1237}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6BD8DC02-2270-4276-952E-E34E973A1237}.Debug|x64.ActiveCfg = Debug|Any CPU + {6BD8DC02-2270-4276-952E-E34E973A1237}.Debug|x64.Build.0 = Debug|Any CPU + {6BD8DC02-2270-4276-952E-E34E973A1237}.Debug|x86.ActiveCfg = Debug|Any CPU + {6BD8DC02-2270-4276-952E-E34E973A1237}.Debug|x86.Build.0 = Debug|Any CPU + {6BD8DC02-2270-4276-952E-E34E973A1237}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6BD8DC02-2270-4276-952E-E34E973A1237}.Release|Any CPU.Build.0 = Release|Any CPU + {6BD8DC02-2270-4276-952E-E34E973A1237}.Release|x64.ActiveCfg = Release|Any CPU + {6BD8DC02-2270-4276-952E-E34E973A1237}.Release|x64.Build.0 = Release|Any CPU + {6BD8DC02-2270-4276-952E-E34E973A1237}.Release|x86.ActiveCfg = Release|Any CPU + {6BD8DC02-2270-4276-952E-E34E973A1237}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -416,31 +620,73 @@ Global {D052C05A-CDCD-48DF-8F4F-0B10E3719819} = {AF61C489-0B09-54BC-BD16-4353135E9C4A} {E9D55F35-C373-3DD8-6914-3213F2F582A2} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} {CF08AE9C-B337-47BA-B273-4B8E5610D7BD} = {E9D55F35-C373-3DD8-6914-3213F2F582A2} - {BA37DF67-98D3-4399-BD68-9679396C1547} = {0AB3BF05-4346-4AA6-1389-037BE0695223} {AE120A0E-5815-2D46-946C-DA544028AD88} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} {C0B5496E-F083-413E-90AE-31282A0ACCA5} = {AE120A0E-5815-2D46-946C-DA544028AD88} {4D5D7319-366D-1BD8-49C5-A840B487E852} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} {C905C120-DE44-4CBC-8F98-5BE148115487} = {4D5D7319-366D-1BD8-49C5-A840B487E852} {138B168B-2473-B06D-4BD5-1B010DA319A1} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} {F3EFD119-44F7-46BF-A03C-060F30A1A1E0} = {138B168B-2473-B06D-4BD5-1B010DA319A1} - {489D5878-3945-4F8E-AD13-E5499A18DFCC} = {0AB3BF05-4346-4AA6-1389-037BE0695223} {E9E1155A-A339-766C-C185-6698FF423EBC} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} {FFC12440-F128-48F5-9BE5-E5263930F876} = {E9E1155A-A339-766C-C185-6698FF423EBC} - {7A402C99-2E66-4EBE-A257-0D7923A96DFD} = {0AB3BF05-4346-4AA6-1389-037BE0695223} {837D2CF4-7AEA-BF76-6362-47ECD7058EEC} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} {716E089D-0661-4D39-813B-063DF9A4C0E4} = {837D2CF4-7AEA-BF76-6362-47ECD7058EEC} - {43E6E19C-39EC-49C8-91EE-49586A4B9D03} = {0AB3BF05-4346-4AA6-1389-037BE0695223} {4BC86D3F-8D7B-ACB9-EA51-9374BC9D421A} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} {2498C58C-53B0-4DF2-B4EA-BA69B35A7488} = {4BC86D3F-8D7B-ACB9-EA51-9374BC9D421A} - {4394178B-1ADD-478E-BF73-574B31C11292} = {0AB3BF05-4346-4AA6-1389-037BE0695223} - {2F2C7E01-5791-4A6D-9A80-C71CAD846FA8} = {0AB3BF05-4346-4AA6-1389-037BE0695223} - {76706BA9-11E0-463A-AF8E-18DFBD9E95A2} = {0AB3BF05-4346-4AA6-1389-037BE0695223} {0587C5BB-3015-204D-19B6-EB01E03E48B6} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} {AEFD93B4-59B8-4B02-A694-CA15E4EC1C08} = {0587C5BB-3015-204D-19B6-EB01E03E48B6} - {C11BFE93-017D-4538-A61F-A8D98F8D2D75} = {0AB3BF05-4346-4AA6-1389-037BE0695223} - {38276FFF-05D8-4005-8863-018CEE833D66} = {0AB3BF05-4346-4AA6-1389-037BE0695223} - {32AC7906-B197-4628-9C47-9337BB5A53E2} = {0AB3BF05-4346-4AA6-1389-037BE0695223} {DD6FAF9E-10A0-95E7-0C06-698A6F70AFEE} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} {03BE4EB1-FD38-445A-9A4B-7B481560E9A2} = {DD6FAF9E-10A0-95E7-0C06-698A6F70AFEE} + {D0D6B8B0-AC35-B3FA-0B96-A9B0E4A3253E} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {21C6533F-5B99-F63D-D6A0-0D1E74554637} = {D0D6B8B0-AC35-B3FA-0B96-A9B0E4A3253E} + {2BDD861B-44BC-463B-88C4-E8E2B516A7EE} = {21C6533F-5B99-F63D-D6A0-0D1E74554637} + {98ABB710-9106-4E5C-AA04-D19D5E92EDC1} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {407BCCB7-2AB0-5429-FB5F-A9376121CF06} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {E7E14295-D412-4B43-44D5-0AAD5D27712E} = {407BCCB7-2AB0-5429-FB5F-A9376121CF06} + {8865807E-C577-4476-BE33-0EBB4E07CD8B} = {E7E14295-D412-4B43-44D5-0AAD5D27712E} + {8CFDC985-5E1F-E566-CA4D-28D1B5AD1B06} = {407BCCB7-2AB0-5429-FB5F-A9376121CF06} + {028AF0DA-6D64-435F-B1CA-C14829892C4F} = {8CFDC985-5E1F-E566-CA4D-28D1B5AD1B06} + {EDEF9ADB-D556-6B20-2E4B-8F977F5C545A} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {AA22B6BC-789B-946B-F9BD-06D6D86B3211} = {EDEF9ADB-D556-6B20-2E4B-8F977F5C545A} + {9DB38617-9738-45C3-A101-8B04869CB07F} = {AA22B6BC-789B-946B-F9BD-06D6D86B3211} + {1A80A494-6197-707A-BB12-E5CAFEB6DBC3} = {EDEF9ADB-D556-6B20-2E4B-8F977F5C545A} + {ABC9A8FF-0332-4987-A2E8-63108394DA26} = {1A80A494-6197-707A-BB12-E5CAFEB6DBC3} + {EDFE54F1-08A0-5241-003B-94DECB51237C} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {50896B77-11EA-ADED-5FA2-195E6FABE81B} = {EDFE54F1-08A0-5241-003B-94DECB51237C} + {DD4F5E02-4366-4CA3-8CF1-941C21C27B6E} = {50896B77-11EA-ADED-5FA2-195E6FABE81B} + {0BF9C624-18C3-4CFE-0DEF-3D1D0C280507} = {EDFE54F1-08A0-5241-003B-94DECB51237C} + {776C6198-A229-4C2C-B19E-3F2744BD5279} = {0BF9C624-18C3-4CFE-0DEF-3D1D0C280507} + {036B8169-510F-3971-AD3F-66559BF086AA} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {92885E6D-C52A-2FB4-A33D-9A8FE23A87BF} = {036B8169-510F-3971-AD3F-66559BF086AA} + {EA780C77-735F-4FA7-8AE9-78413C928BA6} = {92885E6D-C52A-2FB4-A33D-9A8FE23A87BF} + {3794ADC3-BA35-663C-885F-C144081FBEA1} = {036B8169-510F-3971-AD3F-66559BF086AA} + {60326D0F-D9C7-4B8D-B664-BC8DE239AD31} = {3794ADC3-BA35-663C-885F-C144081FBEA1} + {3AB41C38-05DB-BFCC-9BA0-9BC97041A508} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {43109C86-4551-F94F-E9C3-994B9C735653} = {3AB41C38-05DB-BFCC-9BA0-9BC97041A508} + {635D6C93-D9E2-46C2-9294-5C0713CBA245} = {43109C86-4551-F94F-E9C3-994B9C735653} + {FE27F79D-3631-2D96-056A-69F8E94CD8E3} = {3AB41C38-05DB-BFCC-9BA0-9BC97041A508} + {0C37AA6D-37DB-439B-97D5-9E799DDDC831} = {FE27F79D-3631-2D96-056A-69F8E94CD8E3} + {F29503A5-E7F1-B611-4026-76E83B89B0AB} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {8D009ABD-C820-B7D4-2412-0990C1960013} = {F29503A5-E7F1-B611-4026-76E83B89B0AB} + {89593AC0-B761-400D-8213-F6E728980F59} = {8D009ABD-C820-B7D4-2412-0990C1960013} + {7C677DC5-D0E9-6A09-3DB0-042075A9E677} = {F29503A5-E7F1-B611-4026-76E83B89B0AB} + {312F4C56-7865-47FD-8170-E6555DBC4884} = {7C677DC5-D0E9-6A09-3DB0-042075A9E677} + {382D38B3-4D74-95AB-947E-DC7B53CBCF32} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {489DA9C1-7DF6-4B43-A5A6-01AD261B3AC8} = {382D38B3-4D74-95AB-947E-DC7B53CBCF32} + {5B428B36-6857-47BA-8DE1-A8207A2DF1E8} = {489DA9C1-7DF6-4B43-A5A6-01AD261B3AC8} + {A3D04C92-6F74-537C-D0F4-BD2C8CD7687D} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {2BF544BD-4498-3DD9-D97E-96A22189C7D8} = {A3D04C92-6F74-537C-D0F4-BD2C8CD7687D} + {568561C3-C4D9-4783-8103-B68C94E2F8D1} = {2BF544BD-4498-3DD9-D97E-96A22189C7D8} + {CCF0649B-70C0-BDF5-F1BD-87A1DBED8C5D} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {4FFF0ACB-E93F-597A-5968-6C89F4BB5AC7} = {CCF0649B-70C0-BDF5-F1BD-87A1DBED8C5D} + {65A65ABB-1F74-471C-84C5-E9CBA6F27B74} = {4FFF0ACB-E93F-597A-5968-6C89F4BB5AC7} + {46A5ACB5-176C-983D-5434-922CD2C33C4A} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {43930E3C-6ABA-C329-E65B-CFA08664DBAF} = {46A5ACB5-176C-983D-5434-922CD2C33C4A} + {71B7CB00-B0B9-4FEB-9E9F-E8B47FAD5408} = {43930E3C-6ABA-C329-E65B-CFA08664DBAF} + {B6FD6E61-6054-1C88-3299-57EC8B98D824} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {93CD11E9-EE4D-82DD-DD63-4D36831D0976} = {B6FD6E61-6054-1C88-3299-57EC8B98D824} + {418BE048-D472-40C0-917F-060A9E781B82} = {93CD11E9-EE4D-82DD-DD63-4D36831D0976} + {553D81D4-666B-BE26-75F9-480CB214ACEE} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {3A48F700-9635-2216-D291-85337CE5D869} = {553D81D4-666B-BE26-75F9-480CB214ACEE} + {6BD8DC02-2270-4276-952E-E34E973A1237} = {3A48F700-9635-2216-D291-85337CE5D869} EndGlobalSection EndGlobal diff --git a/backend/Tests/Admin/IntegrationTests/Knot.Modules.Admin.IntegrationTests.csproj b/backend/Tests/Admin/IntegrationTests/Knot.Modules.Admin.IntegrationTests.csproj new file mode 100644 index 0000000..b28cb9d --- /dev/null +++ b/backend/Tests/Admin/IntegrationTests/Knot.Modules.Admin.IntegrationTests.csproj @@ -0,0 +1,26 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + diff --git a/backend/Tests/Knot.Modules.Admin.UnitTests/Knot.Modules.Admin.UnitTests.csproj b/backend/Tests/Admin/UnitTests/Knot.Modules.Admin.UnitTests.csproj similarity index 69% rename from backend/Tests/Knot.Modules.Admin.UnitTests/Knot.Modules.Admin.UnitTests.csproj rename to backend/Tests/Admin/UnitTests/Knot.Modules.Admin.UnitTests.csproj index d15a597..3d58229 100644 --- a/backend/Tests/Knot.Modules.Admin.UnitTests/Knot.Modules.Admin.UnitTests.csproj +++ b/backend/Tests/Admin/UnitTests/Knot.Modules.Admin.UnitTests.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -21,8 +21,10 @@ - - + + + + diff --git a/backend/Tests/Knot.Modules.Settings.UnitTests/Knot.Modules.Settings.UnitTests.csproj b/backend/Tests/Admin/UnitTests/Knot.Modules.Settings.UnitTests.csproj similarity index 76% rename from backend/Tests/Knot.Modules.Settings.UnitTests/Knot.Modules.Settings.UnitTests.csproj rename to backend/Tests/Admin/UnitTests/Knot.Modules.Settings.UnitTests.csproj index 319b9b1..9b38437 100644 --- a/backend/Tests/Knot.Modules.Settings.UnitTests/Knot.Modules.Settings.UnitTests.csproj +++ b/backend/Tests/Admin/UnitTests/Knot.Modules.Settings.UnitTests.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -21,8 +21,8 @@ - - + + diff --git a/backend/Tests/Knot.Modules.Admin.UnitTests/ResetUserPasswordCommandHandlerTests.cs b/backend/Tests/Admin/UnitTests/ResetUserPasswordCommandHandlerTests.cs similarity index 97% rename from backend/Tests/Knot.Modules.Admin.UnitTests/ResetUserPasswordCommandHandlerTests.cs rename to backend/Tests/Admin/UnitTests/ResetUserPasswordCommandHandlerTests.cs index c98b46e..3c27630 100644 --- a/backend/Tests/Knot.Modules.Admin.UnitTests/ResetUserPasswordCommandHandlerTests.cs +++ b/backend/Tests/Admin/UnitTests/ResetUserPasswordCommandHandlerTests.cs @@ -1,5 +1,5 @@ using FluentAssertions; -using Host.Application.Admin.Commands; +using Knot.Modules.Admin.Application.Admin.Commands; using Knot.Modules.Auth.Application.Abstractions; using Knot.Modules.Auth.Domain; using Knot.Shared.Kernel; diff --git a/backend/Tests/Knot.Modules.Profiles.UnitTests/Knot.Modules.Profiles.UnitTests.csproj b/backend/Tests/Auth/IntegrationTests/Knot.Modules.Auth.IntegrationTests.csproj similarity index 81% rename from backend/Tests/Knot.Modules.Profiles.UnitTests/Knot.Modules.Profiles.UnitTests.csproj rename to backend/Tests/Auth/IntegrationTests/Knot.Modules.Auth.IntegrationTests.csproj index 6a0be6a..1607c89 100644 --- a/backend/Tests/Knot.Modules.Profiles.UnitTests/Knot.Modules.Profiles.UnitTests.csproj +++ b/backend/Tests/Auth/IntegrationTests/Knot.Modules.Auth.IntegrationTests.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -10,13 +10,14 @@ - + + diff --git a/backend/Tests/Knot.Modules.Auth.UnitTests/Knot.Modules.Auth.UnitTests.csproj b/backend/Tests/Auth/UnitTests/Knot.Modules.Auth.UnitTests.csproj similarity index 85% rename from backend/Tests/Knot.Modules.Auth.UnitTests/Knot.Modules.Auth.UnitTests.csproj rename to backend/Tests/Auth/UnitTests/Knot.Modules.Auth.UnitTests.csproj index aea9f1e..ab17424 100644 --- a/backend/Tests/Knot.Modules.Auth.UnitTests/Knot.Modules.Auth.UnitTests.csproj +++ b/backend/Tests/Auth/UnitTests/Knot.Modules.Auth.UnitTests.csproj @@ -23,9 +23,10 @@ - - + + + diff --git a/backend/Tests/Knot.Modules.Auth.UnitTests/LoginUserCommandHandlerTests.cs b/backend/Tests/Auth/UnitTests/LoginUserCommandHandlerTests.cs similarity index 100% rename from backend/Tests/Knot.Modules.Auth.UnitTests/LoginUserCommandHandlerTests.cs rename to backend/Tests/Auth/UnitTests/LoginUserCommandHandlerTests.cs diff --git a/backend/Tests/Knot.Modules.Conversations.UnitTests/Chats/CreateChatCommandHandlerTests.cs b/backend/Tests/Conversations/UnitTests/Chats/CreateChatCommandHandlerTests.cs similarity index 100% rename from backend/Tests/Knot.Modules.Conversations.UnitTests/Chats/CreateChatCommandHandlerTests.cs rename to backend/Tests/Conversations/UnitTests/Chats/CreateChatCommandHandlerTests.cs diff --git a/backend/Tests/Knot.Modules.Conversations.UnitTests/Chats/GetChatsQueryHandlerTests.cs b/backend/Tests/Conversations/UnitTests/Chats/GetChatsQueryHandlerTests.cs similarity index 100% rename from backend/Tests/Knot.Modules.Conversations.UnitTests/Chats/GetChatsQueryHandlerTests.cs rename to backend/Tests/Conversations/UnitTests/Chats/GetChatsQueryHandlerTests.cs diff --git a/backend/Tests/Knot.Modules.Conversations.UnitTests/GetOrCreateFavoritesCommandHandlerTests.cs b/backend/Tests/Conversations/UnitTests/GetOrCreateFavoritesCommandHandlerTests.cs similarity index 100% rename from backend/Tests/Knot.Modules.Conversations.UnitTests/GetOrCreateFavoritesCommandHandlerTests.cs rename to backend/Tests/Conversations/UnitTests/GetOrCreateFavoritesCommandHandlerTests.cs diff --git a/backend/Tests/Knot.Modules.Conversations.UnitTests/Knot.Modules.Conversations.UnitTests.csproj b/backend/Tests/Conversations/UnitTests/Knot.Modules.Conversations.UnitTests.csproj similarity index 72% rename from backend/Tests/Knot.Modules.Conversations.UnitTests/Knot.Modules.Conversations.UnitTests.csproj rename to backend/Tests/Conversations/UnitTests/Knot.Modules.Conversations.UnitTests.csproj index 4939487..11589b9 100644 --- a/backend/Tests/Knot.Modules.Conversations.UnitTests/Knot.Modules.Conversations.UnitTests.csproj +++ b/backend/Tests/Conversations/UnitTests/Knot.Modules.Conversations.UnitTests.csproj @@ -23,9 +23,11 @@ - - - + + + + + diff --git a/backend/Tests/Knot.Modules.Conversations.UnitTests/Messages/SendMessageCommandHandlerTests.cs b/backend/Tests/Conversations/UnitTests/Messages/SendMessageCommandHandlerTests.cs similarity index 87% rename from backend/Tests/Knot.Modules.Conversations.UnitTests/Messages/SendMessageCommandHandlerTests.cs rename to backend/Tests/Conversations/UnitTests/Messages/SendMessageCommandHandlerTests.cs index c40e483..8e5192a 100644 --- a/backend/Tests/Knot.Modules.Conversations.UnitTests/Messages/SendMessageCommandHandlerTests.cs +++ b/backend/Tests/Conversations/UnitTests/Messages/SendMessageCommandHandlerTests.cs @@ -12,6 +12,8 @@ using Knot.Modules.Conversations.Domain; using Knot.Modules.Messaging.Domain; using Knot.Modules.Conversations.Application.Abstractions; using Knot.Modules.Conversations.Application.Messages.Send; +using Knot.Modules.Settings.Application.Settings.Abstractions; +using Knot.Modules.Settings.Application.Settings.DTOs; namespace Knot.Modules.Conversations.UnitTests.Messages; @@ -21,6 +23,7 @@ public class SendMessageCommandHandlerTests private readonly IMessageRepository _messageRepository; private readonly IChatsUnitOfWork _unitOfWork; private readonly IMediator _mediator; + private readonly IMessagesSettings _messagesSettings; private readonly SendMessageCommandHandler _handler; public SendMessageCommandHandlerTests() @@ -29,8 +32,12 @@ public class SendMessageCommandHandlerTests _messageRepository = Substitute.For(); _unitOfWork = Substitute.For(); _mediator = Substitute.For(); + _messagesSettings = Substitute.For(); + + var config = new Knot.Modules.Settings.Application.Settings.DTOs.MessagesConfig(); + _messagesSettings.Current.Returns(config); - _handler = new SendMessageCommandHandler(_chatRepository, _messageRepository, _unitOfWork, _mediator); + _handler = new SendMessageCommandHandler(_chatRepository, _messageRepository, _unitOfWork, _mediator, _messagesSettings); } [Fact] diff --git a/backend/Tests/Federation/IntegrationTests/Knot.Modules.Federation.IntegrationTests.csproj b/backend/Tests/Federation/IntegrationTests/Knot.Modules.Federation.IntegrationTests.csproj new file mode 100644 index 0000000..1607c89 --- /dev/null +++ b/backend/Tests/Federation/IntegrationTests/Knot.Modules.Federation.IntegrationTests.csproj @@ -0,0 +1,23 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + diff --git a/backend/Tests/Knot.Modules.Federation.UnitTests/Knot.Modules.Federation.UnitTests.csproj b/backend/Tests/Federation/UnitTests/Knot.Modules.Federation.UnitTests.csproj similarity index 69% rename from backend/Tests/Knot.Modules.Federation.UnitTests/Knot.Modules.Federation.UnitTests.csproj rename to backend/Tests/Federation/UnitTests/Knot.Modules.Federation.UnitTests.csproj index 3b7ca74..c5416d2 100644 --- a/backend/Tests/Knot.Modules.Federation.UnitTests/Knot.Modules.Federation.UnitTests.csproj +++ b/backend/Tests/Federation/UnitTests/Knot.Modules.Federation.UnitTests.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -21,8 +21,10 @@ - - + + + + diff --git a/backend/Tests/Knot.Modules.Klipy.UnitTests/Knot.Modules.Klipy.UnitTests.csproj b/backend/Tests/Klipy/UnitTests/Knot.Modules.Klipy.UnitTests.csproj similarity index 69% rename from backend/Tests/Knot.Modules.Klipy.UnitTests/Knot.Modules.Klipy.UnitTests.csproj rename to backend/Tests/Klipy/UnitTests/Knot.Modules.Klipy.UnitTests.csproj index 824f599..21ebf72 100644 --- a/backend/Tests/Knot.Modules.Klipy.UnitTests/Knot.Modules.Klipy.UnitTests.csproj +++ b/backend/Tests/Klipy/UnitTests/Knot.Modules.Klipy.UnitTests.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -21,8 +21,10 @@ - - + + + + diff --git a/backend/Tests/Knot.Modules.Conversations.UnitTests/CreatePersonalChatCommandHandlerTests.cs b/backend/Tests/Knot.Modules.Conversations.UnitTests/CreatePersonalChatCommandHandlerTests.cs deleted file mode 100644 index a54275d..0000000 --- a/backend/Tests/Knot.Modules.Conversations.UnitTests/CreatePersonalChatCommandHandlerTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -using FluentAssertions; -using Knot.Modules.Conversations.Application.Chats.Commands.CreatePersonalChat; -using Knot.Modules.Conversations.Domain; -using Knot.Modules.Conversations.Application.Abstractions; -using Knot.Shared.Kernel; -using NSubstitute; -using Xunit; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Knot.Modules.Conversations.UnitTests; - -public class CreatePersonalChatCommandHandlerTests -{ - private readonly IChatRepository _chatRepository; - private readonly IChatsUnitOfWork _unitOfWork; - private readonly CreatePersonalChatCommandHandler _handler; - - public CreatePersonalChatCommandHandlerTests() - { - _chatRepository = Substitute.For(); - _unitOfWork = Substitute.For(); - _handler = new CreatePersonalChatCommandHandler(_chatRepository, _unitOfWork); - } - - [Fact] - public async Task Handle_ShouldReturnError_WhenChatAlreadyExists() - { - // Arrange - var command = new CreatePersonalChatCommand(Guid.NewGuid(), Guid.NewGuid()); - _chatRepository.GetPersonalChatAsync(command.InitiatorId, command.TargetUserId, Arg.Any()) - .Returns(PersonalChat.Create(command.InitiatorId, command.TargetUserId)); - - // Act - var result = await _handler.Handle(command, CancellationToken.None); - - // Assert - result.IsFailure.Should().BeTrue(); - result.Error.Should().Be(ChatErrors.PersonalChatAlreadyExists); - } -} diff --git a/backend/Tests/Messaging/IntegrationTests/Knot.Modules.Messaging.IntegrationTests.csproj b/backend/Tests/Messaging/IntegrationTests/Knot.Modules.Messaging.IntegrationTests.csproj new file mode 100644 index 0000000..1607c89 --- /dev/null +++ b/backend/Tests/Messaging/IntegrationTests/Knot.Modules.Messaging.IntegrationTests.csproj @@ -0,0 +1,23 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + diff --git a/backend/Tests/Knot.Modules.Messaging.UnitTests/Knot.Modules.Messaging.UnitTests.csproj b/backend/Tests/Messaging/UnitTests/Knot.Modules.Messaging.UnitTests.csproj similarity index 83% rename from backend/Tests/Knot.Modules.Messaging.UnitTests/Knot.Modules.Messaging.UnitTests.csproj rename to backend/Tests/Messaging/UnitTests/Knot.Modules.Messaging.UnitTests.csproj index 6a0be6a..89029aa 100644 --- a/backend/Tests/Knot.Modules.Messaging.UnitTests/Knot.Modules.Messaging.UnitTests.csproj +++ b/backend/Tests/Messaging/UnitTests/Knot.Modules.Messaging.UnitTests.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -17,6 +17,8 @@ + + diff --git a/backend/Tests/Relations/IntegrationTests/Knot.Modules.Relations.IntegrationTests.csproj b/backend/Tests/Relations/IntegrationTests/Knot.Modules.Relations.IntegrationTests.csproj new file mode 100644 index 0000000..1607c89 --- /dev/null +++ b/backend/Tests/Relations/IntegrationTests/Knot.Modules.Relations.IntegrationTests.csproj @@ -0,0 +1,23 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + diff --git a/backend/Tests/Knot.Modules.Relations.UnitTests/Knot.Modules.Relations.UnitTests.csproj b/backend/Tests/Relations/UnitTests/Knot.Modules.Relations.UnitTests.csproj similarity index 69% rename from backend/Tests/Knot.Modules.Relations.UnitTests/Knot.Modules.Relations.UnitTests.csproj rename to backend/Tests/Relations/UnitTests/Knot.Modules.Relations.UnitTests.csproj index f9ca8bf..33cc70b 100644 --- a/backend/Tests/Knot.Modules.Relations.UnitTests/Knot.Modules.Relations.UnitTests.csproj +++ b/backend/Tests/Relations/UnitTests/Knot.Modules.Relations.UnitTests.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -21,8 +21,10 @@ - - + + + + diff --git a/backend/Tests/Shared/IntegrationTests.Shared/BaseIntegrationTest.cs b/backend/Tests/Shared/IntegrationTests.Shared/BaseIntegrationTest.cs new file mode 100644 index 0000000..8645865 --- /dev/null +++ b/backend/Tests/Shared/IntegrationTests.Shared/BaseIntegrationTest.cs @@ -0,0 +1,88 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System.Net.Http; +using Testcontainers.PostgreSql; +using Testcontainers.MongoDb; +using Xunit; +using System.Threading.Tasks; +using System.Collections.Generic; +using Knot.Shared.Kernel; +using NSubstitute; + +namespace Knot.IntegrationTests; + +public abstract class BaseIntegrationTest : IAsyncLifetime +{ + protected readonly PostgreSqlContainer _postgresContainer = new PostgreSqlBuilder() + .WithImage("postgres:15-alpine") + .Build(); + + protected readonly MongoDbContainer _mongoContainer = new MongoDbBuilder() + .WithImage("mongo:6.0") + .Build(); + + protected HttpClient _client; + protected WebApplicationFactory _factory; + protected readonly Guid _userId = Guid.NewGuid(); + protected readonly IUserContext _userContextMock = Substitute.For(); + + public virtual async Task InitializeAsync() + { + await _postgresContainer.StartAsync(); + await _mongoContainer.StartAsync(); + + _userContextMock.UserId.Returns(_userId); + _userContextMock.IsAuthenticated.Returns(true); + + _factory = new IntegrationTestWebApplicationFactory( + _postgresContainer.GetConnectionString(), + _mongoContainer.GetConnectionString(), + _userContextMock); + + _client = _factory.CreateClient(); + } + + public virtual async Task DisposeAsync() + { + await _postgresContainer.StopAsync(); + await _mongoContainer.StopAsync(); + _factory?.Dispose(); + _client?.Dispose(); + } + + private class IntegrationTestWebApplicationFactory : WebApplicationFactory + { + private readonly string _pgConnectionString; + private readonly string _mongoConnectionString; + private readonly IUserContext _userContext; + + public IntegrationTestWebApplicationFactory(string pg, string mongo, IUserContext userContext) + { + _pgConnectionString = pg; + _mongoConnectionString = mongo; + _userContext = userContext; + } + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.ConfigureAppConfiguration((context, config) => + { + config.AddInMemoryCollection(new Dictionary + { + ["ConnectionStrings:DefaultConnection"] = _pgConnectionString, + ["ConnectionStrings:MongoConnection"] = _mongoConnectionString, + ["DATABASE_URL"] = _pgConnectionString, + ["MONGO_CONNECTION"] = _mongoConnectionString, + ["KNOT_MASTER_ENCRYPTION_KEY"] = "TestEncryptionKey_32CharactersLong!" + }); + }); + + builder.ConfigureTestServices(services => { + services.AddScoped(_ => _userContext); + }); + } + } +} diff --git a/backend/Tests/Shared/IntegrationTests.Shared/Knot.IntegrationTests.Shared.csproj b/backend/Tests/Shared/IntegrationTests.Shared/Knot.IntegrationTests.Shared.csproj new file mode 100644 index 0000000..b74a66f --- /dev/null +++ b/backend/Tests/Shared/IntegrationTests.Shared/Knot.IntegrationTests.Shared.csproj @@ -0,0 +1,38 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/Tests/Knot.Modules.Storage.UnitTests/Knot.Modules.Storage.UnitTests.csproj b/backend/Tests/Storage/UnitTests/Knot.Modules.Storage.UnitTests.csproj similarity index 69% rename from backend/Tests/Knot.Modules.Storage.UnitTests/Knot.Modules.Storage.UnitTests.csproj rename to backend/Tests/Storage/UnitTests/Knot.Modules.Storage.UnitTests.csproj index bba2b3d..2c59df3 100644 --- a/backend/Tests/Knot.Modules.Storage.UnitTests/Knot.Modules.Storage.UnitTests.csproj +++ b/backend/Tests/Storage/UnitTests/Knot.Modules.Storage.UnitTests.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -21,8 +21,10 @@ - - + + + + diff --git a/backend/Tests/Stories/IntegrationTests/Knot.Modules.Stories.IntegrationTests.csproj b/backend/Tests/Stories/IntegrationTests/Knot.Modules.Stories.IntegrationTests.csproj new file mode 100644 index 0000000..df3476d --- /dev/null +++ b/backend/Tests/Stories/IntegrationTests/Knot.Modules.Stories.IntegrationTests.csproj @@ -0,0 +1,26 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + diff --git a/backend/Tests/Stories/IntegrationTests/StoriesTests.cs b/backend/Tests/Stories/IntegrationTests/StoriesTests.cs new file mode 100644 index 0000000..3a9d464 --- /dev/null +++ b/backend/Tests/Stories/IntegrationTests/StoriesTests.cs @@ -0,0 +1,34 @@ +using Knot.IntegrationTests; +using FluentAssertions; +using Knot.Modules.Stories.Application.Stories.Commands.CreateStory; +using Knot.Shared.Kernel; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using System.Net; +using System.Net.Http.Json; +using Xunit; +using System; +using System.Threading.Tasks; + +namespace Knot.IntegrationTests.Stories; + +public class StoriesTests : BaseIntegrationTest +{ + [Fact] + public async Task CreateStory_ShouldReturnOk_WhenValidRequest() + { + // Arrange + var request = new CreateStoryRequest("Text", null, "Hello Integration Test", null); + + // Act + var response = await _client.PostAsJsonAsync("api/stories", request); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + + var content = await response.Content.ReadFromJsonAsync(); + } +} + +public record CreateStoryRequest(string Type, string? MediaUrl, string? Content, string? BgColor); diff --git a/backend/Tests/Stories/UnitTests/CreateStoryCommandHandlerTests.cs b/backend/Tests/Stories/UnitTests/CreateStoryCommandHandlerTests.cs new file mode 100644 index 0000000..604e657 --- /dev/null +++ b/backend/Tests/Stories/UnitTests/CreateStoryCommandHandlerTests.cs @@ -0,0 +1,76 @@ +using FluentAssertions; +using Knot.Modules.Stories.Application.Stories.Commands.CreateStory; +using Knot.Modules.Stories.Domain; +using Knot.Shared.Kernel; +using Knot.Modules.Settings.Application.Settings.Abstractions; +using Knot.Modules.Settings.Application.Settings.DTOs; +using Knot.Modules.Stories.Application.Abstractions; +using NSubstitute; +using Xunit; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Knot.Modules.Stories.UnitTests; + +public class CreateStoryCommandHandlerTests +{ + private readonly IStoryRepository _storyRepository; + private readonly ISettingsService _settingsService; + private readonly CreateStoryCommandHandler _handler; + + public CreateStoryCommandHandlerTests() + { + _storyRepository = Substitute.For(); + _settingsService = Substitute.For(); + _handler = new CreateStoryCommandHandler(_storyRepository, _settingsService); + } + + [Fact] + public async Task Handle_ShouldSucceed_WhenValidRequest() + { + // Arrange + var command = new CreateStoryCommand(Guid.NewGuid(), "Text", null, "Hello Story", null); + + // Act + var result = await _handler.Handle(command, CancellationToken.None); + + // Assert + result.IsSuccess.Should().BeTrue(); + await _storyRepository.Received(1).AddAsync(Arg.Any(), Arg.Any()); + } + + [Fact] + public async Task Handle_ShouldFail_WhenKlipyDisabledAndKlipyType() + { + // Arrange + var command = new CreateStoryCommand(Guid.NewGuid(), "Klipy", "http://klipy.com/gif", null, null); + var settings = new SystemSettingsDto { Klipy = new KlipyConfig { Enabled = false } }; + _settingsService.GetSettingsAsync(Arg.Any()).Returns(settings); + + // Act + var result = await _handler.Handle(command, CancellationToken.None); + + // Assert + result.IsFailure.Should().BeTrue(); + result.Error.Code.Should().Be("Stories.KlipyDisabled"); + await _storyRepository.DidNotReceive().AddAsync(Arg.Any(), Arg.Any()); + } + + [Fact] + public async Task Handle_ShouldSucceed_WhenKlipyEnabledAndKlipyType() + { + // Arrange + var command = new CreateStoryCommand(Guid.NewGuid(), "Klipy", "http://klipy.com/gif", null, null); + var settings = new SystemSettingsDto { Klipy = new KlipyConfig { Enabled = true } }; + _settingsService.GetSettingsAsync(Arg.Any()).Returns(settings); + + // Act + var result = await _handler.Handle(command, CancellationToken.None); + + // Assert + result.IsSuccess.Should().BeTrue(); + await _storyRepository.Received(1).AddAsync(Arg.Any(), Arg.Any()); + } +} + diff --git a/backend/Tests/Stories/UnitTests/DeleteStoryCommandHandlerTests.cs b/backend/Tests/Stories/UnitTests/DeleteStoryCommandHandlerTests.cs new file mode 100644 index 0000000..48795ad --- /dev/null +++ b/backend/Tests/Stories/UnitTests/DeleteStoryCommandHandlerTests.cs @@ -0,0 +1,76 @@ +using FluentAssertions; +using Knot.Modules.Stories.Application.Stories.Commands.DeleteStory; +using Knot.Modules.Stories.Domain; +using Knot.Shared.Kernel; +using Knot.Modules.Stories.Application.Abstractions; +using NSubstitute; +using Xunit; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Knot.Modules.Stories.UnitTests; + +public class DeleteStoryCommandHandlerTests +{ + private readonly IStoryRepository _storyRepository; + private readonly DeleteStoryCommandHandler _handler; + + public DeleteStoryCommandHandlerTests() + { + _storyRepository = Substitute.For(); + _handler = new DeleteStoryCommandHandler(_storyRepository); + } + + [Fact] + public async Task Handle_ShouldReturnError_WhenStoryNotFound() + { + // Arrange + var command = new DeleteStoryCommand(Guid.NewGuid(), Guid.NewGuid()); + _storyRepository.GetByIdAsync(command.StoryId, Arg.Any()).Returns((Story?)null); + + // Act + var result = await _handler.Handle(command, CancellationToken.None); + + // Assert + result.IsFailure.Should().BeTrue(); + result.Error.Should().Be(StoryErrors.StoryNotFound); + } + + [Fact] + public async Task Handle_ShouldReturnError_WhenUserIsNotOwner() + { + // Arrange + var ownerId = Guid.NewGuid(); + var viewerId = Guid.NewGuid(); + var story = Story.Create(ownerId, StoryType.Text, null, "Content", null); + var command = new DeleteStoryCommand(viewerId, story.Id); + + _storyRepository.GetByIdAsync(command.StoryId, Arg.Any()).Returns(story); + + // Act + var result = await _handler.Handle(command, CancellationToken.None); + + // Assert + result.IsFailure.Should().BeTrue(); + result.Error.Should().Be(StoryErrors.Unauthorized); + } + + [Fact] + public async Task Handle_ShouldSucceed_WhenOwnerDeletes() + { + // Arrange + var ownerId = Guid.NewGuid(); + var story = Story.Create(ownerId, StoryType.Text, null, "Content", null); + var command = new DeleteStoryCommand(ownerId, story.Id); + + _storyRepository.GetByIdAsync(command.StoryId, Arg.Any()).Returns(story); + + // Act + var result = await _handler.Handle(command, CancellationToken.None); + + // Assert + result.IsSuccess.Should().BeTrue(); + await _storyRepository.Received(1).DeleteAsync(story, Arg.Any()); + } +} diff --git a/backend/Tests/Knot.Modules.Stories.UnitTests/Knot.Modules.Stories.UnitTests.csproj b/backend/Tests/Stories/UnitTests/Knot.Modules.Stories.UnitTests.csproj similarity index 69% rename from backend/Tests/Knot.Modules.Stories.UnitTests/Knot.Modules.Stories.UnitTests.csproj rename to backend/Tests/Stories/UnitTests/Knot.Modules.Stories.UnitTests.csproj index 45223ef..71d70a1 100644 --- a/backend/Tests/Knot.Modules.Stories.UnitTests/Knot.Modules.Stories.UnitTests.csproj +++ b/backend/Tests/Stories/UnitTests/Knot.Modules.Stories.UnitTests.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -21,8 +21,10 @@ - - + + + + diff --git a/backend/Tests/Knot.Modules.TelegramImport.UnitTests/Knot.Modules.TelegramImport.UnitTests.csproj b/backend/Tests/TelegramImport/UnitTests/Knot.Modules.TelegramImport.UnitTests.csproj similarity index 68% rename from backend/Tests/Knot.Modules.TelegramImport.UnitTests/Knot.Modules.TelegramImport.UnitTests.csproj rename to backend/Tests/TelegramImport/UnitTests/Knot.Modules.TelegramImport.UnitTests.csproj index 7ab8a19..bf92713 100644 --- a/backend/Tests/Knot.Modules.TelegramImport.UnitTests/Knot.Modules.TelegramImport.UnitTests.csproj +++ b/backend/Tests/TelegramImport/UnitTests/Knot.Modules.TelegramImport.UnitTests.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -21,8 +21,10 @@ - - + + + + diff --git a/backend/Tests/Users/UnitTests/Knot.Modules.Profiles.UnitTests.csproj b/backend/Tests/Users/UnitTests/Knot.Modules.Profiles.UnitTests.csproj new file mode 100644 index 0000000..3e3dac3 --- /dev/null +++ b/backend/Tests/Users/UnitTests/Knot.Modules.Profiles.UnitTests.csproj @@ -0,0 +1,30 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/Tests/Knot.Modules.Profiles.UnitTests/UpdateProfileCommandHandlerTests.cs b/backend/Tests/Users/UnitTests/UpdateProfileCommandHandlerTests.cs similarity index 74% rename from backend/Tests/Knot.Modules.Profiles.UnitTests/UpdateProfileCommandHandlerTests.cs rename to backend/Tests/Users/UnitTests/UpdateProfileCommandHandlerTests.cs index ef99d04..3739a53 100644 --- a/backend/Tests/Knot.Modules.Profiles.UnitTests/UpdateProfileCommandHandlerTests.cs +++ b/backend/Tests/Users/UnitTests/UpdateProfileCommandHandlerTests.cs @@ -1,41 +1,38 @@ using FluentAssertions; -using Knot.Modules.Profiles.Application.Profiles.Commands.UpdateProfile; +using Knot.Modules.Profiles.Application.Profiles.UpdateProfile; using Knot.Modules.Profiles.Domain; -using Knot.Modules.Profiles.Application.Abstractions; using Knot.Shared.Kernel; using NSubstitute; using Xunit; using System; using System.Threading; using System.Threading.Tasks; +using Knot.Modules.Profiles.Application.Abstractions; namespace Knot.Modules.Profiles.UnitTests; public class UpdateProfileCommandHandlerTests { private readonly IProfileRepository _profileRepository; - private readonly IProfilesUnitOfWork _unitOfWork; private readonly UpdateProfileCommandHandler _handler; public UpdateProfileCommandHandlerTests() { _profileRepository = Substitute.For(); - _unitOfWork = Substitute.For(); - _handler = new UpdateProfileCommandHandler(_profileRepository, _unitOfWork); + _handler = new UpdateProfileCommandHandler(_profileRepository); } [Fact] public async Task Handle_ShouldReturnError_WhenProfileNotFound() { // Arrange - var command = new UpdateProfileCommand(Guid.NewGuid(), "FirstName", "LastName", "Bio"); - _profileRepository.GetByIdAsync(command.UserId, Arg.Any()).Returns((UserProfile?)null); + var command = new UpdateProfileCommand(Guid.NewGuid(), "FirstName", "Bio", null); + _profileRepository.GetByIdAsync(command.UserId, Arg.Any()).Returns((ProfileDocument?)null); // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert result.IsFailure.Should().BeTrue(); - result.Error.Should().Be(ProfileErrors.NotFound); } } diff --git a/backend/Tests/Knot.Modules.WebRtc.UnitTests/Knot.Modules.WebRtc.UnitTests.csproj b/backend/Tests/WebRtc/UnitTests/Knot.Modules.WebRtc.UnitTests.csproj similarity index 69% rename from backend/Tests/Knot.Modules.WebRtc.UnitTests/Knot.Modules.WebRtc.UnitTests.csproj rename to backend/Tests/WebRtc/UnitTests/Knot.Modules.WebRtc.UnitTests.csproj index fdc5794..be78aa8 100644 --- a/backend/Tests/Knot.Modules.WebRtc.UnitTests/Knot.Modules.WebRtc.UnitTests.csproj +++ b/backend/Tests/WebRtc/UnitTests/Knot.Modules.WebRtc.UnitTests.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -21,8 +21,10 @@ - - + + + + diff --git a/backend/src/Docs/profiles_module_documentation.md b/backend/src/Docs/profiles_module_documentation.md new file mode 100644 index 0000000..e495128 --- /dev/null +++ b/backend/src/Docs/profiles_module_documentation.md @@ -0,0 +1,105 @@ +# Документация модуля Profiles (Профили пользователей) + +## Введение + +Модуль `Knot.Modules.Profiles` отвечает за управление публичными данными пользователей, их аватарами и настройками отображения в мессенджере Knot Messager. + +### Разделение ответственности (Auth vs Profiles) +В соответствии с принципами **DDD (Domain-Driven Design)** и **Чистой Архитектуры**, модуль Profiles отделен от модуля Auth: +* **Auth (Postgres):** Занимается идентификацией (логин, пароль, токены, email). +* **Profiles (MongoDB + S3):** Занимается представлением пользователя (имя, био, аватар, настройки видимости). + +Это разделение позволяет изменять структуру профиля (добавлять новые социальные ссылки, поля "О себе") без выполнения миграций в основной реляционной БД. + +--- + +## Архитектура хранения данных (Гибридный подход) + +Модуль использует **гибридную схему хранения**, оптимизированную для производительности и гибкости: + +1. **Metadata (MongoDB):** Документ `ProfileDocument` содержит все текстовые данные профиля. Использование NoSQL позволяет легко расширять схему данных. +2. **Media (S3/MinIO):** Файлы аватаров хранятся во внешнем объектном хранилище. В MongoDB хранится только `AvatarUrl` (ссылка на файл). +3. **Identity Link (UserId):** Поле `Id` в MongoDB-документе совпадает с `UserId` из PostgreSQL. Это единственный ключ для связи модулей. + +--- + +## Доменная модель + +### ProfileDocument (Aggregate Root) +Основная сущность в модуле. Находится в слое `Domain`. +* **Username:** Уникальный хендл пользователя. +* **DisplayName:** Публичное имя (может меняться). +* **Bio:** Короткая биографическая справка. +* **AvatarUrl:** Путь к файлу в формате `/api/files/{fileId}`. +* **HideStoryViews:** Флаг приватности (скрывать просмотр сторис от других). + +### Доменные события +Модуль генерирует и слушает события для обеспечения согласованности: +* `UserRegisteredDomainEvent`: Слушается модулем Profiles (от Auth). При получении создается начальный документ профиля в MongoDB. +* `ProfileAvatarChangedDomainEvent`: Генерируется при смене аватара (для потенциальной очистки кэша). + +--- + +## Ключевые возможности + +### 1. Управление аватарами +Модуль предоставляет эндпоинты для загрузки и удаления аватаров: +* **Автоматическое кадрирование:** При вызове `/avatar/crop` используется библиотека `ImageSharp` для вырезания области изображения и ресайза до `400x400` пикселей в формате JPEG. +* **Авто-очистка:** При загрузке нового аватара модуль автоматически удаляет старый файл из S3, предотвращая появление "файлов-сирот". + +### 2. Поиск пользователей +Реализован через `ProfileRepository` с использованием регулярных выражений MongoDB (case-insensitive) по полям `Username` и `DisplayName`. + +### 3. Настройки приватности +Пользователь может управлять отображением своей активности (например, `HideStoryViews`), что сохраняется непосредственно в документе профиля. + +--- + +## Структура проекта + +```text +Knot.Modules.Profiles/ +├── Application/ +│ ├── Abstractions/ # Интерфейсы IProfileRepository, IAvatarStorageService +│ ├── Profiles/ # Case-обработчики (Handlers) +│ │ ├── Avatar/ # Загрузка, обрезка, удаление аватара +│ │ ├── GetUser/ # Получение данных профиля +│ │ ├── Search/ # Поиск по базе профилей +│ │ └── UpdateProfile/ # Обновление текстовых данных +│ └── Integration/ # Обработчики событий от других модулей +├── Domain/ +│ ├── ProfileDocument.cs # Корень агрегата (Mongo Document) +│ └── Events/ # Доменные события +├── Infrastructure/ +│ └── Database/ # Реализация репозиториев для MongoDB и S3 +├── Presentation/ +│ └── Endpoints/ # Carter-модули (API эндпоинты) +└── DependencyInjection.cs # Регистрация сервисов и MongoDB клиента +``` + +--- + +## Технические детали реализации + +### Интеграция с S3 (MinIO) +В модуле реализован адаптер `AvatarStorageService`, который инкапсулирует работу с `IFileStorageService`. Он отвечает за: +1. Генерацию уникальных ключей файлов. +2. Шифрование контента (через Shared Infrastructure). +3. Очистку путей (преобразование URL в ID файла для удаления). + +### Настройка MongoDB +Репозиторий `ProfileRepository` использует типизированную коллекцию `IMongoCollection`. Первичный ключ `Id` мапится как строка (BsonType.String) для совместимости с `Guid` из .NET. + +--- + +## API Эндпоинты (Примеры) + +| Метод | Путь | Описание | +| :--- | :--- | :--- | +| `GET` | `/api/profiles/{id}` | Получить данные профиля пользователя. | +| `GET` | `/api/profiles/search?q=text` | Найти пользователей по имени или никнейму. | +| `PUT` | `/api/profiles/profile` | Обновить DisplayName, Bio, Birthday. | +| `POST` | `/api/profiles/avatar` | Простая загрузка аватара (FormFile). | +| `POST` | `/api/profiles/avatar/crop` | Загрузка с указанием координат обрезки (x, y, w, h). | +| `DELETE` | `/api/profiles/avatar` | Удаление текущего аватара и файла из S3. | +| `PUT` | `/api/profiles/settings` | Обновить настройки приватности профиля. | diff --git a/backend/src/Docs/settings_module_documentation.md b/backend/src/Docs/settings_module_documentation.md new file mode 100644 index 0000000..b07387b --- /dev/null +++ b/backend/src/Docs/settings_module_documentation.md @@ -0,0 +1,129 @@ +# Документация модуля Settings (Настройки) + +## Введение + +Модуль `Knot.Modules.Settings` является централизованным компонентом для управления глобальными конфигурациями и параметрами системы в архитектуре модульного монолита (Modular Monolith) Knot Messager. Он обеспечивает единый источник истины (Single Source of Truth) для настроек всех других модулей (WebRTC, Federation, Messages, Chats, Admin и др.). + +## Основные принципы (DDD и Clean Architecture) + +- **Независимость и Инкапсуляция:** Модуль выступает как самостоятельная единица и не зависит от реализаций других модулей. Другие модули ссылаются на `Settings` только через абстракции или контракты. +- **Единственный Источник Истины:** Любое изменение системной конфигурации (включение/отключение фич, лимиты, домены) происходит через этот модуль. +- **Событийная Модель (Domain Events):** При изменении настроек инфраструктура Settings генерирует доменное событие `SystemSettingsUpdatedDomainEvent`, позволяя другим модулям (например, `Federation` для отправки новых Capabilities) реагировать на изменения асинхронно, не создавая жестких связей. + +## Структура модуля + +Модуль спроектирован по принципам Чистой Архитектуры и разделен на слои: + +```text +Knot.Modules.Settings/ +├── Application/ # Слой Приложения (Application Layer) +│ ├── Settings/ +│ │ ├── Abstractions/ # Определение интерфейсов доступа к каждому разделу настроек +│ │ │ ├── ISettingsService.cs +│ │ │ ├── IMessagesSettings.cs +│ │ │ ├── IWebRtcSettings.cs +│ │ │ └── ... +│ │ ├── DTOs/ # Структуры данных конфигураций +│ │ ├── SystemSettingsDto.cs +│ │ ├── PublicConfigDto.cs +│ │ ├── KlipyConfig.cs +│ │ └── ... +├── Domain/ # Доменный Слой (Domain Layer) +│ ├── Events/ +│ │ └── SystemSettingsUpdatedDomainEvent.cs # Событие об изменении настроек +├── Infrastructure/ # Слой Инфраструктуры (Infrastructure Layer) +│ ├── Configuration/ +│ │ └── SettingsService.cs # Реализация логики кэширования и обновления +├── DependencyInjection.cs # Регистрация модуля в DI (AddSettingsModule) +``` + +## Как это работает? + +### 1. Интерфейсы сегрегации (Interface Segregation Principle - ISP) + +Чтобы модули не зависели от огромного объекта настройки всей системы `SystemSettingsDto`, в слое `Application/Settings/Abstractions/ISettingsService.cs` мы разделили конфигурации на интерфейсы по специализациям: + +- `ISystemSettings` — Системные и общие параметры. +- `IWebRtcSettings` — Настройки аудио/видео вызовов (WebRTC, TURN). +- `IMessagesSettings` — Настройки сообщений (файлы, ограничения). +- `IFederationSettings` — Настройки ActivityPub/Федерации. +- `IKlipySettings` — Настройки интеграции внешней библиотеки гифок (Klipy). + +### 2. Доступ к настройкам из других модулей + +Если, например, модулю бесед (Conversations) требуется проверить, включены ли интеграции Klipy, он инжектит специфичный интерфейс `IKlipySettings`, не получая доступа к настройкам админки или WebRTC: + +```csharp +public class CreateStoryCommandHandler +{ + private readonly IKlipySettings _klipySettings; + + public CreateStoryCommandHandler(IKlipySettings klipySettings) + { + _klipySettings = klipySettings; + } + + public async Task Handle(...) + { + if (!_klipySettings.Current.Enabled) { + // Отбросить логику + } + } +} +``` + +### 3. Хранение и шифрование данных + +Реализация `SettingsService` хранит данные централизованно (в таблице `SystemSettings` БД через `SystemDbContext` в `Knot.Shared.Infrastructure`) в формате JSON. +Для безопасности вся JSON-строка **зашифрована** с использованием `IEncryptionService`. + +Особенности хранения: +- **Кэширование**: `SettingsService` зарегистрирован как `Singleton`. При запуске сервера конфигурация загружается один раз из базы данных (или кеша/файла) и сохраняется в памяти. +- **Обновление**: Метод `UpdateSettingsAsync` сохраняет новый стейт в БД и обновляет объект в памяти (`_current`), предотвращая лишние запросы к базе данных при обычной работе мессенджера. + +## Жизненный цикл обновления настроек (Управление) + +Администратор изменяет настройки через UI (или API), после чего происходит следующий процесс: + +1. Вызывается эндпоинт в модуле **Admin**: `PUT /api/admin/settings`. +2. Команда `UpdateSettingsCommand` валидирует входные данные. +3. Команда обращается к `ISettingsService.UpdateSettingsAsync(newSettings)`. +4. `SettingsService` шифрует новые настройки, обновляет запись в БД, и заменяет кэш в оперативной памяти. +5. После успешного сохранения, диспетчер MediatR (в `Admin` модуле) публикует `SystemSettingsUpdatedDomainEvent`. +6. Событие отлавливается через `INotificationHandler` независимыми слушателями, например: + - Модулем `Federation` для рассылки всем "соседним" инстансам новых возможностей `Capabilities`. + +## Регистрация модуля + +При старте приложения в `Host/Program.cs` модуль инициализируется: + +```csharp +// Регистрация сервисов модуля настроек +builder.Services.AddSettingsModule(builder.Configuration); + +// Дополнительно можно вызывать Initialize(...) чтобы загрузить кэш из БД при старте +``` + +Под капотом `DependencyInjection.cs`: +```csharp +public static IServiceCollection AddSettingsModule(this IServiceCollection services, IConfiguration configuration) +{ + services.AddSingleton(); + + // Привязываем конкретные абстракции к одному Singleton инстансу + services.AddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(sp => sp.GetRequiredService()); + // ... и остальные +} +``` + +## Публичная конфигурация клиента (PublicConfig) + +Для клиентской части (Frontend) предусмотрен специальный DTO объект `PublicConfigDto`. +Это subset (урезанная часть) от `SystemSettingsDto`, которая не содержит чувствительной информации о сервере и ключах: + +- Доступные лимиты медиа файлов. +- Включены ли аудио/видео звонки. +- Включена ли федерация для поиска глобальных пользователей. + +Таким образом, клиент может запросить `GET /api/app/config`, а бэкенд отдаст безопасный набор доступных разрешений через маппинг `SettingsService.Current`. diff --git a/backend/src/Host/Host.csproj b/backend/src/Host/Host.csproj index cf96201..a0ea507 100644 --- a/backend/src/Host/Host.csproj +++ b/backend/src/Host/Host.csproj @@ -43,3 +43,5 @@ + + diff --git a/backend/src/Host/Program.cs b/backend/src/Host/Program.cs index 71e70aa..c534e42 100644 --- a/backend/src/Host/Program.cs +++ b/backend/src/Host/Program.cs @@ -2,6 +2,8 @@ using Knot.Modules.Messaging; using Carter; using Knot.Shared.Infrastructure; using Knot.Modules.Auth; +using Knot.Modules.Settings; +using Knot.Modules.Admin; using Knot.Modules.Conversations; using Knot.Modules.Conversations.Infrastructure.SignalR; using Knot.Modules.Auth.Infrastructure.Persistence; @@ -50,11 +52,16 @@ builder.Configuration.AddInMemoryCollection( .ToDictionary(kv => kv.Key, kv => kv.Value)); builder.Services.AddAuthModule(builder.Configuration); +builder.Services.AddSettingsModule(builder.Configuration); builder.Services.AddConversationsModule(builder.Configuration); builder.Services.AddSharedInfrastructure(builder.Configuration); // CQRS / MediatR для команд в Host (например, AdminController) builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly)); +builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies( + typeof(Knot.Modules.Settings.DependencyInjection).Assembly, + typeof(Knot.Modules.Admin.DependencyInjection).Assembly +)); // Carter для вызова Minimal APIs (Endpoints) builder.Services.AddCarter(); @@ -99,8 +106,6 @@ builder.Services.AddRouting(options => builder.Services.AddMemoryCache(); builder.Services.AddHttpClient(); - - builder.Services.AddSignalR() .AddJsonProtocol(options => { @@ -162,8 +167,8 @@ using (var scope = app.Services.CreateScope()) Knot.Modules.Messaging.Infrastructure.Persistence.Mongo.EncryptedStringSerializer.EncryptionService = encryptionService; // Initialize Global Settings Cache - var settingsService = scope.ServiceProvider.GetRequiredService(); - if (settingsService is Knot.Shared.Infrastructure.Configuration.SettingsService concreteSettings) + var settingsService = scope.ServiceProvider.GetRequiredService(); + if (settingsService is Knot.Modules.Settings.Infrastructure.Configuration.SettingsService concreteSettings) { concreteSettings.Initialize(); } @@ -203,3 +208,4 @@ public class CustomUserIdProvider : IUserIdProvider return connection.User?.FindFirstValue("sub") ?? connection.User?.FindFirstValue(ClaimTypes.NameIdentifier); } } + diff --git a/backend/src/Modules/Admin/Application/Admin/Commands/CleanRun/CleanRunCommand.cs b/backend/src/Modules/Admin/Application/Admin/Commands/CleanRun/CleanRunCommand.cs index 394cb52..4e055b4 100644 --- a/backend/src/Modules/Admin/Application/Admin/Commands/CleanRun/CleanRunCommand.cs +++ b/backend/src/Modules/Admin/Application/Admin/Commands/CleanRun/CleanRunCommand.cs @@ -14,7 +14,7 @@ using Knot.Modules.Conversations.Domain; using MongoDB.Driver; using Microsoft.EntityFrameworkCore; -namespace Host.Application.Admin.Commands; +namespace Knot.Modules.Admin.Application.Admin.Commands; public record CleanRunCommand(IFileStorageService FileStorage, AuthDbContext IdentityDb) : ICommand; @@ -99,4 +99,4 @@ internal sealed class CleanRunCommandHandler : ICommandHandler; @@ -44,4 +44,4 @@ internal sealed class ResetUserPasswordCommandHandler : ICommandHandler; @@ -37,3 +38,5 @@ internal sealed class TestKlipyConnectionCommandHandler : ICommandHandler; @@ -43,4 +44,6 @@ internal sealed class UpdateSettingsCommandHandler : ICommandHandler; @@ -102,4 +102,4 @@ internal sealed class CleanDryRunQueryHandler : IQueryHandler; @@ -23,4 +23,4 @@ internal sealed class GetDashboardStatsQueryHandler : IQueryHandler; @@ -24,4 +25,5 @@ internal sealed class GetSettingsQueryHandler : IQueryHandler; @@ -74,4 +74,4 @@ internal sealed class GetUserDetailsQueryHandler : IQueryHandler>; @@ -43,4 +43,4 @@ internal sealed class SearchUsersQueryHandler : IQueryHandler public sealed record SystemSettingsUpdatedDomainEvent(SystemSettingsDto Settings) : IDomainEvent; + diff --git a/backend/src/Modules/Admin/Knot.Modules.Admin.csproj b/backend/src/Modules/Admin/Knot.Modules.Admin.csproj index d057df2..73227c8 100644 --- a/backend/src/Modules/Admin/Knot.Modules.Admin.csproj +++ b/backend/src/Modules/Admin/Knot.Modules.Admin.csproj @@ -18,5 +18,7 @@ <_Parameter1>Knot.Modules.Admin.UnitTests + - \ No newline at end of file + + diff --git a/backend/src/Modules/Admin/Presentation/Endpoints/AdminEndpoints.cs b/backend/src/Modules/Admin/Presentation/Endpoints/AdminEndpoints.cs index e7ceac8..61b655b 100644 --- a/backend/src/Modules/Admin/Presentation/Endpoints/AdminEndpoints.cs +++ b/backend/src/Modules/Admin/Presentation/Endpoints/AdminEndpoints.cs @@ -1,18 +1,19 @@ using Carter; using Knot.Modules.Auth.Application.Auth.DTOs; using Knot.Shared.Kernel; -using Knot.Shared.Kernel.Configuration; +using Knot.Modules.Settings.Application.Settings.Abstractions; +using Knot.Modules.Settings.Application.Settings.DTOs; using Knot.Modules.Auth.Application.Users.Register; using Knot.Shared.Kernel.Storage; using Knot.Modules.Auth.Infrastructure.Persistence; using MediatR; -using Host.Application.Admin.Queries; -using Host.Application.Admin.Commands; +using Knot.Modules.Admin.Application.Admin.Queries; +using Knot.Modules.Admin.Application.Admin.Commands; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Mvc; -using Host.Application.Admin.Commands.TestKlipy; +using Knot.Modules.Admin.Application.Admin.Commands.TestKlipy; namespace Knot.Host.Presentation.Endpoints; @@ -90,4 +91,5 @@ public sealed class AdminEndpoints : ICarterModule return Results.Ok(result.Value); }); } -} \ No newline at end of file +} + diff --git a/backend/src/Modules/Auth/Application/Users/Register/RegisterUserCommandHandler.cs b/backend/src/Modules/Auth/Application/Users/Register/RegisterUserCommandHandler.cs index 5d2ce71..bc6eaa0 100644 --- a/backend/src/Modules/Auth/Application/Users/Register/RegisterUserCommandHandler.cs +++ b/backend/src/Modules/Auth/Application/Users/Register/RegisterUserCommandHandler.cs @@ -2,7 +2,8 @@ using Knot.Modules.Auth.Domain; using Knot.Modules.Auth.Application.Abstractions; using Knot.Shared.Kernel; -using Knot.Shared.Kernel.Configuration; +using Knot.Modules.Settings.Application.Settings.Abstractions; +using Knot.Modules.Settings.Application.Settings.DTOs; using Knot.Modules.Auth.Application.Users.Auth; using BCrypt.Net; @@ -87,3 +88,4 @@ public sealed class RegisterUserCommandHandler : ICommandHandler /// public static User Create(string username, string passwordHash, string displayName, string? email = null, string? bio = null) { - return new User(Guid.NewGuid(), username, passwordHash, displayName, email, bio); + var user = new User(Guid.NewGuid(), username, passwordHash, displayName, email, bio); + user.RaiseDomainEvent(new UserRegisteredDomainEvent(user.Id, user.Username, user.DisplayName, user.Bio)); + return user; } /// diff --git a/backend/src/Modules/Auth/Knot.Modules.Auth.csproj b/backend/src/Modules/Auth/Knot.Modules.Auth.csproj index 887161b..b078e70 100644 --- a/backend/src/Modules/Auth/Knot.Modules.Auth.csproj +++ b/backend/src/Modules/Auth/Knot.Modules.Auth.csproj @@ -21,13 +21,17 @@ - + + + + + diff --git a/backend/src/Modules/Conversations/Application/Folders/Commands/AddToFolder/AddToFolderCommand.cs b/backend/src/Modules/Conversations/Application/Folders/Commands/AddToFolder/AddToFolderCommand.cs index 4c7ba71..c589dbf 100644 --- a/backend/src/Modules/Conversations/Application/Folders/Commands/AddToFolder/AddToFolderCommand.cs +++ b/backend/src/Modules/Conversations/Application/Folders/Commands/AddToFolder/AddToFolderCommand.cs @@ -1,6 +1,7 @@ using Knot.Modules.Conversations.Domain; using Knot.Modules.Conversations.Application.Abstractions; -using Knot.Shared.Kernel.Configuration; +using Knot.Modules.Settings.Application.Settings.Abstractions; +using Knot.Modules.Settings.Application.Settings.DTOs; using Knot.Shared.Kernel; using MediatR; @@ -48,3 +49,4 @@ internal sealed class AddToFolderCommandHandler : ICommandHandler - - + + @@ -25,7 +25,7 @@ - + @@ -36,3 +36,5 @@ + + diff --git a/backend/src/Modules/Federation/Application/Federation/Commands/HandshakeFederationCommand.cs b/backend/src/Modules/Federation/Application/Federation/Commands/HandshakeFederationCommand.cs index 2b8a4dd..fdb6661 100644 --- a/backend/src/Modules/Federation/Application/Federation/Commands/HandshakeFederationCommand.cs +++ b/backend/src/Modules/Federation/Application/Federation/Commands/HandshakeFederationCommand.cs @@ -2,7 +2,8 @@ using Knot.Shared.Kernel.Constants; using System.Security.Cryptography; using MediatR; using Knot.Shared.Kernel; -using Knot.Shared.Kernel.Configuration; +using Knot.Modules.Settings.Application.Settings.Abstractions; +using Knot.Modules.Settings.Application.Settings.DTOs; namespace Host.Application.Federation.Commands; @@ -63,4 +64,4 @@ internal sealed class HandshakeFederationCommandHandler : ICommandHandler + - \ No newline at end of file + + diff --git a/backend/src/Modules/Federation/Presentation/Endpoints/FederationEndpoints.cs b/backend/src/Modules/Federation/Presentation/Endpoints/FederationEndpoints.cs index 181a3de..a753458 100644 --- a/backend/src/Modules/Federation/Presentation/Endpoints/FederationEndpoints.cs +++ b/backend/src/Modules/Federation/Presentation/Endpoints/FederationEndpoints.cs @@ -9,7 +9,8 @@ using System; using Host.Application.Federation.Commands; using Knot.Modules.Federation.Application.Abstractions; using Knot.Shared.Kernel.Storage; -using Knot.Shared.Kernel.Configuration; +using Knot.Modules.Settings.Application.Settings.Abstractions; +using Knot.Modules.Settings.Application.Settings.DTOs; namespace Host.Endpoints; @@ -63,4 +64,4 @@ public sealed class FederationEndpoints : ICarterModule } }); } -} \ No newline at end of file +} diff --git a/backend/src/Modules/Klipy/Application/Klipy/Queries/GetTrendingGifsQuery.cs b/backend/src/Modules/Klipy/Application/Klipy/Queries/GetTrendingGifsQuery.cs index d8af201..737f764 100644 --- a/backend/src/Modules/Klipy/Application/Klipy/Queries/GetTrendingGifsQuery.cs +++ b/backend/src/Modules/Klipy/Application/Klipy/Queries/GetTrendingGifsQuery.cs @@ -1,5 +1,6 @@ using Knot.Shared.Kernel; -using Knot.Shared.Kernel.Configuration; +using Knot.Modules.Settings.Application.Settings.Abstractions; +using Knot.Modules.Settings.Application.Settings.DTOs; using Knot.Shared.Kernel.Constants; using Microsoft.Extensions.Caching.Memory; using System; @@ -61,3 +62,4 @@ internal sealed class GetTrendingGifsQueryHandler : IQueryHandler(result); } } + diff --git a/backend/src/Modules/Klipy/Application/Klipy/Queries/SearchGifsQuery.cs b/backend/src/Modules/Klipy/Application/Klipy/Queries/SearchGifsQuery.cs index 007bc59..485a046 100644 --- a/backend/src/Modules/Klipy/Application/Klipy/Queries/SearchGifsQuery.cs +++ b/backend/src/Modules/Klipy/Application/Klipy/Queries/SearchGifsQuery.cs @@ -1,5 +1,6 @@ using Knot.Shared.Kernel; -using Knot.Shared.Kernel.Configuration; +using Knot.Modules.Settings.Application.Settings.Abstractions; +using Knot.Modules.Settings.Application.Settings.DTOs; using Knot.Shared.Kernel.Constants; using Microsoft.Extensions.Caching.Memory; using System; @@ -65,3 +66,4 @@ internal sealed class SearchGifsQueryHandler : IQueryHandler(result); } } + diff --git a/backend/src/Modules/Klipy/Knot.Modules.Klipy.csproj b/backend/src/Modules/Klipy/Knot.Modules.Klipy.csproj index aedece0..9afda16 100644 --- a/backend/src/Modules/Klipy/Knot.Modules.Klipy.csproj +++ b/backend/src/Modules/Klipy/Knot.Modules.Klipy.csproj @@ -13,5 +13,7 @@ + - \ No newline at end of file + + diff --git a/backend/src/Modules/Messaging/Knot.Modules.Messaging.csproj b/backend/src/Modules/Messaging/Knot.Modules.Messaging.csproj index 0fec715..f7bcc0b 100644 --- a/backend/src/Modules/Messaging/Knot.Modules.Messaging.csproj +++ b/backend/src/Modules/Messaging/Knot.Modules.Messaging.csproj @@ -1,4 +1,4 @@ - + @@ -7,7 +7,8 @@ - + + @@ -17,3 +18,5 @@ + + diff --git a/backend/src/Modules/Profiles/Application/Abstractions/IAvatarStorageService.cs b/backend/src/Modules/Profiles/Application/Abstractions/IAvatarStorageService.cs new file mode 100644 index 0000000..45c880c --- /dev/null +++ b/backend/src/Modules/Profiles/Application/Abstractions/IAvatarStorageService.cs @@ -0,0 +1,22 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Knot.Modules.Profiles.Application.Abstractions; + +/// +/// Интерфейс для хранения аватаров в S3 (MinIO). +/// Отдельный от IFileStorageService модуля Storage, +/// чтобы не создавать прямой зависимости на Storage модуль. +/// +public interface IAvatarStorageService +{ + /// + /// Загружает файл в S3-бакет аватаров и возвращает fileId (ключ объекта). + /// + Task UploadAsync(System.IO.Stream stream, string fileName, string contentType, CancellationToken ct = default); + + /// + /// Удаляет файл аватара из S3 по fileId. + /// + Task DeleteAsync(string fileId, CancellationToken ct = default); +} diff --git a/backend/src/Modules/Profiles/Application/Abstractions/IProfileRepository.cs b/backend/src/Modules/Profiles/Application/Abstractions/IProfileRepository.cs index 8440cdd..d412e41 100644 --- a/backend/src/Modules/Profiles/Application/Abstractions/IProfileRepository.cs +++ b/backend/src/Modules/Profiles/Application/Abstractions/IProfileRepository.cs @@ -1,15 +1,15 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Knot.Modules.Profiles.Domain; -using Knot.Modules.Profiles.Application.Abstractions; namespace Knot.Modules.Profiles.Application.Abstractions; public interface IProfileRepository { - Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); - Task AddAsync(Profile profile, CancellationToken cancellationToken = default); - void Update(Profile profile); - Task> SearchProfilesAsync(string query, System.Threading.CancellationToken ct = default); + Task GetByIdAsync(Guid userId, CancellationToken ct = default); + Task AddAsync(ProfileDocument profile, CancellationToken ct = default); + Task UpdateAsync(ProfileDocument profile, CancellationToken ct = default); + Task> SearchAsync(string query, CancellationToken ct = default); } diff --git a/backend/src/Modules/Profiles/Application/Abstractions/IProfilesUnitOfWork.cs b/backend/src/Modules/Profiles/Application/Abstractions/IProfilesUnitOfWork.cs index 1c00a7c..eba54c7 100644 --- a/backend/src/Modules/Profiles/Application/Abstractions/IProfilesUnitOfWork.cs +++ b/backend/src/Modules/Profiles/Application/Abstractions/IProfilesUnitOfWork.cs @@ -1,11 +1,9 @@ using System.Threading; using System.Threading.Tasks; -using Knot.Modules.Profiles.Application.Abstractions; -using Knot.Modules.Profiles.Domain; namespace Knot.Modules.Profiles.Application.Abstractions; public interface IProfilesUnitOfWork { - Task SaveChangesAsync(CancellationToken cancellationToken = default); + Task SaveChangesAsync(CancellationToken ct = default); } diff --git a/backend/src/Modules/Profiles/Application/Profiles/Avatar/CropAvatar.cs b/backend/src/Modules/Profiles/Application/Profiles/Avatar/CropAvatar.cs index 9732231..fac3893 100644 --- a/backend/src/Modules/Profiles/Application/Profiles/Avatar/CropAvatar.cs +++ b/backend/src/Modules/Profiles/Application/Profiles/Avatar/CropAvatar.cs @@ -1,69 +1,71 @@ using Knot.Shared.Kernel; -using Knot.Modules.Profiles.Domain; - -using Knot.Shared.Kernel.Storage; +using Knot.Modules.Profiles.Application.Abstractions; +using Knot.Modules.Profiles.Application.Profiles.DTOs; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; -using Knot.Modules.Profiles.Application.Abstractions; namespace Knot.Modules.Profiles.Application.Profiles.Avatar; -public sealed record CropAvatarCommand(Guid ProfileId, Stream FileStream, string FileName, string ContentType, int X, int Y, int Width, int Height) : ICommand; +public sealed record CropAvatarCommand( + Guid UserId, + Stream FileStream, + string FileName, + string ContentType, + int X, int Y, int Width, int Height) : ICommand; -internal sealed class CropAvatarCommandHandler : ICommandHandler +internal sealed class CropAvatarCommandHandler : ICommandHandler { - private readonly IProfileRepository _profileRepository; - private readonly IProfilesUnitOfWork _unitOfWork; - private readonly IFileStorageService _fileStorage; + private readonly IProfileRepository _repository; + private readonly IAvatarStorageService _avatarStorage; - public CropAvatarCommandHandler(IProfileRepository profileRepository, IProfilesUnitOfWork unitOfWork, IFileStorageService fileStorage) + public CropAvatarCommandHandler(IProfileRepository repository, IAvatarStorageService avatarStorage) { - _profileRepository = profileRepository; - _unitOfWork = unitOfWork; - _fileStorage = fileStorage; + _repository = repository; + _avatarStorage = avatarStorage; } - public async Task> Handle(CropAvatarCommand request, CancellationToken cancellationToken) + public async Task> Handle(CropAvatarCommand request, CancellationToken cancellationToken) { - var profile = await _profileRepository.GetByIdAsync(request.ProfileId, cancellationToken); - if (profile == null) - { - return Result.Failure(ProfilesErrors.ProfileNotFound); - } + var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken); + if (profile is null) + return Result.Failure(ProfilesErrors.ProfileNotFound); - string avatarUrl; + // Обрезать и ресайзнуть изображение до 400×400 + using var ms = await CropAndResizeAsync(request, cancellationToken); - using (var image = await SixLabors.ImageSharp.Image.LoadAsync(request.FileStream, cancellationToken)) - { - int startX = Math.Max(0, Math.Min(request.X, image.Width - 1)); - int startY = Math.Max(0, Math.Min(request.Y, image.Height - 1)); - int rectWidth = Math.Max(1, Math.Min(request.Width, image.Width - startX)); - int rectHeight = Math.Max(1, Math.Min(request.Height, image.Height - startY)); + // Удалить старый аватар из S3 + if (!string.IsNullOrEmpty(profile.AvatarUrl)) + await _avatarStorage.DeleteAsync(profile.AvatarUrl, cancellationToken); - image.Mutate(ctx => ctx.Crop(new SixLabors.ImageSharp.Rectangle(startX, startY, rectWidth, rectHeight))); - image.Mutate(ctx => ctx.Resize(400, 400)); - - using var outStream = new MemoryStream(); - await image.SaveAsJpegAsync(outStream, cancellationToken); - outStream.Position = 0; - - var id = await _fileStorage.UploadFileAsync(outStream, request.FileName, "image/jpeg"); - avatarUrl = $"/api/files/{id}"; - } + var fileId = await _avatarStorage.UploadAsync(ms, "avatar.jpg", "image/jpeg", cancellationToken); + var avatarUrl = $"/api/files/{fileId}"; profile.UpdateAvatar(avatarUrl); - await _unitOfWork.SaveChangesAsync(cancellationToken); + await _repository.UpdateAsync(profile, cancellationToken); - var dto = new ProfileProfileDto( - profile.Id, - profile.Profilename, - profile.DisplayName, - profile.Avatar, - profile.Bio, - profile.Birthday, - profile.CreatedAt - ); + return Result.Success(UserProfileDto.FromDocument(profile)); + } - return Result.Success(dto); + private static async Task CropAndResizeAsync(CropAvatarCommand request, CancellationToken ct) + { + using var image = await Image.LoadAsync(request.FileStream, ct); + + var startX = Math.Clamp(request.X, 0, image.Width - 1); + var startY = Math.Clamp(request.Y, 0, image.Height - 1); + var width = Math.Clamp(request.Width, 1, image.Width - startX); + var height = Math.Clamp(request.Height, 1, image.Height - startY); + + image.Mutate(ctx => ctx + .Crop(new Rectangle(startX, startY, width, height)) + .Resize(400, 400)); + + var output = new MemoryStream(); + await image.SaveAsJpegAsync(output, ct); + output.Position = 0; + return output; } } diff --git a/backend/src/Modules/Profiles/Application/Profiles/Avatar/DeleteAvatar.cs b/backend/src/Modules/Profiles/Application/Profiles/Avatar/DeleteAvatar.cs deleted file mode 100644 index cc22a5c..0000000 --- a/backend/src/Modules/Profiles/Application/Profiles/Avatar/DeleteAvatar.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Knot.Shared.Kernel; -using Knot.Modules.Profiles.Domain; - - -using Knot.Modules.Profiles.Application.Abstractions; -namespace Knot.Modules.Profiles.Application.Profiles.Avatar; - -public sealed record DeleteAvatarCommand(Guid ProfileId) : ICommand; - -internal sealed class DeleteAvatarCommandHandler : ICommandHandler -{ - private readonly IProfileRepository _profileRepository; - private readonly IProfilesUnitOfWork _unitOfWork; - - public DeleteAvatarCommandHandler(IProfileRepository profileRepository, IProfilesUnitOfWork unitOfWork) - { - _profileRepository = profileRepository; - _unitOfWork = unitOfWork; - } - - public async Task> Handle(DeleteAvatarCommand request, CancellationToken cancellationToken) - { - var profile = await _profileRepository.GetByIdAsync(request.ProfileId, cancellationToken); - if (profile == null) - { - return Result.Failure(ProfilesErrors.ProfileNotFound); - } - - profile.UpdateAvatar(null); - await _unitOfWork.SaveChangesAsync(cancellationToken); - - var dto = new ProfileProfileDto( - profile.Id, - profile.Profilename, - profile.DisplayName, - profile.Avatar, - profile.Bio, - profile.Birthday, - profile.CreatedAt - ); - - return Result.Success(dto); - } -} diff --git a/backend/src/Modules/Profiles/Application/Profiles/Avatar/UploadAvatar.cs b/backend/src/Modules/Profiles/Application/Profiles/Avatar/UploadAvatar.cs index 17a8401..6f60e8c 100644 --- a/backend/src/Modules/Profiles/Application/Profiles/Avatar/UploadAvatar.cs +++ b/backend/src/Modules/Profiles/Application/Profiles/Avatar/UploadAvatar.cs @@ -1,50 +1,79 @@ using Knot.Shared.Kernel; -using Knot.Modules.Profiles.Domain; - -using Knot.Shared.Kernel.Storage; - using Knot.Modules.Profiles.Application.Abstractions; +using Knot.Modules.Profiles.Application.Profiles.DTOs; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + namespace Knot.Modules.Profiles.Application.Profiles.Avatar; -public sealed record UploadAvatarCommand(Guid ProfileId, Stream FileStream, string FileName, string ContentType) : ICommand; +// ─── Upload ──────────────────────────────────────────────────────────────── -internal sealed class UploadAvatarCommandHandler : ICommandHandler +public sealed record UploadAvatarCommand( + Guid UserId, + Stream FileStream, + string FileName, + string ContentType) : ICommand; + +internal sealed class UploadAvatarCommandHandler : ICommandHandler { - private readonly IProfileRepository _profileRepository; - private readonly IProfilesUnitOfWork _unitOfWork; - private readonly IFileStorageService _fileStorage; + private readonly IProfileRepository _repository; + private readonly IAvatarStorageService _avatarStorage; - public UploadAvatarCommandHandler(IProfileRepository profileRepository, IProfilesUnitOfWork unitOfWork, IFileStorageService fileStorage) + public UploadAvatarCommandHandler(IProfileRepository repository, IAvatarStorageService avatarStorage) { - _profileRepository = profileRepository; - _unitOfWork = unitOfWork; - _fileStorage = fileStorage; + _repository = repository; + _avatarStorage = avatarStorage; } - public async Task> Handle(UploadAvatarCommand request, CancellationToken cancellationToken) + public async Task> Handle(UploadAvatarCommand request, CancellationToken cancellationToken) { - var profile = await _profileRepository.GetByIdAsync(request.ProfileId, cancellationToken); - if (profile == null) - { - return Result.Failure(ProfilesErrors.ProfileNotFound); - } + var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken); + if (profile is null) + return Result.Failure(ProfilesErrors.ProfileNotFound); - var fileId = await _fileStorage.UploadFileAsync(request.FileStream, request.FileName, request.ContentType); + // Удалить старый аватар из S3, если был + if (!string.IsNullOrEmpty(profile.AvatarUrl)) + await _avatarStorage.DeleteAsync(profile.AvatarUrl, cancellationToken); + + var fileId = await _avatarStorage.UploadAsync(request.FileStream, request.FileName, request.ContentType, cancellationToken); var avatarUrl = $"/api/files/{fileId}"; profile.UpdateAvatar(avatarUrl); - await _unitOfWork.SaveChangesAsync(cancellationToken); + await _repository.UpdateAsync(profile, cancellationToken); - var dto = new ProfileProfileDto( - profile.Id, - profile.Profilename, - profile.DisplayName, - profile.Avatar, - profile.Bio, - profile.Birthday, - profile.CreatedAt - ); - - return Result.Success(dto); + return Result.Success(UserProfileDto.FromDocument(profile)); + } +} + +// ─── Delete ──────────────────────────────────────────────────────────────── + +public sealed record DeleteAvatarCommand(Guid UserId) : ICommand; + +internal sealed class DeleteAvatarCommandHandler : ICommandHandler +{ + private readonly IProfileRepository _repository; + private readonly IAvatarStorageService _avatarStorage; + + public DeleteAvatarCommandHandler(IProfileRepository repository, IAvatarStorageService avatarStorage) + { + _repository = repository; + _avatarStorage = avatarStorage; + } + + public async Task> Handle(DeleteAvatarCommand request, CancellationToken cancellationToken) + { + var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken); + if (profile is null) + return Result.Failure(ProfilesErrors.ProfileNotFound); + + if (!string.IsNullOrEmpty(profile.AvatarUrl)) + await _avatarStorage.DeleteAsync(profile.AvatarUrl, cancellationToken); + + profile.RemoveAvatar(); + await _repository.UpdateAsync(profile, cancellationToken); + + return Result.Success(UserProfileDto.FromDocument(profile)); } } diff --git a/backend/src/Modules/Profiles/Application/Profiles/DTOs/ProfilesRequests.cs b/backend/src/Modules/Profiles/Application/Profiles/DTOs/ProfilesRequests.cs new file mode 100644 index 0000000..ebf67a7 --- /dev/null +++ b/backend/src/Modules/Profiles/Application/Profiles/DTOs/ProfilesRequests.cs @@ -0,0 +1,6 @@ +using System; + +namespace Knot.Modules.Profiles.Application.Profiles.DTOs; + +public record UpdateProfileRequest(string? DisplayName, string? Bio, DateTime? Birthday); +public record UpdateSettingsRequest(bool? HideStoryViews); diff --git a/backend/src/Modules/Profiles/Application/Profiles/DTOs/UpdateProfileRequest.cs b/backend/src/Modules/Profiles/Application/Profiles/DTOs/UpdateProfileRequest.cs deleted file mode 100644 index f46f152..0000000 --- a/backend/src/Modules/Profiles/Application/Profiles/DTOs/UpdateProfileRequest.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System; - -using Knot.Modules.Profiles.Application.Abstractions; -using Knot.Modules.Profiles.Domain; -namespace Knot.Modules.Profiles.Application.Profiles.DTOs; - -public sealed record UpdateProfileRequest(string? DisplayName, string? Bio, DateTime? Birthday); diff --git a/backend/src/Modules/Profiles/Application/Profiles/DTOs/UpdateSettingsRequest.cs b/backend/src/Modules/Profiles/Application/Profiles/DTOs/UpdateSettingsRequest.cs deleted file mode 100644 index 7fe83f2..0000000 --- a/backend/src/Modules/Profiles/Application/Profiles/DTOs/UpdateSettingsRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Knot.Modules.Profiles.Application.Abstractions; -using Knot.Modules.Profiles.Domain; -namespace Knot.Modules.Profiles.Application.Profiles.DTOs; - -public sealed record UpdateSettingsRequest(bool? HideStoryViews); diff --git a/backend/src/Modules/Profiles/Application/Profiles/DTOs/UserDto.cs b/backend/src/Modules/Profiles/Application/Profiles/DTOs/UserDto.cs deleted file mode 100644 index e897d11..0000000 --- a/backend/src/Modules/Profiles/Application/Profiles/DTOs/UserDto.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -using Knot.Modules.Profiles.Application.Abstractions; -using Knot.Modules.Profiles.Domain; -namespace Knot.Modules.Profiles.Application.Profiles.DTOs; - -public record ProfileDto( - Guid Id, - string Profilename, - string DisplayName, - string? Avatar, - bool IsOnline, - DateTime LastSeen -); diff --git a/backend/src/Modules/Profiles/Application/Profiles/DTOs/UserProfileDto.cs b/backend/src/Modules/Profiles/Application/Profiles/DTOs/UserProfileDto.cs index d8ef1da..2bdfe81 100644 --- a/backend/src/Modules/Profiles/Application/Profiles/DTOs/UserProfileDto.cs +++ b/backend/src/Modules/Profiles/Application/Profiles/DTOs/UserProfileDto.cs @@ -1,18 +1,29 @@ using System; - -using Knot.Modules.Profiles.Application.Abstractions; using Knot.Modules.Profiles.Domain; + namespace Knot.Modules.Profiles.Application.Profiles.DTOs; -public record ProfileProfileDto( +public record UserProfileDto( Guid Id, - string Profilename, + string UserName, string DisplayName, - string? Avatar, + string? AvatarUrl, string? Bio, DateTime? Birthday, DateTime CreatedAt, - bool? HideStoryViews = null, + bool HideStoryViews = false, bool IsOnline = false, - DateTime? LastSeen = null -); + DateTime? LastSeen = null) +{ + public static UserProfileDto FromDocument(ProfileDocument doc) => + new( + doc.Id, + doc.Username, + doc.DisplayName, + doc.AvatarUrl, + doc.Bio, + doc.Birthday, + doc.CreatedAt, + doc.HideStoryViews + ); +} diff --git a/backend/src/Modules/Profiles/Application/Profiles/GetUser/GetUser.cs b/backend/src/Modules/Profiles/Application/Profiles/GetUser/GetUser.cs index 334e7e9..e39eadf 100644 --- a/backend/src/Modules/Profiles/Application/Profiles/GetUser/GetUser.cs +++ b/backend/src/Modules/Profiles/Application/Profiles/GetUser/GetUser.cs @@ -1,36 +1,29 @@ using Knot.Shared.Kernel; -using Knot.Modules.Profiles.Domain; - - using Knot.Modules.Profiles.Application.Abstractions; +using Knot.Modules.Profiles.Application.Profiles.DTOs; +using System; +using System.Threading; +using System.Threading.Tasks; + namespace Knot.Modules.Profiles.Application.Profiles.GetProfile; -public sealed record GetProfileQuery(Guid Id) : IQuery; +public sealed record GetProfileQuery(Guid UserId) : IQuery; -internal sealed class GetProfileQueryHandler : IQueryHandler +internal sealed class GetProfileQueryHandler : IQueryHandler { - private readonly IProfileRepository _profileRepository; + private readonly IProfileRepository _repository; - public GetProfileQueryHandler(IProfileRepository profileRepository) + public GetProfileQueryHandler(IProfileRepository repository) { - _profileRepository = profileRepository; + _repository = repository; } - public async Task> Handle(GetProfileQuery request, CancellationToken cancellationToken) + public async Task> Handle(GetProfileQuery request, CancellationToken cancellationToken) { - var profile = await _profileRepository.GetByIdAsync(request.Id, cancellationToken); - if (profile == null) - { - return Result.Failure(ProfilesErrors.ProfileNotFound); - } + var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken); + if (profile is null) + return Result.Failure(ProfilesErrors.ProfileNotFound); - var dto = new ProfileProfileDto( - profile.Id, - profile.Profilename, - profile.Name, - profile.AvatarUrl - ); - - return Result.Success(dto); + return Result.Success(UserProfileDto.FromDocument(profile)); } } diff --git a/backend/src/Modules/Profiles/Application/Profiles/Integration/UserRegisteredDomainEventHandler.cs b/backend/src/Modules/Profiles/Application/Profiles/Integration/UserRegisteredDomainEventHandler.cs new file mode 100644 index 0000000..ec6ac20 --- /dev/null +++ b/backend/src/Modules/Profiles/Application/Profiles/Integration/UserRegisteredDomainEventHandler.cs @@ -0,0 +1,38 @@ +using Knot.Modules.Profiles.Application.Abstractions; +using Knot.Modules.Profiles.Domain; +using Knot.Shared.Kernel.Events; +using MediatR; +using System.Threading; +using System.Threading.Tasks; + +namespace Knot.Modules.Profiles.Application.Profiles.Integration; + +/// +/// Реакция модуля Profiles на создание пользователя в модуле Auth. +/// Создает соответствующий документ в MongoDB. +/// +internal sealed class UserRegisteredDomainEventHandler : INotificationHandler +{ + private readonly IProfileRepository _repository; + + public UserRegisteredDomainEventHandler(IProfileRepository repository) + { + _repository = repository; + } + + public async Task Handle(UserRegisteredDomainEvent notification, CancellationToken cancellationToken) + { + // Проверяем, существует ли уже профиль (защита от дублей) + var existing = await _repository.GetByIdAsync(notification.UserId, cancellationToken); + if (existing is not null) return; + + var profile = ProfileDocument.Create( + notification.UserId, + notification.Username, + notification.DisplayName, + notification.Bio + ); + + await _repository.AddAsync(profile, cancellationToken); + } +} diff --git a/backend/src/Modules/Profiles/Application/Profiles/Search/SearchUsers.cs b/backend/src/Modules/Profiles/Application/Profiles/Search/SearchUsers.cs index b9adadc..162b063 100644 --- a/backend/src/Modules/Profiles/Application/Profiles/Search/SearchUsers.cs +++ b/backend/src/Modules/Profiles/Application/Profiles/Search/SearchUsers.cs @@ -1,34 +1,28 @@ using Knot.Shared.Kernel; -using Knot.Modules.Profiles.Domain; - - using Knot.Modules.Profiles.Application.Abstractions; +using Knot.Modules.Profiles.Application.Profiles.DTOs; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + namespace Knot.Modules.Profiles.Application.Profiles.Search; -public sealed record SearchProfilesQuery(string Query) : IQuery>; +public sealed record SearchProfilesQuery(string Query) : IQuery>; -internal sealed class SearchProfilesQueryHandler : IQueryHandler> +internal sealed class SearchProfilesQueryHandler : IQueryHandler> { - private readonly IProfileRepository _profileRepository; + private readonly IProfileRepository _repository; - public SearchProfilesQueryHandler(IProfileRepository profileRepository) + public SearchProfilesQueryHandler(IProfileRepository repository) { - _profileRepository = profileRepository; + _repository = repository; } - public async Task>> Handle(SearchProfilesQuery request, CancellationToken cancellationToken) + public async Task>> Handle(SearchProfilesQuery request, CancellationToken cancellationToken) { - var profiles = await _profileRepository.SearchProfilesAsync(request.Query, cancellationToken); - - var result = profiles.Select(profile => new ProfileDto( - profile.Id, - profile.Profilename, - profile.DisplayName, - profile.Avatar, - false, - DateTime.UtcNow - )).ToList(); - - return Result.Success(result); + var profiles = await _repository.SearchAsync(request.Query, cancellationToken); + var dtos = profiles.Select(UserProfileDto.FromDocument).ToList(); + return Result.Success(dtos); } } diff --git a/backend/src/Modules/Profiles/Application/Profiles/UpdateProfile/UpdateProfile.cs b/backend/src/Modules/Profiles/Application/Profiles/UpdateProfile/UpdateProfile.cs index 27d24e2..df2d880 100644 --- a/backend/src/Modules/Profiles/Application/Profiles/UpdateProfile/UpdateProfile.cs +++ b/backend/src/Modules/Profiles/Application/Profiles/UpdateProfile/UpdateProfile.cs @@ -1,43 +1,40 @@ using Knot.Shared.Kernel; - - +using Knot.Modules.Profiles.Application.Abstractions; +using Knot.Modules.Profiles.Application.Profiles.DTOs; +using System; +using System.Threading; +using System.Threading.Tasks; namespace Knot.Modules.Profiles.Application.Profiles.UpdateProfile; -public sealed record UpdateProfileCommand(Guid UserId, string? DisplayName, string? Bio, DateTime? Birthday) : ICommand; +public sealed record UpdateProfileCommand( + Guid UserId, + string? DisplayName, + string? Bio, + DateTime? Birthday) : ICommand; internal sealed class UpdateProfileCommandHandler : ICommandHandler { - private readonly IProfileRepository _userRepository; - private readonly IProfilesUnitOfWork _unitOfWork; + private readonly IProfileRepository _repository; - public UpdateProfileCommandHandler(IProfileRepository userRepository, IProfilesUnitOfWork unitOfWork) + public UpdateProfileCommandHandler(IProfileRepository repository) { - _userRepository = userRepository; - _unitOfWork = unitOfWork; + _repository = repository; } public async Task> Handle(UpdateProfileCommand request, CancellationToken cancellationToken) { - var user = await _userRepository.GetByIdAsync(request.UserId, cancellationToken); - if (user == null) - { - return Result.Failure(IdentityErrors.UserNotFound); - } + var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken); + if (profile is null) + return Result.Failure(ProfilesErrors.ProfileNotFound); - user.UpdateProfile(request.DisplayName ?? user.DisplayName, request.Bio, request.Birthday); - await _unitOfWork.SaveChangesAsync(cancellationToken); + profile.UpdateProfile( + request.DisplayName ?? profile.DisplayName, + request.Bio, + request.Birthday); - var dto = new UserProfileDto( - user.Id, - user.Username, - user.DisplayName, - user.Avatar, - user.Bio, - user.Birthday, - user.CreatedAt - ); + await _repository.UpdateAsync(profile, cancellationToken); - return Result.Success(dto); + return Result.Success(UserProfileDto.FromDocument(profile)); } } diff --git a/backend/src/Modules/Profiles/Application/Profiles/UpdateSettings/UpdateSettings.cs b/backend/src/Modules/Profiles/Application/Profiles/UpdateSettings/UpdateSettings.cs index e47f941..7b2a702 100644 --- a/backend/src/Modules/Profiles/Application/Profiles/UpdateSettings/UpdateSettings.cs +++ b/backend/src/Modules/Profiles/Application/Profiles/UpdateSettings/UpdateSettings.cs @@ -1,41 +1,32 @@ using Knot.Shared.Kernel; -using Knot.Modules.Profiles.Domain; - - using Knot.Modules.Profiles.Application.Abstractions; +using Knot.Modules.Profiles.Application.Profiles.DTOs; +using System; +using System.Threading; +using System.Threading.Tasks; + namespace Knot.Modules.Profiles.Application.Profiles.UpdateSettings; -public sealed record UpdateSettingsCommand(Guid ProfileId, bool? HideStoryViews) : ICommand; +public sealed record UpdateSettingsCommand(Guid UserId, bool? HideStoryViews) : ICommand; -internal sealed class UpdateSettingsCommandHandler : ICommandHandler +internal sealed class UpdateSettingsCommandHandler : ICommandHandler { - private readonly IProfileRepository _profileRepository; - private readonly IProfilesUnitOfWork _unitOfWork; + private readonly IProfileRepository _repository; - public UpdateSettingsCommandHandler(IProfileRepository profileRepository, IProfilesUnitOfWork unitOfWork) + public UpdateSettingsCommandHandler(IProfileRepository repository) { - _profileRepository = profileRepository; - _unitOfWork = unitOfWork; + _repository = repository; } - public async Task> Handle(UpdateSettingsCommand request, CancellationToken cancellationToken) + public async Task> Handle(UpdateSettingsCommand request, CancellationToken cancellationToken) { - var profile = await _profileRepository.GetByIdAsync(request.ProfileId, cancellationToken); - if (profile == null) - { - return Result.Failure(ProfilesErrors.ProfileNotFound); - } + var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken); + if (profile is null) + return Result.Failure(ProfilesErrors.ProfileNotFound); profile.UpdateSettings(request.HideStoryViews ?? profile.HideStoryViews); - await _unitOfWork.SaveChangesAsync(cancellationToken); + await _repository.UpdateAsync(profile, cancellationToken); - var dto = new ProfileProfileDto( - profile.Id, - profile.Profilename, - profile.Name, - profile.AvatarUrl - ); - - return Result.Success(dto); + return Result.Success(UserProfileDto.FromDocument(profile)); } } diff --git a/backend/src/Modules/Profiles/Application/Profiles/UserDto.cs b/backend/src/Modules/Profiles/Application/Profiles/UserDto.cs deleted file mode 100644 index d1510cc..0000000 --- a/backend/src/Modules/Profiles/Application/Profiles/UserDto.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -using Knot.Modules.Profiles.Application.Abstractions; -using Knot.Modules.Profiles.Domain; -namespace Knot.Modules.Profiles.Application.Profiles; - -public record ProfileDto( - Guid Id, - string Profilename, - string DisplayName, - string? Avatar, - bool IsOnline, - DateTime LastSeen -); diff --git a/backend/src/Modules/Profiles/Application/Profiles/UserProfileDto.cs b/backend/src/Modules/Profiles/Application/Profiles/UserProfileDto.cs deleted file mode 100644 index 3e4c805..0000000 --- a/backend/src/Modules/Profiles/Application/Profiles/UserProfileDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Knot.Modules.Profiles.Application.Profiles; -using System; -public record UserProfileDto(Guid Id, string UserName, string Name, string AvatarUrl) -{ - public UserProfileDto(Guid a, string b, string c, string d, string e, bool f, int g, int h, int i, string j = "") : this(a,b,c,e) {} - public UserProfileDto(Guid a, string b, string c, string d, string e, DateTime? f, DateTime g, string h="", string i="", string j="") : this(a,b,c,e) {} -} -public record ProfileProfileDto(Guid Id, string UserName, string Name, string AvatarUrl) -{ - public ProfileProfileDto(Guid a, string b, string c, string d, string e, bool f, int g, int h, int i, string j = "") : this(a,b,c,e) {} - public ProfileProfileDto(Guid a, string b, string c, string d, string e, DateTime? f, DateTime g, string h="", string i="", string j="") : this(a,b,c,e) {} -} diff --git a/backend/src/Modules/Profiles/DependencyInjection.cs b/backend/src/Modules/Profiles/DependencyInjection.cs new file mode 100644 index 0000000..c9b56bd --- /dev/null +++ b/backend/src/Modules/Profiles/DependencyInjection.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Knot.Modules.Profiles.Application.Abstractions; +using Knot.Modules.Profiles.Infrastructure.Database; +using MongoDB.Driver; + +namespace Knot.Modules.Profiles; + +public static class DependencyInjection +{ + public static IServiceCollection AddProfilesModule(this IServiceCollection services, IConfiguration configuration) + { + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + // MongoDB Registration + var mongoConnection = configuration.GetConnectionString("MongoConnection") + ?? configuration["MONGO_URL"] + ?? "mongodb://localhost:27017"; + + var mongoClient = new MongoClient(mongoConnection); + var database = mongoClient.GetDatabase("knot_messager"); + services.AddSingleton(database); + + services.AddMediatR(config => + config.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly)); + + return services; + } +} diff --git a/backend/src/Modules/Profiles/Domain/Events/ProfileDomainEvents.cs b/backend/src/Modules/Profiles/Domain/Events/ProfileDomainEvents.cs new file mode 100644 index 0000000..191e9d5 --- /dev/null +++ b/backend/src/Modules/Profiles/Domain/Events/ProfileDomainEvents.cs @@ -0,0 +1,14 @@ +using Knot.Shared.Kernel; +using System; + +namespace Knot.Modules.Profiles.Domain.Events; + +/// +/// Доменное событие: профиль пользователя создан при регистрации. +/// +public sealed record ProfileCreatedDomainEvent(Guid UserId, string Username) : IDomainEvent; + +/// +/// Доменное событие: аватар профиля был изменён (для возможной инвалидации CDN-кэша). +/// +public sealed record ProfileAvatarChangedDomainEvent(Guid UserId, string? OldAvatarFileId, string? NewAvatarFileId) : IDomainEvent; diff --git a/backend/src/Modules/Profiles/Domain/ProfileDocument.cs b/backend/src/Modules/Profiles/Domain/ProfileDocument.cs new file mode 100644 index 0000000..352d5f7 --- /dev/null +++ b/backend/src/Modules/Profiles/Domain/ProfileDocument.cs @@ -0,0 +1,69 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using System; + +namespace Knot.Modules.Profiles.Domain; + +/// +/// MongoDB-документ профиля пользователя. +/// Id совпадает с UserId из модуля Auth (Postgres). +/// Аватар хранится в S3 — здесь лежит только ссылка. +/// +public sealed class ProfileDocument +{ + [BsonId] + [BsonRepresentation(BsonType.String)] + public Guid Id { get; private set; } + + public string Username { get; private set; } + + public string DisplayName { get; private set; } + + public string? Bio { get; private set; } + + /// + /// URL или ключ объекта в S3-хранилище (MinIO). + /// Пример: "/api/files/{fileId}" + /// + public string? AvatarUrl { get; private set; } + + public DateTime? Birthday { get; private set; } + + /// + /// Скрывать ли просмотры сторис от других пользователей. + /// + public bool HideStoryViews { get; private set; } + + public DateTime CreatedAt { get; private set; } + + // Для MongoDB — protected-конструктор через BSON-десериализацию + protected ProfileDocument() { } + + private ProfileDocument(Guid id, string username, string displayName, string? bio) + { + Id = id; + Username = username; + DisplayName = displayName; + Bio = bio; + CreatedAt = DateTime.UtcNow; + } + + public static ProfileDocument Create(Guid userId, string username, string displayName, string? bio = null) + => new(userId, username, displayName, bio); + + public void UpdateProfile(string displayName, string? bio, DateTime? birthday) + { + DisplayName = displayName; + Bio = bio; + Birthday = birthday; + } + + public void UpdateAvatar(string? avatarUrl) + => AvatarUrl = avatarUrl; + + public void RemoveAvatar() + => AvatarUrl = null; + + public void UpdateSettings(bool hideStoryViews) + => HideStoryViews = hideStoryViews; +} diff --git a/backend/src/Modules/Profiles/Infrastructure/Database/AvatarStorageService.cs b/backend/src/Modules/Profiles/Infrastructure/Database/AvatarStorageService.cs new file mode 100644 index 0000000..41df150 --- /dev/null +++ b/backend/src/Modules/Profiles/Infrastructure/Database/AvatarStorageService.cs @@ -0,0 +1,33 @@ +using Knot.Modules.Profiles.Application.Abstractions; +using Knot.Shared.Kernel.Storage; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Knot.Modules.Profiles.Infrastructure.Database; + +/// +/// Адаптер к S3-хранилищу для аватаров. +/// Инжектирует общий IFileStorageService и передает ему управление. +/// +internal sealed class AvatarStorageService : IAvatarStorageService +{ + private readonly IFileStorageService _storage; + + public AvatarStorageService(IFileStorageService storage) + { + _storage = storage; + } + + public async Task UploadAsync(Stream stream, string fileName, string contentType, CancellationToken ct = default) + { + return await _storage.UploadFileAsync(stream, fileName, contentType); + } + + public async Task DeleteAsync(string fileId, CancellationToken ct = default) + { + // Если fileId содержит "/api/files/", обрезаем его до чистого ID + var cleanId = fileId.Replace("/api/files/", ""); + await _storage.DeleteFileAsync(cleanId); + } +} diff --git a/backend/src/Modules/Profiles/Infrastructure/Database/ProfileRepository.cs b/backend/src/Modules/Profiles/Infrastructure/Database/ProfileRepository.cs new file mode 100644 index 0000000..96f70b8 --- /dev/null +++ b/backend/src/Modules/Profiles/Infrastructure/Database/ProfileRepository.cs @@ -0,0 +1,48 @@ +using Knot.Modules.Profiles.Application.Abstractions; +using Knot.Modules.Profiles.Domain; +using MongoDB.Driver; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Knot.Modules.Profiles.Infrastructure.Database; + +internal sealed class ProfileRepository : IProfileRepository +{ + private readonly IMongoCollection _profiles; + + public ProfileRepository(IMongoDatabase database) + { + _profiles = database.GetCollection("profiles"); + } + + public async Task GetByIdAsync(Guid userId, CancellationToken ct = default) + { + return await _profiles.Find(p => p.Id == userId).FirstOrDefaultAsync(ct); + } + + public async Task AddAsync(ProfileDocument profile, CancellationToken ct = default) + { + await _profiles.InsertOneAsync(profile, null, ct); + } + + public async Task UpdateAsync(ProfileDocument profile, CancellationToken ct = default) + { + await _profiles.ReplaceOneAsync(p => p.Id == profile.Id, profile, new ReplaceOptions { IsUpsert = false }, ct); + } + + public async Task> SearchAsync(string query, CancellationToken ct = default) + { + if (string.IsNullOrWhiteSpace(query)) + return new List(); + + // Простой регистронезависимый поиск по Regex (в реальной системе лучше использовать Text Index) + var filter = Builders.Filter.Or( + Builders.Filter.Regex(p => p.Username, new MongoDB.Bson.BsonRegularExpression(query, "i")), + Builders.Filter.Regex(p => p.DisplayName, new MongoDB.Bson.BsonRegularExpression(query, "i")) + ); + + return await _profiles.Find(filter).Limit(20).ToListAsync(ct); + } +} diff --git a/backend/src/Modules/Profiles/Infrastructure/Database/ProfilesUnitOfWork.cs b/backend/src/Modules/Profiles/Infrastructure/Database/ProfilesUnitOfWork.cs new file mode 100644 index 0000000..230459e --- /dev/null +++ b/backend/src/Modules/Profiles/Infrastructure/Database/ProfilesUnitOfWork.cs @@ -0,0 +1,12 @@ +using Knot.Modules.Profiles.Application.Abstractions; +using System.Threading; +using System.Threading.Tasks; + +namespace Knot.Modules.Profiles.Infrastructure.Database; + +internal sealed class ProfilesUnitOfWork : IProfilesUnitOfWork +{ + // MongoDB updates are atomic per document by default in the driver, + // so for simple ProfileDocument updates, we don't need distributed transactions. + public Task SaveChangesAsync(CancellationToken ct = default) => Task.CompletedTask; +} diff --git a/backend/src/Modules/Profiles/Knot.Modules.Profiles.csproj b/backend/src/Modules/Profiles/Knot.Modules.Profiles.csproj index 6dbbdda..3a70efd 100644 --- a/backend/src/Modules/Profiles/Knot.Modules.Profiles.csproj +++ b/backend/src/Modules/Profiles/Knot.Modules.Profiles.csproj @@ -15,12 +15,22 @@ - + - + + + + + + + + + + + diff --git a/backend/src/Modules/Relations/Application/Abstractions/IRelationsDbContext.cs b/backend/src/Modules/Relations/Application/Abstractions/IRelationsDbContext.cs index 9ea1bba..5584f3e 100644 --- a/backend/src/Modules/Relations/Application/Abstractions/IRelationsDbContext.cs +++ b/backend/src/Modules/Relations/Application/Abstractions/IRelationsDbContext.cs @@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore; using System.Threading; using System.Threading.Tasks; -using Knot.Modules.Relations.Application.Abstractions; namespace Knot.Modules.Relations.Application.Abstractions; public interface IContactsDbContext diff --git a/backend/src/Modules/Relations/Application/Contacts/ContactDto.cs b/backend/src/Modules/Relations/Application/Contacts/ContactDto.cs new file mode 100644 index 0000000..98b0fba --- /dev/null +++ b/backend/src/Modules/Relations/Application/Contacts/ContactDto.cs @@ -0,0 +1,41 @@ +using System; + +namespace Knot.Modules.Relations.Application.Contacts; + +public class ContactDto +{ + public Guid Id { get; init; } + public string Username { get; init; } = string.Empty; + public string DisplayName { get; init; } = string.Empty; + public string Avatar { get; init; } = string.Empty; + public bool IsOnline { get; init; } + public DateTime? LastSeen { get; init; } + public Guid RelationId { get; init; } + public bool IsBlocked { get; init; } + public bool IsExternal { get; init; } + public string? Domain { get; init; } + + public ContactDto( + Guid id, + string username, + string displayName, + string avatar, + bool isOnline, + DateTime? lastSeen, + Guid relationId, + bool isBlocked = false, + bool isExternal = false, + string? domain = null) + { + Id = id; + Username = username; + DisplayName = displayName; + Avatar = avatar; + IsOnline = isOnline; + LastSeen = lastSeen; + RelationId = relationId; + IsBlocked = isBlocked; + IsExternal = isExternal; + Domain = domain; + } +} diff --git a/backend/src/Modules/Relations/Application/Contacts/GetContacts.cs b/backend/src/Modules/Relations/Application/Contacts/GetContacts.cs index 0ca03ac..43c627a 100644 --- a/backend/src/Modules/Relations/Application/Contacts/GetContacts.cs +++ b/backend/src/Modules/Relations/Application/Contacts/GetContacts.cs @@ -10,18 +10,6 @@ using Knot.Modules.Relations.Application.Abstractions; namespace Knot.Modules.Relations.Application.Contacts; -public record ContactDto( - Guid Id, - string Username, - string DisplayName, - string Avatar, - bool IsOnline, - DateTime? LastSeen, - Guid RelationId, - bool IsBlocked = false, - bool IsExternal = false, - string? Domain = null); - public record GetContactsQuery(Guid UserId) : IQuery>; internal sealed class GetContactsQueryHandler : IQueryHandler> @@ -56,7 +44,7 @@ internal sealed class GetContactsQueryHandler : IQueryHandler(options => + options.UseNpgsql(connectionString)); + + services.AddMediatR(config => + config.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly)); + + return services; + } +} diff --git a/backend/src/Modules/Relations/Knot.Modules.Relations.csproj b/backend/src/Modules/Relations/Knot.Modules.Relations.csproj index c293968..67e2845 100644 --- a/backend/src/Modules/Relations/Knot.Modules.Relations.csproj +++ b/backend/src/Modules/Relations/Knot.Modules.Relations.csproj @@ -8,6 +8,7 @@ + @@ -15,11 +16,15 @@ - + + + + + diff --git a/backend/src/Modules/Relations/Presentation/Endpoints/FriendsEndpoints.cs b/backend/src/Modules/Relations/Presentation/Endpoints/ContactsEndpoints.cs similarity index 95% rename from backend/src/Modules/Relations/Presentation/Endpoints/FriendsEndpoints.cs rename to backend/src/Modules/Relations/Presentation/Endpoints/ContactsEndpoints.cs index 7d51408..104e6e2 100644 --- a/backend/src/Modules/Relations/Presentation/Endpoints/FriendsEndpoints.cs +++ b/backend/src/Modules/Relations/Presentation/Endpoints/ContactsEndpoints.cs @@ -1,13 +1,12 @@ using Carter; using Knot.Shared.Kernel; -using Knot.Modules.Relations.Application.Friends; -using Knot.Modules.Relations.Application.Friends.DTOs; +using Knot.Shared.Infrastructure; +using Knot.Modules.Relations.Application.Contacts; using MediatR; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Mvc; -using Knot.Modules.Relations.Application.Contacts; namespace Knot.Modules.Relations.Presentation.Endpoints; diff --git a/backend/src/Modules/Relations/rel_err.json b/backend/src/Modules/Relations/rel_err.json new file mode 100644 index 0000000..91a6b96 --- /dev/null +++ b/backend/src/Modules/Relations/rel_err.json @@ -0,0 +1,69 @@ +{ + "$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": "CS1520", + "level": "error", + "message": "Метод должен иметь тип возвращаемого значения", + "locations": [ + { + "resultFile": { + "uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Relations/Application/Contacts/RemoveContact.cs", + "region": { + "startLine": 17, + "startColumn": 12, + "endLine": 17, + "endColumn": 35 + } + } + } + ] + }, + { + "ruleId": "CS1520", + "level": "error", + "message": "Метод должен иметь тип возвращаемого значения", + "locations": [ + { + "resultFile": { + "uri": "file:///E:/GIT/forkmessager/backend/src/Modules/Relations/Application/Contacts/RemoveContact.cs", + "region": { + "startLine": 18, + "startColumn": 12, + "endLine": 18, + "endColumn": 39 + } + } + } + ] + } + ], + "rules": { + "CS1520": { + "id": "CS1520", + "defaultLevel": "error", + "helpUri": "https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS1520)", + "properties": { + "category": "Compiler", + "isEnabledByDefault": true, + "tags": [ + "Compiler", + "Telemetry", + "NotConfigurable" + ] + } + } + } + } + ] +} \ No newline at end of file diff --git a/backend/src/Shared/Knot.Shared.Kernel/Configuration/ISettingsService.cs b/backend/src/Modules/Settings/Application/Settings/Abstractions/ISettingsService.cs similarity index 88% rename from backend/src/Shared/Knot.Shared.Kernel/Configuration/ISettingsService.cs rename to backend/src/Modules/Settings/Application/Settings/Abstractions/ISettingsService.cs index c053d7a..46cef1d 100644 --- a/backend/src/Shared/Knot.Shared.Kernel/Configuration/ISettingsService.cs +++ b/backend/src/Modules/Settings/Application/Settings/Abstractions/ISettingsService.cs @@ -1,7 +1,8 @@ using System.Threading; using System.Threading.Tasks; +using Knot.Modules.Settings.Application.Settings.DTOs; -namespace Knot.Shared.Kernel.Configuration; +namespace Knot.Modules.Settings.Application.Settings.Abstractions; public interface ISettingsService { diff --git a/backend/src/Modules/Settings/Application/Settings/Commands/UpdateSettingsCommand.cs b/backend/src/Modules/Settings/Application/Settings/Commands/UpdateSettingsCommand.cs new file mode 100644 index 0000000..5c7b1d9 --- /dev/null +++ b/backend/src/Modules/Settings/Application/Settings/Commands/UpdateSettingsCommand.cs @@ -0,0 +1,44 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Knot.Shared.Kernel; +using System.Security.Cryptography; +using Knot.Modules.Settings.Application.Settings.DTOs; +using Knot.Modules.Settings.Application.Settings.Abstractions; + +namespace Knot.Modules.Settings.Application.Settings.Commands; + +public record UpdateSettingsCommand(SystemSettingsDto Settings) : ICommand; + +internal sealed class UpdateSettingsCommandHandler : ICommandHandler +{ + private readonly ISettingsService _settingsService; + private readonly IMediator _mediator; + + public UpdateSettingsCommandHandler(ISettingsService settingsService, IMediator mediator) + { + _settingsService = settingsService; + _mediator = mediator; + } + + public async Task> Handle(UpdateSettingsCommand request, CancellationToken cancellationToken) + { + if (request.Settings.Federation.Enabled && + (string.IsNullOrEmpty(request.Settings.Federation.PrivateKey) || + string.IsNullOrEmpty(request.Settings.Federation.PublicKey))) + { + using var rsa = RSA.Create(2048); + request.Settings.Federation.PrivateKey = Convert.ToBase64String(rsa.ExportPkcs8PrivateKey()); + request.Settings.Federation.PublicKey = Convert.ToBase64String(rsa.ExportRSAPublicKey()); + } + + await _settingsService.UpdateSettingsAsync(request.Settings, cancellationToken); + + // Notify other modules via Domain Event if needed, but here we can just publish + await _mediator.Publish(new Knot.Modules.Settings.Domain.Events.SystemSettingsUpdatedDomainEvent(request.Settings), cancellationToken); + + return Result.Success(request.Settings); + } +} + diff --git a/backend/src/Modules/Settings/Application/Config/DTOs/PublicConfigDto.cs b/backend/src/Modules/Settings/Application/Settings/DTOs/PublicConfigDto.cs similarity index 96% rename from backend/src/Modules/Settings/Application/Config/DTOs/PublicConfigDto.cs rename to backend/src/Modules/Settings/Application/Settings/DTOs/PublicConfigDto.cs index 71bd0f2..7fc200a 100644 --- a/backend/src/Modules/Settings/Application/Config/DTOs/PublicConfigDto.cs +++ b/backend/src/Modules/Settings/Application/Settings/DTOs/PublicConfigDto.cs @@ -1,7 +1,7 @@ -using Knot.Shared.Kernel.Configuration; using System.Collections.Generic; +using Knot.Modules.Settings.Application.Settings.DTOs; -namespace Knot.Modules.Settings.Application.Config.DTOs; +namespace Knot.Modules.Settings.Application.Settings.DTOs; public record PublicConfigDto { @@ -74,7 +74,7 @@ public record PublicConfigDto }, Import = new ImportConfigDto { - EnableTelegramImport = settings.Import.EnableTelegramImport + Enabled = settings.Import.EnableTelegramImport }, Federation = new FederationConfigDto { @@ -148,7 +148,7 @@ public record KlipyConfigDto public record ImportConfigDto { - public bool EnableTelegramImport { get; init; } + public bool Enabled { get; init; } } public record FederationConfigDto @@ -156,4 +156,4 @@ public record FederationConfigDto public bool Enabled { get; init; } public string ServerDescription { get; init; } = string.Empty; public List AllowedDomains { get; init; } = new(); -} \ No newline at end of file +} diff --git a/backend/src/Shared/Knot.Shared.Kernel/Configuration/SystemSettingsDto.cs b/backend/src/Modules/Settings/Application/Settings/DTOs/SystemSettingsDto.cs similarity index 82% rename from backend/src/Shared/Knot.Shared.Kernel/Configuration/SystemSettingsDto.cs rename to backend/src/Modules/Settings/Application/Settings/DTOs/SystemSettingsDto.cs index 7ed1776..d34cfaf 100644 --- a/backend/src/Shared/Knot.Shared.Kernel/Configuration/SystemSettingsDto.cs +++ b/backend/src/Modules/Settings/Application/Settings/DTOs/SystemSettingsDto.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Knot.Shared.Kernel.Configuration; +namespace Knot.Modules.Settings.Application.Settings.DTOs; public class SystemConfig { @@ -13,22 +13,18 @@ public class SystemConfig public class StoriesConfig { public bool Enabled { get; set; } = true; - public int MaxStoriesPerPeriod { get; set; } = 5; // 1 to 10 - public int StoryLifetimeHours { get; set; } = 24; // 4 to 48 - - // Text Stories + public int MaxStoriesPerPeriod { get; set; } = 5; + public int StoryLifetimeHours { get; set; } = 24; public bool TextStoriesEnabled { get; set; } = true; - public int TextStoryDurationSeconds { get; set; } = 15; // 5 to 30 - - // Media Stories - public int MediaStoryMaxDurationSeconds { get; set; } = 30; // 5 to 60 - public int MaxMediaSizeBytes { get; set; } = 15 * 1024 * 1024; // 15MB + public int TextStoryDurationSeconds { get; set; } = 15; + public int MediaStoryMaxDurationSeconds { get; set; } = 30; + public int MaxMediaSizeBytes { get; set; } = 15 * 1024 * 1024; } public class ChatsConfig { public bool SupportGroups { get; set; } = true; - public int MaxGroupParticipants { get; set; } = 200000; // 5 to 1,000,000 + public int MaxGroupParticipants { get; set; } = 200000; public bool AutoCleanChats { get; set; } = false; public bool AllowChatToGroupConversion { get; set; } = true; public bool EnableFolders { get; set; } = true; @@ -36,13 +32,11 @@ public class ChatsConfig public class MessagesConfig { - public int DailyMessageLimitPerUser { get; set; } = 0; // 0 = unlimited - public int ChatMessageLimit { get; set; } = 0; // 0 = unlimited - + public int DailyMessageLimitPerUser { get; set; } = 0; + public int ChatMessageLimit { get; set; } = 0; public bool AllowMedia { get; set; } = true; - public int MaxMediaSizeBytes { get; set; } = 50 * 1024 * 1024; // 50MB + public int MaxMediaSizeBytes { get; set; } = 50 * 1024 * 1024; public List AllowedMediaTypes { get; set; } = new() { "image/jpeg", "image/png", "video/mp4", "image/gif" }; - public bool AllowVoiceMessages { get; set; } = true; public bool AllowForwarding { get; set; } = true; public bool AllowReactions { get; set; } = true; @@ -99,9 +93,9 @@ public class RemoteCapabilities public class FederationConfig { public bool Enabled { get; set; } = false; - public string ServerDescription { get; set; } = string.Empty; // 20 to 120 chars - public string? PrivateKey { get; set; } // RSA PEM - public string? PublicKey { get; set; } // RSA PEM + public string ServerDescription { get; set; } = string.Empty; + public string? PrivateKey { get; set; } + public string? PublicKey { get; set; } public List AllowedDomains { get; set; } = new(); } diff --git a/backend/src/Modules/Settings/Application/Config/Queries/GetPublicConfigQuery.cs b/backend/src/Modules/Settings/Application/Settings/Queries/GetPublicConfigQuery.cs similarity index 66% rename from backend/src/Modules/Settings/Application/Config/Queries/GetPublicConfigQuery.cs rename to backend/src/Modules/Settings/Application/Settings/Queries/GetPublicConfigQuery.cs index 214811d..2e394df 100644 --- a/backend/src/Modules/Settings/Application/Config/Queries/GetPublicConfigQuery.cs +++ b/backend/src/Modules/Settings/Application/Settings/Queries/GetPublicConfigQuery.cs @@ -1,16 +1,13 @@ -using Knot.Modules.Settings.Application.Config.DTOs; -using Knot.Shared.Kernel; -using Knot.Shared.Kernel.Configuration; -using MediatR; -using Knot.Modules.Auth.Application.Auth.DTOs; - -using Knot.Modules.Auth.Application.Users; using System.Threading; using System.Threading.Tasks; +using MediatR; +using Knot.Shared.Kernel; +using Knot.Modules.Settings.Application.Settings.Abstractions; +using Knot.Modules.Settings.Application.Settings.DTOs; -namespace Host.Application.Config.Queries; +namespace Knot.Modules.Settings.Application.Settings.Queries; -public record GetPublicConfigQuery : IQuery; +public record GetPublicConfigQuery() : IQuery; internal sealed class GetPublicConfigQueryHandler : IQueryHandler { @@ -25,4 +22,4 @@ internal sealed class GetPublicConfigQueryHandler : IQueryHandler; + +internal sealed class GetSettingsQueryHandler : IQueryHandler +{ + private readonly ISettingsService _settingsService; + + public GetSettingsQueryHandler(ISettingsService settingsService) + { + _settingsService = settingsService; + } + + public async Task> Handle(GetSettingsQuery request, CancellationToken cancellationToken) + { + var settings = await _settingsService.GetSettingsAsync(cancellationToken); + return Result.Success(settings); + } +} diff --git a/backend/src/Modules/Settings/DependencyInjection.cs b/backend/src/Modules/Settings/DependencyInjection.cs index 4adfa41..c5a9a78 100644 --- a/backend/src/Modules/Settings/DependencyInjection.cs +++ b/backend/src/Modules/Settings/DependencyInjection.cs @@ -1,7 +1,26 @@ -namespace Knot.Modules.Settings; using Microsoft.Extensions.DependencyInjection; -public static class DependencyInjection { - public static IServiceCollection AddSettingsModule(this IServiceCollection services) { +using Microsoft.Extensions.Configuration; +using Knot.Modules.Settings.Application.Settings.Abstractions; +using Knot.Modules.Settings.Infrastructure.Configuration; + +namespace Knot.Modules.Settings; + +public static class DependencyInjection +{ + public static IServiceCollection AddSettingsModule(this IServiceCollection services, IConfiguration configuration) + { + services.AddSingleton(); + + services.AddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(sp => sp.GetRequiredService()); + return services; } } \ No newline at end of file diff --git a/backend/src/Modules/Settings/Domain/Events/SystemSettingsUpdatedDomainEvent.cs b/backend/src/Modules/Settings/Domain/Events/SystemSettingsUpdatedDomainEvent.cs new file mode 100644 index 0000000..68c3f1c --- /dev/null +++ b/backend/src/Modules/Settings/Domain/Events/SystemSettingsUpdatedDomainEvent.cs @@ -0,0 +1,11 @@ +using Knot.Modules.Settings.Application.Settings.DTOs; +using Knot.Shared.Kernel; + +namespace Knot.Modules.Settings.Domain.Events; + +/// +/// Domain Event: System settings updated. +/// Owned by Settings module. +/// +public sealed record SystemSettingsUpdatedDomainEvent(SystemSettingsDto Settings) : IDomainEvent; + diff --git a/backend/src/Shared/Knot.Shared.Infrastructure/Configuration/SettingsService.cs b/backend/src/Modules/Settings/Infrastructure/Configuration/SettingsService.cs similarity index 88% rename from backend/src/Shared/Knot.Shared.Infrastructure/Configuration/SettingsService.cs rename to backend/src/Modules/Settings/Infrastructure/Configuration/SettingsService.cs index 3a2c431..a3fa9ad 100644 --- a/backend/src/Shared/Knot.Shared.Infrastructure/Configuration/SettingsService.cs +++ b/backend/src/Modules/Settings/Infrastructure/Configuration/SettingsService.cs @@ -1,12 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; using System.Text.Json; -using Knot.Shared.Infrastructure.Persistence; -using Knot.Shared.Infrastructure.Persistence.Entities; -using Knot.Shared.Kernel.Configuration; -using Knot.Shared.Kernel.Security; +using System.Threading; +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Knot.Shared.Infrastructure.Persistence; +using Knot.Shared.Infrastructure.Persistence.Entities; +using Knot.Shared.Kernel.Security; +using Knot.Modules.Settings.Application.Settings.Abstractions; +using Knot.Modules.Settings.Application.Settings.DTOs; -namespace Knot.Shared.Infrastructure.Configuration; +namespace Knot.Modules.Settings.Infrastructure.Configuration; public class SettingsService : ISettingsService, ISystemSettings, @@ -26,7 +32,7 @@ public class SettingsService : ISettingsService, { _serviceProvider = serviceProvider; _encryptionService = encryptionService; - _current = new SystemSettingsDto(); // Default + _current = new SystemSettingsDto(); } public SystemSettingsDto Current => _current; @@ -63,7 +69,7 @@ public class SettingsService : ISettingsService, } catch { - // If decryption fails or JSON is invalid, return default + // Fail safe } return new SystemSettingsDto(); @@ -88,7 +94,7 @@ public class SettingsService : ISettingsService, } await db.SaveChangesAsync(cancellationToken); - _current = settings; // Update in-memory reference + _current = settings; } public void Initialize() diff --git a/backend/src/Modules/Settings/Knot.Modules.Settings.csproj b/backend/src/Modules/Settings/Knot.Modules.Settings.csproj index aedece0..c78e61a 100644 --- a/backend/src/Modules/Settings/Knot.Modules.Settings.csproj +++ b/backend/src/Modules/Settings/Knot.Modules.Settings.csproj @@ -7,11 +7,12 @@ - - - + - \ No newline at end of file + + + + diff --git a/backend/src/Modules/Settings/Presentation/Endpoints/SettingsEndpoints.cs b/backend/src/Modules/Settings/Presentation/Endpoints/SettingsEndpoints.cs deleted file mode 100644 index ed43cef..0000000 --- a/backend/src/Modules/Settings/Presentation/Endpoints/SettingsEndpoints.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Carter; -using MediatR; -using Knot.Shared.Kernel.Constants; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing; - -namespace Host.Endpoints; - -/// -/// Регистрация эндпоинтов для конфигурации приложения. -/// -public sealed class SettingsEndpoints : ICarterModule -{ - public void AddRoutes(IEndpointRouteBuilder app) - { - var group = app.MapGroup(Routes.ApiConfig); - - group.MapGet("/", async (ISender sender, CancellationToken ct) => - { - var result = await sender.Send(new Host.Application.Config.Queries.GetPublicConfigQuery(), ct); - return result.IsSuccess ? Results.Ok(result.Value) : Results.BadRequest(result.Error); - }); - } -} \ No newline at end of file diff --git a/backend/src/Modules/Storage/Knot.Modules.Storage.csproj b/backend/src/Modules/Storage/Knot.Modules.Storage.csproj index 7afe63f..a00fe29 100644 --- a/backend/src/Modules/Storage/Knot.Modules.Storage.csproj +++ b/backend/src/Modules/Storage/Knot.Modules.Storage.csproj @@ -17,6 +17,9 @@ + + + diff --git a/backend/src/Modules/Storage/Presentation/Endpoints/FilesEndpoints.cs b/backend/src/Modules/Storage/Presentation/Endpoints/FilesEndpoints.cs index 8e106d6..5ab0469 100644 --- a/backend/src/Modules/Storage/Presentation/Endpoints/FilesEndpoints.cs +++ b/backend/src/Modules/Storage/Presentation/Endpoints/FilesEndpoints.cs @@ -6,7 +6,8 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Mvc; -using Knot.Shared.Kernel.Configuration; +using Knot.Modules.Settings.Application.Settings.Abstractions; +using Knot.Modules.Settings.Application.Settings.DTOs; using System.Text; using System.Security.Cryptography; @@ -85,3 +86,4 @@ public sealed class FilesEndpoints : ICarterModule }); } } + diff --git a/backend/src/Modules/Stories/Application/Stories/Commands/CreateStory/CreateStoryCommand.cs b/backend/src/Modules/Stories/Application/Stories/Commands/CreateStory/CreateStoryCommand.cs index d163a6b..0981b7b 100644 --- a/backend/src/Modules/Stories/Application/Stories/Commands/CreateStory/CreateStoryCommand.cs +++ b/backend/src/Modules/Stories/Application/Stories/Commands/CreateStory/CreateStoryCommand.cs @@ -3,11 +3,12 @@ using System.Threading; using System.Threading.Tasks; using MediatR; using Knot.Shared.Kernel; -using Knot.Shared.Kernel.Configuration; +using Knot.Modules.Settings.Application.Settings.Abstractions; +using Knot.Modules.Settings.Application.Settings.DTOs; using Knot.Modules.Stories.Domain; -namespace Host.Application.Stories.Commands.CreateStory; +namespace Knot.Modules.Stories.Application.Stories.Commands.CreateStory; public record CreateStoryCommand(Guid UserId, string Type, string? MediaUrl, string? Content, string? BgColor) : ICommand; @@ -51,4 +52,3 @@ internal sealed class CreateStoryCommandHandler : ICommandHandler; internal sealed class DeleteStoryCommandHandler : ICommandHandler { - private readonly IStoriesDbContext _context; + private readonly Knot.Modules.Stories.Application.Abstractions.IStoryRepository _repository; - public DeleteStoryCommandHandler(IStoriesDbContext context) + public DeleteStoryCommandHandler(Knot.Modules.Stories.Application.Abstractions.IStoryRepository repository) { - _context = context; + _repository = repository; } public async Task> Handle(DeleteStoryCommand request, CancellationToken cancellationToken) { - var story = await _context.Stories.FindAsync(new object[] { request.StoryId }, cancellationToken); + var story = await _repository.GetByIdAsync(request.StoryId, cancellationToken); if (story == null) { return Result.Failure(StoryErrors.StoryNotFound); @@ -39,11 +39,12 @@ internal sealed class DeleteStoryCommandHandler : ICommandHandler(StoryErrors.Unauthorized); } - _context.Stories.Remove(story); - await _context.SaveChangesAsync(cancellationToken); + await _repository.DeleteAsync(story, cancellationToken); return Result.Success(new MessageResponse("Story deleted")); } } + + 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 d0c7170..347737e 100644 --- a/backend/src/Modules/Stories/Application/Stories/Commands/ViewStory/ViewStoryCommand.cs +++ b/backend/src/Modules/Stories/Application/Stories/Commands/ViewStory/ViewStoryCommand.cs @@ -1,4 +1,4 @@ -using Host.Application.Stories; +using Knot.Modules.Stories.Application.Stories; using System; using System.Linq; using System.Threading; @@ -17,7 +17,7 @@ using Knot.Modules.Stories.Infrastructure.Database; -namespace Host.Application.Stories.Commands.ViewStory; +namespace Knot.Modules.Stories.Application.Stories.Commands.ViewStory; public record ViewStoryCommand(Guid UserId, Guid StoryId) : ICommand; @@ -87,3 +87,5 @@ internal sealed class ViewStoryCommandHandler : ICommandHandler>; @@ -100,3 +100,4 @@ internal sealed class GetStoriesQueryHandler : IQueryHandler>; @@ -75,3 +75,5 @@ internal sealed class GetStoryViewersQueryHandler : IQueryHandler; @@ -77,3 +77,4 @@ internal sealed class GetUserStoriesQueryHandler : IQueryHandler - - + + + + + + diff --git a/backend/src/Modules/Stories/Presentation/Endpoints/StoriesEndpoints.cs b/backend/src/Modules/Stories/Presentation/Endpoints/StoriesEndpoints.cs index 502caf0..ab1de90 100644 --- a/backend/src/Modules/Stories/Presentation/Endpoints/StoriesEndpoints.cs +++ b/backend/src/Modules/Stories/Presentation/Endpoints/StoriesEndpoints.cs @@ -6,14 +6,14 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Mvc; -using Host.Application.Stories.Queries.GetStories; -using Host.Application.Stories.Commands.CreateStory; -using Host.Application.Stories.Queries.GetUserStories; -using Host.Application.Stories.Commands.ViewStory; -using Host.Application.Stories.Queries.GetStoryViewers; -using Host.Application.Stories.Commands.DeleteStory; +using Knot.Modules.Stories.Application.Stories.Queries.GetStories; +using Knot.Modules.Stories.Application.Stories.Commands.CreateStory; +using Knot.Modules.Stories.Application.Stories.Queries.GetUserStories; +using Knot.Modules.Stories.Application.Stories.Commands.ViewStory; +using Knot.Modules.Stories.Application.Stories.Queries.GetStoryViewers; +using Knot.Modules.Stories.Application.Stories.Commands.DeleteStory; -namespace Knot.Host.Presentation.Endpoints; +namespace Knot.Modules.Stories.Presentation.Endpoints; public sealed class StoriesEndpoints : ICarterModule { @@ -60,3 +60,5 @@ public sealed class StoriesEndpoints : ICarterModule }); } } + + diff --git a/backend/src/Modules/TelegramImport/Application/TelegramImport/AnalyzeImportCommand.cs b/backend/src/Modules/TelegramImport/Application/TelegramImport/AnalyzeImportCommand.cs index e61d838..d4ee4d2 100644 --- a/backend/src/Modules/TelegramImport/Application/TelegramImport/AnalyzeImportCommand.cs +++ b/backend/src/Modules/TelegramImport/Application/TelegramImport/AnalyzeImportCommand.cs @@ -11,7 +11,8 @@ using System.Threading.Tasks; using AngleSharp.Html.Parser; using AngleSharp.Dom; using Knot.Shared.Kernel; -using Knot.Shared.Kernel.Configuration; +using Knot.Modules.Settings.Application.Settings.Abstractions; +using Knot.Modules.Settings.Application.Settings.DTOs; using MediatR; namespace Knot.Modules.TelegramImport.Application.TelegramImport; @@ -120,3 +121,4 @@ internal sealed class AnalyzeImportCommandHandler : ICommandHandler + + @@ -13,3 +14,4 @@ + diff --git a/backend/src/Modules/WebRtc/Application/WebRtc/Queries/GetIceServersQuery.cs b/backend/src/Modules/WebRtc/Application/WebRtc/Queries/GetIceServersQuery.cs index 3ee4275..aa07b33 100644 --- a/backend/src/Modules/WebRtc/Application/WebRtc/Queries/GetIceServersQuery.cs +++ b/backend/src/Modules/WebRtc/Application/WebRtc/Queries/GetIceServersQuery.cs @@ -1,5 +1,6 @@ using Knot.Shared.Kernel; -using Knot.Shared.Kernel.Configuration; +using Knot.Modules.Settings.Application.Settings.Abstractions; +using Knot.Modules.Settings.Application.Settings.DTOs; using MediatR; using System.Collections.Generic; using System.Threading; @@ -62,4 +63,4 @@ internal sealed class GetIceServersQueryHandler : IQueryHandler + - \ No newline at end of file + + diff --git a/backend/src/Shared/Knot.Shared.Infrastructure/DependencyInjection.cs b/backend/src/Shared/Knot.Shared.Infrastructure/DependencyInjection.cs index bb518f5..d92ca89 100644 --- a/backend/src/Shared/Knot.Shared.Infrastructure/DependencyInjection.cs +++ b/backend/src/Shared/Knot.Shared.Infrastructure/DependencyInjection.cs @@ -4,9 +4,7 @@ using Knot.Shared.Kernel; using Knot.Shared.Kernel.Security; using Knot.Shared.Kernel.Storage; using Knot.Shared.Infrastructure.Persistence; -using Knot.Shared.Infrastructure.Configuration; using Knot.Shared.Infrastructure.Statistics; -using Knot.Shared.Kernel.Configuration; using Microsoft.EntityFrameworkCore; using Minio; using System; @@ -38,17 +36,6 @@ public static class DependencyInjection services.AddMemoryCache(); - services.AddSingleton(); - services.AddSingleton(sp => sp.GetRequiredService()); - services.AddSingleton(sp => sp.GetRequiredService()); - services.AddSingleton(sp => sp.GetRequiredService()); - services.AddSingleton(sp => sp.GetRequiredService()); - services.AddSingleton(sp => sp.GetRequiredService()); - services.AddSingleton(sp => sp.GetRequiredService()); - services.AddSingleton(sp => sp.GetRequiredService()); - services.AddSingleton(sp => sp.GetRequiredService()); - services.AddSingleton(sp => sp.GetRequiredService()); - services.AddScoped(); services.AddHostedService(); diff --git a/backend/src/Shared/Knot.Shared.Infrastructure/Knot.Shared.Infrastructure.csproj b/backend/src/Shared/Knot.Shared.Infrastructure/Knot.Shared.Infrastructure.csproj index 2b8a639..512c35b 100644 --- a/backend/src/Shared/Knot.Shared.Infrastructure/Knot.Shared.Infrastructure.csproj +++ b/backend/src/Shared/Knot.Shared.Infrastructure/Knot.Shared.Infrastructure.csproj @@ -28,3 +28,5 @@ + + diff --git a/backend/src/Shared/Knot.Shared.Kernel/Events/IdentityEvents.cs b/backend/src/Shared/Knot.Shared.Kernel/Events/IdentityEvents.cs new file mode 100644 index 0000000..5f0b6d6 --- /dev/null +++ b/backend/src/Shared/Knot.Shared.Kernel/Events/IdentityEvents.cs @@ -0,0 +1,15 @@ +using System; +using Knot.Shared.Kernel; +using MediatR; + +namespace Knot.Shared.Kernel.Events; + +/// +/// Domain event published when a new user is registered in the Identity system. +/// Handled by other modules to initialize their own data (e.g. Profiles, Notifications). +/// +public sealed record UserRegisteredDomainEvent( + Guid UserId, + string Username, + string DisplayName, + string? Bio) : IDomainEvent; diff --git a/backend/src/Shared/Knot.Shared.Kernel/Knot.Shared.Kernel.csproj b/backend/src/Shared/Knot.Shared.Kernel/Knot.Shared.Kernel.csproj index f32bac9..6557bcc 100644 --- a/backend/src/Shared/Knot.Shared.Kernel/Knot.Shared.Kernel.csproj +++ b/backend/src/Shared/Knot.Shared.Kernel/Knot.Shared.Kernel.csproj @@ -11,3 +11,4 @@ +