89 Commits
Author SHA1 Message Date
max f4f61071ed внедрил пресеты статусов и модульную архитектуру 2026-04-18 00:43:46 +03:00
max e8c9a55fe9 безопасная обертка IsUserInvisibleAsync 2026-04-17 23:03:15 +03:00
max bccbf12a11 фикс <был недавно> 2026-04-17 22:24:09 +03:00
Халимов Рустам c8b0384392 Референс на контракты профиля 2026-04-17 21:07:09 +03:00
max 5245e8b7ae скрытие статуса online через сокеты и поле isInvisible в профиле 2026-04-17 20:47:28 +03:00
max b346372555 внедрена защищенная смена пароля (бэк + фронт) 2026-04-17 19:52:57 +03:00
max d2e6eb578a внедрил систему статусов (BIO) 2026-04-17 18:33:46 +03:00
max ae710c3d43 поправка групп 2026-04-17 17:56:30 +03:00
Халимов Рустам 600e43eec5 Merge remote-tracking branch 'origin/bugfix_web' into bugfix_web 2026-04-17 00:47:47 +03:00
Халимов Рустам 7b251686b2 Исправление подгрузки данных профиля 2026-04-17 00:43:07 +03:00
max 00682b2977 e# speciallyse enter a commit message to explain why this merge is
necessary,
if it merges an updated upstream into a topic branch.
2026-04-17 00:42:48 +03:00
max e05572fc3f Исправил неработющую функцию добавления пользователя через чат в группу 2026-04-17 00:42:03 +03:00
Халимов Рустам c264b7df27 исправил ошибки компиляции TypeScript 2026-04-17 00:37:37 +03:00
Халимов Рустам 56f75ae32b Получение данных профиля 2026-04-17 00:33:45 +03:00
Халимов Рустам ca9cf27716 О себе, редактирование в профиле 2026-04-17 00:26:38 +03:00
Халимов Рустам 7225e3272e Убрал аватары и имена внутри чата личных чатов 2026-04-16 23:54:19 +03:00
Халимов Рустам 86ae06beb6 Отступы в баблах 2026-04-16 23:48:36 +03:00
Халимов Рустам 454f70f716 Вставка и перетаскивание в поле ввода 2026-04-16 23:42:54 +03:00
Халимов Рустам e700609d30 Дубликат печати, разметка 2026-04-16 23:36:12 +03:00
Халимов Рустам 88f39aa51f Change for env 2026-04-16 22:42:26 +03:00
Халимов Рустам c3dbbaa7b8 Rename all 2026-04-16 22:30:37 +03:00
Халимов Рустам 2eb4f48ca0 Test create 2026-04-16 22:28:41 +03:00
Халимов Рустам 0c1adaab6c Rename web 2026-04-16 22:26:04 +03:00
Халимов Рустам a52726d0e6 Only web 2026-04-16 22:24:41 +03:00
Халимов Рустам d812a7a40c Change services name 2026-04-16 22:21:58 +03:00
Халимов Рустам c8b4fed25a Change ports 2026-04-16 22:18:04 +03:00
Халимов Рустам 8399d32490 Редактирование и плеер 2026-04-08 15:23:10 +03:00
Халимов Рустам 3905094ff4 Правка миграций 2026-04-07 21:22:38 +03:00
Халимов Рустам 9e8625aea1 Миграции 2026-04-07 21:20:06 +03:00
Халимов Рустам 5b905c94da Компоуз 2026-04-07 21:12:34 +03:00
Халимов Рустам 0eecc01374 Сборка 2026-04-07 21:05:23 +03:00
Халимов Рустам c37e1723d4 Фикс .env 2026-04-07 17:29:22 +03:00
Халимов Рустам 02043d4d97 Минимальная адаптация под мобилку 2026-04-07 12:29:41 +03:00
Халимов Рустам 852efa090e Опросы 2026-04-07 11:11:35 +03:00
Халимов Рустам c45f4db61c Заготовка опросов 2026-04-07 01:01:29 +03:00
Халимов Рустам 02a85fc587 Прокрутка 2026-04-06 23:42:46 +03:00
Халимов Рустам e09860700c Удален мусор 2026-04-06 23:36:42 +03:00
Халимов Рустам 32c9bc43cf Починка импорта, плеер для аудио 2026-04-06 23:35:45 +03:00
Халимов Рустам d96e4ec7d4 Правка импорта 2026-04-06 22:35:54 +03:00
Халимов Рустам 1558b20470 Импорт 2026-04-06 22:20:13 +03:00
Халимов Рустам fa185afc73 Информация о звонках в чате 2026-04-06 01:51:48 +03:00
Халимов Рустам 90096ce2bc Звонки 2026-04-06 01:27:27 +03:00
Халимов Рустам 65d5f5fee9 Звонки 2026-04-05 23:41:55 +03:00
Халимов Рустам e11240f78f Аватар 2026-04-05 01:27:58 +03:00
Халимов Рустам 53f193970c Локализация 2026-04-05 00:34:25 +03:00
Халимов Рустам f41ad0bcf8 Мелкие правки 2026-04-05 00:11:10 +03:00
Халимов Рустам b49ff47762 Истории, обрезка 2026-04-03 23:28:00 +03:00
Халимов Рустам 182bbe2ad8 Обрезка видео 2026-04-03 21:32:02 +03:00
Халимов Рустам a04f04a448 Фиксы по историям 2026-04-03 21:13:28 +03:00
Халимов Рустам 2ab5b295e8 Прокрутка дерганая 2026-04-03 16:57:06 +03:00
Халимов Рустам 1b8abbc995 Переделаны истории 2026-04-03 01:03:32 +03:00
Халимов Рустам 943699139b Разделение сообщения 2026-04-02 22:11:03 +03:00
Халимов Рустам 757142eda7 Аватары заглушки 2026-04-02 15:26:05 +03:00
Халимов Рустам a42007df2d Дизайн 2026-04-02 14:57:32 +03:00
Халимов Рустам 9df7d7aaf1 Анимации, профиль, медиа 2026-04-02 02:37:03 +03:00
Халимов Рустам 4ae7dd60ce Рабочий чат 2026-04-01 23:18:55 +03:00
Халимов Рустам 249c344df8 Файлы, правки отображения 2026-04-01 17:42:29 +03:00
Халимов Рустам 2c6d6f831f Правки админки, вход 2026-03-31 23:55:38 +03:00
Халимов Рустам bce8f92101 Восстановление очистки админки 2026-03-31 21:31:22 +03:00
Халимов Рустам aa69c44a64 Новый стиль 2026-03-31 21:11:05 +03:00
Халимов Рустам 4faa7561a0 Админка рабочая 2026-03-31 15:43:24 +03:00
Халимов Рустам 8025340e45 Админка 2026-03-31 01:54:35 +03:00
Халимов Рустам 60d3cf12ae Сборка 2026-03-31 00:38:34 +03:00
Халимов Рустам b09e197626 Сборка 2026-03-31 00:16:18 +03:00
Халимов Рустам f1f8f6e012 Апи 2026-03-30 23:57:34 +03:00
Халимов Рустам d3f1e3f361 Эндпоинты 2026-03-30 23:41:01 +03:00
Халимов Рустам ce212c11c1 Конфиг 2026-03-30 22:54:31 +03:00
Халимов Рустам 143c0b7dc4 Правки 2026-03-30 22:48:35 +03:00
Халимов Рустам cb93ff7240 Зависимости на контракты 2026-03-30 17:48:39 +03:00
Халимов Рустам 09e5cbaa76 Разделение 2026-03-30 15:35:28 +03:00
Халимов Рустам 465e84e686 Очистка 2026-03-30 11:14:20 +03:00
Халимов Рустам 44d59dab2a Удалены тесты 2026-03-30 10:30:06 +03:00
Халимов Рустам b719c50413 Фикс 2026-03-30 01:45:40 +03:00
Халимов Рустам 9438cf1b35 Структура 2026-03-30 01:45:19 +03:00
Халимов Рустам 89b2eeea6c Структура 2026-03-30 00:09:12 +03:00
Халимов Рустам 0209802e9e Контракты 2026-03-29 23:39:17 +03:00
Халимов Рустам 22bc964f27 Билд 2026-03-29 14:26:14 +03:00
Халимов Рустам 9bae9752cc Админка 2026-03-28 01:34:51 +03:00
Халимов Рустам ba5c5210d4 Миграции, сборка 2026-03-27 23:09:16 +03:00
Халимов Рустам 030ae1e4e4 Фронт 2026-03-27 16:04:46 +03:00
Халимов Рустам 28fb8c25de Документация админки 2026-03-27 15:48:09 +03:00
Халимов Рустам 11f2b232a3 Сборка бэк 2026-03-27 15:45:34 +03:00
Халимов Рустам 16978c423c WebRtc модуль и документация, импорт телеграма 2026-03-27 01:44:05 +03:00
Халимов Рустам b399649cfd Модуль связей и Klipy 2026-03-27 01:22:20 +03:00
Халимов Рустам 7ef73b414c Структура, доп модули, федерация, документация 2026-03-27 00:55:01 +03:00
Халимов Рустам 7cb6ac61dd Модуль историй с документацией 2026-03-23 01:43:27 +03:00
Халимов Рустам 6e532b021d Перепиливание под чистый DDD 2026-03-22 23:59:33 +03:00
Халимов Рустам 5da1a2f45d Архитектура 2026-03-21 02:19:38 +03:00
Халимов Рустам f3e0941647 Фикс чата 2026-03-20 01:40:02 +03:00
630 changed files with 27378 additions and 11959 deletions
+479 -61
View File
@@ -13,19 +13,113 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Shared.Infrastructure"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Identity", "Identity", "{0EC62A1A-9858-3A60-8A0C-FC9AACFA2EC7}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Profiles", "Profiles", "{CA5186C2-289A-9324-4152-9805A73A9773}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Identity", "src\Modules\Identity\Knot.Modules.Identity.csproj", "{5BB2F421-C4C4-40A1-8D6E-FEBB79AE7C98}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Profiles", "src\Modules\Profiles\Knot.Modules.Profiles.csproj", "{CD14770B-8BCD-4D93-AD2C-581ED030E209}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Chats", "Chats", "{B4154CE3-9153-E2B0-0515-138FFCE32938}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Relations", "Relations", "{10980DF6-D81A-2FEE-4ACB-4D508FA38BC6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Chats", "src\Modules\Chats\Knot.Modules.Chats.csproj", "{695CC919-E321-4DC0-8D70-C631C51624E1}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Relations", "src\Modules\Relations\Knot.Modules.Relations.csproj", "{B97352F3-709A-42CF-A448-07B9996F5FBE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Stories", "Stories", "{F83653D1-5494-68B8-6444-7DEF58635BF6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Identity.UnitTests", "tests\Knot.Modules.Identity.UnitTests\Knot.Modules.Identity.UnitTests.csproj", "{AB2F334E-DD7C-4314-93B9-941AFC6B2ACD}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Stories", "src\Modules\Stories\Knot.Modules.Stories.csproj", "{BC98E6FC-B46A-4554-B75E-B912714A915C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Chats.UnitTests", "tests\Knot.Modules.Chats.UnitTests\Knot.Modules.Chats.UnitTests.csproj", "{F48A3E3A-EEBB-43AA-81AB-7DD7FE5F29B4}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Storage", "Storage", "{AF61C489-0B09-54BC-BD16-4353135E9C4A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Storage", "src\Modules\Storage\Knot.Modules.Storage.csproj", "{D052C05A-CDCD-48DF-8F4F-0B10E3719819}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Auth", "Auth", "{E9D55F35-C373-3DD8-6914-3213F2F582A2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Auth", "src\Modules\Auth\Knot.Modules.Auth.csproj", "{CF08AE9C-B337-47BA-B273-4B8E5610D7BD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Messaging", "Messaging", "{AE120A0E-5815-2D46-946C-DA544028AD88}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Messaging", "src\Modules\Messaging\Knot.Modules.Messaging.csproj", "{C0B5496E-F083-413E-90AE-31282A0ACCA5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TelegramImport", "TelegramImport", "{4D5D7319-366D-1BD8-49C5-A840B487E852}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.TelegramImport", "src\Modules\TelegramImport\Knot.Modules.TelegramImport.csproj", "{C905C120-DE44-4CBC-8F98-5BE148115487}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Conversations", "Conversations", "{138B168B-2473-B06D-4BD5-1B010DA319A1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Conversations", "src\Modules\Conversations\Knot.Modules.Conversations.csproj", "{F3EFD119-44F7-46BF-A03C-060F30A1A1E0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Admin", "Admin", "{E9E1155A-A339-766C-C185-6698FF423EBC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Admin", "src\Modules\Admin\Knot.Modules.Admin.csproj", "{FFC12440-F128-48F5-9BE5-E5263930F876}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Federation", "Federation", "{837D2CF4-7AEA-BF76-6362-47ECD7058EEC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Federation", "src\Modules\Federation\Knot.Modules.Federation.csproj", "{716E089D-0661-4D39-813B-063DF9A4C0E4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Klipy", "Klipy", "{4BC86D3F-8D7B-ACB9-EA51-9374BC9D421A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Klipy", "src\Modules\Klipy\Knot.Modules.Klipy.csproj", "{2498C58C-53B0-4DF2-B4EA-BA69B35A7488}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Settings", "Settings", "{0587C5BB-3015-204D-19B6-EB01E03E48B6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.Settings", "src\Modules\Settings\Knot.Modules.Settings.csproj", "{AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebRtc", "WebRtc", "{DD6FAF9E-10A0-95E7-0C06-698A6F70AFEE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Modules.WebRtc", "src\Modules\WebRtc\Knot.Modules.WebRtc.csproj", "{03BE4EB1-FD38-445A-9A4B-7B481560E9A2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Host", "src\Host\Host.csproj", "{98ABB710-9106-4E5C-AA04-D19D5E92EDC1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Contracts", "Contracts", "{FFBE31B8-50B9-B415-22BC-2F92C97CADA3}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Admin", "Admin", "{1C8A9B45-FCC1-A5B3-3601-CB39D7CED4E3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Contracts.Admin", "src\Contracts\Admin\Knot.Contracts.Admin.csproj", "{D3C7D267-5646-4907-A451-E7CFA853BBF5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Auth", "Auth", "{28F8F726-0D9B-F051-A252-088B861055C6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Contracts.Auth", "src\Contracts\Auth\Knot.Contracts.Auth.csproj", "{D65EE034-84B9-4EBE-BC1F-5606AB4524A4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Conversations", "Conversations", "{19B3F750-A3A3-3AEB-CE4F-46FB52BB3151}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Contracts.Conversations", "src\Contracts\Conversations\Knot.Contracts.Conversations.csproj", "{17B31CB0-F2C5-47A6-9BF2-C55403C97DC7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Federation", "Federation", "{E97EBA1B-122C-1D64-94D9-187425FF5DF0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Contracts.Federation", "src\Contracts\Federation\Knot.Contracts.Federation.csproj", "{62A821F2-F296-4888-8AB1-A80ED514AC08}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Klipy", "Klipy", "{7555181C-EB04-A6C5-D475-6FC0320F57B5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Contracts.Klipy", "src\Contracts\Klipy\Knot.Contracts.Klipy.csproj", "{D0CE2D52-EC1C-444E-A5C5-79813E1814D4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Messaging", "Messaging", "{3E835F76-AFEE-6419-1441-2CED62F241B2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Contracts.Messaging", "src\Contracts\Messaging\Knot.Contracts.Messaging.csproj", "{87048157-DF94-49E6-A34F-7DEF3255FA35}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Profiles", "Profiles", "{0286CA1A-73E9-C153-D97B-31BA60AC101B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Contracts.Profiles", "src\Contracts\Profiles\Knot.Contracts.Profiles.csproj", "{D28F8DB7-077E-47F6-B07D-BB93AF950A0D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Relations", "Relations", "{319B016B-E46C-7232-249F-DBE50922C757}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Contracts.Relations", "src\Contracts\Relations\Knot.Contracts.Relations.csproj", "{75CDF4D0-53B2-4FCE-9C2D-06940FD2E817}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Settings", "Settings", "{C91F5B42-26A0-36B8-1C54-AE965E022F68}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Contracts.Settings", "src\Contracts\Settings\Knot.Contracts.Settings.csproj", "{338C69EA-F921-41B3-BB6C-116AADEBF356}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Storage", "Storage", "{6F45E82C-238A-FDE4-59E5-CCCD65D77EB8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Contracts.Storage", "src\Contracts\Storage\Knot.Contracts.Storage.csproj", "{531F88C6-B9A5-4BF6-A121-AAA95DE4A288}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Stories", "Stories", "{3DE423CC-7CB5-260E-2A62-D5E89000EF8F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Contracts.Stories", "src\Contracts\Stories\Knot.Contracts.Stories.csproj", "{613354F0-3B35-4406-8250-C8E88A0DECF6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TelegramImport", "TelegramImport", "{5C1B4B87-5A65-F9C7-EAA3-6456BCBFD6EC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Contracts.TelegramImport", "src\Contracts\TelegramImport\Knot.Contracts.TelegramImport.csproj", "{A47CCC4C-8B19-4A12-8B05-BE4AAEABB1BA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebRtc", "WebRtc", "{6DD751EB-CB14-7F42-4B4B-7E4DCC49705A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Knot.Contracts.WebRtc", "src\Contracts\WebRtc\Knot.Contracts.WebRtc.csproj", "{D939680D-864D-4FED-A81D-8F30EE6A97A0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -61,54 +155,330 @@ Global
{6C13E9A4-234C-4BF1-A145-F1BBA2AC56CB}.Release|x64.Build.0 = Release|Any CPU
{6C13E9A4-234C-4BF1-A145-F1BBA2AC56CB}.Release|x86.ActiveCfg = Release|Any CPU
{6C13E9A4-234C-4BF1-A145-F1BBA2AC56CB}.Release|x86.Build.0 = Release|Any CPU
{5BB2F421-C4C4-40A1-8D6E-FEBB79AE7C98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5BB2F421-C4C4-40A1-8D6E-FEBB79AE7C98}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5BB2F421-C4C4-40A1-8D6E-FEBB79AE7C98}.Debug|x64.ActiveCfg = Debug|Any CPU
{5BB2F421-C4C4-40A1-8D6E-FEBB79AE7C98}.Debug|x64.Build.0 = Debug|Any CPU
{5BB2F421-C4C4-40A1-8D6E-FEBB79AE7C98}.Debug|x86.ActiveCfg = Debug|Any CPU
{5BB2F421-C4C4-40A1-8D6E-FEBB79AE7C98}.Debug|x86.Build.0 = Debug|Any CPU
{5BB2F421-C4C4-40A1-8D6E-FEBB79AE7C98}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5BB2F421-C4C4-40A1-8D6E-FEBB79AE7C98}.Release|Any CPU.Build.0 = Release|Any CPU
{5BB2F421-C4C4-40A1-8D6E-FEBB79AE7C98}.Release|x64.ActiveCfg = Release|Any CPU
{5BB2F421-C4C4-40A1-8D6E-FEBB79AE7C98}.Release|x64.Build.0 = Release|Any CPU
{5BB2F421-C4C4-40A1-8D6E-FEBB79AE7C98}.Release|x86.ActiveCfg = Release|Any CPU
{5BB2F421-C4C4-40A1-8D6E-FEBB79AE7C98}.Release|x86.Build.0 = Release|Any CPU
{695CC919-E321-4DC0-8D70-C631C51624E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{695CC919-E321-4DC0-8D70-C631C51624E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{695CC919-E321-4DC0-8D70-C631C51624E1}.Debug|x64.ActiveCfg = Debug|Any CPU
{695CC919-E321-4DC0-8D70-C631C51624E1}.Debug|x64.Build.0 = Debug|Any CPU
{695CC919-E321-4DC0-8D70-C631C51624E1}.Debug|x86.ActiveCfg = Debug|Any CPU
{695CC919-E321-4DC0-8D70-C631C51624E1}.Debug|x86.Build.0 = Debug|Any CPU
{695CC919-E321-4DC0-8D70-C631C51624E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{695CC919-E321-4DC0-8D70-C631C51624E1}.Release|Any CPU.Build.0 = Release|Any CPU
{695CC919-E321-4DC0-8D70-C631C51624E1}.Release|x64.ActiveCfg = Release|Any CPU
{695CC919-E321-4DC0-8D70-C631C51624E1}.Release|x64.Build.0 = Release|Any CPU
{695CC919-E321-4DC0-8D70-C631C51624E1}.Release|x86.ActiveCfg = Release|Any CPU
{695CC919-E321-4DC0-8D70-C631C51624E1}.Release|x86.Build.0 = Release|Any CPU
{AB2F334E-DD7C-4314-93B9-941AFC6B2ACD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AB2F334E-DD7C-4314-93B9-941AFC6B2ACD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AB2F334E-DD7C-4314-93B9-941AFC6B2ACD}.Debug|x64.ActiveCfg = Debug|Any CPU
{AB2F334E-DD7C-4314-93B9-941AFC6B2ACD}.Debug|x64.Build.0 = Debug|Any CPU
{AB2F334E-DD7C-4314-93B9-941AFC6B2ACD}.Debug|x86.ActiveCfg = Debug|Any CPU
{AB2F334E-DD7C-4314-93B9-941AFC6B2ACD}.Debug|x86.Build.0 = Debug|Any CPU
{AB2F334E-DD7C-4314-93B9-941AFC6B2ACD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AB2F334E-DD7C-4314-93B9-941AFC6B2ACD}.Release|Any CPU.Build.0 = Release|Any CPU
{AB2F334E-DD7C-4314-93B9-941AFC6B2ACD}.Release|x64.ActiveCfg = Release|Any CPU
{AB2F334E-DD7C-4314-93B9-941AFC6B2ACD}.Release|x64.Build.0 = Release|Any CPU
{AB2F334E-DD7C-4314-93B9-941AFC6B2ACD}.Release|x86.ActiveCfg = Release|Any CPU
{AB2F334E-DD7C-4314-93B9-941AFC6B2ACD}.Release|x86.Build.0 = Release|Any CPU
{F48A3E3A-EEBB-43AA-81AB-7DD7FE5F29B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F48A3E3A-EEBB-43AA-81AB-7DD7FE5F29B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F48A3E3A-EEBB-43AA-81AB-7DD7FE5F29B4}.Debug|x64.ActiveCfg = Debug|Any CPU
{F48A3E3A-EEBB-43AA-81AB-7DD7FE5F29B4}.Debug|x64.Build.0 = Debug|Any CPU
{F48A3E3A-EEBB-43AA-81AB-7DD7FE5F29B4}.Debug|x86.ActiveCfg = Debug|Any CPU
{F48A3E3A-EEBB-43AA-81AB-7DD7FE5F29B4}.Debug|x86.Build.0 = Debug|Any CPU
{F48A3E3A-EEBB-43AA-81AB-7DD7FE5F29B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F48A3E3A-EEBB-43AA-81AB-7DD7FE5F29B4}.Release|Any CPU.Build.0 = Release|Any CPU
{F48A3E3A-EEBB-43AA-81AB-7DD7FE5F29B4}.Release|x64.ActiveCfg = Release|Any CPU
{F48A3E3A-EEBB-43AA-81AB-7DD7FE5F29B4}.Release|x64.Build.0 = Release|Any CPU
{F48A3E3A-EEBB-43AA-81AB-7DD7FE5F29B4}.Release|x86.ActiveCfg = Release|Any CPU
{F48A3E3A-EEBB-43AA-81AB-7DD7FE5F29B4}.Release|x86.Build.0 = Release|Any CPU
{CD14770B-8BCD-4D93-AD2C-581ED030E209}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CD14770B-8BCD-4D93-AD2C-581ED030E209}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CD14770B-8BCD-4D93-AD2C-581ED030E209}.Debug|x64.ActiveCfg = Debug|Any CPU
{CD14770B-8BCD-4D93-AD2C-581ED030E209}.Debug|x64.Build.0 = Debug|Any CPU
{CD14770B-8BCD-4D93-AD2C-581ED030E209}.Debug|x86.ActiveCfg = Debug|Any CPU
{CD14770B-8BCD-4D93-AD2C-581ED030E209}.Debug|x86.Build.0 = Debug|Any CPU
{CD14770B-8BCD-4D93-AD2C-581ED030E209}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CD14770B-8BCD-4D93-AD2C-581ED030E209}.Release|Any CPU.Build.0 = Release|Any CPU
{CD14770B-8BCD-4D93-AD2C-581ED030E209}.Release|x64.ActiveCfg = Release|Any CPU
{CD14770B-8BCD-4D93-AD2C-581ED030E209}.Release|x64.Build.0 = Release|Any CPU
{CD14770B-8BCD-4D93-AD2C-581ED030E209}.Release|x86.ActiveCfg = Release|Any CPU
{CD14770B-8BCD-4D93-AD2C-581ED030E209}.Release|x86.Build.0 = Release|Any CPU
{B97352F3-709A-42CF-A448-07B9996F5FBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B97352F3-709A-42CF-A448-07B9996F5FBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B97352F3-709A-42CF-A448-07B9996F5FBE}.Debug|x64.ActiveCfg = Debug|Any CPU
{B97352F3-709A-42CF-A448-07B9996F5FBE}.Debug|x64.Build.0 = Debug|Any CPU
{B97352F3-709A-42CF-A448-07B9996F5FBE}.Debug|x86.ActiveCfg = Debug|Any CPU
{B97352F3-709A-42CF-A448-07B9996F5FBE}.Debug|x86.Build.0 = Debug|Any CPU
{B97352F3-709A-42CF-A448-07B9996F5FBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B97352F3-709A-42CF-A448-07B9996F5FBE}.Release|Any CPU.Build.0 = Release|Any CPU
{B97352F3-709A-42CF-A448-07B9996F5FBE}.Release|x64.ActiveCfg = Release|Any CPU
{B97352F3-709A-42CF-A448-07B9996F5FBE}.Release|x64.Build.0 = Release|Any CPU
{B97352F3-709A-42CF-A448-07B9996F5FBE}.Release|x86.ActiveCfg = Release|Any CPU
{B97352F3-709A-42CF-A448-07B9996F5FBE}.Release|x86.Build.0 = Release|Any CPU
{BC98E6FC-B46A-4554-B75E-B912714A915C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BC98E6FC-B46A-4554-B75E-B912714A915C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BC98E6FC-B46A-4554-B75E-B912714A915C}.Debug|x64.ActiveCfg = Debug|Any CPU
{BC98E6FC-B46A-4554-B75E-B912714A915C}.Debug|x64.Build.0 = Debug|Any CPU
{BC98E6FC-B46A-4554-B75E-B912714A915C}.Debug|x86.ActiveCfg = Debug|Any CPU
{BC98E6FC-B46A-4554-B75E-B912714A915C}.Debug|x86.Build.0 = Debug|Any CPU
{BC98E6FC-B46A-4554-B75E-B912714A915C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BC98E6FC-B46A-4554-B75E-B912714A915C}.Release|Any CPU.Build.0 = Release|Any CPU
{BC98E6FC-B46A-4554-B75E-B912714A915C}.Release|x64.ActiveCfg = Release|Any CPU
{BC98E6FC-B46A-4554-B75E-B912714A915C}.Release|x64.Build.0 = Release|Any CPU
{BC98E6FC-B46A-4554-B75E-B912714A915C}.Release|x86.ActiveCfg = Release|Any CPU
{BC98E6FC-B46A-4554-B75E-B912714A915C}.Release|x86.Build.0 = Release|Any CPU
{D052C05A-CDCD-48DF-8F4F-0B10E3719819}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D052C05A-CDCD-48DF-8F4F-0B10E3719819}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D052C05A-CDCD-48DF-8F4F-0B10E3719819}.Debug|x64.ActiveCfg = Debug|Any CPU
{D052C05A-CDCD-48DF-8F4F-0B10E3719819}.Debug|x64.Build.0 = Debug|Any CPU
{D052C05A-CDCD-48DF-8F4F-0B10E3719819}.Debug|x86.ActiveCfg = Debug|Any CPU
{D052C05A-CDCD-48DF-8F4F-0B10E3719819}.Debug|x86.Build.0 = Debug|Any CPU
{D052C05A-CDCD-48DF-8F4F-0B10E3719819}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D052C05A-CDCD-48DF-8F4F-0B10E3719819}.Release|Any CPU.Build.0 = Release|Any CPU
{D052C05A-CDCD-48DF-8F4F-0B10E3719819}.Release|x64.ActiveCfg = Release|Any CPU
{D052C05A-CDCD-48DF-8F4F-0B10E3719819}.Release|x64.Build.0 = Release|Any CPU
{D052C05A-CDCD-48DF-8F4F-0B10E3719819}.Release|x86.ActiveCfg = Release|Any CPU
{D052C05A-CDCD-48DF-8F4F-0B10E3719819}.Release|x86.Build.0 = Release|Any CPU
{CF08AE9C-B337-47BA-B273-4B8E5610D7BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CF08AE9C-B337-47BA-B273-4B8E5610D7BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CF08AE9C-B337-47BA-B273-4B8E5610D7BD}.Debug|x64.ActiveCfg = Debug|Any CPU
{CF08AE9C-B337-47BA-B273-4B8E5610D7BD}.Debug|x64.Build.0 = Debug|Any CPU
{CF08AE9C-B337-47BA-B273-4B8E5610D7BD}.Debug|x86.ActiveCfg = Debug|Any CPU
{CF08AE9C-B337-47BA-B273-4B8E5610D7BD}.Debug|x86.Build.0 = Debug|Any CPU
{CF08AE9C-B337-47BA-B273-4B8E5610D7BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CF08AE9C-B337-47BA-B273-4B8E5610D7BD}.Release|Any CPU.Build.0 = Release|Any CPU
{CF08AE9C-B337-47BA-B273-4B8E5610D7BD}.Release|x64.ActiveCfg = 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.Build.0 = Release|Any CPU
{C0B5496E-F083-413E-90AE-31282A0ACCA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C0B5496E-F083-413E-90AE-31282A0ACCA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C0B5496E-F083-413E-90AE-31282A0ACCA5}.Debug|x64.ActiveCfg = Debug|Any CPU
{C0B5496E-F083-413E-90AE-31282A0ACCA5}.Debug|x64.Build.0 = Debug|Any CPU
{C0B5496E-F083-413E-90AE-31282A0ACCA5}.Debug|x86.ActiveCfg = Debug|Any CPU
{C0B5496E-F083-413E-90AE-31282A0ACCA5}.Debug|x86.Build.0 = Debug|Any CPU
{C0B5496E-F083-413E-90AE-31282A0ACCA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C0B5496E-F083-413E-90AE-31282A0ACCA5}.Release|Any CPU.Build.0 = Release|Any CPU
{C0B5496E-F083-413E-90AE-31282A0ACCA5}.Release|x64.ActiveCfg = Release|Any CPU
{C0B5496E-F083-413E-90AE-31282A0ACCA5}.Release|x64.Build.0 = Release|Any CPU
{C0B5496E-F083-413E-90AE-31282A0ACCA5}.Release|x86.ActiveCfg = Release|Any CPU
{C0B5496E-F083-413E-90AE-31282A0ACCA5}.Release|x86.Build.0 = Release|Any CPU
{C905C120-DE44-4CBC-8F98-5BE148115487}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C905C120-DE44-4CBC-8F98-5BE148115487}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C905C120-DE44-4CBC-8F98-5BE148115487}.Debug|x64.ActiveCfg = Debug|Any CPU
{C905C120-DE44-4CBC-8F98-5BE148115487}.Debug|x64.Build.0 = Debug|Any CPU
{C905C120-DE44-4CBC-8F98-5BE148115487}.Debug|x86.ActiveCfg = Debug|Any CPU
{C905C120-DE44-4CBC-8F98-5BE148115487}.Debug|x86.Build.0 = Debug|Any CPU
{C905C120-DE44-4CBC-8F98-5BE148115487}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C905C120-DE44-4CBC-8F98-5BE148115487}.Release|Any CPU.Build.0 = Release|Any CPU
{C905C120-DE44-4CBC-8F98-5BE148115487}.Release|x64.ActiveCfg = Release|Any CPU
{C905C120-DE44-4CBC-8F98-5BE148115487}.Release|x64.Build.0 = Release|Any CPU
{C905C120-DE44-4CBC-8F98-5BE148115487}.Release|x86.ActiveCfg = Release|Any CPU
{C905C120-DE44-4CBC-8F98-5BE148115487}.Release|x86.Build.0 = Release|Any CPU
{F3EFD119-44F7-46BF-A03C-060F30A1A1E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3EFD119-44F7-46BF-A03C-060F30A1A1E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3EFD119-44F7-46BF-A03C-060F30A1A1E0}.Debug|x64.ActiveCfg = Debug|Any CPU
{F3EFD119-44F7-46BF-A03C-060F30A1A1E0}.Debug|x64.Build.0 = Debug|Any CPU
{F3EFD119-44F7-46BF-A03C-060F30A1A1E0}.Debug|x86.ActiveCfg = Debug|Any CPU
{F3EFD119-44F7-46BF-A03C-060F30A1A1E0}.Debug|x86.Build.0 = Debug|Any CPU
{F3EFD119-44F7-46BF-A03C-060F30A1A1E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3EFD119-44F7-46BF-A03C-060F30A1A1E0}.Release|Any CPU.Build.0 = Release|Any CPU
{F3EFD119-44F7-46BF-A03C-060F30A1A1E0}.Release|x64.ActiveCfg = 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.Build.0 = Release|Any CPU
{FFC12440-F128-48F5-9BE5-E5263930F876}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FFC12440-F128-48F5-9BE5-E5263930F876}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFC12440-F128-48F5-9BE5-E5263930F876}.Debug|x64.ActiveCfg = Debug|Any CPU
{FFC12440-F128-48F5-9BE5-E5263930F876}.Debug|x64.Build.0 = Debug|Any CPU
{FFC12440-F128-48F5-9BE5-E5263930F876}.Debug|x86.ActiveCfg = Debug|Any CPU
{FFC12440-F128-48F5-9BE5-E5263930F876}.Debug|x86.Build.0 = Debug|Any CPU
{FFC12440-F128-48F5-9BE5-E5263930F876}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FFC12440-F128-48F5-9BE5-E5263930F876}.Release|Any CPU.Build.0 = Release|Any CPU
{FFC12440-F128-48F5-9BE5-E5263930F876}.Release|x64.ActiveCfg = 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.Build.0 = Release|Any CPU
{716E089D-0661-4D39-813B-063DF9A4C0E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{716E089D-0661-4D39-813B-063DF9A4C0E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{716E089D-0661-4D39-813B-063DF9A4C0E4}.Debug|x64.ActiveCfg = Debug|Any CPU
{716E089D-0661-4D39-813B-063DF9A4C0E4}.Debug|x64.Build.0 = Debug|Any CPU
{716E089D-0661-4D39-813B-063DF9A4C0E4}.Debug|x86.ActiveCfg = Debug|Any CPU
{716E089D-0661-4D39-813B-063DF9A4C0E4}.Debug|x86.Build.0 = Debug|Any CPU
{716E089D-0661-4D39-813B-063DF9A4C0E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{716E089D-0661-4D39-813B-063DF9A4C0E4}.Release|Any CPU.Build.0 = Release|Any CPU
{716E089D-0661-4D39-813B-063DF9A4C0E4}.Release|x64.ActiveCfg = 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.Build.0 = Release|Any CPU
{2498C58C-53B0-4DF2-B4EA-BA69B35A7488}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2498C58C-53B0-4DF2-B4EA-BA69B35A7488}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2498C58C-53B0-4DF2-B4EA-BA69B35A7488}.Debug|x64.ActiveCfg = Debug|Any CPU
{2498C58C-53B0-4DF2-B4EA-BA69B35A7488}.Debug|x64.Build.0 = Debug|Any CPU
{2498C58C-53B0-4DF2-B4EA-BA69B35A7488}.Debug|x86.ActiveCfg = Debug|Any CPU
{2498C58C-53B0-4DF2-B4EA-BA69B35A7488}.Debug|x86.Build.0 = Debug|Any CPU
{2498C58C-53B0-4DF2-B4EA-BA69B35A7488}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2498C58C-53B0-4DF2-B4EA-BA69B35A7488}.Release|Any CPU.Build.0 = Release|Any CPU
{2498C58C-53B0-4DF2-B4EA-BA69B35A7488}.Release|x64.ActiveCfg = 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.Build.0 = Release|Any CPU
{AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}.Debug|x64.ActiveCfg = Debug|Any CPU
{AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}.Debug|x64.Build.0 = Debug|Any CPU
{AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}.Debug|x86.ActiveCfg = Debug|Any CPU
{AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}.Debug|x86.Build.0 = Debug|Any CPU
{AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}.Release|Any CPU.Build.0 = Release|Any CPU
{AEFD93B4-59B8-4B02-A694-CA15E4EC1C08}.Release|x64.ActiveCfg = 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.Build.0 = Release|Any CPU
{03BE4EB1-FD38-445A-9A4B-7B481560E9A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{03BE4EB1-FD38-445A-9A4B-7B481560E9A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{03BE4EB1-FD38-445A-9A4B-7B481560E9A2}.Debug|x64.ActiveCfg = Debug|Any CPU
{03BE4EB1-FD38-445A-9A4B-7B481560E9A2}.Debug|x64.Build.0 = Debug|Any CPU
{03BE4EB1-FD38-445A-9A4B-7B481560E9A2}.Debug|x86.ActiveCfg = Debug|Any CPU
{03BE4EB1-FD38-445A-9A4B-7B481560E9A2}.Debug|x86.Build.0 = Debug|Any CPU
{03BE4EB1-FD38-445A-9A4B-7B481560E9A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03BE4EB1-FD38-445A-9A4B-7B481560E9A2}.Release|Any CPU.Build.0 = Release|Any CPU
{03BE4EB1-FD38-445A-9A4B-7B481560E9A2}.Release|x64.ActiveCfg = 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.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
{D3C7D267-5646-4907-A451-E7CFA853BBF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D3C7D267-5646-4907-A451-E7CFA853BBF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D3C7D267-5646-4907-A451-E7CFA853BBF5}.Debug|x64.ActiveCfg = Debug|Any CPU
{D3C7D267-5646-4907-A451-E7CFA853BBF5}.Debug|x64.Build.0 = Debug|Any CPU
{D3C7D267-5646-4907-A451-E7CFA853BBF5}.Debug|x86.ActiveCfg = Debug|Any CPU
{D3C7D267-5646-4907-A451-E7CFA853BBF5}.Debug|x86.Build.0 = Debug|Any CPU
{D3C7D267-5646-4907-A451-E7CFA853BBF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D3C7D267-5646-4907-A451-E7CFA853BBF5}.Release|Any CPU.Build.0 = Release|Any CPU
{D3C7D267-5646-4907-A451-E7CFA853BBF5}.Release|x64.ActiveCfg = Release|Any CPU
{D3C7D267-5646-4907-A451-E7CFA853BBF5}.Release|x64.Build.0 = Release|Any CPU
{D3C7D267-5646-4907-A451-E7CFA853BBF5}.Release|x86.ActiveCfg = Release|Any CPU
{D3C7D267-5646-4907-A451-E7CFA853BBF5}.Release|x86.Build.0 = Release|Any CPU
{D65EE034-84B9-4EBE-BC1F-5606AB4524A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D65EE034-84B9-4EBE-BC1F-5606AB4524A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D65EE034-84B9-4EBE-BC1F-5606AB4524A4}.Debug|x64.ActiveCfg = Debug|Any CPU
{D65EE034-84B9-4EBE-BC1F-5606AB4524A4}.Debug|x64.Build.0 = Debug|Any CPU
{D65EE034-84B9-4EBE-BC1F-5606AB4524A4}.Debug|x86.ActiveCfg = Debug|Any CPU
{D65EE034-84B9-4EBE-BC1F-5606AB4524A4}.Debug|x86.Build.0 = Debug|Any CPU
{D65EE034-84B9-4EBE-BC1F-5606AB4524A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D65EE034-84B9-4EBE-BC1F-5606AB4524A4}.Release|Any CPU.Build.0 = Release|Any CPU
{D65EE034-84B9-4EBE-BC1F-5606AB4524A4}.Release|x64.ActiveCfg = Release|Any CPU
{D65EE034-84B9-4EBE-BC1F-5606AB4524A4}.Release|x64.Build.0 = Release|Any CPU
{D65EE034-84B9-4EBE-BC1F-5606AB4524A4}.Release|x86.ActiveCfg = Release|Any CPU
{D65EE034-84B9-4EBE-BC1F-5606AB4524A4}.Release|x86.Build.0 = Release|Any CPU
{17B31CB0-F2C5-47A6-9BF2-C55403C97DC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{17B31CB0-F2C5-47A6-9BF2-C55403C97DC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{17B31CB0-F2C5-47A6-9BF2-C55403C97DC7}.Debug|x64.ActiveCfg = Debug|Any CPU
{17B31CB0-F2C5-47A6-9BF2-C55403C97DC7}.Debug|x64.Build.0 = Debug|Any CPU
{17B31CB0-F2C5-47A6-9BF2-C55403C97DC7}.Debug|x86.ActiveCfg = Debug|Any CPU
{17B31CB0-F2C5-47A6-9BF2-C55403C97DC7}.Debug|x86.Build.0 = Debug|Any CPU
{17B31CB0-F2C5-47A6-9BF2-C55403C97DC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{17B31CB0-F2C5-47A6-9BF2-C55403C97DC7}.Release|Any CPU.Build.0 = Release|Any CPU
{17B31CB0-F2C5-47A6-9BF2-C55403C97DC7}.Release|x64.ActiveCfg = Release|Any CPU
{17B31CB0-F2C5-47A6-9BF2-C55403C97DC7}.Release|x64.Build.0 = Release|Any CPU
{17B31CB0-F2C5-47A6-9BF2-C55403C97DC7}.Release|x86.ActiveCfg = Release|Any CPU
{17B31CB0-F2C5-47A6-9BF2-C55403C97DC7}.Release|x86.Build.0 = Release|Any CPU
{62A821F2-F296-4888-8AB1-A80ED514AC08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{62A821F2-F296-4888-8AB1-A80ED514AC08}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62A821F2-F296-4888-8AB1-A80ED514AC08}.Debug|x64.ActiveCfg = Debug|Any CPU
{62A821F2-F296-4888-8AB1-A80ED514AC08}.Debug|x64.Build.0 = Debug|Any CPU
{62A821F2-F296-4888-8AB1-A80ED514AC08}.Debug|x86.ActiveCfg = Debug|Any CPU
{62A821F2-F296-4888-8AB1-A80ED514AC08}.Debug|x86.Build.0 = Debug|Any CPU
{62A821F2-F296-4888-8AB1-A80ED514AC08}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62A821F2-F296-4888-8AB1-A80ED514AC08}.Release|Any CPU.Build.0 = Release|Any CPU
{62A821F2-F296-4888-8AB1-A80ED514AC08}.Release|x64.ActiveCfg = Release|Any CPU
{62A821F2-F296-4888-8AB1-A80ED514AC08}.Release|x64.Build.0 = Release|Any CPU
{62A821F2-F296-4888-8AB1-A80ED514AC08}.Release|x86.ActiveCfg = Release|Any CPU
{62A821F2-F296-4888-8AB1-A80ED514AC08}.Release|x86.Build.0 = Release|Any CPU
{D0CE2D52-EC1C-444E-A5C5-79813E1814D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D0CE2D52-EC1C-444E-A5C5-79813E1814D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D0CE2D52-EC1C-444E-A5C5-79813E1814D4}.Debug|x64.ActiveCfg = Debug|Any CPU
{D0CE2D52-EC1C-444E-A5C5-79813E1814D4}.Debug|x64.Build.0 = Debug|Any CPU
{D0CE2D52-EC1C-444E-A5C5-79813E1814D4}.Debug|x86.ActiveCfg = Debug|Any CPU
{D0CE2D52-EC1C-444E-A5C5-79813E1814D4}.Debug|x86.Build.0 = Debug|Any CPU
{D0CE2D52-EC1C-444E-A5C5-79813E1814D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0CE2D52-EC1C-444E-A5C5-79813E1814D4}.Release|Any CPU.Build.0 = Release|Any CPU
{D0CE2D52-EC1C-444E-A5C5-79813E1814D4}.Release|x64.ActiveCfg = Release|Any CPU
{D0CE2D52-EC1C-444E-A5C5-79813E1814D4}.Release|x64.Build.0 = Release|Any CPU
{D0CE2D52-EC1C-444E-A5C5-79813E1814D4}.Release|x86.ActiveCfg = Release|Any CPU
{D0CE2D52-EC1C-444E-A5C5-79813E1814D4}.Release|x86.Build.0 = Release|Any CPU
{87048157-DF94-49E6-A34F-7DEF3255FA35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{87048157-DF94-49E6-A34F-7DEF3255FA35}.Debug|Any CPU.Build.0 = Debug|Any CPU
{87048157-DF94-49E6-A34F-7DEF3255FA35}.Debug|x64.ActiveCfg = Debug|Any CPU
{87048157-DF94-49E6-A34F-7DEF3255FA35}.Debug|x64.Build.0 = Debug|Any CPU
{87048157-DF94-49E6-A34F-7DEF3255FA35}.Debug|x86.ActiveCfg = Debug|Any CPU
{87048157-DF94-49E6-A34F-7DEF3255FA35}.Debug|x86.Build.0 = Debug|Any CPU
{87048157-DF94-49E6-A34F-7DEF3255FA35}.Release|Any CPU.ActiveCfg = Release|Any CPU
{87048157-DF94-49E6-A34F-7DEF3255FA35}.Release|Any CPU.Build.0 = Release|Any CPU
{87048157-DF94-49E6-A34F-7DEF3255FA35}.Release|x64.ActiveCfg = Release|Any CPU
{87048157-DF94-49E6-A34F-7DEF3255FA35}.Release|x64.Build.0 = Release|Any CPU
{87048157-DF94-49E6-A34F-7DEF3255FA35}.Release|x86.ActiveCfg = Release|Any CPU
{87048157-DF94-49E6-A34F-7DEF3255FA35}.Release|x86.Build.0 = Release|Any CPU
{D28F8DB7-077E-47F6-B07D-BB93AF950A0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D28F8DB7-077E-47F6-B07D-BB93AF950A0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D28F8DB7-077E-47F6-B07D-BB93AF950A0D}.Debug|x64.ActiveCfg = Debug|Any CPU
{D28F8DB7-077E-47F6-B07D-BB93AF950A0D}.Debug|x64.Build.0 = Debug|Any CPU
{D28F8DB7-077E-47F6-B07D-BB93AF950A0D}.Debug|x86.ActiveCfg = Debug|Any CPU
{D28F8DB7-077E-47F6-B07D-BB93AF950A0D}.Debug|x86.Build.0 = Debug|Any CPU
{D28F8DB7-077E-47F6-B07D-BB93AF950A0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D28F8DB7-077E-47F6-B07D-BB93AF950A0D}.Release|Any CPU.Build.0 = Release|Any CPU
{D28F8DB7-077E-47F6-B07D-BB93AF950A0D}.Release|x64.ActiveCfg = Release|Any CPU
{D28F8DB7-077E-47F6-B07D-BB93AF950A0D}.Release|x64.Build.0 = Release|Any CPU
{D28F8DB7-077E-47F6-B07D-BB93AF950A0D}.Release|x86.ActiveCfg = Release|Any CPU
{D28F8DB7-077E-47F6-B07D-BB93AF950A0D}.Release|x86.Build.0 = Release|Any CPU
{75CDF4D0-53B2-4FCE-9C2D-06940FD2E817}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75CDF4D0-53B2-4FCE-9C2D-06940FD2E817}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75CDF4D0-53B2-4FCE-9C2D-06940FD2E817}.Debug|x64.ActiveCfg = Debug|Any CPU
{75CDF4D0-53B2-4FCE-9C2D-06940FD2E817}.Debug|x64.Build.0 = Debug|Any CPU
{75CDF4D0-53B2-4FCE-9C2D-06940FD2E817}.Debug|x86.ActiveCfg = Debug|Any CPU
{75CDF4D0-53B2-4FCE-9C2D-06940FD2E817}.Debug|x86.Build.0 = Debug|Any CPU
{75CDF4D0-53B2-4FCE-9C2D-06940FD2E817}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75CDF4D0-53B2-4FCE-9C2D-06940FD2E817}.Release|Any CPU.Build.0 = Release|Any CPU
{75CDF4D0-53B2-4FCE-9C2D-06940FD2E817}.Release|x64.ActiveCfg = Release|Any CPU
{75CDF4D0-53B2-4FCE-9C2D-06940FD2E817}.Release|x64.Build.0 = Release|Any CPU
{75CDF4D0-53B2-4FCE-9C2D-06940FD2E817}.Release|x86.ActiveCfg = Release|Any CPU
{75CDF4D0-53B2-4FCE-9C2D-06940FD2E817}.Release|x86.Build.0 = Release|Any CPU
{338C69EA-F921-41B3-BB6C-116AADEBF356}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{338C69EA-F921-41B3-BB6C-116AADEBF356}.Debug|Any CPU.Build.0 = Debug|Any CPU
{338C69EA-F921-41B3-BB6C-116AADEBF356}.Debug|x64.ActiveCfg = Debug|Any CPU
{338C69EA-F921-41B3-BB6C-116AADEBF356}.Debug|x64.Build.0 = Debug|Any CPU
{338C69EA-F921-41B3-BB6C-116AADEBF356}.Debug|x86.ActiveCfg = Debug|Any CPU
{338C69EA-F921-41B3-BB6C-116AADEBF356}.Debug|x86.Build.0 = Debug|Any CPU
{338C69EA-F921-41B3-BB6C-116AADEBF356}.Release|Any CPU.ActiveCfg = Release|Any CPU
{338C69EA-F921-41B3-BB6C-116AADEBF356}.Release|Any CPU.Build.0 = Release|Any CPU
{338C69EA-F921-41B3-BB6C-116AADEBF356}.Release|x64.ActiveCfg = Release|Any CPU
{338C69EA-F921-41B3-BB6C-116AADEBF356}.Release|x64.Build.0 = Release|Any CPU
{338C69EA-F921-41B3-BB6C-116AADEBF356}.Release|x86.ActiveCfg = Release|Any CPU
{338C69EA-F921-41B3-BB6C-116AADEBF356}.Release|x86.Build.0 = Release|Any CPU
{531F88C6-B9A5-4BF6-A121-AAA95DE4A288}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{531F88C6-B9A5-4BF6-A121-AAA95DE4A288}.Debug|Any CPU.Build.0 = Debug|Any CPU
{531F88C6-B9A5-4BF6-A121-AAA95DE4A288}.Debug|x64.ActiveCfg = Debug|Any CPU
{531F88C6-B9A5-4BF6-A121-AAA95DE4A288}.Debug|x64.Build.0 = Debug|Any CPU
{531F88C6-B9A5-4BF6-A121-AAA95DE4A288}.Debug|x86.ActiveCfg = Debug|Any CPU
{531F88C6-B9A5-4BF6-A121-AAA95DE4A288}.Debug|x86.Build.0 = Debug|Any CPU
{531F88C6-B9A5-4BF6-A121-AAA95DE4A288}.Release|Any CPU.ActiveCfg = Release|Any CPU
{531F88C6-B9A5-4BF6-A121-AAA95DE4A288}.Release|Any CPU.Build.0 = Release|Any CPU
{531F88C6-B9A5-4BF6-A121-AAA95DE4A288}.Release|x64.ActiveCfg = Release|Any CPU
{531F88C6-B9A5-4BF6-A121-AAA95DE4A288}.Release|x64.Build.0 = Release|Any CPU
{531F88C6-B9A5-4BF6-A121-AAA95DE4A288}.Release|x86.ActiveCfg = Release|Any CPU
{531F88C6-B9A5-4BF6-A121-AAA95DE4A288}.Release|x86.Build.0 = Release|Any CPU
{613354F0-3B35-4406-8250-C8E88A0DECF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{613354F0-3B35-4406-8250-C8E88A0DECF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{613354F0-3B35-4406-8250-C8E88A0DECF6}.Debug|x64.ActiveCfg = Debug|Any CPU
{613354F0-3B35-4406-8250-C8E88A0DECF6}.Debug|x64.Build.0 = Debug|Any CPU
{613354F0-3B35-4406-8250-C8E88A0DECF6}.Debug|x86.ActiveCfg = Debug|Any CPU
{613354F0-3B35-4406-8250-C8E88A0DECF6}.Debug|x86.Build.0 = Debug|Any CPU
{613354F0-3B35-4406-8250-C8E88A0DECF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{613354F0-3B35-4406-8250-C8E88A0DECF6}.Release|Any CPU.Build.0 = Release|Any CPU
{613354F0-3B35-4406-8250-C8E88A0DECF6}.Release|x64.ActiveCfg = Release|Any CPU
{613354F0-3B35-4406-8250-C8E88A0DECF6}.Release|x64.Build.0 = Release|Any CPU
{613354F0-3B35-4406-8250-C8E88A0DECF6}.Release|x86.ActiveCfg = Release|Any CPU
{613354F0-3B35-4406-8250-C8E88A0DECF6}.Release|x86.Build.0 = Release|Any CPU
{A47CCC4C-8B19-4A12-8B05-BE4AAEABB1BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A47CCC4C-8B19-4A12-8B05-BE4AAEABB1BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A47CCC4C-8B19-4A12-8B05-BE4AAEABB1BA}.Debug|x64.ActiveCfg = Debug|Any CPU
{A47CCC4C-8B19-4A12-8B05-BE4AAEABB1BA}.Debug|x64.Build.0 = Debug|Any CPU
{A47CCC4C-8B19-4A12-8B05-BE4AAEABB1BA}.Debug|x86.ActiveCfg = Debug|Any CPU
{A47CCC4C-8B19-4A12-8B05-BE4AAEABB1BA}.Debug|x86.Build.0 = Debug|Any CPU
{A47CCC4C-8B19-4A12-8B05-BE4AAEABB1BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A47CCC4C-8B19-4A12-8B05-BE4AAEABB1BA}.Release|Any CPU.Build.0 = Release|Any CPU
{A47CCC4C-8B19-4A12-8B05-BE4AAEABB1BA}.Release|x64.ActiveCfg = Release|Any CPU
{A47CCC4C-8B19-4A12-8B05-BE4AAEABB1BA}.Release|x64.Build.0 = Release|Any CPU
{A47CCC4C-8B19-4A12-8B05-BE4AAEABB1BA}.Release|x86.ActiveCfg = Release|Any CPU
{A47CCC4C-8B19-4A12-8B05-BE4AAEABB1BA}.Release|x86.Build.0 = Release|Any CPU
{D939680D-864D-4FED-A81D-8F30EE6A97A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D939680D-864D-4FED-A81D-8F30EE6A97A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D939680D-864D-4FED-A81D-8F30EE6A97A0}.Debug|x64.ActiveCfg = Debug|Any CPU
{D939680D-864D-4FED-A81D-8F30EE6A97A0}.Debug|x64.Build.0 = Debug|Any CPU
{D939680D-864D-4FED-A81D-8F30EE6A97A0}.Debug|x86.ActiveCfg = Debug|Any CPU
{D939680D-864D-4FED-A81D-8F30EE6A97A0}.Debug|x86.Build.0 = Debug|Any CPU
{D939680D-864D-4FED-A81D-8F30EE6A97A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D939680D-864D-4FED-A81D-8F30EE6A97A0}.Release|Any CPU.Build.0 = Release|Any CPU
{D939680D-864D-4FED-A81D-8F30EE6A97A0}.Release|x64.ActiveCfg = Release|Any CPU
{D939680D-864D-4FED-A81D-8F30EE6A97A0}.Release|x64.Build.0 = Release|Any CPU
{D939680D-864D-4FED-A81D-8F30EE6A97A0}.Release|x86.ActiveCfg = Release|Any CPU
{D939680D-864D-4FED-A81D-8F30EE6A97A0}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -118,11 +488,59 @@ Global
{C829F859-FF48-4025-BF22-8E63672D113C} = {C8E42992-5E42-0C2B-DBFE-AA848D06431C}
{6C13E9A4-234C-4BF1-A145-F1BBA2AC56CB} = {C8E42992-5E42-0C2B-DBFE-AA848D06431C}
{EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{0EC62A1A-9858-3A60-8A0C-FC9AACFA2EC7} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{5BB2F421-C4C4-40A1-8D6E-FEBB79AE7C98} = {0EC62A1A-9858-3A60-8A0C-FC9AACFA2EC7}
{B4154CE3-9153-E2B0-0515-138FFCE32938} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{695CC919-E321-4DC0-8D70-C631C51624E1} = {B4154CE3-9153-E2B0-0515-138FFCE32938}
{AB2F334E-DD7C-4314-93B9-941AFC6B2ACD} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{F48A3E3A-EEBB-43AA-81AB-7DD7FE5F29B4} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{CA5186C2-289A-9324-4152-9805A73A9773} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{CD14770B-8BCD-4D93-AD2C-581ED030E209} = {CA5186C2-289A-9324-4152-9805A73A9773}
{10980DF6-D81A-2FEE-4ACB-4D508FA38BC6} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{B97352F3-709A-42CF-A448-07B9996F5FBE} = {10980DF6-D81A-2FEE-4ACB-4D508FA38BC6}
{F83653D1-5494-68B8-6444-7DEF58635BF6} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{BC98E6FC-B46A-4554-B75E-B912714A915C} = {F83653D1-5494-68B8-6444-7DEF58635BF6}
{AF61C489-0B09-54BC-BD16-4353135E9C4A} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{D052C05A-CDCD-48DF-8F4F-0B10E3719819} = {AF61C489-0B09-54BC-BD16-4353135E9C4A}
{E9D55F35-C373-3DD8-6914-3213F2F582A2} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{CF08AE9C-B337-47BA-B273-4B8E5610D7BD} = {E9D55F35-C373-3DD8-6914-3213F2F582A2}
{AE120A0E-5815-2D46-946C-DA544028AD88} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{C0B5496E-F083-413E-90AE-31282A0ACCA5} = {AE120A0E-5815-2D46-946C-DA544028AD88}
{4D5D7319-366D-1BD8-49C5-A840B487E852} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{C905C120-DE44-4CBC-8F98-5BE148115487} = {4D5D7319-366D-1BD8-49C5-A840B487E852}
{138B168B-2473-B06D-4BD5-1B010DA319A1} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{F3EFD119-44F7-46BF-A03C-060F30A1A1E0} = {138B168B-2473-B06D-4BD5-1B010DA319A1}
{E9E1155A-A339-766C-C185-6698FF423EBC} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{FFC12440-F128-48F5-9BE5-E5263930F876} = {E9E1155A-A339-766C-C185-6698FF423EBC}
{837D2CF4-7AEA-BF76-6362-47ECD7058EEC} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{716E089D-0661-4D39-813B-063DF9A4C0E4} = {837D2CF4-7AEA-BF76-6362-47ECD7058EEC}
{4BC86D3F-8D7B-ACB9-EA51-9374BC9D421A} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{2498C58C-53B0-4DF2-B4EA-BA69B35A7488} = {4BC86D3F-8D7B-ACB9-EA51-9374BC9D421A}
{0587C5BB-3015-204D-19B6-EB01E03E48B6} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{AEFD93B4-59B8-4B02-A694-CA15E4EC1C08} = {0587C5BB-3015-204D-19B6-EB01E03E48B6}
{DD6FAF9E-10A0-95E7-0C06-698A6F70AFEE} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{03BE4EB1-FD38-445A-9A4B-7B481560E9A2} = {DD6FAF9E-10A0-95E7-0C06-698A6F70AFEE}
{98ABB710-9106-4E5C-AA04-D19D5E92EDC1} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{FFBE31B8-50B9-B415-22BC-2F92C97CADA3} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{1C8A9B45-FCC1-A5B3-3601-CB39D7CED4E3} = {FFBE31B8-50B9-B415-22BC-2F92C97CADA3}
{D3C7D267-5646-4907-A451-E7CFA853BBF5} = {1C8A9B45-FCC1-A5B3-3601-CB39D7CED4E3}
{28F8F726-0D9B-F051-A252-088B861055C6} = {FFBE31B8-50B9-B415-22BC-2F92C97CADA3}
{D65EE034-84B9-4EBE-BC1F-5606AB4524A4} = {28F8F726-0D9B-F051-A252-088B861055C6}
{19B3F750-A3A3-3AEB-CE4F-46FB52BB3151} = {FFBE31B8-50B9-B415-22BC-2F92C97CADA3}
{17B31CB0-F2C5-47A6-9BF2-C55403C97DC7} = {19B3F750-A3A3-3AEB-CE4F-46FB52BB3151}
{E97EBA1B-122C-1D64-94D9-187425FF5DF0} = {FFBE31B8-50B9-B415-22BC-2F92C97CADA3}
{62A821F2-F296-4888-8AB1-A80ED514AC08} = {E97EBA1B-122C-1D64-94D9-187425FF5DF0}
{7555181C-EB04-A6C5-D475-6FC0320F57B5} = {FFBE31B8-50B9-B415-22BC-2F92C97CADA3}
{D0CE2D52-EC1C-444E-A5C5-79813E1814D4} = {7555181C-EB04-A6C5-D475-6FC0320F57B5}
{3E835F76-AFEE-6419-1441-2CED62F241B2} = {FFBE31B8-50B9-B415-22BC-2F92C97CADA3}
{87048157-DF94-49E6-A34F-7DEF3255FA35} = {3E835F76-AFEE-6419-1441-2CED62F241B2}
{0286CA1A-73E9-C153-D97B-31BA60AC101B} = {FFBE31B8-50B9-B415-22BC-2F92C97CADA3}
{D28F8DB7-077E-47F6-B07D-BB93AF950A0D} = {0286CA1A-73E9-C153-D97B-31BA60AC101B}
{319B016B-E46C-7232-249F-DBE50922C757} = {FFBE31B8-50B9-B415-22BC-2F92C97CADA3}
{75CDF4D0-53B2-4FCE-9C2D-06940FD2E817} = {319B016B-E46C-7232-249F-DBE50922C757}
{C91F5B42-26A0-36B8-1C54-AE965E022F68} = {FFBE31B8-50B9-B415-22BC-2F92C97CADA3}
{338C69EA-F921-41B3-BB6C-116AADEBF356} = {C91F5B42-26A0-36B8-1C54-AE965E022F68}
{6F45E82C-238A-FDE4-59E5-CCCD65D77EB8} = {FFBE31B8-50B9-B415-22BC-2F92C97CADA3}
{531F88C6-B9A5-4BF6-A121-AAA95DE4A288} = {6F45E82C-238A-FDE4-59E5-CCCD65D77EB8}
{3DE423CC-7CB5-260E-2A62-D5E89000EF8F} = {FFBE31B8-50B9-B415-22BC-2F92C97CADA3}
{613354F0-3B35-4406-8250-C8E88A0DECF6} = {3DE423CC-7CB5-260E-2A62-D5E89000EF8F}
{5C1B4B87-5A65-F9C7-EAA3-6456BCBFD6EC} = {FFBE31B8-50B9-B415-22BC-2F92C97CADA3}
{A47CCC4C-8B19-4A12-8B05-BE4AAEABB1BA} = {5C1B4B87-5A65-F9C7-EAA3-6456BCBFD6EC}
{6DD751EB-CB14-7F42-4B4B-7E4DCC49705A} = {FFBE31B8-50B9-B415-22BC-2F92C97CADA3}
{D939680D-864D-4FED-A81D-8F30EE6A97A0} = {6DD751EB-CB14-7F42-4B4B-7E4DCC49705A}
EndGlobalSection
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>
@@ -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="FluentAssertions" Version="8.0.1" />
<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" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Modules\Admin\Knot.Modules.Admin.csproj" />
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,28 @@
<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.0.1" />
<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" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,58 @@
using FluentAssertions;
using Knot.Modules.Admin.Application.Admin.Commands;
using Knot.Modules.Auth.Application.Abstractions;
using Knot.Modules.Auth.Domain;
using Knot.Shared.Kernel;
using NSubstitute;
using Xunit;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Knot.Modules.Admin.UnitTests;
public class ResetUserPasswordCommandHandlerTests
{
private readonly IAuthUnitOfWork _authUnitOfWork;
private readonly IUserRepository _userRepository;
private readonly ResetUserPasswordCommandHandler _handler;
public ResetUserPasswordCommandHandlerTests()
{
_authUnitOfWork = Substitute.For<IAuthUnitOfWork>();
_userRepository = Substitute.For<IUserRepository>();
_handler = new ResetUserPasswordCommandHandler(_userRepository, _authUnitOfWork);
}
[Fact]
public async Task Handle_ShouldReturnError_WhenUserNotFound()
{
// Arrange
var command = new ResetUserPasswordCommand(Guid.NewGuid(), "NewP@ssw0rd");
_userRepository.GetByIdAsync(command.UserId, Arg.Any<CancellationToken>()).Returns((User?)null);
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.IsFailure.Should().BeTrue();
result.Error.Should().Be(AuthErrors.UserNotFound);
}
[Fact]
public async Task Handle_ShouldSucceed_WhenUserFound()
{
// Arrange
var user = User.Create("User", "pass", "salt", "admin");
var command = new ResetUserPasswordCommand(user.Id, "NewP@ssw0rd");
_userRepository.GetByIdAsync(command.UserId, Arg.Any<CancellationToken>()).Returns(user);
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeTrue();
await _authUnitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
}
}
@@ -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>
@@ -23,7 +23,10 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Modules\Chats\Knot.Modules.Chats.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Auth\Knot.Modules.Auth.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup>
</Project>
@@ -1,15 +1,15 @@
using FluentAssertions;
using NSubstitute;
using Knot.Modules.Identity.Application.Abstractions;
using Knot.Modules.Identity.Application.Users.Login;
using Knot.Modules.Identity.Domain;
using Knot.Modules.Auth.Application.Abstractions;
using Knot.Modules.Auth.Application.Users.Login;
using Knot.Modules.Auth.Domain;
using Knot.Shared.Kernel;
using Knot.Modules.Identity.Application.Users.Auth;
using Knot.Modules.Auth.Application.Users.Auth;
using Xunit;
using System.Threading;
using System.Threading.Tasks;
namespace Knot.Modules.Identity.UnitTests;
namespace Knot.Modules.Auth.UnitTests;
public class LoginUserCommandHandlerTests
{
@@ -61,7 +61,7 @@ public class LoginUserCommandHandlerTests
// Assert
result.IsFailure.Should().BeTrue();
result.Error.Code.Should().Be(IdentityErrors.IdentityInvalidCredentials.Code);
result.Error.Code.Should().Be(AuthErrors.IdentityInvalidCredentials.Code);
}
[Fact]
@@ -82,6 +82,7 @@ public class LoginUserCommandHandlerTests
// Assert
result.IsFailure.Should().BeTrue();
result.Error.Code.Should().Be(IdentityErrors.IdentityInvalidCredentials.Code);
result.Error.Code.Should().Be(AuthErrors.IdentityInvalidCredentials.Code);
}
}
@@ -7,11 +7,12 @@ using FluentAssertions;
using NSubstitute;
using Xunit;
using Knot.Shared.Kernel;
using Knot.Modules.Chats.Domain;
using Knot.Modules.Chats.Application.Abstractions;
using Knot.Modules.Chats.Application.Chats.Create;
using Knot.Modules.Conversations.Domain;
using Knot.Modules.Messaging.Domain;
using Knot.Modules.Conversations.Application.Abstractions;
using Knot.Modules.Conversations.Application.Chats.Create;
namespace Knot.Modules.Chats.UnitTests.Chats;
namespace Knot.Modules.Conversations.UnitTests.Chats;
public class CreateChatCommandHandlerTests
{
@@ -49,3 +50,5 @@ public class CreateChatCommandHandlerTests
await _unitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
}
}
@@ -7,18 +7,20 @@ using FluentAssertions;
using NSubstitute;
using Xunit;
using Knot.Shared.Kernel;
using Knot.Modules.Chats.Domain;
using Knot.Modules.Chats.Application.Abstractions;
using Knot.Modules.Chats.Application.Chats.GetChats;
using Knot.Modules.Chats.Application.DTOs;
using Knot.Modules.Conversations.Domain;
using Knot.Modules.Messaging.Domain;
using Knot.Modules.Conversations.Application.Abstractions;
using Knot.Modules.Conversations.Application.Chats.GetChats;
using Knot.Modules.Conversations.Application.DTOs;
namespace Knot.Modules.Chats.UnitTests.Chats;
namespace Knot.Modules.Conversations.UnitTests.Chats;
public class GetChatsQueryHandlerTests
{
private readonly IChatRepository _chatRepository;
private readonly IUserDisplayNameProvider _userProvider;
private readonly IMessageRepository _messageRepository;
private readonly IMessageReactionRepository _reactionRepository;
private readonly GetChatsQueryHandler _handler;
public GetChatsQueryHandlerTests()
@@ -26,8 +28,9 @@ public class GetChatsQueryHandlerTests
_chatRepository = Substitute.For<IChatRepository>();
_userProvider = Substitute.For<IUserDisplayNameProvider>();
_messageRepository = Substitute.For<IMessageRepository>();
_reactionRepository = Substitute.For<IMessageReactionRepository>();
_handler = new GetChatsQueryHandler(_chatRepository, _userProvider, _messageRepository);
_handler = new GetChatsQueryHandler(_chatRepository, _userProvider, _messageRepository, _reactionRepository);
}
[Fact]
@@ -54,9 +57,6 @@ public class GetChatsQueryHandlerTests
_userProvider.GetUsersInfoAsync(Arg.Any<IEnumerable<Guid>>(), Arg.Any<CancellationToken>())
.Returns(new Dictionary<Guid, UserInfo>());
_messageRepository.GetUnreadCountAsync(Arg.Any<Guid>(), userId, Arg.Any<CancellationToken>())
.Returns(0);
// Act
var result = await _handler.Handle(request, CancellationToken.None);
@@ -65,8 +65,10 @@ public class GetChatsQueryHandlerTests
result.Value.Should().NotBeNull();
// It always appends synthetic "favorites" chat at the end if not found
result.Value.Count.Should().Be(3);
result.Value.Count.Should().Be(2);
result.Value.Any(c => c.Name == "Test Chat").Should().BeTrue();
result.Value.Any(c => c.Type == "favorites").Should().BeTrue();
}
}
@@ -1,12 +1,13 @@
using FluentAssertions;
using NSubstitute;
using Knot.Modules.Chats.Application.Abstractions;
using Knot.Modules.Chats.Application.Chats.GetOrCreateFavorites;
using Knot.Modules.Chats.Domain;
using Knot.Modules.Conversations.Application.Abstractions;
using Knot.Modules.Conversations.Application.Chats.GetOrCreateFavorites;
using Knot.Modules.Conversations.Domain;
using Knot.Modules.Messaging.Domain;
using Knot.Shared.Kernel;
using Xunit;
namespace Knot.Modules.Chats.UnitTests;
namespace Knot.Modules.Conversations.UnitTests;
public class GetOrCreateFavoritesCommandHandlerTests
{
@@ -69,3 +70,5 @@ public class GetOrCreateFavoritesCommandHandlerTests
await _unitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
}
}
@@ -23,7 +23,11 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Modules\Identity\Knot.Modules.Identity.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Chats\Knot.Modules.Conversations.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Messaging\Knot.Modules.Messaging.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Conversations\Knot.Modules.Conversations.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup>
</Project>
@@ -8,11 +8,14 @@ using MediatR;
using NSubstitute;
using Xunit;
using Knot.Shared.Kernel;
using Knot.Modules.Chats.Domain;
using Knot.Modules.Chats.Application.Abstractions;
using Knot.Modules.Chats.Application.Messages.Send;
using Knot.Modules.Conversations.Domain;
using Knot.Modules.Messaging.Domain;
using Knot.Modules.Conversations.Application.Abstractions;
using Knot.Modules.Conversations.Application.Messages.Send;
using Knot.Modules.Settings.Application.Settings.Abstractions;
using Knot.Modules.Settings.Application.Settings.DTOs;
namespace Knot.Modules.Chats.UnitTests.Messages;
namespace Knot.Modules.Conversations.UnitTests.Messages;
public class SendMessageCommandHandlerTests
{
@@ -20,6 +23,7 @@ public class SendMessageCommandHandlerTests
private readonly IMessageRepository _messageRepository;
private readonly IChatsUnitOfWork _unitOfWork;
private readonly IMediator _mediator;
private readonly IMessagesSettings _messagesSettings;
private readonly SendMessageCommandHandler _handler;
public SendMessageCommandHandlerTests()
@@ -28,8 +32,12 @@ public class SendMessageCommandHandlerTests
_messageRepository = Substitute.For<IMessageRepository>();
_unitOfWork = Substitute.For<IChatsUnitOfWork>();
_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]
@@ -95,3 +103,5 @@ public class SendMessageCommandHandlerTests
await _unitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
}
}
@@ -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>
@@ -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="FluentAssertions" Version="8.0.1" />
<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" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Modules\Federation\Knot.Modules.Federation.csproj" />
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup>
</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="FluentAssertions" Version="8.0.1" />
<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" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Modules\Klipy\Knot.Modules.Klipy.csproj" />
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup>
</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>
@@ -0,0 +1,24 @@
<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" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup>
</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>
@@ -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="FluentAssertions" Version="8.0.1" />
<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" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Modules\Relations\Knot.Modules.Relations.csproj" />
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup>
</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>
@@ -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="FluentAssertions" Version="8.0.1" />
<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" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Modules\Storage\Knot.Modules.Storage.csproj" />
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup>
</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>());
}
}
@@ -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="FluentAssertions" Version="8.0.1" />
<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" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Modules\Stories\Knot.Modules.Stories.csproj" />
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup>
</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="FluentAssertions" Version="8.0.1" />
<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" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Modules\TelegramImport\Knot.Modules.TelegramImport.csproj" />
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup>
</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>
@@ -0,0 +1,35 @@
using FluentAssertions;
using Knot.Modules.Profiles.Application.Profiles.UpdateProfile;
using Knot.Shared.Kernel;
using NSubstitute;
using Xunit;
using System;
using System.Threading;
using System.Threading.Tasks;
using Knot.Contracts.Profiles.Domain;
using Knot.Contracts.Profiles.Application.DTOs;
namespace Knot.Modules.Profiles.UnitTests;
public class UpdateProfileCommandHandlerTests
{
private readonly IProfileRepository _profileRepository;
private readonly UpdateProfileCommandHandler _handler;
public UpdateProfileCommandHandlerTests()
{
_profileRepository = Substitute.For<IProfileRepository>();
_handler = new UpdateProfileCommandHandler(_profileRepository);
}
[Fact]
public async Task Handle_ShouldReturnError_WhenProfileNotFound()
{
var command = new UpdateProfileCommand(Guid.NewGuid(), "FirstName", "Bio", null, null);
_profileRepository.GetAsync(command.UserId, Arg.Any<CancellationToken>()).Returns((UserProfileDto?)null);
var result = await _handler.Handle(command, CancellationToken.None);
result.IsFailure.Should().BeTrue();
}
}
@@ -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="FluentAssertions" Version="8.0.1" />
<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" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Modules\WebRtc\Knot.Modules.WebRtc.csproj" />
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,22 @@
namespace Knot.Contracts.Admin.Abstractions;
/// <summary>
/// Базовый класс для агрегатов - сущностей с бизнес-логикой и доменными событиями.
/// </summary>
public abstract class AggregateRoot<TId> : Entity<TId> where TId : notnull
{
private readonly List<IDomainEvent> _domainEvents = new();
public IReadOnlyList<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
protected AggregateRoot(TId id) : base(id) { }
protected void RaiseDomainEvent(IDomainEvent domainEvent)
{
_domainEvents.Add(domainEvent);
}
public void ClearDomainEvents()
{
_domainEvents.Clear();
}
}
@@ -0,0 +1,43 @@
namespace Knot.Contracts.Admin.Abstractions;
/// <summary>
/// Базовый класс для сущностей с уникальным идентификатором.
/// </summary>
public abstract class Entity<TId> where TId : notnull
{
public TId Id { get; protected set; }
protected Entity(TId id)
{
Id = id;
}
public override bool Equals(object? obj)
{
if (obj is not Entity<TId> other)
return false;
if (ReferenceEquals(this, other))
return true;
if (GetType() != other.GetType())
return false;
return Id.Equals(other.Id);
}
public override int GetHashCode() => Id.GetHashCode();
public static bool operator ==(Entity<TId>? a, Entity<TId>? b)
{
if (a is null && b is null)
return true;
if (a is null || b is null)
return false;
return a.Equals(b);
}
public static bool operator !=(Entity<TId>? a, Entity<TId>? b) => !(a == b);
}
@@ -0,0 +1,15 @@
using MediatR;
namespace Knot.Contracts.Admin.Abstractions;
public interface ICommand : IRequest<Result> { }
public interface ICommand<TResponse> : IRequest<Result<TResponse>> { }
public interface ICommandHandler<in TCommand> : IRequestHandler<TCommand, Result>
where TCommand : ICommand
{ }
public interface ICommandHandler<in TCommand, TResponse> : IRequestHandler<TCommand, Result<TResponse>>
where TCommand : ICommand<TResponse>
{ }
@@ -0,0 +1,8 @@
using MediatR;
namespace Knot.Contracts.Admin.Abstractions;
/// <summary>
/// Интерфейс для доменных событий.
/// </summary>
public interface IDomainEvent : INotification { }
@@ -0,0 +1,13 @@
using MediatR;
namespace Knot.Contracts.Admin.Abstractions;
public interface IQuery<TResponse> : IRequest<Result<TResponse>>
{
}
public interface IQueryHandler<TQuery, TResponse>
: IRequestHandler<TQuery, Result<TResponse>>
where TQuery : IQuery<TResponse>
{
}
@@ -0,0 +1,3 @@
namespace Knot.Contracts.Admin.Abstractions;
public record MessageResponse(string Message);
@@ -0,0 +1,63 @@
namespace Knot.Contracts.Admin.Abstractions;
/// <summary>
/// Представляет ошибку в доменной логике.
/// </summary>
public sealed record Error(string Code, string Description)
{
public static readonly Error None = new(string.Empty, string.Empty);
public static Error NotFound(string code, string description) => new(code, description);
}
/// <summary>
/// Общая обертка для результата операции. Позволяет избегать использования исключений для управления потоком.
/// </summary>
public class Result
{
public bool IsSuccess { get; }
public bool IsFailure => !IsSuccess;
public Error Error { get; }
protected Result(bool isSuccess, Error error)
{
if (isSuccess && error != Error.None)
{
throw new InvalidOperationException();
}
if (!isSuccess && error == Error.None)
{
throw new InvalidOperationException();
}
IsSuccess = isSuccess;
Error = error;
}
public static Result Success() => new(true, Error.None);
public static Result Failure(Error error) => new(false, error);
public static Result<TValue> Success<TValue>(TValue value) => Result<TValue>.Success(value);
public static Result<TValue> Failure<TValue>(Error error) => Result<TValue>.Failure(error);
}
/// <summary>
/// Результ операции, содержащий значение.
/// </summary>
public class Result<TValue> : Result
{
private readonly TValue? _value;
protected internal Result(TValue? value, bool isSuccess, Error error)
: base(isSuccess, error)
{
_value = value;
}
public TValue Value => IsSuccess
? _value!
: throw new InvalidOperationException("Нельзя получить значение ошибочного результата.");
public static Result<TValue> Success(TValue value) => new(value, true, Error.None);
public new static Result<TValue> Failure(Error error) => new(default, false, error);
}
@@ -0,0 +1,3 @@
namespace Knot.Contracts.Admin.Abstractions;
public record SuccessResponse(bool Success);
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MediatR" Version="12.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.4" />
</ItemGroup>
</Project>
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Knot.Contracts.Auth.Abstractions;
public interface IUserQueryService
{
Task<List<UserInfo>> GetAllUsersAsync(CancellationToken cancellationToken);
Task<List<UserInfo>> SearchUsersAsync(string query, CancellationToken cancellationToken);
}
public record UserInfo(Guid Id, string? Avatar, string? UserName, string? DisplayName, DateTime CreatedAt, string? PhoneNumber, string? Email);
@@ -0,0 +1,7 @@
using Knot.Shared.Kernel;
using System;
using System.Collections.Generic;
namespace Knot.Contracts.Auth.Application.Abstractions;
public record GetUsersExistenceQuery(List<Guid> UserIds) : IQuery<List<Guid>>;
@@ -0,0 +1,10 @@
using Knot.Contracts.Auth.Domain;
using Microsoft.EntityFrameworkCore;
namespace Knot.Contracts.Auth.Application.Abstractions;
public interface IAuthDbContext
{
DbSet<T> Set<T>() where T : class;
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,10 @@
using Knot.Contracts.Auth.Domain;
using Knot.Shared.Kernel;
namespace Knot.Contracts.Auth.Application.Abstractions;
public interface IAuthUnitOfWork : IUnitOfWork
{
IUserRepository UserRepository { get; }
void Add(UserContract user);
}
@@ -0,0 +1,9 @@
namespace Knot.Contracts.Auth.Application.Abstractions;
public interface IJwtTokenProvider
{
string GenerateAccessToken(Guid userId, string username);
string GenerateRefreshToken();
string Generate(Guid userId, string username, string displayName, string? avatar);
string Generate(Domain.UserContract user);
}
@@ -0,0 +1,14 @@
using Knot.Shared.Kernel;
namespace Knot.Contracts.Auth.Application.Auth.DTOs;
public static class AuthErrors
{
public static Error IdentityInvalidCredentials => new("Auth.InvalidCredentials", "Invalid credentials");
public static Error IdentityRegistrationDisabled => new("Auth.RegistrationDisabled", "Registration is disabled");
public static Error IdentityUsernameNotUnique => new("Auth.UsernameNotUnique", "Username is already taken");
public static Error UserNotFound => new("Auth.UserNotFound", "User not found");
public static Error PasswordConfirmationMismatch => new("Auth.PasswordConfirmationMismatch", "Passwords do not match");
public static Error PasswordTooShort => new("Auth.PasswordTooShort", "Password must be at least 8 characters");
public static Error OldPasswordInvalid => new("Auth.OldPasswordInvalid", "Current password is incorrect");
}
@@ -0,0 +1,17 @@
namespace Knot.Contracts.Auth.Application.Auth.DTOs;
public class AuthResponseDto
{
public required string AccessToken { get; set; }
public required string RefreshToken { get; set; }
public Guid UserId { get; set; }
public string Username { get; set; } = string.Empty;
public string? DisplayName { get; set; }
public string? Avatar { get; set; }
}
public class ResetPasswordDto
{
public required string Token { get; set; }
public required string NewPassword { get; set; }
}
@@ -0,0 +1,16 @@
namespace Knot.Contracts.Auth.Domain;
public interface IUserRepository
{
Task<UserContract?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
Task<List<UserContract>> GetByIdsAsync(IEnumerable<Guid> ids, CancellationToken cancellationToken = default);
Task<UserContract?> GetByUsernameAsync(string username, CancellationToken cancellationToken = default);
Task<bool> IsUsernameUniqueAsync(string username, CancellationToken cancellationToken = default);
Task<List<UserContract>> SearchUsersAsync(string query, CancellationToken cancellationToken = default);
Task<bool> IsBannedAsync(Guid userId, CancellationToken cancellationToken = default);
Task<UserContract?> GetByRefreshTokenAsync(string refreshToken, CancellationToken cancellationToken = default);
Task UpdateAsync(UserContract user, CancellationToken cancellationToken = default);
void Add(UserContract user);
Task RemoveAsync(UserContract user, CancellationToken cancellationToken = default);
void Update(UserContract user);
}
@@ -0,0 +1,22 @@
namespace Knot.Contracts.Auth.Domain;
public class UserContract
{
public Guid Id { get; set; }
public string Username { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
public string PasswordHash { get; set; } = string.Empty;
public string? PhoneNumber { get; set; }
public string? Email { get; set; }
public string? Bio { get; set; }
public string? Avatar { get; set; }
public DateTime? Birthday { get; set; }
public bool IsBot { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? BannedUntil { get; set; }
public bool IsBanned { get; set; }
public bool IsOnline { get; set; }
public bool IsExternal { get; set; }
public string? Domain { get; set; }
public DateTime? LastSeen { get; set; }
}
@@ -0,0 +1,8 @@
using Knot.Shared.Kernel;
namespace Knot.Contracts.Auth.Domain;
/// <summary>
/// Доменное событие: изменился статус пользователя (онлайн/офлайн).
/// </summary>
public sealed record UserStatusChangedDomainEvent(Guid UserId, bool IsOnline, DateTime LastSeen) : IDomainEvent;
@@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Knot.Contracts.Auth.Domain;
namespace Knot.Contracts.Auth.Infrastructure.Persistence;
public interface IAuthDbContext
{
IQueryable<UserContract> Users { get; }
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MediatR" Version="12.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.4" />
</ItemGroup>
</Project>
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Knot.Contracts.Cleanup.Abstractions;
public interface ICleanupService
{
Task<CleanupData> GetCleanupDataAsync(CancellationToken cancellationToken);
Task CleanOrphanMessagesAsync(List<Guid> messageIds, CancellationToken cancellationToken);
Task CleanOrphanFilesAsync(List<string> fileIds, CancellationToken cancellationToken);
}
public record CleanupData(
List<ChatCleanupInfo> Chats,
List<MessageCleanupInfo> Messages,
List<StoryCleanupInfo> Stories,
List<UserCleanupInfo> Users,
List<FileCleanupInfo> Files);
public record ChatCleanupInfo(Guid Id, string? Avatar);
public record MessageCleanupInfo(Guid Id, Guid ChatId, bool IsDeleted, string? MediaUrl);
public record StoryCleanupInfo(Guid Id, string? MediaUrl);
public record UserCleanupInfo(Guid Id, string? Avatar);
public record FileCleanupInfo(string FileId);
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Knot.Contracts.Conversations.Abstractions;
public interface IChatQueryService
{
Task<List<ChatInfo>> GetAllChatsAsync(CancellationToken cancellationToken);
}
public record ChatInfo(Guid Id, string? Avatar);
@@ -0,0 +1,11 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Knot.Shared.Kernel;
namespace Knot.Contracts.Conversations.Abstractions;
public interface IUserDeleterService
{
Task<Result> DeleteUserAsync(Guid userId, CancellationToken cancellationToken);
}
@@ -0,0 +1,6 @@
namespace Knot.Contracts.Conversations.Abstractions;
public interface IUserStatusService
{
bool IsUserOnline(string userId);
}
@@ -0,0 +1,7 @@
using Knot.Shared.Kernel;
namespace Knot.Contracts.Conversations.Application.Abstractions;
public interface IChatsUnitOfWork : IUnitOfWork
{
}
@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.SignalR;
namespace Knot.Contracts.Conversations.Application.Abstractions;
public interface IStoryNotificationsClient
{
Task StoryViewed(Guid storyId, Guid userId);
Task StoryReactionAdded(Guid storyId, Guid userId, string reaction);
Task StoryReactionRemoved(Guid storyId, Guid userId);
}
public interface IChatHubClient
{
Task MessageReceived(object message);
Task MessageDeleted(Guid messageId);
Task MessageEdited(Guid messageId, object newContent);
Task ReactionAdded(Guid messageId, string reaction, Guid userId);
Task ReactionRemoved(Guid messageId, string reaction, Guid userId);
Task UserStatusChanged(Guid userId, bool isOnline);
Task ChatUpdated(object chat);
Task ChatDeleted(Guid chatId);
Task UserJoinedChat(Guid chatId, Guid userId);
Task UserLeftChat(Guid chatId, Guid userId);
}
@@ -0,0 +1,8 @@
using System;
namespace Knot.Contracts.Conversations.Application.Abstractions;
public interface IUserStatusService
{
bool IsUserOnline(string userId);
}
@@ -1,6 +1,6 @@
using Knot.Shared.Kernel;
namespace Knot.Modules.Chats.Domain;
namespace Knot.Contracts.Conversations.Domain;
public sealed record ChatCreatedDomainEvent(Chat Chat) : IDomainEvent;
public sealed record ChatMemberAddedDomainEvent(Guid ChatId, Guid UserId) : IDomainEvent;
@@ -35,22 +35,24 @@ public sealed class Chat : AggregateRoot<Guid>
public string? Description { get; private set; }
public string? Avatar { get; private set; }
public DateTime CreatedAt { get; private set; }
public long LastMessageSequenceId { get; private set; }
public bool IsImporting { get; private set; }
public Guid? ImportJobId { get; private set; }
private readonly List<ChatMember> _members = new();
public IReadOnlyCollection<ChatMember> Members => _members.AsReadOnly();
private Chat(Guid id, ChatType type, string? name, string? avatar, string? description = null) : base(id)
private Chat(Guid id, ChatType type, string? name, string? avatar, string? description = null, bool isImporting = false, Guid? importJobId = null) : base(id)
{
Type = type;
Name = name;
Avatar = avatar;
Description = description;
CreatedAt = DateTime.UtcNow;
IsImporting = isImporting;
ImportJobId = importJobId;
}
/// <summary>
/// Создает личный чат между двумя пользователями.
/// </summary>
public static Chat CreatePersonal()
{
var chat = new Chat(Guid.NewGuid(), ChatType.Personal, null, null);
@@ -58,9 +60,6 @@ public sealed class Chat : AggregateRoot<Guid>
return chat;
}
/// <summary>
/// Создает групповой чат.
/// </summary>
public static Chat CreateGroup(string name, string? avatar = null)
{
var chat = new Chat(Guid.NewGuid(), ChatType.Group, name, avatar);
@@ -68,16 +67,18 @@ public sealed class Chat : AggregateRoot<Guid>
return chat;
}
/// <summary>
/// Фабричный метод для создания чата.
/// </summary>
public static Chat Create(string? name, ChatType type, string? avatar = null, string? description = null)
public static Chat Create(string? name, ChatType type, string? avatar = null, string? description = null, bool isImporting = false, Guid? importJobId = null)
{
var chat = new Chat(Guid.NewGuid(), type, name, avatar, description);
var chat = new Chat(Guid.NewGuid(), type, name, avatar, description, isImporting, importJobId);
chat.RaiseDomainEvent(new ChatCreatedDomainEvent(chat));
return chat;
}
public void CompleteImport()
{
IsImporting = false;
}
public void AddMember(Guid userId, string role = "member")
{
if (_members.Any(m => m.UserId == userId))
@@ -103,6 +104,11 @@ public sealed class Chat : AggregateRoot<Guid>
public void UpdateDescription(string? description) => Description = description;
public void UpdateAvatar(string? avatarUrl) => Avatar = avatarUrl;
public long IncrementSequenceId()
{
return ++LastMessageSequenceId;
}
}
/// <summary>
@@ -117,7 +123,10 @@ public sealed class ChatMember : Entity<Guid>
public bool IsPinned { get; private set; }
public bool IsMuted { get; private set; }
// For EF Core
public Guid? LastReadMessageId { get; private set; }
public long LastReadSequenceId { get; private set; }
public Guid? LastDeliveredMessageId { get; private set; }
private ChatMember() : base(Guid.Empty) { Role = "member"; }
internal ChatMember(Guid chatId, Guid userId, string role) : base(Guid.NewGuid())
@@ -129,4 +138,18 @@ public sealed class ChatMember : Entity<Guid>
}
public void TogglePin() => IsPinned = !IsPinned;
public void UpdateReadCursor(Guid messageId, long sequenceId)
{
if (sequenceId > LastReadSequenceId)
{
LastReadMessageId = messageId;
LastReadSequenceId = sequenceId;
}
}
public void UpdateDeliveredCursor(Guid messageId)
{
LastDeliveredMessageId = messageId;
}
}
@@ -0,0 +1,8 @@
namespace Knot.Contracts.Conversations.Domain;
public static class ChatConstants
{
public const int DefaultMessageQueryLimit = 50;
public const int MaxSharedMediaQueryLimit = 1000;
public const int MaxGroupNameLength = 100;
}
@@ -0,0 +1,19 @@
using Knot.Shared.Kernel;
namespace Knot.Contracts.Conversations.Domain;
public static class ChatErrors
{
public static readonly Error ChatNotFound = new Error("Chat.NotFound", "Чат не найден");
public static readonly Error OnlyOwnerCanUpdate = new Error("Chat.OnlyOwnerCanUpdate", "Только владелец может редактировать чат");
public static readonly Error Unauthorized = new Error("Chat.Unauthorized", "Нет доступа к этому чату");
public static readonly Error FoldersDisabled = new Error("Chat.FoldersDisabled", "Папки отключены");
public static readonly Error FileEmpty = new Error("Chat.FileEmpty", "Файл пуст");
public static Error FileTooLarge(long maxMb) => new Error("Chat.FileTooLarge", $"Файл слишком большой (максимум {maxMb} МБ)");
public static readonly Error ChatsNotFound = new Error("Chat.NotFound", "Чат не найден");
public static readonly Error ChatsForbidden = new Error("Chat.Forbidden", "Доступ запрещен");
public static readonly Error MediaDisabled = new Error("Chat.MediaDisabled", "Медиафайлы отключены");
public static readonly Error PollsDisabled = new Error("Chat.PollsDisabled", "Опросы отключены");
public static readonly Error NotFound = new Error("Chat.NotFound", "Не найдено");
public static readonly Error NotMember = new Error("Chat.NotMember", "Вы не являетесь участником чата");
}
@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using Knot.Shared.Kernel;
namespace Knot.Contracts.Conversations.Domain;
/// <summary>
/// Сущность папки для группировки чатов.
/// </summary>
public sealed class Folder : AggregateRoot<Guid>
{
public string Name { get; private set; }
public string? Icon { get; private set; }
public bool IsDefault { get; private set; }
public FolderType Type { get; private set; }
public Folder(Guid id, string name, string? icon = null, bool isDefault = false, FolderType type = FolderType.Custom)
: base(id)
{
Name = name;
Icon = icon;
IsDefault = isDefault;
Type = type;
}
public void Update(string name, string? icon)
{
if (IsDefault) throw new InvalidOperationException("Cannot rename default folders.");
Name = name;
Icon = icon;
}
}
public enum FolderType
{
All,
New,
Muted,
Custom
}
/// <summary>
/// Настройки конкретного чата для конкретного пользователя.
/// </summary>
public sealed class UserChatSettings : Entity<Guid>
{
public Guid UserId { get; private set; }
public Guid ChatId { get; private set; }
private readonly List<Guid> _folderIds = new();
public IReadOnlyCollection<Guid> FolderIds => _folderIds.AsReadOnly();
public bool IsMuted { get; private set; }
private UserChatSettings() : base(Guid.NewGuid()) { }
public UserChatSettings(Guid userId, Guid chatId) : base(Guid.NewGuid())
{
UserId = userId;
ChatId = chatId;
}
public static UserChatSettings Create(Guid userId, Guid chatId) => new(userId, chatId);
public void AddToFolder(Guid folderId)
{
if (!_folderIds.Contains(folderId)) _folderIds.Add(folderId);
}
public void RemoveFromFolder(Guid folderId)
{
_folderIds.Remove(folderId);
}
public void SetMute(bool isMuted) => IsMuted = isMuted;
}
/// <summary>
/// Глобальные настройки папок пользователя.
/// </summary>
public sealed class UserFolderSettings : AggregateRoot<Guid>
{
public Guid UserId { get; private set; }
public List<Guid> HiddenDefaultFolderIds { get; private set; } = new();
public List<Guid> CustomFolderIds { get; private set; } = new();
public UserFolderSettings(Guid userId) : base(Guid.NewGuid())
{
UserId = userId;
}
public void HideFolder(Guid folderId)
{
if (!HiddenDefaultFolderIds.Contains(folderId)) HiddenDefaultFolderIds.Add(folderId);
}
public void ShowFolder(Guid folderId)
{
HiddenDefaultFolderIds.Remove(folderId);
}
}
@@ -0,0 +1,39 @@
using Knot.Contracts.Conversations.Domain;
namespace Knot.Contracts.Conversations.Domain;
public interface IChatRepository
{
void Add(Chat chat);
void Update(Chat chat);
void Remove(Chat chat);
Task<Chat?> GetByIdAsync(Guid id, CancellationToken cancellationToken);
Task<Chat?> GetFavoritesAsync(Guid userId, CancellationToken cancellationToken);
Task<List<Chat>> GetUserChatsAsync(Guid userId, CancellationToken cancellationToken);
}
public interface IFolderRepository
{
void Add(Folder folder);
void Update(Folder folder);
void Remove(Folder folder);
Task<Folder?> GetByIdAsync(Guid id, CancellationToken cancellationToken);
Task<List<Folder>> GetUserFoldersAsync(Guid userId, CancellationToken cancellationToken);
}
public interface IUserChatSettingsRepository
{
void Add(UserChatSettings settings);
void Update(UserChatSettings settings);
void Remove(UserChatSettings settings);
void RemoveRange(IEnumerable<UserChatSettings> settings);
Task<UserChatSettings?> GetAsync(Guid userId, Guid chatId, CancellationToken cancellationToken);
Task<List<UserChatSettings>> GetByUserIdAsync(Guid userId, CancellationToken cancellationToken);
}
public interface IUserFolderSettingsRepository
{
Task<UserFolderSettings?> GetByUserIdAsync(Guid userId, CancellationToken cancellationToken);
Task UpdateAsync(UserFolderSettings settings, CancellationToken cancellationToken);
Task RemoveByUserIdAsync(Guid userId, CancellationToken cancellationToken);
}
@@ -0,0 +1,18 @@
using System.Linq;
using Knot.Contracts.Conversations.Domain;
namespace Knot.Contracts.Conversations.Infrastructure.Persistence;
public interface IChatsDbContext
{
IQueryable<Chat> Chats { get; }
}
/// <summary>
/// Упрощенная проекция чата для запросов (не доменная сущность).
/// </summary>
public class Chat
{
public Guid Id { get; set; }
public string? Avatar { get; set; }
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
<ProjectReference Include="..\Messaging\Knot.Contracts.Messaging.csproj" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MediatR" Version="12.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.4" />
</ItemGroup>
</Project>
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MediatR" Version="12.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.4" />
</ItemGroup>
</Project>
@@ -0,0 +1,6 @@
namespace Knot.Contracts.Klipy.Abstractions;
public interface IKlipyClient
{
Task<bool> TestConnectionAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,9 @@
using System.Threading;
using System.Threading.Tasks;
namespace Knot.Contracts.Klipy.Application.Abstractions;
public interface IKlipyClient
{
Task<bool> TestConnectionAsync(string apiKey, string appName, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MediatR" Version="12.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.4" />
</ItemGroup>
</Project>
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Knot.Contracts.Messaging.Abstractions;
public interface IMessageQueryService
{
Task<List<MessageInfo>> GetAllMessagesAsync(CancellationToken cancellationToken);
Task DeleteMessagesAsync(List<Guid> messageIds, CancellationToken cancellationToken);
}
public record MessageInfo(Guid Id, Guid ChatId, bool IsDeleted, string? MediaUrl);
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Knot.Contracts.Messaging.Abstractions;
public interface IUserStatsService
{
Task<Dictionary<Guid, UserStats>> GetStatsForUsersAsync(List<Guid> userIds, CancellationToken cancellationToken = default);
}
public record UserStats(int MessageCount, long StorageSize);
@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Knot.Contracts.Messaging.Application.Abstractions;
public interface IChatAccessProvider
{
Task<List<Guid>> GetValidChatIdsForUserAsync(Guid userId, CancellationToken ct);
}
@@ -0,0 +1,7 @@
namespace Knot.Contracts.Messaging.Application.Abstractions;
public interface IMessageNotifier
{
Task NotifyNewMessageAsync(Guid chatId, object messagePayload, CancellationToken cancellationToken);
Task NotifyMessageUpdateAsync(Guid chatId, string updateType, object updatePayload, CancellationToken cancellationToken);
}
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Knot.Contracts.Messaging.Application.Abstractions;
public interface IMessageQueryService
{
Task<List<MessageInfo>> GetAllMessagesAsync(CancellationToken cancellationToken);
Task<List<MessageInfo>> GetOrphanedMessagesAsync(HashSet<Guid> activeChatIds, CancellationToken cancellationToken);
Task DeleteMessagesAsync(List<Guid> messageIds, CancellationToken cancellationToken);
}
public record MessageInfo(Guid Id, Guid ChatId, bool IsDeleted, string? MediaUrl, List<MediaInfo>? Media);
public record MediaInfo(string Url);
@@ -0,0 +1,24 @@
using Knot.Contracts.Messaging.Domain;
namespace Knot.Contracts.Messaging.Application.Abstractions;
public interface IMessageRepository
{
void Add(Message message);
Task<Message?> GetByIdAsync(Guid id, CancellationToken cancellationToken);
Task<List<Message>> GetChatMessagesAsync(Guid chatId, int limit, int offset, CancellationToken cancellationToken);
Task<Message?> GetLatestChatMessageAsync(Guid chatId, CancellationToken cancellationToken);
Task<List<Message>> GetPinnedMessagesAsync(Guid chatId, CancellationToken cancellationToken);
Task<List<Message>> SearchMessagesAsync(string query, Guid? chatId, Guid requestingUserId, CancellationToken cancellationToken);
Task<List<Message>> GetChatMessagesCursorAsync(Guid chatId, DateTime? cursor, long? sequenceId, int limit, CancellationToken cancellationToken);
Task<List<Message>> GetChatMessagesAroundAsync(Guid chatId, long sequenceId, int limit, CancellationToken cancellationToken);
Task<Message?> GetLastStoryMessageAsync(Guid chatId, Guid storyId, CancellationToken cancellationToken);
Task UpdateAsync(Message message, CancellationToken cancellationToken);
Task<List<Message>> GetAllMessagesAsync(CancellationToken cancellationToken);
Task DeleteChatMessagesAsync(Guid chatId, CancellationToken cancellationToken);
Task DeleteUserMessagesAsync(Guid userId, CancellationToken cancellationToken);
Task RemoveAsync(Guid id, CancellationToken cancellationToken);
Task DeleteAsync(Message message, CancellationToken cancellationToken);
}
@@ -0,0 +1,12 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Knot.Contracts.Messaging.Application.Abstractions;
public interface IUserStatsService
{
Task<Dictionary<Guid, UserStats>> GetStatsForUsersAsync(List<Guid> userIds, CancellationToken cancellationToken = default);
}
public record UserStats(int MessageCount, long StorageSize);
@@ -0,0 +1,33 @@
using System;
namespace Knot.Contracts.Messaging.Domain;
public class CallMessage : Message
{
public override string Type => "call";
public override string? Content { get; protected set; }
public string CallType { get; protected set; }
public string CallStatus { get; protected set; }
public int? Duration { get; protected set; }
public CallMessage() : base() { }
public CallMessage(
Guid id,
Guid chatId,
Guid senderId,
string callType,
string callStatus,
int? duration,
Guid? replyToId,
Guid? forwardedFromId,
DateTime createdAt,
bool isImported = false)
: base(id, chatId, senderId, replyToId, forwardedFromId, createdAt, isImported)
{
CallType = callType;
CallStatus = callStatus;
Duration = duration;
Content = $"Call {callStatus}";
}
}
@@ -0,0 +1,20 @@
using Knot.Shared.Kernel;
namespace Knot.Contracts.Messaging.Domain;
/// <summary>
/// Запись о том, что конкретный пользователь удалил у себя сообщение.
/// </summary>
public sealed class DeletedMessage : Entity<Guid>
{
public Guid MessageId { get; private set; }
public Guid UserId { get; private set; }
internal DeletedMessage(Guid messageId, Guid userId) : base(Guid.NewGuid())
{
MessageId = messageId;
UserId = userId;
}
private DeletedMessage() : base(Guid.Empty) { }
}
@@ -0,0 +1,9 @@
namespace Knot.Contracts.Messaging.Domain;
public interface IMessageReactionRepository
{
Task AddAsync(MessageReaction reaction, CancellationToken cancellationToken);
Task RemoveAsync(Guid messageId, Guid userId, string emoji, CancellationToken cancellationToken);
Task<List<MessageReaction>> GetReactionsForMessageAsync(Guid messageId, CancellationToken cancellationToken);
Task<List<MessageReaction>> GetReactionsForMessagesAsync(IEnumerable<Guid> messageIds, CancellationToken cancellationToken);
}
@@ -0,0 +1,17 @@
using System;
namespace Knot.Contracts.Messaging.Domain;
public class Media
{
public Guid Id { get; set; }
public string Type { get; set; } = string.Empty;
public string? Url { get; set; }
public string? ThumbnailUrl { get; set; }
public long? Size { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public string? FileId { get; set; }
public string? Filename { get; set; }
public string? Duration { get; set; }
}
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
namespace Knot.Contracts.Messaging.Domain;
public class MediaMessage : Message
{
public override string Type => MediaType.ToString().ToLower();
public override string? Content { get; protected set; }
public string? Caption { get => Content; private set => Content = value; }
public MediaType MediaType { get; private set; }
private List<Media> _media = new();
public IReadOnlyCollection<Media> Media => _media.AsReadOnly();
private MediaMessage() : base() { MediaType = MediaType.File; }
public MediaMessage(Guid id, Guid chatId, Guid senderId, MediaType mediaType, string? caption, Guid? replyToId, Guid? forwardedFromId, DateTime createdAt, bool isImported)
: base(id, chatId, senderId, replyToId, forwardedFromId, createdAt, isImported) { MediaType = mediaType; Caption = caption; }
public MediaMessage(Guid id, Guid chatId, Guid senderId, string mediaType, string? caption, Guid? replyToId, Guid? forwardedFromId, DateTime createdAt, bool isImported)
: this(id, chatId, senderId, Enum.TryParse<MediaType>(mediaType, true, out var mt) ? mt : MediaType.File, caption, replyToId, forwardedFromId, createdAt, isImported) { }
public void AddMedia(string type, string url, string? filename, long? size, string? duration = null) => _media.Add(new Media { Type = type, Url = url, Filename = filename, FileId = filename, Size = size, Duration = duration });
public override void Edit(string newCaption) => base.Edit(newCaption);
public override void Delete() { Caption = null; base.Delete(); }
}
@@ -0,0 +1,9 @@
namespace Knot.Contracts.Messaging.Domain;
public enum MediaType
{
Image,
Video,
Voice,
File
}
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using Knot.Shared.Kernel;
namespace Knot.Contracts.Messaging.Domain;
public abstract class Message : AggregateRoot<Guid>
{
public Guid ChatId { get; protected set; }
public Guid SenderId { get; protected set; }
public DateTime CreatedAt { get; protected set; }
public long SequenceId { get; protected set; }
public void SetSequenceId(long sequenceId) => SequenceId = sequenceId;
public Guid? ReplyToId { get; protected set; }
public Guid? ForwardedFromId { get; protected set; }
public MessageState State { get; protected set; }
public abstract string Type { get; }
public abstract string? Content { get; protected set; }
public bool IsEdited => HasState(MessageState.IsEdited);
public bool IsDeleted => HasState(MessageState.IsDeleted);
protected List<DeletedMessage> _deletedFor = new();
public IReadOnlyCollection<DeletedMessage> DeletedFor => _deletedFor.AsReadOnly();
protected Message() : base(Guid.Empty) { }
protected Message(Guid id, Guid chatId, Guid senderId, Guid? replyToId, Guid? forwardedFromId, DateTime createdAt, bool isImported)
: base(id)
{
ChatId = chatId;
SenderId = senderId;
ReplyToId = replyToId;
ForwardedFromId = forwardedFromId;
CreatedAt = createdAt;
if (isImported) AddState(MessageState.IsImported);
}
public void AddState(MessageState state) => State |= state;
public void RemoveState(MessageState state) => State &= ~state;
public bool HasState(MessageState state) => (State & state) == state;
public bool IsDeletedForUser(Guid userId) => _deletedFor.Exists(d => d.UserId == userId);
public virtual void Delete() => AddState(MessageState.IsDeleted);
public virtual void Edit(string newContent)
{
Content = newContent;
AddState(MessageState.IsEdited);
}
public void DeleteForUser(Guid userId)
{
if (!_deletedFor.Exists(x => x.UserId == userId))
_deletedFor.Add(new DeletedMessage(Id, userId));
}
}
@@ -0,0 +1,28 @@
using Knot.Shared.Kernel;
namespace Knot.Contracts.Messaging.Domain;
/// <summary>
/// Доменное событие: сообщение отправлено.
/// </summary>
public sealed record MessageSentDomainEvent(Guid MessageId, Guid ChatId, Guid SenderId, string? Content) : IDomainEvent;
/// <summary>
/// Доменное событие: сообщение удалено.
/// </summary>
public sealed record MessageDeletedDomainEvent(Guid MessageId, Guid ChatId) : IDomainEvent;
/// <summary>
/// Доменное событие: сообщение отредактировано.
/// </summary>
public sealed record MessageEditedDomainEvent(Guid MessageId, Guid ChatId, string NewContent) : IDomainEvent;
/// <summary>
/// Доменное событие: реакция добавлена.
/// </summary>
public sealed record MessageReactionAddedDomainEvent(Guid MessageId, Guid ChatId, Guid UserId, string Emoji) : IDomainEvent;
/// <summary>
/// Доменное событие: реакция удалена.
/// </summary>
public sealed record MessageReactionRemovedDomainEvent(Guid MessageId, Guid ChatId, Guid UserId, string Emoji) : IDomainEvent;
@@ -0,0 +1,25 @@
using System;
using Knot.Shared.Kernel;
namespace Knot.Contracts.Messaging.Domain;
public sealed class MessageReaction : AggregateRoot<Guid>
{
public Guid MessageId { get; private set; }
public Guid UserId { get; private set; }
public string Emoji { get; private set; }
public DateTime CreatedAt { get; private set; }
private MessageReaction() : base(Guid.Empty)
{
Emoji = default!;
}
public MessageReaction(Guid messageId, Guid userId, string emoji) : base(Guid.NewGuid())
{
MessageId = messageId;
UserId = userId;
Emoji = emoji;
CreatedAt = DateTime.UtcNow;
}
}
@@ -0,0 +1,14 @@
namespace Knot.Contracts.Messaging.Domain;
/// <summary>
/// Состояние сообщения
/// </summary>
[Flags]
public enum MessageState
{
None = 0,
IsEdited = 1,
IsDeleted = 2,
IsImported = 4,
IsPinned = 8
}
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
namespace Knot.Contracts.Messaging.Domain;
public class PollMessage : Message
{
public override string Type => "poll";
public override string? Content { get; protected set; }
public List<PollOption> Options { get; set; } = new();
public List<PollVote> Votes { get; set; } = new();
public bool IsMultipleChoice { get; set; }
public bool IsAnonymous { get; set; }
public DateTime? ExpiresAt { get; set; }
public bool IsClosed { get; set; }
public PollMessage() : base() { }
public PollMessage(Guid id, Guid chatId, Guid senderId, string? question, List<PollOption>? options, bool isAnonymous, bool isMultiple, DateTime? expiresAt, Guid? replyToId, Guid? forwardedFromId, DateTime createdAt, bool isImported)
: base(id, chatId, senderId, replyToId, forwardedFromId, createdAt, isImported)
{
Content = question ?? "Poll";
Options = options ?? new List<PollOption>();
IsAnonymous = isAnonymous;
IsMultipleChoice = isMultiple;
ExpiresAt = expiresAt;
}
public static PollMessage Create(Guid id, Guid chatId, Guid senderId, string? question, List<string> options, bool isAnonymous, bool isMultiple, DateTime? expiresAt, Guid? replyToId, Guid? forwardedFromId)
{
var poll = new PollMessage(id, chatId, senderId, question, null, isAnonymous, isMultiple, expiresAt, replyToId, forwardedFromId, DateTime.UtcNow, false);
foreach (var opt in options)
{
poll.Options.Add(new PollOption { Text = opt });
}
return poll;
}
}
@@ -0,0 +1,8 @@
namespace Knot.Contracts.Messaging.Domain;
public class PollOption
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Text { get; set; } = string.Empty;
public int VoteCount { get; set; }
}
@@ -0,0 +1,10 @@
using System;
namespace Knot.Contracts.Messaging.Domain;
public class PollVote
{
public Guid OptionId { get; set; }
public Guid UserId { get; set; }
public DateTime VotedAt { get; set; }
}
@@ -0,0 +1,23 @@
using System;
using Knot.Shared.Kernel;
namespace Knot.Contracts.Messaging.Domain;
public sealed class Reaction : Entity<Guid>
{
public Guid MessageId { get; private set; }
public Guid UserId { get; private set; }
public string Emoji { get; private set; }
internal Reaction(Guid messageId, Guid userId, string emoji) : base(Guid.NewGuid())
{
MessageId = messageId;
UserId = userId;
Emoji = emoji;
}
private Reaction() : base(Guid.Empty)
{
Emoji = string.Empty;
}
}
@@ -0,0 +1,31 @@
using System;
namespace Knot.Contracts.Messaging.Domain;
public class StoryMessage : Message
{
public override string Type => "story";
public override string? Content { get; protected set; }
public Guid? StoryId { get; private set; }
public string? StoryMediaUrl { get; private set; }
public string? StoryMediaType { get; private set; }
public StoryMessage() : base() { }
public StoryMessage(Guid id, Guid chatId, Guid senderId, Guid storyId, string? storyMediaUrl, string? storyMediaType, DateTime createdAt)
: base(id, chatId, senderId, null, null, createdAt, false)
{
StoryId = storyId;
StoryMediaUrl = storyMediaUrl;
StoryMediaType = storyMediaType;
}
public StoryMessage(Guid id, Guid chatId, Guid senderId, Guid storyId, string? storyMediaUrl, string? storyMediaType, string? content, Guid? replyToId, Guid? forwardedFromId, DateTime createdAt, bool isImported)
: this(id, chatId, senderId, storyId, storyMediaUrl, storyMediaType, createdAt)
{
Content = content;
ReplyToId = replyToId;
ForwardedFromId = forwardedFromId;
if (isImported) AddState(MessageState.IsImported);
}
}
@@ -0,0 +1,18 @@
using System;
namespace Knot.Contracts.Messaging.Domain;
public class TextMessage : Message
{
public override string Type => "text";
public override string? Content { get; protected set; }
public string? Quote { get; protected set; }
public TextMessage() : base() { }
public TextMessage(Guid id, Guid chatId, Guid senderId, string content, Guid? replyToId, Guid? forwardedFromId, DateTime createdAt, bool isImported = false)
: base(id, chatId, senderId, replyToId, forwardedFromId, createdAt, isImported) => Content = content;
public TextMessage(Guid id, Guid chatId, Guid senderId, string content, Guid? replyToId, string? quote, Guid? forwardedFromId, DateTime createdAt, bool isImported = false)
: this(id, chatId, senderId, content, replyToId, forwardedFromId, createdAt, isImported) => Quote = quote;
}
@@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Knot.Contracts.Messaging.Infrastructure.Persistence;
public interface IMongoMessageCollection
{
Task<List<Message>> GetAllAsync(CancellationToken cancellationToken);
Task<List<Message>> GetOrphanedMessagesAsync(HashSet<Guid> activeChatIds, CancellationToken cancellationToken);
Task DeleteManyAsync(List<Guid> messageIds, CancellationToken cancellationToken);
}
public class Message
{
public Guid Id { get; set; }
public Guid ChatId { get; set; }
public bool IsDeleted { get; set; }
public string? MediaUrl { get; set; }
public List<Media>? Media { get; set; }
}
public class Media
{
public string Url { get; set; } = string.Empty;
}
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MediatR" Version="12.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.4" />
</ItemGroup>
</Project>
@@ -0,0 +1,16 @@
using Knot.Shared.Kernel;
namespace Knot.Contracts.Profiles.Application.DTOs;
public static class ProfilesErrors
{
public static Error ProfileNotFound => new("Profiles.NotFound", "Profile not found");
public static Error UsernameAlreadyExists => new("Profiles.UsernameExists", "Username already exists");
public static Error AvatarNotFound => new("Profiles.AvatarNotFound", "Avatar not found");
public static Error InvalidAvatarFormat => new("Profiles.InvalidAvatarFormat", "Invalid avatar format");
public static Error AvatarUploadFailed => new("Profiles.AvatarUploadFailed", "Avatar upload failed");
public static Error BioTooLong => new("Profiles.BioTooLong", "Bio must be 200 characters or less");
public static Error StatusTextTooLong => new("Profiles.StatusTextTooLong", "Status text must be 50 characters or less");
public static Error StatusEmpty => new("Statuses.Empty", "Status emoji or text is required");
public static Error InvalidPreset => new("Statuses.InvalidPreset", "Unknown status preset");
}
@@ -0,0 +1,21 @@
using MediatR;
namespace Knot.Contracts.Profiles.Application.DTOs;
public record SearchProfilesRequest(string Query, int Limit = 20) : IRequest<List<UserProfileDto>>;
public record UpdateProfileRequest(
string? DisplayName,
string? About,
string? Avatar,
bool RemoveAvatar = false
) : IRequest<UserProfileDto>;
public record UpdateSettingsRequest(
bool? ShowLastSeen,
bool? ShowAvatar,
bool? ShowAbout,
bool? ShowPhoneNumber,
bool? ShowForwardedMessages,
bool? ShowAddToGroupPermission
) : IRequest<UserProfileDto>;
@@ -0,0 +1,9 @@
namespace Knot.Contracts.Profiles.Application.DTOs;
public sealed class StatusPresetDto
{
public string Id { get; set; } = "";
public string Emoji { get; set; } = "";
public string TextRu { get; set; } = "";
public string TextEn { get; set; } = "";
}

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