Сборка бэк

This commit is contained in:
Халимов Рустам
2026-03-27 15:45:34 +03:00
parent 16978c423c
commit 11f2b232a3
135 changed files with 2162 additions and 725 deletions
+398 -152
View File
@@ -13,8 +13,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Shared.Infrastructure"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}"
EndProject 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}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Profiles", "Profiles", "{CA5186C2-289A-9324-4152-9805A73A9773}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Profiles", "src\Modules\Profiles\Knot.Modules.Profiles.csproj", "{CD14770B-8BCD-4D93-AD2C-581ED030E209}" 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 EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Auth", "src\Modules\Auth\Knot.Modules.Auth.csproj", "{CF08AE9C-B337-47BA-B273-4B8E5610D7BD}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Auth", "src\Modules\Auth\Knot.Modules.Auth.csproj", "{CF08AE9C-B337-47BA-B273-4B8E5610D7BD}"
EndProject 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}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Messaging", "Messaging", "{AE120A0E-5815-2D46-946C-DA544028AD88}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Messaging", "src\Modules\Messaging\Knot.Modules.Messaging.csproj", "{C0B5496E-F083-413E-90AE-31282A0ACCA5}" 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 EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Conversations", "src\Modules\Conversations\Knot.Modules.Conversations.csproj", "{F3EFD119-44F7-46BF-A03C-060F30A1A1E0}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Conversations", "src\Modules\Conversations\Knot.Modules.Conversations.csproj", "{F3EFD119-44F7-46BF-A03C-060F30A1A1E0}"
EndProject 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}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Admin", "Admin", "{E9E1155A-A339-766C-C185-6698FF423EBC}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Admin", "src\Modules\Admin\Knot.Modules.Admin.csproj", "{FFC12440-F128-48F5-9BE5-E5263930F876}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Admin", "src\Modules\Admin\Knot.Modules.Admin.csproj", "{FFC12440-F128-48F5-9BE5-E5263930F876}"
EndProject 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}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Federation", "Federation", "{837D2CF4-7AEA-BF76-6362-47ECD7058EEC}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Federation", "src\Modules\Federation\Knot.Modules.Federation.csproj", "{716E089D-0661-4D39-813B-063DF9A4C0E4}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Federation", "src\Modules\Federation\Knot.Modules.Federation.csproj", "{716E089D-0661-4D39-813B-063DF9A4C0E4}"
EndProject 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}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Klipy", "Klipy", "{4BC86D3F-8D7B-ACB9-EA51-9374BC9D421A}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Klipy", "src\Modules\Klipy\Knot.Modules.Klipy.csproj", "{2498C58C-53B0-4DF2-B4EA-BA69B35A7488}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Klipy", "src\Modules\Klipy\Knot.Modules.Klipy.csproj", "{2498C58C-53B0-4DF2-B4EA-BA69B35A7488}"
EndProject 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}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Settings", "Settings", "{0587C5BB-3015-204D-19B6-EB01E03E48B6}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Settings", "src\Modules\Settings\Knot.Modules.Settings.csproj", "{AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Settings", "src\Modules\Settings\Knot.Modules.Settings.csproj", "{AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}"
EndProject 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}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebRtc", "WebRtc", "{DD6FAF9E-10A0-95E7-0C06-698A6F70AFEE}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.WebRtc", "src\Modules\WebRtc\Knot.Modules.WebRtc.csproj", "{03BE4EB1-FD38-445A-9A4B-7B481560E9A2}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.WebRtc", "src\Modules\WebRtc\Knot.Modules.WebRtc.csproj", "{03BE4EB1-FD38-445A-9A4B-7B481560E9A2}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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|x64.Build.0 = Release|Any CPU
{CF08AE9C-B337-47BA-B273-4B8E5610D7BD}.Release|x86.ActiveCfg = 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 {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.ActiveCfg = Debug|Any CPU
{C0B5496E-F083-413E-90AE-31282A0ACCA5}.Debug|Any CPU.Build.0 = 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 {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|x64.Build.0 = Release|Any CPU
{F3EFD119-44F7-46BF-A03C-060F30A1A1E0}.Release|x86.ActiveCfg = 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 {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.ActiveCfg = Debug|Any CPU
{FFC12440-F128-48F5-9BE5-E5263930F876}.Debug|Any CPU.Build.0 = 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 {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|x64.Build.0 = Release|Any CPU
{FFC12440-F128-48F5-9BE5-E5263930F876}.Release|x86.ActiveCfg = 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 {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.ActiveCfg = Debug|Any CPU
{716E089D-0661-4D39-813B-063DF9A4C0E4}.Debug|Any CPU.Build.0 = 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 {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|x64.Build.0 = Release|Any CPU
{716E089D-0661-4D39-813B-063DF9A4C0E4}.Release|x86.ActiveCfg = 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 {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.ActiveCfg = Debug|Any CPU
{2498C58C-53B0-4DF2-B4EA-BA69B35A7488}.Debug|Any CPU.Build.0 = 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 {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|x64.Build.0 = Release|Any CPU
{2498C58C-53B0-4DF2-B4EA-BA69B35A7488}.Release|x86.ActiveCfg = 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 {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.ActiveCfg = Debug|Any CPU
{AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}.Debug|Any CPU.Build.0 = 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 {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|x64.Build.0 = Release|Any CPU
{AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}.Release|x86.ActiveCfg = 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 {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.ActiveCfg = Debug|Any CPU
{03BE4EB1-FD38-445A-9A4B-7B481560E9A2}.Debug|Any CPU.Build.0 = 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 {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|x64.Build.0 = Release|Any CPU
{03BE4EB1-FD38-445A-9A4B-7B481560E9A2}.Release|x86.ActiveCfg = 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 {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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -416,31 +620,73 @@ Global
{D052C05A-CDCD-48DF-8F4F-0B10E3719819} = {AF61C489-0B09-54BC-BD16-4353135E9C4A} {D052C05A-CDCD-48DF-8F4F-0B10E3719819} = {AF61C489-0B09-54BC-BD16-4353135E9C4A}
{E9D55F35-C373-3DD8-6914-3213F2F582A2} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} {E9D55F35-C373-3DD8-6914-3213F2F582A2} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{CF08AE9C-B337-47BA-B273-4B8E5610D7BD} = {E9D55F35-C373-3DD8-6914-3213F2F582A2} {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} {AE120A0E-5815-2D46-946C-DA544028AD88} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{C0B5496E-F083-413E-90AE-31282A0ACCA5} = {AE120A0E-5815-2D46-946C-DA544028AD88} {C0B5496E-F083-413E-90AE-31282A0ACCA5} = {AE120A0E-5815-2D46-946C-DA544028AD88}
{4D5D7319-366D-1BD8-49C5-A840B487E852} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} {4D5D7319-366D-1BD8-49C5-A840B487E852} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{C905C120-DE44-4CBC-8F98-5BE148115487} = {4D5D7319-366D-1BD8-49C5-A840B487E852} {C905C120-DE44-4CBC-8F98-5BE148115487} = {4D5D7319-366D-1BD8-49C5-A840B487E852}
{138B168B-2473-B06D-4BD5-1B010DA319A1} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} {138B168B-2473-B06D-4BD5-1B010DA319A1} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{F3EFD119-44F7-46BF-A03C-060F30A1A1E0} = {138B168B-2473-B06D-4BD5-1B010DA319A1} {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} {E9E1155A-A339-766C-C185-6698FF423EBC} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{FFC12440-F128-48F5-9BE5-E5263930F876} = {E9E1155A-A339-766C-C185-6698FF423EBC} {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} {837D2CF4-7AEA-BF76-6362-47ECD7058EEC} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{716E089D-0661-4D39-813B-063DF9A4C0E4} = {837D2CF4-7AEA-BF76-6362-47ECD7058EEC} {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} {4BC86D3F-8D7B-ACB9-EA51-9374BC9D421A} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{2498C58C-53B0-4DF2-B4EA-BA69B35A7488} = {4BC86D3F-8D7B-ACB9-EA51-9374BC9D421A} {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} {0587C5BB-3015-204D-19B6-EB01E03E48B6} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{AEFD93B4-59B8-4B02-A694-CA15E4EC1C08} = {0587C5BB-3015-204D-19B6-EB01E03E48B6} {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} {DD6FAF9E-10A0-95E7-0C06-698A6F70AFEE} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{03BE4EB1-FD38-445A-9A4B-7B481560E9A2} = {DD6FAF9E-10A0-95E7-0C06-698A6F70AFEE} {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 EndGlobalSection
EndGlobal EndGlobal
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Shared\IntegrationTests.Shared\Knot.IntegrationTests.Shared.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Admin\Knot.Modules.Admin.csproj" />
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup>
</Project>
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
@@ -21,8 +21,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Modules\Admin\Knot.Modules.Admin.csproj" /> <ProjectReference Include="..\..\..\src\Modules\Admin\Knot.Modules.Admin.csproj" />
<ProjectReference Include="..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" /> <ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
@@ -21,8 +21,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" /> <ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
<ProjectReference Include="..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" /> <ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -1,5 +1,5 @@
using FluentAssertions; using FluentAssertions;
using Host.Application.Admin.Commands; using Knot.Modules.Admin.Application.Admin.Commands;
using Knot.Modules.Auth.Application.Abstractions; using Knot.Modules.Auth.Application.Abstractions;
using Knot.Modules.Auth.Domain; using Knot.Modules.Auth.Domain;
using Knot.Shared.Kernel; using Knot.Shared.Kernel;
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
@@ -10,13 +10,14 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" /> <PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="xunit" Version="2.9.2" /> <PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Using Include="Xunit" /> <Using Include="Xunit" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -23,9 +23,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Modules\Identity\Knot.Modules.Auth.csproj" /> <ProjectReference Include="..\..\..\src\Modules\Auth\Knot.Modules.Auth.csproj" />
<ProjectReference Include="..\..\src\Modules\Auth\Knot.Modules.Auth.csproj" /> <ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -23,9 +23,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Modules\Chats\Knot.Modules.Conversations.csproj" /> <ProjectReference Include="..\..\..\src\Modules\Chats\Knot.Modules.Conversations.csproj" />
<ProjectReference Include="..\..\src\Modules\Messaging\Knot.Modules.Messaging.csproj" /> <ProjectReference Include="..\..\..\src\Modules\Messaging\Knot.Modules.Messaging.csproj" />
<ProjectReference Include="..\..\src\Modules\Conversations\Knot.Modules.Conversations.csproj" /> <ProjectReference Include="..\..\..\src\Modules\Conversations\Knot.Modules.Conversations.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -12,6 +12,8 @@ using Knot.Modules.Conversations.Domain;
using Knot.Modules.Messaging.Domain; using Knot.Modules.Messaging.Domain;
using Knot.Modules.Conversations.Application.Abstractions; using Knot.Modules.Conversations.Application.Abstractions;
using Knot.Modules.Conversations.Application.Messages.Send; 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; namespace Knot.Modules.Conversations.UnitTests.Messages;
@@ -21,6 +23,7 @@ public class SendMessageCommandHandlerTests
private readonly IMessageRepository _messageRepository; private readonly IMessageRepository _messageRepository;
private readonly IChatsUnitOfWork _unitOfWork; private readonly IChatsUnitOfWork _unitOfWork;
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly IMessagesSettings _messagesSettings;
private readonly SendMessageCommandHandler _handler; private readonly SendMessageCommandHandler _handler;
public SendMessageCommandHandlerTests() public SendMessageCommandHandlerTests()
@@ -29,8 +32,12 @@ public class SendMessageCommandHandlerTests
_messageRepository = Substitute.For<IMessageRepository>(); _messageRepository = Substitute.For<IMessageRepository>();
_unitOfWork = Substitute.For<IChatsUnitOfWork>(); _unitOfWork = Substitute.For<IChatsUnitOfWork>();
_mediator = Substitute.For<IMediator>(); _mediator = Substitute.For<IMediator>();
_messagesSettings = Substitute.For<IMessagesSettings>();
_handler = new SendMessageCommandHandler(_chatRepository, _messageRepository, _unitOfWork, _mediator); var config = new Knot.Modules.Settings.Application.Settings.DTOs.MessagesConfig();
_messagesSettings.Current.Returns(config);
_handler = new SendMessageCommandHandler(_chatRepository, _messageRepository, _unitOfWork, _mediator, _messagesSettings);
} }
[Fact] [Fact]
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup>
</Project>
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
@@ -21,8 +21,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Modules\Federation\Knot.Modules.Federation.csproj" /> <ProjectReference Include="..\..\..\src\Modules\Federation\Knot.Modules.Federation.csproj" />
<ProjectReference Include="..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" /> <ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
@@ -21,8 +21,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Modules\Klipy\Knot.Modules.Klipy.csproj" /> <ProjectReference Include="..\..\..\src\Modules\Klipy\Knot.Modules.Klipy.csproj" />
<ProjectReference Include="..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" /> <ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -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<IChatRepository>();
_unitOfWork = Substitute.For<IChatsUnitOfWork>();
_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<CancellationToken>())
.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);
}
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup>
</Project>
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
@@ -17,6 +17,8 @@
<ItemGroup> <ItemGroup>
<Using Include="Xunit" /> <Using Include="Xunit" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup>
</Project>
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
@@ -21,8 +21,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Modules\Relations\Knot.Modules.Relations.csproj" /> <ProjectReference Include="..\..\..\src\Modules\Relations\Knot.Modules.Relations.csproj" />
<ProjectReference Include="..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" /> <ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -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<Program> _factory;
protected readonly Guid _userId = Guid.NewGuid();
protected readonly IUserContext _userContextMock = Substitute.For<IUserContext>();
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<Program>
{
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<string, string?>
{
["ConnectionStrings:DefaultConnection"] = _pgConnectionString,
["ConnectionStrings:MongoConnection"] = _mongoConnectionString,
["DATABASE_URL"] = _pgConnectionString,
["MONGO_CONNECTION"] = _mongoConnectionString,
["KNOT_MASTER_ENCRYPTION_KEY"] = "TestEncryptionKey_32CharactersLong!"
});
});
builder.ConfigureTestServices(services => {
services.AddScoped(_ => _userContext);
});
}
}
}
@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="FluentAssertions" Version="8.9.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.5" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="10.0.5" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="Respawn" Version="7.0.0" />
<PackageReference Include="Testcontainers.MongoDb" Version="4.11.0" />
<PackageReference Include="Testcontainers.PostgreSql" Version="4.11.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Host\Host.csproj" />
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Infrastructure\Knot.Shared.Infrastructure.csproj" />
</ItemGroup>
</Project>
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
@@ -21,8 +21,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Modules\Storage\Knot.Modules.Storage.csproj" /> <ProjectReference Include="..\..\..\src\Modules\Storage\Knot.Modules.Storage.csproj" />
<ProjectReference Include="..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" /> <ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Shared\IntegrationTests.Shared\Knot.IntegrationTests.Shared.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Stories\Knot.Modules.Stories.csproj" />
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup>
</Project>
@@ -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<dynamic>();
}
}
public record CreateStoryRequest(string Type, string? MediaUrl, string? Content, string? BgColor);
@@ -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<IStoryRepository>();
_settingsService = Substitute.For<ISettingsService>();
_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<Story>(), Arg.Any<CancellationToken>());
}
[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<CancellationToken>()).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<Story>(), Arg.Any<CancellationToken>());
}
[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<CancellationToken>()).Returns(settings);
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeTrue();
await _storyRepository.Received(1).AddAsync(Arg.Any<Story>(), Arg.Any<CancellationToken>());
}
}
@@ -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<IStoryRepository>();
_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<CancellationToken>()).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<CancellationToken>()).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<CancellationToken>()).Returns(story);
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeTrue();
await _storyRepository.Received(1).DeleteAsync(story, Arg.Any<CancellationToken>());
}
}
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
@@ -21,8 +21,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Modules\Stories\Knot.Modules.Stories.csproj" /> <ProjectReference Include="..\..\..\src\Modules\Stories\Knot.Modules.Stories.csproj" />
<ProjectReference Include="..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" /> <ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
@@ -21,8 +21,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Modules\TelegramImport\Knot.Modules.TelegramImport.csproj" /> <ProjectReference Include="..\..\..\src\Modules\TelegramImport\Knot.Modules.TelegramImport.csproj" />
<ProjectReference Include="..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" /> <ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageReference Include="FluentAssertions" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Modules\Profiles\Knot.Modules.Profiles.csproj" />
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup>
</Project>
@@ -1,41 +1,38 @@
using FluentAssertions; 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.Domain;
using Knot.Modules.Profiles.Application.Abstractions;
using Knot.Shared.Kernel; using Knot.Shared.Kernel;
using NSubstitute; using NSubstitute;
using Xunit; using Xunit;
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Knot.Modules.Profiles.Application.Abstractions;
namespace Knot.Modules.Profiles.UnitTests; namespace Knot.Modules.Profiles.UnitTests;
public class UpdateProfileCommandHandlerTests public class UpdateProfileCommandHandlerTests
{ {
private readonly IProfileRepository _profileRepository; private readonly IProfileRepository _profileRepository;
private readonly IProfilesUnitOfWork _unitOfWork;
private readonly UpdateProfileCommandHandler _handler; private readonly UpdateProfileCommandHandler _handler;
public UpdateProfileCommandHandlerTests() public UpdateProfileCommandHandlerTests()
{ {
_profileRepository = Substitute.For<IProfileRepository>(); _profileRepository = Substitute.For<IProfileRepository>();
_unitOfWork = Substitute.For<IProfilesUnitOfWork>(); _handler = new UpdateProfileCommandHandler(_profileRepository);
_handler = new UpdateProfileCommandHandler(_profileRepository, _unitOfWork);
} }
[Fact] [Fact]
public async Task Handle_ShouldReturnError_WhenProfileNotFound() public async Task Handle_ShouldReturnError_WhenProfileNotFound()
{ {
// Arrange // Arrange
var command = new UpdateProfileCommand(Guid.NewGuid(), "FirstName", "LastName", "Bio"); var command = new UpdateProfileCommand(Guid.NewGuid(), "FirstName", "Bio", null);
_profileRepository.GetByIdAsync(command.UserId, Arg.Any<CancellationToken>()).Returns((UserProfile?)null); _profileRepository.GetByIdAsync(command.UserId, Arg.Any<CancellationToken>()).Returns((ProfileDocument?)null);
// Act // Act
var result = await _handler.Handle(command, CancellationToken.None); var result = await _handler.Handle(command, CancellationToken.None);
// Assert // Assert
result.IsFailure.Should().BeTrue(); result.IsFailure.Should().BeTrue();
result.Error.Should().Be(ProfileErrors.NotFound);
} }
} }
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
@@ -21,8 +21,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Modules\WebRtc\Knot.Modules.WebRtc.csproj" /> <ProjectReference Include="..\..\..\src\Modules\WebRtc\Knot.Modules.WebRtc.csproj" />
<ProjectReference Include="..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" /> <ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -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<ProfileDocument>`. Первичный ключ `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` | Обновить настройки приватности профиля. |
@@ -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<SystemSettingsUpdatedDomainEvent>` независимыми слушателями, например:
- Модулем `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<SettingsService>();
// Привязываем конкретные абстракции к одному Singleton инстансу
services.AddSingleton<ISettingsService>(sp => sp.GetRequiredService<SettingsService>());
services.AddSingleton<IMessagesSettings>(sp => sp.GetRequiredService<SettingsService>());
// ... и остальные
}
```
## Публичная конфигурация клиента (PublicConfig)
Для клиентской части (Frontend) предусмотрен специальный DTO объект `PublicConfigDto`.
Это subset (урезанная часть) от `SystemSettingsDto`, которая не содержит чувствительной информации о сервере и ключах:
- Доступные лимиты медиа файлов.
- Включены ли аудио/видео звонки.
- Включена ли федерация для поиска глобальных пользователей.
Таким образом, клиент может запросить `GET /api/app/config`, а бэкенд отдаст безопасный набор доступных разрешений через маппинг `SettingsService.Current`.
+2
View File
@@ -43,3 +43,5 @@
</ItemGroup> </ItemGroup>
</Project> </Project>
+10 -4
View File
@@ -2,6 +2,8 @@ using Knot.Modules.Messaging;
using Carter; using Carter;
using Knot.Shared.Infrastructure; using Knot.Shared.Infrastructure;
using Knot.Modules.Auth; using Knot.Modules.Auth;
using Knot.Modules.Settings;
using Knot.Modules.Admin;
using Knot.Modules.Conversations; using Knot.Modules.Conversations;
using Knot.Modules.Conversations.Infrastructure.SignalR; using Knot.Modules.Conversations.Infrastructure.SignalR;
using Knot.Modules.Auth.Infrastructure.Persistence; using Knot.Modules.Auth.Infrastructure.Persistence;
@@ -50,11 +52,16 @@ builder.Configuration.AddInMemoryCollection(
.ToDictionary(kv => kv.Key, kv => kv.Value)); .ToDictionary(kv => kv.Key, kv => kv.Value));
builder.Services.AddAuthModule(builder.Configuration); builder.Services.AddAuthModule(builder.Configuration);
builder.Services.AddSettingsModule(builder.Configuration);
builder.Services.AddConversationsModule(builder.Configuration); builder.Services.AddConversationsModule(builder.Configuration);
builder.Services.AddSharedInfrastructure(builder.Configuration); builder.Services.AddSharedInfrastructure(builder.Configuration);
// CQRS / MediatR для команд в Host (например, AdminController) // CQRS / MediatR для команд в Host (например, AdminController)
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly)); 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) // Carter для вызова Minimal APIs (Endpoints)
builder.Services.AddCarter(); builder.Services.AddCarter();
@@ -99,8 +106,6 @@ builder.Services.AddRouting(options =>
builder.Services.AddMemoryCache(); builder.Services.AddMemoryCache();
builder.Services.AddHttpClient(); builder.Services.AddHttpClient();
builder.Services.AddSignalR() builder.Services.AddSignalR()
.AddJsonProtocol(options => .AddJsonProtocol(options =>
{ {
@@ -162,8 +167,8 @@ using (var scope = app.Services.CreateScope())
Knot.Modules.Messaging.Infrastructure.Persistence.Mongo.EncryptedStringSerializer.EncryptionService = encryptionService; Knot.Modules.Messaging.Infrastructure.Persistence.Mongo.EncryptedStringSerializer.EncryptionService = encryptionService;
// Initialize Global Settings Cache // Initialize Global Settings Cache
var settingsService = scope.ServiceProvider.GetRequiredService<Knot.Shared.Kernel.Configuration.ISettingsService>(); var settingsService = scope.ServiceProvider.GetRequiredService<Knot.Modules.Settings.Application.Settings.Abstractions.ISettingsService>();
if (settingsService is Knot.Shared.Infrastructure.Configuration.SettingsService concreteSettings) if (settingsService is Knot.Modules.Settings.Infrastructure.Configuration.SettingsService concreteSettings)
{ {
concreteSettings.Initialize(); concreteSettings.Initialize();
} }
@@ -203,3 +208,4 @@ public class CustomUserIdProvider : IUserIdProvider
return connection.User?.FindFirstValue("sub") ?? connection.User?.FindFirstValue(ClaimTypes.NameIdentifier); return connection.User?.FindFirstValue("sub") ?? connection.User?.FindFirstValue(ClaimTypes.NameIdentifier);
} }
} }
@@ -14,7 +14,7 @@ using Knot.Modules.Conversations.Domain;
using MongoDB.Driver; using MongoDB.Driver;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Host.Application.Admin.Commands; namespace Knot.Modules.Admin.Application.Admin.Commands;
public record CleanRunCommand(IFileStorageService FileStorage, AuthDbContext IdentityDb) : ICommand<MessageResponse>; public record CleanRunCommand(IFileStorageService FileStorage, AuthDbContext IdentityDb) : ICommand<MessageResponse>;
@@ -12,7 +12,7 @@ using Knot.Modules.Auth.Domain;
using Knot.Modules.Auth.Infrastructure.Persistence; using Knot.Modules.Auth.Infrastructure.Persistence;
using Knot.Modules.Auth.Application.Abstractions; using Knot.Modules.Auth.Application.Abstractions;
namespace Host.Application.Admin.Commands; namespace Knot.Modules.Admin.Application.Admin.Commands;
public record ResetUserPasswordCommand(Guid UserId, string NewPassword) : ICommand<SuccessResponse>; public record ResetUserPasswordCommand(Guid UserId, string NewPassword) : ICommand<SuccessResponse>;
@@ -2,10 +2,11 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediatR; using MediatR;
using Knot.Shared.Kernel; 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.Application.Abstractions; using Knot.Modules.Stories.Application.Abstractions;
namespace Host.Application.Admin.Commands.TestKlipy; namespace Knot.Modules.Admin.Application.Admin.Commands.TestKlipy;
public record TestKlipyConnectionCommand() : ICommand<bool>; public record TestKlipyConnectionCommand() : ICommand<bool>;
@@ -37,3 +38,5 @@ internal sealed class TestKlipyConnectionCommandHandler : ICommandHandler<TestKl
return Result.Success(true); return Result.Success(true);
} }
} }
@@ -4,10 +4,11 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediatR; using MediatR;
using Knot.Shared.Kernel; using Knot.Shared.Kernel;
using Knot.Shared.Kernel.Configuration; using Knot.Modules.Settings.Application.Settings.Abstractions;
using Knot.Modules.Settings.Application.Settings.DTOs;
using System.Security.Cryptography; using System.Security.Cryptography;
namespace Host.Application.Admin.Commands; namespace Knot.Modules.Admin.Application.Admin.Commands;
public record UpdateSettingsCommand(SystemSettingsDto Settings) : ICommand<SystemSettingsDto>; public record UpdateSettingsCommand(SystemSettingsDto Settings) : ICommand<SystemSettingsDto>;
@@ -44,3 +45,5 @@ internal sealed class UpdateSettingsCommandHandler : ICommandHandler<UpdateSetti
return Result.Success(request.Settings); return Result.Success(request.Settings);
} }
} }
@@ -17,7 +17,7 @@ using Knot.Modules.Auth.Application.Auth.DTOs;
using Knot.Modules.Auth.Application.Users; using Knot.Modules.Auth.Application.Users;
namespace Host.Application.Admin.Queries; namespace Knot.Modules.Admin.Application.Admin.Queries;
public record CleanDryRunQuery(IFileStorageService FileStorage, AuthDbContext IdentityDb) : IQuery<CleanupDryRunResultDto>; public record CleanDryRunQuery(IFileStorageService FileStorage, AuthDbContext IdentityDb) : IQuery<CleanupDryRunResultDto>;
@@ -5,7 +5,7 @@ using MediatR;
using Knot.Shared.Kernel; using Knot.Shared.Kernel;
using Knot.Shared.Kernel.Services; using Knot.Shared.Kernel.Services;
namespace Host.Application.Admin.Queries; namespace Knot.Modules.Admin.Application.Admin.Queries;
public record GetDashboardStatsQuery() : IQuery<DashboardStatsDto>; public record GetDashboardStatsQuery() : IQuery<DashboardStatsDto>;
@@ -4,9 +4,10 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediatR; using MediatR;
using Knot.Shared.Kernel; 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.Admin.Queries; namespace Knot.Modules.Admin.Application.Admin.Queries;
public record GetSettingsQuery() : IQuery<SystemSettingsDto>; public record GetSettingsQuery() : IQuery<SystemSettingsDto>;
@@ -25,3 +26,4 @@ internal sealed class GetSettingsQueryHandler : IQueryHandler<GetSettingsQuery,
return Result.Success(settings); return Result.Success(settings);
} }
} }
@@ -14,7 +14,7 @@ using Knot.Modules.Auth.Domain;
using Knot.Modules.Conversations.Domain; using Knot.Modules.Conversations.Domain;
using MongoDB.Driver; using MongoDB.Driver;
namespace Host.Application.Admin.Queries; namespace Knot.Modules.Admin.Application.Admin.Queries;
public record GetUserDetailsQuery(Guid UserId) : IQuery<AdminUserDetailsDto>; public record GetUserDetailsQuery(Guid UserId) : IQuery<AdminUserDetailsDto>;
@@ -11,7 +11,7 @@ using Knot.Modules.Auth.Application.Auth.DTOs;
using Knot.Modules.Auth.Application.Users; using Knot.Modules.Auth.Application.Users;
using Knot.Modules.Auth.Domain; using Knot.Modules.Auth.Domain;
namespace Host.Application.Admin.Queries; namespace Knot.Modules.Admin.Application.Admin.Queries;
public record SearchUsersQuery(string Query) : IQuery<List<AdminUserDto>>; public record SearchUsersQuery(string Query) : IQuery<List<AdminUserDto>>;
@@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using Knot.Shared.Kernel; using Knot.Shared.Kernel;
using Knot.Shared.Kernel.Configuration; using Knot.Modules.Settings.Application.Settings.Abstractions;
using Knot.Modules.Settings.Application.Settings.DTOs;
namespace Knot.Modules.Admin.Domain.Events; namespace Knot.Modules.Admin.Domain.Events;
@@ -8,3 +9,4 @@ namespace Knot.Modules.Admin.Domain.Events;
/// Доменное событие: глобальные настройки системы обновлены. /// Доменное событие: глобальные настройки системы обновлены.
/// </summary> /// </summary>
public sealed record SystemSettingsUpdatedDomainEvent(SystemSettingsDto Settings) : IDomainEvent; public sealed record SystemSettingsUpdatedDomainEvent(SystemSettingsDto Settings) : IDomainEvent;
@@ -18,5 +18,7 @@
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute"> <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>Knot.Modules.Admin.UnitTests</_Parameter1> <_Parameter1>Knot.Modules.Admin.UnitTests</_Parameter1>
</AssemblyAttribute> </AssemblyAttribute>
<ProjectReference Include="..\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -1,18 +1,19 @@
using Carter; using Carter;
using Knot.Modules.Auth.Application.Auth.DTOs; using Knot.Modules.Auth.Application.Auth.DTOs;
using Knot.Shared.Kernel; 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.Modules.Auth.Application.Users.Register;
using Knot.Shared.Kernel.Storage; using Knot.Shared.Kernel.Storage;
using Knot.Modules.Auth.Infrastructure.Persistence; using Knot.Modules.Auth.Infrastructure.Persistence;
using MediatR; using MediatR;
using Host.Application.Admin.Queries; using Knot.Modules.Admin.Application.Admin.Queries;
using Host.Application.Admin.Commands; using Knot.Modules.Admin.Application.Admin.Commands;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Host.Application.Admin.Commands.TestKlipy; using Knot.Modules.Admin.Application.Admin.Commands.TestKlipy;
namespace Knot.Host.Presentation.Endpoints; namespace Knot.Host.Presentation.Endpoints;
@@ -91,3 +92,4 @@ public sealed class AdminEndpoints : ICarterModule
}); });
} }
} }
@@ -2,7 +2,8 @@
using Knot.Modules.Auth.Domain; using Knot.Modules.Auth.Domain;
using Knot.Modules.Auth.Application.Abstractions; using Knot.Modules.Auth.Application.Abstractions;
using Knot.Shared.Kernel; 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 Knot.Modules.Auth.Application.Users.Auth;
using BCrypt.Net; using BCrypt.Net;
@@ -87,3 +88,4 @@ public sealed class RegisterUserCommandHandler : ICommandHandler<RegisterUserCom
} }
} }
+4 -1
View File
@@ -1,4 +1,5 @@
using Knot.Shared.Kernel; using Knot.Shared.Kernel;
using Knot.Shared.Kernel.Events;
namespace Knot.Modules.Auth.Domain; namespace Knot.Modules.Auth.Domain;
@@ -40,7 +41,9 @@ public sealed class User : AggregateRoot<Guid>
/// </summary> /// </summary>
public static User Create(string username, string passwordHash, string displayName, string? email = null, string? bio = null) 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;
} }
/// <summary> /// <summary>
@@ -21,13 +21,17 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.4" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.4" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.4" /> <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.4" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.16.0" /> <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.16.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.16.0" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.16.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <FrameworkReference Include="Microsoft.AspNetCore.App" />
<ProjectReference Include="..\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -1,6 +1,7 @@
using Knot.Modules.Conversations.Domain; using Knot.Modules.Conversations.Domain;
using Knot.Modules.Conversations.Application.Abstractions; 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 Knot.Shared.Kernel;
using MediatR; using MediatR;
@@ -48,3 +49,4 @@ internal sealed class AddToFolderCommandHandler : ICommandHandler<AddToFolderCom
return Result.Success(); return Result.Success();
} }
} }
@@ -2,7 +2,8 @@ using Knot.Modules.Messaging.Application.Abstractions;
using Knot.Modules.Messaging.Domain; using Knot.Modules.Messaging.Domain;
using Knot.Modules.Conversations.Domain; using Knot.Modules.Conversations.Domain;
using Knot.Shared.Kernel; 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.Conversations.Application.Abstractions; using Knot.Modules.Conversations.Application.Abstractions;
namespace Knot.Modules.Conversations.Application.Messages.Send; namespace Knot.Modules.Conversations.Application.Messages.Send;
@@ -167,3 +168,4 @@ public sealed class SendMessageCommandHandler : ICommandHandler<SendMessageComma
@@ -6,7 +6,8 @@ using System.Threading.Tasks;
using MediatR; using MediatR;
using Knot.Shared.Kernel; using Knot.Shared.Kernel;
using Knot.Shared.Kernel.Storage; using Knot.Shared.Kernel.Storage;
using Knot.Shared.Kernel.Configuration; using Knot.Modules.Settings.Application.Settings.Abstractions;
using Knot.Modules.Settings.Application.Settings.DTOs;
using Knot.Modules.Conversations.Application.DTOs; using Knot.Modules.Conversations.Application.DTOs;
namespace Knot.Modules.Conversations.Application.Messages.UploadFile; namespace Knot.Modules.Conversations.Application.Messages.UploadFile;
@@ -45,3 +46,4 @@ internal sealed class UploadFileCommandHandler : ICommandHandler<UploadFileComma
} }
} }
@@ -15,8 +15,8 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.4" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.4" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.4" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
<PackageReference Include="MongoDB.Driver" Version="3.7.1" /> <PackageReference Include="MongoDB.Driver" Version="3.2.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
</ItemGroup> </ItemGroup>
@@ -25,7 +25,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<InternalsVisibleTo Include="Knot.Modules.Chats.UnitTests" /> <InternalsVisibleTo Include="Knot.Modules.Conversations.UnitTests" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" /> <InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
</ItemGroup> </ItemGroup>
@@ -36,3 +36,5 @@
</PropertyGroup> </PropertyGroup>
</Project> </Project>
@@ -2,7 +2,8 @@ using Knot.Shared.Kernel.Constants;
using System.Security.Cryptography; using System.Security.Cryptography;
using MediatR; using MediatR;
using Knot.Shared.Kernel; 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; namespace Host.Application.Federation.Commands;
@@ -11,7 +11,8 @@ using Knot.Shared.Kernel;
using Knot.Modules.Auth.Domain; using Knot.Modules.Auth.Domain;
using Knot.Modules.Messaging.Application.Abstractions; using Knot.Modules.Messaging.Application.Abstractions;
using Knot.Modules.Messaging.Domain; using Knot.Modules.Messaging.Domain;
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 Knot.Shared.Kernel.Constants;
namespace Host.Application.Federation.Commands; namespace Host.Application.Federation.Commands;
@@ -204,3 +205,4 @@ internal sealed class InboundFederationCommandHandler : ICommandHandler<InboundF
return Result.Success(); return Result.Success();
} }
} }
@@ -4,7 +4,8 @@ using System.Threading.Tasks;
using MediatR; using MediatR;
using Knot.Shared.Kernel; using Knot.Shared.Kernel;
using Knot.Modules.Auth.Domain; using Knot.Modules.Auth.Domain;
using Knot.Shared.Kernel.Configuration; using Knot.Modules.Settings.Application.Settings.Abstractions;
using Knot.Modules.Settings.Application.Settings.DTOs;
using System.Linq; using System.Linq;
namespace Host.Application.Federation.Commands; namespace Host.Application.Federation.Commands;
@@ -38,3 +39,4 @@ internal sealed class ResolveUserCommandHandler : ICommandHandler<ResolveUserCom
)); ));
} }
} }
@@ -7,7 +7,8 @@ using Knot.Modules.Messaging.Domain;
using Knot.Modules.Federation.Application.Federation.Services; using Knot.Modules.Federation.Application.Federation.Services;
using Knot.Modules.Conversations.Domain; using Knot.Modules.Conversations.Domain;
using Knot.Modules.Federation.Application.Abstractions; using Knot.Modules.Federation.Application.Abstractions;
using Knot.Shared.Kernel.Configuration; using Knot.Modules.Settings.Application.Settings.Abstractions;
using Knot.Modules.Settings.Application.Settings.DTOs;
using Knot.Modules.Auth.Domain; using Knot.Modules.Auth.Domain;
namespace Knot.Modules.Federation.Application.Federation.Events; namespace Knot.Modules.Federation.Application.Federation.Events;
@@ -95,3 +96,4 @@ public sealed class FederatedMessageActionsHandler :
.ToList(); .ToList();
} }
} }
@@ -7,7 +7,8 @@ using Knot.Modules.Messaging.Domain;
using Knot.Modules.Federation.Application.Federation.Services; using Knot.Modules.Federation.Application.Federation.Services;
using Knot.Modules.Conversations.Domain; using Knot.Modules.Conversations.Domain;
using Knot.Modules.Federation.Application.Abstractions; using Knot.Modules.Federation.Application.Abstractions;
using Knot.Shared.Kernel.Configuration; using Knot.Modules.Settings.Application.Settings.Abstractions;
using Knot.Modules.Settings.Application.Settings.DTOs;
using Knot.Modules.Auth.Domain; using Knot.Modules.Auth.Domain;
namespace Knot.Modules.Federation.Application.Federation.Events; namespace Knot.Modules.Federation.Application.Federation.Events;
@@ -88,3 +89,4 @@ public sealed class MessageSentDomainEventHandler : INotificationHandler<Message
} }
} }
} }
@@ -3,10 +3,11 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediatR; using MediatR;
using Knot.Modules.Admin.Domain.Events; using Knot.Modules.Settings.Domain.Events;
using Knot.Modules.Federation.Application.Abstractions; using Knot.Modules.Federation.Application.Abstractions;
using Knot.Modules.Federation.Application.Federation.Services; using Knot.Modules.Federation.Application.Federation.Services;
using Knot.Shared.Kernel.Configuration; using Knot.Modules.Settings.Application.Settings.Abstractions;
using Knot.Modules.Settings.Application.Settings.DTOs;
namespace Knot.Modules.Federation.Application.Federation.Events; namespace Knot.Modules.Federation.Application.Federation.Events;
@@ -69,3 +70,5 @@ public sealed class SystemSettingsUpdatedDomainEventHandler : INotificationHandl
} }
} }
} }
@@ -7,7 +7,8 @@ using Knot.Modules.Auth.Domain;
using Knot.Modules.Conversations.Domain; using Knot.Modules.Conversations.Domain;
using Knot.Modules.Federation.Application.Abstractions; using Knot.Modules.Federation.Application.Abstractions;
using Knot.Modules.Federation.Application.Federation.Services; using Knot.Modules.Federation.Application.Federation.Services;
using Knot.Shared.Kernel.Configuration; using Knot.Modules.Settings.Application.Settings.Abstractions;
using Knot.Modules.Settings.Application.Settings.DTOs;
namespace Knot.Modules.Federation.Application.Federation.Events; namespace Knot.Modules.Federation.Application.Federation.Events;
@@ -84,3 +85,4 @@ public sealed class UserStatusChangedDomainEventHandler : INotificationHandler<U
} }
} }
} }
@@ -8,7 +8,8 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Knot.Modules.Federation.Application.Abstractions; using Knot.Modules.Federation.Application.Abstractions;
using Knot.Shared.Kernel; 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.Security; using Knot.Shared.Kernel.Security;
namespace Knot.Modules.Federation.Application.Federation.Services; namespace Knot.Modules.Federation.Application.Federation.Services;
@@ -105,3 +106,4 @@ public sealed class FederationPacketService
return Result.Success(packet); return Result.Success(packet);
} }
} }
@@ -8,7 +8,8 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Knot.Modules.Federation.Application.Abstractions; using Knot.Modules.Federation.Application.Abstractions;
using Knot.Shared.Kernel; 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.Security; using Knot.Shared.Kernel.Security;
namespace Knot.Modules.Federation.Infrastructure.Services; namespace Knot.Modules.Federation.Infrastructure.Services;
@@ -56,3 +57,4 @@ public sealed class FederationGateway : IFederationGateway
} }
} }
} }
@@ -13,5 +13,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Carter" Version="10.0.0" /> <PackageReference Include="Carter" Version="10.0.0" />
<ProjectReference Include="..\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -9,7 +9,8 @@ using System;
using Host.Application.Federation.Commands; using Host.Application.Federation.Commands;
using Knot.Modules.Federation.Application.Abstractions; using Knot.Modules.Federation.Application.Abstractions;
using Knot.Shared.Kernel.Storage; 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; namespace Host.Endpoints;
@@ -1,5 +1,6 @@
using Knot.Shared.Kernel; 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 Knot.Shared.Kernel.Constants;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using System; using System;
@@ -61,3 +62,4 @@ internal sealed class GetTrendingGifsQueryHandler : IQueryHandler<GetTrendingGif
return Result.Success<JsonElement?>(result); return Result.Success<JsonElement?>(result);
} }
} }
@@ -1,5 +1,6 @@
using Knot.Shared.Kernel; 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 Knot.Shared.Kernel.Constants;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using System; using System;
@@ -65,3 +66,4 @@ internal sealed class SearchGifsQueryHandler : IQueryHandler<SearchGifsQuery, Js
return Result.Success<JsonElement?>(result); return Result.Success<JsonElement?>(result);
} }
} }
@@ -13,5 +13,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Carter" Version="10.0.0" /> <PackageReference Include="Carter" Version="10.0.0" />
<ProjectReference Include="..\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" /> <ProjectReference Include="..\..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
@@ -7,7 +7,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.8" /> <PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.4" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.4" />
<PackageReference Include="MongoDB.Driver" Version="3.0.0" /> <PackageReference Include="MongoDB.Driver" Version="3.2.0" />
<ProjectReference Include="..\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
@@ -17,3 +18,5 @@
</PropertyGroup> </PropertyGroup>
</Project> </Project>
@@ -0,0 +1,22 @@
using System.Threading;
using System.Threading.Tasks;
namespace Knot.Modules.Profiles.Application.Abstractions;
/// <summary>
/// Интерфейс для хранения аватаров в S3 (MinIO).
/// Отдельный от IFileStorageService модуля Storage,
/// чтобы не создавать прямой зависимости на Storage модуль.
/// </summary>
public interface IAvatarStorageService
{
/// <summary>
/// Загружает файл в S3-бакет аватаров и возвращает fileId (ключ объекта).
/// </summary>
Task<string> UploadAsync(System.IO.Stream stream, string fileName, string contentType, CancellationToken ct = default);
/// <summary>
/// Удаляет файл аватара из S3 по fileId.
/// </summary>
Task DeleteAsync(string fileId, CancellationToken ct = default);
}
@@ -1,15 +1,15 @@
using System; using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Knot.Modules.Profiles.Domain; using Knot.Modules.Profiles.Domain;
using Knot.Modules.Profiles.Application.Abstractions;
namespace Knot.Modules.Profiles.Application.Abstractions; namespace Knot.Modules.Profiles.Application.Abstractions;
public interface IProfileRepository public interface IProfileRepository
{ {
Task<Profile?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default); Task<ProfileDocument?> GetByIdAsync(Guid userId, CancellationToken ct = default);
Task AddAsync(Profile profile, CancellationToken cancellationToken = default); Task AddAsync(ProfileDocument profile, CancellationToken ct = default);
void Update(Profile profile); Task UpdateAsync(ProfileDocument profile, CancellationToken ct = default);
Task<System.Collections.Generic.List<Profile>> SearchProfilesAsync(string query, System.Threading.CancellationToken ct = default); Task<List<ProfileDocument>> SearchAsync(string query, CancellationToken ct = default);
} }
@@ -1,11 +1,9 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Knot.Modules.Profiles.Application.Abstractions;
using Knot.Modules.Profiles.Domain;
namespace Knot.Modules.Profiles.Application.Abstractions; namespace Knot.Modules.Profiles.Application.Abstractions;
public interface IProfilesUnitOfWork public interface IProfilesUnitOfWork
{ {
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default); Task SaveChangesAsync(CancellationToken ct = default);
} }
@@ -1,69 +1,71 @@
using Knot.Shared.Kernel; using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Domain; using Knot.Modules.Profiles.Application.Abstractions;
using Knot.Modules.Profiles.Application.Profiles.DTOs;
using Knot.Shared.Kernel.Storage;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing; 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; 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<ProfileProfileDto>; public sealed record CropAvatarCommand(
Guid UserId,
Stream FileStream,
string FileName,
string ContentType,
int X, int Y, int Width, int Height) : ICommand<UserProfileDto>;
internal sealed class CropAvatarCommandHandler : ICommandHandler<CropAvatarCommand, ProfileProfileDto> internal sealed class CropAvatarCommandHandler : ICommandHandler<CropAvatarCommand, UserProfileDto>
{ {
private readonly IProfileRepository _profileRepository; private readonly IProfileRepository _repository;
private readonly IProfilesUnitOfWork _unitOfWork; private readonly IAvatarStorageService _avatarStorage;
private readonly IFileStorageService _fileStorage;
public CropAvatarCommandHandler(IProfileRepository profileRepository, IProfilesUnitOfWork unitOfWork, IFileStorageService fileStorage) public CropAvatarCommandHandler(IProfileRepository repository, IAvatarStorageService avatarStorage)
{ {
_profileRepository = profileRepository; _repository = repository;
_unitOfWork = unitOfWork; _avatarStorage = avatarStorage;
_fileStorage = fileStorage;
} }
public async Task<Result<ProfileProfileDto>> Handle(CropAvatarCommand request, CancellationToken cancellationToken) public async Task<Result<UserProfileDto>> Handle(CropAvatarCommand request, CancellationToken cancellationToken)
{ {
var profile = await _profileRepository.GetByIdAsync(request.ProfileId, cancellationToken); var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
if (profile == null) if (profile is null)
{ return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
return Result.Failure<ProfileProfileDto>(ProfilesErrors.ProfileNotFound);
}
string avatarUrl; // Обрезать и ресайзнуть изображение до 400×400
using var ms = await CropAndResizeAsync(request, cancellationToken);
using (var image = await SixLabors.ImageSharp.Image.LoadAsync(request.FileStream, cancellationToken)) // Удалить старый аватар из S3
{ if (!string.IsNullOrEmpty(profile.AvatarUrl))
int startX = Math.Max(0, Math.Min(request.X, image.Width - 1)); await _avatarStorage.DeleteAsync(profile.AvatarUrl, cancellationToken);
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));
image.Mutate(ctx => ctx.Crop(new SixLabors.ImageSharp.Rectangle(startX, startY, rectWidth, rectHeight))); var fileId = await _avatarStorage.UploadAsync(ms, "avatar.jpg", "image/jpeg", cancellationToken);
image.Mutate(ctx => ctx.Resize(400, 400)); var avatarUrl = $"/api/files/{fileId}";
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}";
}
profile.UpdateAvatar(avatarUrl); profile.UpdateAvatar(avatarUrl);
await _unitOfWork.SaveChangesAsync(cancellationToken); await _repository.UpdateAsync(profile, cancellationToken);
var dto = new ProfileProfileDto( return Result.Success(UserProfileDto.FromDocument(profile));
profile.Id, }
profile.Profilename,
profile.DisplayName,
profile.Avatar,
profile.Bio,
profile.Birthday,
profile.CreatedAt
);
return Result.Success(dto); private static async Task<MemoryStream> 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;
} }
} }
@@ -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<ProfileProfileDto>;
internal sealed class DeleteAvatarCommandHandler : ICommandHandler<DeleteAvatarCommand, ProfileProfileDto>
{
private readonly IProfileRepository _profileRepository;
private readonly IProfilesUnitOfWork _unitOfWork;
public DeleteAvatarCommandHandler(IProfileRepository profileRepository, IProfilesUnitOfWork unitOfWork)
{
_profileRepository = profileRepository;
_unitOfWork = unitOfWork;
}
public async Task<Result<ProfileProfileDto>> Handle(DeleteAvatarCommand request, CancellationToken cancellationToken)
{
var profile = await _profileRepository.GetByIdAsync(request.ProfileId, cancellationToken);
if (profile == null)
{
return Result.Failure<ProfileProfileDto>(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);
}
}
@@ -1,50 +1,79 @@
using Knot.Shared.Kernel; 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.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; namespace Knot.Modules.Profiles.Application.Profiles.Avatar;
public sealed record UploadAvatarCommand(Guid ProfileId, Stream FileStream, string FileName, string ContentType) : ICommand<ProfileProfileDto>; // ─── Upload ────────────────────────────────────────────────────────────────
internal sealed class UploadAvatarCommandHandler : ICommandHandler<UploadAvatarCommand, ProfileProfileDto> public sealed record UploadAvatarCommand(
{ Guid UserId,
private readonly IProfileRepository _profileRepository; Stream FileStream,
private readonly IProfilesUnitOfWork _unitOfWork; string FileName,
private readonly IFileStorageService _fileStorage; string ContentType) : ICommand<UserProfileDto>;
public UploadAvatarCommandHandler(IProfileRepository profileRepository, IProfilesUnitOfWork unitOfWork, IFileStorageService fileStorage) internal sealed class UploadAvatarCommandHandler : ICommandHandler<UploadAvatarCommand, UserProfileDto>
{ {
_profileRepository = profileRepository; private readonly IProfileRepository _repository;
_unitOfWork = unitOfWork; private readonly IAvatarStorageService _avatarStorage;
_fileStorage = fileStorage;
public UploadAvatarCommandHandler(IProfileRepository repository, IAvatarStorageService avatarStorage)
{
_repository = repository;
_avatarStorage = avatarStorage;
} }
public async Task<Result<ProfileProfileDto>> Handle(UploadAvatarCommand request, CancellationToken cancellationToken) public async Task<Result<UserProfileDto>> Handle(UploadAvatarCommand request, CancellationToken cancellationToken)
{ {
var profile = await _profileRepository.GetByIdAsync(request.ProfileId, cancellationToken); var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
if (profile == null) if (profile is null)
{ return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
return Result.Failure<ProfileProfileDto>(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}"; var avatarUrl = $"/api/files/{fileId}";
profile.UpdateAvatar(avatarUrl); profile.UpdateAvatar(avatarUrl);
await _unitOfWork.SaveChangesAsync(cancellationToken); await _repository.UpdateAsync(profile, cancellationToken);
var dto = new ProfileProfileDto( return Result.Success(UserProfileDto.FromDocument(profile));
profile.Id, }
profile.Profilename, }
profile.DisplayName,
profile.Avatar, // ─── Delete ────────────────────────────────────────────────────────────────
profile.Bio,
profile.Birthday, public sealed record DeleteAvatarCommand(Guid UserId) : ICommand<UserProfileDto>;
profile.CreatedAt
); internal sealed class DeleteAvatarCommandHandler : ICommandHandler<DeleteAvatarCommand, UserProfileDto>
{
return Result.Success(dto); private readonly IProfileRepository _repository;
private readonly IAvatarStorageService _avatarStorage;
public DeleteAvatarCommandHandler(IProfileRepository repository, IAvatarStorageService avatarStorage)
{
_repository = repository;
_avatarStorage = avatarStorage;
}
public async Task<Result<UserProfileDto>> Handle(DeleteAvatarCommand request, CancellationToken cancellationToken)
{
var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
if (profile is null)
return Result.Failure<UserProfileDto>(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));
} }
} }
@@ -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);
@@ -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);
@@ -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);
@@ -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
);
@@ -1,18 +1,29 @@
using System; using System;
using Knot.Modules.Profiles.Application.Abstractions;
using Knot.Modules.Profiles.Domain; using Knot.Modules.Profiles.Domain;
namespace Knot.Modules.Profiles.Application.Profiles.DTOs; namespace Knot.Modules.Profiles.Application.Profiles.DTOs;
public record ProfileProfileDto( public record UserProfileDto(
Guid Id, Guid Id,
string Profilename, string UserName,
string DisplayName, string DisplayName,
string? Avatar, string? AvatarUrl,
string? Bio, string? Bio,
DateTime? Birthday, DateTime? Birthday,
DateTime CreatedAt, DateTime CreatedAt,
bool? HideStoryViews = null, bool HideStoryViews = false,
bool IsOnline = 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
); );
}
@@ -1,36 +1,29 @@
using Knot.Shared.Kernel; using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Domain;
using Knot.Modules.Profiles.Application.Abstractions; 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; namespace Knot.Modules.Profiles.Application.Profiles.GetProfile;
public sealed record GetProfileQuery(Guid Id) : IQuery<ProfileProfileDto>; public sealed record GetProfileQuery(Guid UserId) : IQuery<UserProfileDto>;
internal sealed class GetProfileQueryHandler : IQueryHandler<GetProfileQuery, ProfileProfileDto> internal sealed class GetProfileQueryHandler : IQueryHandler<GetProfileQuery, UserProfileDto>
{ {
private readonly IProfileRepository _profileRepository; private readonly IProfileRepository _repository;
public GetProfileQueryHandler(IProfileRepository profileRepository) public GetProfileQueryHandler(IProfileRepository repository)
{ {
_profileRepository = profileRepository; _repository = repository;
} }
public async Task<Result<ProfileProfileDto>> Handle(GetProfileQuery request, CancellationToken cancellationToken) public async Task<Result<UserProfileDto>> Handle(GetProfileQuery request, CancellationToken cancellationToken)
{ {
var profile = await _profileRepository.GetByIdAsync(request.Id, cancellationToken); var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
if (profile == null) if (profile is null)
{ return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
return Result.Failure<ProfileProfileDto>(ProfilesErrors.ProfileNotFound);
}
var dto = new ProfileProfileDto( return Result.Success(UserProfileDto.FromDocument(profile));
profile.Id,
profile.Profilename,
profile.Name,
profile.AvatarUrl
);
return Result.Success(dto);
} }
} }
@@ -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;
/// <summary>
/// Реакция модуля Profiles на создание пользователя в модуле Auth.
/// Создает соответствующий документ в MongoDB.
/// </summary>
internal sealed class UserRegisteredDomainEventHandler : INotificationHandler<UserRegisteredDomainEvent>
{
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);
}
}
@@ -1,34 +1,28 @@
using Knot.Shared.Kernel; using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Domain;
using Knot.Modules.Profiles.Application.Abstractions; 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; namespace Knot.Modules.Profiles.Application.Profiles.Search;
public sealed record SearchProfilesQuery(string Query) : IQuery<List<ProfileDto>>; public sealed record SearchProfilesQuery(string Query) : IQuery<List<UserProfileDto>>;
internal sealed class SearchProfilesQueryHandler : IQueryHandler<SearchProfilesQuery, List<ProfileDto>> internal sealed class SearchProfilesQueryHandler : IQueryHandler<SearchProfilesQuery, List<UserProfileDto>>
{ {
private readonly IProfileRepository _profileRepository; private readonly IProfileRepository _repository;
public SearchProfilesQueryHandler(IProfileRepository profileRepository) public SearchProfilesQueryHandler(IProfileRepository repository)
{ {
_profileRepository = profileRepository; _repository = repository;
} }
public async Task<Result<List<ProfileDto>>> Handle(SearchProfilesQuery request, CancellationToken cancellationToken) public async Task<Result<List<UserProfileDto>>> Handle(SearchProfilesQuery request, CancellationToken cancellationToken)
{ {
var profiles = await _profileRepository.SearchProfilesAsync(request.Query, cancellationToken); var profiles = await _repository.SearchAsync(request.Query, cancellationToken);
var dtos = profiles.Select(UserProfileDto.FromDocument).ToList();
var result = profiles.Select(profile => new ProfileDto( return Result.Success(dtos);
profile.Id,
profile.Profilename,
profile.DisplayName,
profile.Avatar,
false,
DateTime.UtcNow
)).ToList();
return Result.Success(result);
} }
} }
@@ -1,43 +1,40 @@
using Knot.Shared.Kernel; 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; namespace Knot.Modules.Profiles.Application.Profiles.UpdateProfile;
public sealed record UpdateProfileCommand(Guid UserId, string? DisplayName, string? Bio, DateTime? Birthday) : ICommand<UserProfileDto>; public sealed record UpdateProfileCommand(
Guid UserId,
string? DisplayName,
string? Bio,
DateTime? Birthday) : ICommand<UserProfileDto>;
internal sealed class UpdateProfileCommandHandler : ICommandHandler<UpdateProfileCommand, UserProfileDto> internal sealed class UpdateProfileCommandHandler : ICommandHandler<UpdateProfileCommand, UserProfileDto>
{ {
private readonly IProfileRepository _userRepository; private readonly IProfileRepository _repository;
private readonly IProfilesUnitOfWork _unitOfWork;
public UpdateProfileCommandHandler(IProfileRepository userRepository, IProfilesUnitOfWork unitOfWork) public UpdateProfileCommandHandler(IProfileRepository repository)
{ {
_userRepository = userRepository; _repository = repository;
_unitOfWork = unitOfWork;
} }
public async Task<Result<UserProfileDto>> Handle(UpdateProfileCommand request, CancellationToken cancellationToken) public async Task<Result<UserProfileDto>> Handle(UpdateProfileCommand request, CancellationToken cancellationToken)
{ {
var user = await _userRepository.GetByIdAsync(request.UserId, cancellationToken); var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
if (user == null) if (profile is null)
{ return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
return Result.Failure<UserProfileDto>(IdentityErrors.UserNotFound);
}
user.UpdateProfile(request.DisplayName ?? user.DisplayName, request.Bio, request.Birthday); profile.UpdateProfile(
await _unitOfWork.SaveChangesAsync(cancellationToken); request.DisplayName ?? profile.DisplayName,
request.Bio,
request.Birthday);
var dto = new UserProfileDto( await _repository.UpdateAsync(profile, cancellationToken);
user.Id,
user.Username,
user.DisplayName,
user.Avatar,
user.Bio,
user.Birthday,
user.CreatedAt
);
return Result.Success(dto); return Result.Success(UserProfileDto.FromDocument(profile));
} }
} }
@@ -1,41 +1,32 @@
using Knot.Shared.Kernel; using Knot.Shared.Kernel;
using Knot.Modules.Profiles.Domain;
using Knot.Modules.Profiles.Application.Abstractions; 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; namespace Knot.Modules.Profiles.Application.Profiles.UpdateSettings;
public sealed record UpdateSettingsCommand(Guid ProfileId, bool? HideStoryViews) : ICommand<ProfileProfileDto>; public sealed record UpdateSettingsCommand(Guid UserId, bool? HideStoryViews) : ICommand<UserProfileDto>;
internal sealed class UpdateSettingsCommandHandler : ICommandHandler<UpdateSettingsCommand, ProfileProfileDto> internal sealed class UpdateSettingsCommandHandler : ICommandHandler<UpdateSettingsCommand, UserProfileDto>
{ {
private readonly IProfileRepository _profileRepository; private readonly IProfileRepository _repository;
private readonly IProfilesUnitOfWork _unitOfWork;
public UpdateSettingsCommandHandler(IProfileRepository profileRepository, IProfilesUnitOfWork unitOfWork) public UpdateSettingsCommandHandler(IProfileRepository repository)
{ {
_profileRepository = profileRepository; _repository = repository;
_unitOfWork = unitOfWork;
} }
public async Task<Result<ProfileProfileDto>> Handle(UpdateSettingsCommand request, CancellationToken cancellationToken) public async Task<Result<UserProfileDto>> Handle(UpdateSettingsCommand request, CancellationToken cancellationToken)
{ {
var profile = await _profileRepository.GetByIdAsync(request.ProfileId, cancellationToken); var profile = await _repository.GetByIdAsync(request.UserId, cancellationToken);
if (profile == null) if (profile is null)
{ return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);
return Result.Failure<ProfileProfileDto>(ProfilesErrors.ProfileNotFound);
}
profile.UpdateSettings(request.HideStoryViews ?? profile.HideStoryViews); profile.UpdateSettings(request.HideStoryViews ?? profile.HideStoryViews);
await _unitOfWork.SaveChangesAsync(cancellationToken); await _repository.UpdateAsync(profile, cancellationToken);
var dto = new ProfileProfileDto( return Result.Success(UserProfileDto.FromDocument(profile));
profile.Id,
profile.Profilename,
profile.Name,
profile.AvatarUrl
);
return Result.Success(dto);
} }
} }
@@ -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
);
@@ -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) {}
}
@@ -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<IProfileRepository, ProfileRepository>();
services.AddScoped<IProfilesUnitOfWork, ProfilesUnitOfWork>();
services.AddScoped<IAvatarStorageService, AvatarStorageService>();
// 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<IMongoDatabase>(database);
services.AddMediatR(config =>
config.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly));
return services;
}
}
@@ -0,0 +1,14 @@
using Knot.Shared.Kernel;
using System;
namespace Knot.Modules.Profiles.Domain.Events;
/// <summary>
/// Доменное событие: профиль пользователя создан при регистрации.
/// </summary>
public sealed record ProfileCreatedDomainEvent(Guid UserId, string Username) : IDomainEvent;
/// <summary>
/// Доменное событие: аватар профиля был изменён (для возможной инвалидации CDN-кэша).
/// </summary>
public sealed record ProfileAvatarChangedDomainEvent(Guid UserId, string? OldAvatarFileId, string? NewAvatarFileId) : IDomainEvent;
@@ -0,0 +1,69 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System;
namespace Knot.Modules.Profiles.Domain;
/// <summary>
/// MongoDB-документ профиля пользователя.
/// Id совпадает с UserId из модуля Auth (Postgres).
/// Аватар хранится в S3 — здесь лежит только ссылка.
/// </summary>
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; }
/// <summary>
/// URL или ключ объекта в S3-хранилище (MinIO).
/// Пример: "/api/files/{fileId}"
/// </summary>
public string? AvatarUrl { get; private set; }
public DateTime? Birthday { get; private set; }
/// <summary>
/// Скрывать ли просмотры сторис от других пользователей.
/// </summary>
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;
}
@@ -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;
/// <summary>
/// Адаптер к S3-хранилищу для аватаров.
/// Инжектирует общий IFileStorageService и передает ему управление.
/// </summary>
internal sealed class AvatarStorageService : IAvatarStorageService
{
private readonly IFileStorageService _storage;
public AvatarStorageService(IFileStorageService storage)
{
_storage = storage;
}
public async Task<string> 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);
}
}
@@ -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<ProfileDocument> _profiles;
public ProfileRepository(IMongoDatabase database)
{
_profiles = database.GetCollection<ProfileDocument>("profiles");
}
public async Task<ProfileDocument?> 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<List<ProfileDocument>> SearchAsync(string query, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(query))
return new List<ProfileDocument>();
// Простой регистронезависимый поиск по Regex (в реальной системе лучше использовать Text Index)
var filter = Builders<ProfileDocument>.Filter.Or(
Builders<ProfileDocument>.Filter.Regex(p => p.Username, new MongoDB.Bson.BsonRegularExpression(query, "i")),
Builders<ProfileDocument>.Filter.Regex(p => p.DisplayName, new MongoDB.Bson.BsonRegularExpression(query, "i"))
);
return await _profiles.Find(filter).Limit(20).ToListAsync(ct);
}
}
@@ -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;
}
@@ -15,12 +15,22 @@
<PackageReference Include="MediatR" Version="12.0.1" /> <PackageReference Include="MediatR" Version="12.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.4" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" /> <PackageReference Include="MongoDB.Driver" Version="3.2.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <ProjectReference Include="..\..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\..\Shared\Knot.Shared.Infrastructure\Knot.Shared.Infrastructure.csproj" />
<ProjectReference Include="..\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="Knot.Modules.Profiles.UnitTests" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Knot.Modules.Relations.Application.Abstractions;
namespace Knot.Modules.Relations.Application.Abstractions; namespace Knot.Modules.Relations.Application.Abstractions;
public interface IContactsDbContext public interface IContactsDbContext
@@ -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;
}
}
@@ -10,18 +10,6 @@ using Knot.Modules.Relations.Application.Abstractions;
namespace Knot.Modules.Relations.Application.Contacts; 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<List<ContactDto>>; public record GetContactsQuery(Guid UserId) : IQuery<List<ContactDto>>;
internal sealed class GetContactsQueryHandler : IQueryHandler<GetContactsQuery, List<ContactDto>> internal sealed class GetContactsQueryHandler : IQueryHandler<GetContactsQuery, List<ContactDto>>
@@ -56,7 +44,7 @@ internal sealed class GetContactsQueryHandler : IQueryHandler<GetContactsQuery,
replica.Username, replica.Username,
replica.DisplayName, replica.DisplayName,
replica.Avatar, replica.Avatar,
false, // Presence handled by another service or real-time false,
null, null,
rel.Id, rel.Id,
rel.Status == ContactStatus.Blocked, rel.Status == ContactStatus.Blocked,
@@ -14,9 +14,6 @@ internal sealed class RemoveContactCommandHandler : ICommandHandler<RemoveContac
{ {
private readonly IContactsDbContext _context; private readonly IContactsDbContext _context;
public ContactRelationNotFound() : base("Contacts.NotFound", "Contact relation not found.") { }
public ContactRelationUnauthorized() : base("Contacts.Unauthorized", "You are not authorized to remove this contact.") { }
public RemoveContactCommandHandler(IContactsDbContext context) public RemoveContactCommandHandler(IContactsDbContext context)
{ {
_context = context; _context = context;

Some files were not shown because too many files have changed in this diff Show More