Compare commits

..
33 Commits
Author SHA1 Message Date
Халимов Рустам 4e06e48657 Переписаны модули, тесты, обработка ошибок 2026-03-10 21:08:03 +03:00
Халимов Рустам 5c9c6ab975 Создание заказов 2026-03-08 00:55:47 +03:00
Халимов Рустам 92f073e827 docs: add comprehensive Russian README 2026-03-06 23:56:10 +03:00
Халимов Рустам 28bb69f4bc chore: remove build artifacts (bin/obj) from tracking 2026-03-06 23:49:10 +03:00
Халимов Рустам ae20033cfc Обновления игнора 2026-03-06 23:43:53 +03:00
Халимов Рустам f5e20c1fb2 Изображения в услугах, поиске, возможность открыть карточку услуги 2026-03-06 23:14:39 +03:00
Халимов Рустам 420a86b92f Правка поиска, сортировка 2026-03-06 21:43:37 +03:00
Халимов Рустам 15c29fc296 Создание услуг, починка аватара, поиск, карта 2026-03-06 13:51:42 +03:00
Халимов Рустам e41692a6e2 Услуги 2026-03-05 15:24:33 +03:00
Халимов Рустам 36e7c07a0d Стать исполнителем новая версия 2026-02-27 23:32:09 +03:00
Халимов Рустам c824e26cc0 Стать исполнителем 2026-02-19 21:51:17 +03:00
Халимов Рустам 628fce5b50 Правка регистрации, расписание, смена ролей 2026-02-16 12:10:44 +03:00
Халимов Рустам 3524e0d0af ИНН и описание 2026-02-16 00:06:10 +03:00
Халимов Рустам abe0ccb390 Readme 2026-02-13 23:00:43 +03:00
Халимов Рустам cc73945ae2 Компетенции и аватар 2026-02-13 22:37:07 +03:00
Халимов Рустам 1e72d006c4 Страница профиля 2026-02-13 14:24:01 +03:00
Халимов Рустам b24b9d697f Добавлен CORS, исправлен профиль 2026-02-13 11:45:41 +03:00
Халимов Рустам a474313cec Исправлено именование проекта тестов. 2026-02-12 16:21:23 +03:00
Халимов Рустам 46f008aefa Модуль репутации 2026-02-12 16:11:14 +03:00
Халимов Рустам d6c1f63842 Модуль коллаборации 2026-02-11 14:25:13 +03:00
Халимов Рустам b7ba835689 Исправления сваггер 2026-02-11 13:24:56 +03:00
Халимов Рустам 3b2c0029a1 Модуль заказов 2026-02-10 22:32:28 +03:00
Халимов Рустам 12c86f9937 Реализация и тесты Geo модуля 2026-02-10 21:51:02 +03:00
Халимов Рустам 9d3999f690 Реализован модуль каталога, миграции, сваггер 2026-02-10 15:32:40 +03:00
Халимов Рустам 4c9e44e992 Fix build 2026-02-10 00:44:40 +03:00
Халимов Рустам 65b86e718d Fix swagger 2026-02-10 00:38:21 +03:00
Халимов Рустам ae69d85fe2 swagger 2026-02-10 00:24:10 +03:00
Халимов Рустам ee40b38968 Identity 2026-02-09 23:54:03 +03:00
Халимов Рустам c98c96e408 Struct 2026-02-09 17:43:37 +03:00
Халимов Рустам 3dd6577313 Rename 2026-02-09 16:36:34 +03:00
Халимов Рустам e3bebb120e obj ignore 2026-02-09 16:14:45 +03:00
Халимов Рустам fcf7714a4d Build 2026-02-09 15:06:08 +03:00
Халимов Рустам c59c28c274 Update ignore 2026-02-09 13:42:32 +03:00
349 changed files with 21263 additions and 14466 deletions
+38
View File
@@ -0,0 +1,38 @@
# Сборочные артефакты .NET
[Bb]in/
[Oo]bj/
[Oo]ut/
[Tt]est[Rr]esults/
# Пользовательские настройки Visual Studio
*.user
*.suo
*.userosscache
*.sln.docstates
# Отладочные символы и бинарники
*.pdb
*.dll
*.exe
# Логи
*.log
# Скрипты (локальные)
*.ps1
# Docker и окружение
.env
.env.*
!.env.example
docker-compose.override.yml
# Загруженные файлы (хранятся вне репозитория)
uploads/
# IDE и ОС
.DS_Store
Thumbs.db
.idea/
.vscode/
*.txt
+32
View File
@@ -0,0 +1,32 @@
# Build stage
FROM mcr.microsoft.com/dotnet/sdk:10.0-preview AS build
WORKDIR /src
# Copy solution and project files
COPY ["Nashel.sln", "./"]
COPY ["src/", "src/"]
COPY ["tests/", "tests/"]
# Restore dependencies
RUN dotnet restore "Nashel.sln"
# Publish the application
WORKDIR "/src/src/Host"
RUN dotnet publish "Nashel.Host.csproj" -c Release -o /app/publish /p:UseAppHost=false
# Final stage
FROM mcr.microsoft.com/dotnet/aspnet:10.0-preview AS final
WORKDIR /app
# Устанавливаем зависимости Npgsql / Kerberos (нужны для PostgreSQL через Npgsql в .NET 10 preview)
RUN apt-get update && apt-get install -y --no-install-recommends \
libgssapi-krb5-2 \
krb5-user \
&& rm -rf /var/lib/apt/lists/*
COPY --from=build /app/publish .
# Copy wwwroot folder for static files (uploads)
COPY src/Host/wwwroot /app/wwwroot
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "Nashel.Host.dll"]
+27
View File
@@ -0,0 +1,27 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0-preview AS build
WORKDIR /src
# Устанавливаем инструмент для миграций EF Core
RUN dotnet tool install --global dotnet-ef --version 9.0.0
ENV PATH="${PATH}:/root/.dotnet/tools"
# Копируем решение и проекты
COPY ["Nashel.sln", "./"]
COPY ["src/", "src/"]
COPY ["tests/", "tests/"]
# Восстанавливаем зависимости
RUN dotnet restore "Nashel.sln"
WORKDIR "/src/src/Host"
ENTRYPOINT ["sh", "-c", "\
echo 'Running migrations...' && \
export PATH=\"$PATH:/root/.dotnet/tools\" && \
dotnet ef database update --project ../Modules/Identity/Infrastructure --startup-project . --context IdentityDbContext && \
dotnet ef database update --project ../Modules/Catalog/Infrastructure --startup-project . --context CatalogDbContext && \
dotnet ef database update --project ../Modules/Collaboration/Infrastructure --startup-project . --context CollaborationDbContext && \
dotnet ef database update --project ../Modules/Order/Infrastructure --startup-project . --context OrderDbContext && \
dotnet ef database update --project ../Modules/Reputation/Infrastructure --startup-project . --context ReputationDbContext && \
dotnet ef database update --project ../Modules/Geo/Infrastructure --startup-project . --context GeoDbContext && \
echo 'All migrations completed successfully!'\
"]
-171
View File
@@ -1,171 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildingBlocks", "BuildingBlocks", "{90298CBA-BD6F-3A0A-69D5-97CAD7B05E7E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.RF.BuildingBlocks", "src\BuildingBlocks\Nashel.RF.BuildingBlocks.csproj", "{CC3A5E56-8FC3-4C75-932A-ADE592F65515}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Host", "Host", "{981C2CF6-E5BB-602A-511C-234B538302B0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.RF.Host", "src\Host\Nashel.RF.Host.csproj", "{1F5F02BF-AD09-41E9-8130-1A939AE5012D}"
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}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.RF.Modules.Identity", "src\Modules\Identity\Nashel.RF.Modules.Identity.csproj", "{A53458FA-F0D6-4666-9D3E-3740665D2BC3}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Geo", "Geo", "{1F785BA6-BCCC-EB5A-C0AC-017D258EF3FD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.RF.Modules.Geo", "src\Modules\Geo\Nashel.RF.Modules.Geo.csproj", "{946AE1A0-0055-4AEC-A4AC-CB8F9F871079}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Catalog", "Catalog", "{1E1EF545-4C18-AEE2-B997-7EF431518D3F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.RF.Modules.Catalog", "src\Modules\Catalog\Nashel.RF.Modules.Catalog.csproj", "{B209DA77-ABD4-49BE-9FE5-769BE44D8391}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Order", "Order", "{2C821288-5B9D-778E-74EE-053FE87F223E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.RF.Modules.Order", "src\Modules\Order\Nashel.RF.Modules.Order.csproj", "{27619C7F-D02E-4E15-ABFA-8FD59ED23D82}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Collaboration", "Collaboration", "{0D6C7A58-55D3-AEBB-0CCB-984ED0F92F71}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.RF.Modules.Collaboration", "src\Modules\Collaboration\Nashel.RF.Modules.Collaboration.csproj", "{D59D021C-A351-4E12-85A7-D7B4D31ED695}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Reputation", "Reputation", "{305CF822-D9CA-C4BE-7183-DD5447E36C7E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.RF.Modules.Reputation", "src\Modules\Reputation\Nashel.RF.Modules.Reputation.csproj", "{75715051-86CB-472A-BEFA-0E9AAA5EB24F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{CC3A5E56-8FC3-4C75-932A-ADE592F65515}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CC3A5E56-8FC3-4C75-932A-ADE592F65515}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC3A5E56-8FC3-4C75-932A-ADE592F65515}.Debug|x64.ActiveCfg = Debug|Any CPU
{CC3A5E56-8FC3-4C75-932A-ADE592F65515}.Debug|x64.Build.0 = Debug|Any CPU
{CC3A5E56-8FC3-4C75-932A-ADE592F65515}.Debug|x86.ActiveCfg = Debug|Any CPU
{CC3A5E56-8FC3-4C75-932A-ADE592F65515}.Debug|x86.Build.0 = Debug|Any CPU
{CC3A5E56-8FC3-4C75-932A-ADE592F65515}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC3A5E56-8FC3-4C75-932A-ADE592F65515}.Release|Any CPU.Build.0 = Release|Any CPU
{CC3A5E56-8FC3-4C75-932A-ADE592F65515}.Release|x64.ActiveCfg = Release|Any CPU
{CC3A5E56-8FC3-4C75-932A-ADE592F65515}.Release|x64.Build.0 = Release|Any CPU
{CC3A5E56-8FC3-4C75-932A-ADE592F65515}.Release|x86.ActiveCfg = Release|Any CPU
{CC3A5E56-8FC3-4C75-932A-ADE592F65515}.Release|x86.Build.0 = Release|Any CPU
{1F5F02BF-AD09-41E9-8130-1A939AE5012D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1F5F02BF-AD09-41E9-8130-1A939AE5012D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1F5F02BF-AD09-41E9-8130-1A939AE5012D}.Debug|x64.ActiveCfg = Debug|Any CPU
{1F5F02BF-AD09-41E9-8130-1A939AE5012D}.Debug|x64.Build.0 = Debug|Any CPU
{1F5F02BF-AD09-41E9-8130-1A939AE5012D}.Debug|x86.ActiveCfg = Debug|Any CPU
{1F5F02BF-AD09-41E9-8130-1A939AE5012D}.Debug|x86.Build.0 = Debug|Any CPU
{1F5F02BF-AD09-41E9-8130-1A939AE5012D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1F5F02BF-AD09-41E9-8130-1A939AE5012D}.Release|Any CPU.Build.0 = Release|Any CPU
{1F5F02BF-AD09-41E9-8130-1A939AE5012D}.Release|x64.ActiveCfg = Release|Any CPU
{1F5F02BF-AD09-41E9-8130-1A939AE5012D}.Release|x64.Build.0 = Release|Any CPU
{1F5F02BF-AD09-41E9-8130-1A939AE5012D}.Release|x86.ActiveCfg = Release|Any CPU
{1F5F02BF-AD09-41E9-8130-1A939AE5012D}.Release|x86.Build.0 = Release|Any CPU
{A53458FA-F0D6-4666-9D3E-3740665D2BC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A53458FA-F0D6-4666-9D3E-3740665D2BC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A53458FA-F0D6-4666-9D3E-3740665D2BC3}.Debug|x64.ActiveCfg = Debug|Any CPU
{A53458FA-F0D6-4666-9D3E-3740665D2BC3}.Debug|x64.Build.0 = Debug|Any CPU
{A53458FA-F0D6-4666-9D3E-3740665D2BC3}.Debug|x86.ActiveCfg = Debug|Any CPU
{A53458FA-F0D6-4666-9D3E-3740665D2BC3}.Debug|x86.Build.0 = Debug|Any CPU
{A53458FA-F0D6-4666-9D3E-3740665D2BC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A53458FA-F0D6-4666-9D3E-3740665D2BC3}.Release|Any CPU.Build.0 = Release|Any CPU
{A53458FA-F0D6-4666-9D3E-3740665D2BC3}.Release|x64.ActiveCfg = Release|Any CPU
{A53458FA-F0D6-4666-9D3E-3740665D2BC3}.Release|x64.Build.0 = Release|Any CPU
{A53458FA-F0D6-4666-9D3E-3740665D2BC3}.Release|x86.ActiveCfg = Release|Any CPU
{A53458FA-F0D6-4666-9D3E-3740665D2BC3}.Release|x86.Build.0 = Release|Any CPU
{946AE1A0-0055-4AEC-A4AC-CB8F9F871079}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{946AE1A0-0055-4AEC-A4AC-CB8F9F871079}.Debug|Any CPU.Build.0 = Debug|Any CPU
{946AE1A0-0055-4AEC-A4AC-CB8F9F871079}.Debug|x64.ActiveCfg = Debug|Any CPU
{946AE1A0-0055-4AEC-A4AC-CB8F9F871079}.Debug|x64.Build.0 = Debug|Any CPU
{946AE1A0-0055-4AEC-A4AC-CB8F9F871079}.Debug|x86.ActiveCfg = Debug|Any CPU
{946AE1A0-0055-4AEC-A4AC-CB8F9F871079}.Debug|x86.Build.0 = Debug|Any CPU
{946AE1A0-0055-4AEC-A4AC-CB8F9F871079}.Release|Any CPU.ActiveCfg = Release|Any CPU
{946AE1A0-0055-4AEC-A4AC-CB8F9F871079}.Release|Any CPU.Build.0 = Release|Any CPU
{946AE1A0-0055-4AEC-A4AC-CB8F9F871079}.Release|x64.ActiveCfg = Release|Any CPU
{946AE1A0-0055-4AEC-A4AC-CB8F9F871079}.Release|x64.Build.0 = Release|Any CPU
{946AE1A0-0055-4AEC-A4AC-CB8F9F871079}.Release|x86.ActiveCfg = Release|Any CPU
{946AE1A0-0055-4AEC-A4AC-CB8F9F871079}.Release|x86.Build.0 = Release|Any CPU
{B209DA77-ABD4-49BE-9FE5-769BE44D8391}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B209DA77-ABD4-49BE-9FE5-769BE44D8391}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B209DA77-ABD4-49BE-9FE5-769BE44D8391}.Debug|x64.ActiveCfg = Debug|Any CPU
{B209DA77-ABD4-49BE-9FE5-769BE44D8391}.Debug|x64.Build.0 = Debug|Any CPU
{B209DA77-ABD4-49BE-9FE5-769BE44D8391}.Debug|x86.ActiveCfg = Debug|Any CPU
{B209DA77-ABD4-49BE-9FE5-769BE44D8391}.Debug|x86.Build.0 = Debug|Any CPU
{B209DA77-ABD4-49BE-9FE5-769BE44D8391}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B209DA77-ABD4-49BE-9FE5-769BE44D8391}.Release|Any CPU.Build.0 = Release|Any CPU
{B209DA77-ABD4-49BE-9FE5-769BE44D8391}.Release|x64.ActiveCfg = Release|Any CPU
{B209DA77-ABD4-49BE-9FE5-769BE44D8391}.Release|x64.Build.0 = Release|Any CPU
{B209DA77-ABD4-49BE-9FE5-769BE44D8391}.Release|x86.ActiveCfg = Release|Any CPU
{B209DA77-ABD4-49BE-9FE5-769BE44D8391}.Release|x86.Build.0 = Release|Any CPU
{27619C7F-D02E-4E15-ABFA-8FD59ED23D82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{27619C7F-D02E-4E15-ABFA-8FD59ED23D82}.Debug|Any CPU.Build.0 = Debug|Any CPU
{27619C7F-D02E-4E15-ABFA-8FD59ED23D82}.Debug|x64.ActiveCfg = Debug|Any CPU
{27619C7F-D02E-4E15-ABFA-8FD59ED23D82}.Debug|x64.Build.0 = Debug|Any CPU
{27619C7F-D02E-4E15-ABFA-8FD59ED23D82}.Debug|x86.ActiveCfg = Debug|Any CPU
{27619C7F-D02E-4E15-ABFA-8FD59ED23D82}.Debug|x86.Build.0 = Debug|Any CPU
{27619C7F-D02E-4E15-ABFA-8FD59ED23D82}.Release|Any CPU.ActiveCfg = Release|Any CPU
{27619C7F-D02E-4E15-ABFA-8FD59ED23D82}.Release|Any CPU.Build.0 = Release|Any CPU
{27619C7F-D02E-4E15-ABFA-8FD59ED23D82}.Release|x64.ActiveCfg = Release|Any CPU
{27619C7F-D02E-4E15-ABFA-8FD59ED23D82}.Release|x64.Build.0 = Release|Any CPU
{27619C7F-D02E-4E15-ABFA-8FD59ED23D82}.Release|x86.ActiveCfg = Release|Any CPU
{27619C7F-D02E-4E15-ABFA-8FD59ED23D82}.Release|x86.Build.0 = Release|Any CPU
{D59D021C-A351-4E12-85A7-D7B4D31ED695}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D59D021C-A351-4E12-85A7-D7B4D31ED695}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D59D021C-A351-4E12-85A7-D7B4D31ED695}.Debug|x64.ActiveCfg = Debug|Any CPU
{D59D021C-A351-4E12-85A7-D7B4D31ED695}.Debug|x64.Build.0 = Debug|Any CPU
{D59D021C-A351-4E12-85A7-D7B4D31ED695}.Debug|x86.ActiveCfg = Debug|Any CPU
{D59D021C-A351-4E12-85A7-D7B4D31ED695}.Debug|x86.Build.0 = Debug|Any CPU
{D59D021C-A351-4E12-85A7-D7B4D31ED695}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D59D021C-A351-4E12-85A7-D7B4D31ED695}.Release|Any CPU.Build.0 = Release|Any CPU
{D59D021C-A351-4E12-85A7-D7B4D31ED695}.Release|x64.ActiveCfg = Release|Any CPU
{D59D021C-A351-4E12-85A7-D7B4D31ED695}.Release|x64.Build.0 = Release|Any CPU
{D59D021C-A351-4E12-85A7-D7B4D31ED695}.Release|x86.ActiveCfg = Release|Any CPU
{D59D021C-A351-4E12-85A7-D7B4D31ED695}.Release|x86.Build.0 = Release|Any CPU
{75715051-86CB-472A-BEFA-0E9AAA5EB24F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75715051-86CB-472A-BEFA-0E9AAA5EB24F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75715051-86CB-472A-BEFA-0E9AAA5EB24F}.Debug|x64.ActiveCfg = Debug|Any CPU
{75715051-86CB-472A-BEFA-0E9AAA5EB24F}.Debug|x64.Build.0 = Debug|Any CPU
{75715051-86CB-472A-BEFA-0E9AAA5EB24F}.Debug|x86.ActiveCfg = Debug|Any CPU
{75715051-86CB-472A-BEFA-0E9AAA5EB24F}.Debug|x86.Build.0 = Debug|Any CPU
{75715051-86CB-472A-BEFA-0E9AAA5EB24F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75715051-86CB-472A-BEFA-0E9AAA5EB24F}.Release|Any CPU.Build.0 = Release|Any CPU
{75715051-86CB-472A-BEFA-0E9AAA5EB24F}.Release|x64.ActiveCfg = Release|Any CPU
{75715051-86CB-472A-BEFA-0E9AAA5EB24F}.Release|x64.Build.0 = Release|Any CPU
{75715051-86CB-472A-BEFA-0E9AAA5EB24F}.Release|x86.ActiveCfg = Release|Any CPU
{75715051-86CB-472A-BEFA-0E9AAA5EB24F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{90298CBA-BD6F-3A0A-69D5-97CAD7B05E7E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{CC3A5E56-8FC3-4C75-932A-ADE592F65515} = {90298CBA-BD6F-3A0A-69D5-97CAD7B05E7E}
{981C2CF6-E5BB-602A-511C-234B538302B0} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{1F5F02BF-AD09-41E9-8130-1A939AE5012D} = {981C2CF6-E5BB-602A-511C-234B538302B0}
{EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{0EC62A1A-9858-3A60-8A0C-FC9AACFA2EC7} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{A53458FA-F0D6-4666-9D3E-3740665D2BC3} = {0EC62A1A-9858-3A60-8A0C-FC9AACFA2EC7}
{1F785BA6-BCCC-EB5A-C0AC-017D258EF3FD} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{946AE1A0-0055-4AEC-A4AC-CB8F9F871079} = {1F785BA6-BCCC-EB5A-C0AC-017D258EF3FD}
{1E1EF545-4C18-AEE2-B997-7EF431518D3F} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{B209DA77-ABD4-49BE-9FE5-769BE44D8391} = {1E1EF545-4C18-AEE2-B997-7EF431518D3F}
{2C821288-5B9D-778E-74EE-053FE87F223E} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{27619C7F-D02E-4E15-ABFA-8FD59ED23D82} = {2C821288-5B9D-778E-74EE-053FE87F223E}
{0D6C7A58-55D3-AEBB-0CCB-984ED0F92F71} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{D59D021C-A351-4E12-85A7-D7B4D31ED695} = {0D6C7A58-55D3-AEBB-0CCB-984ED0F92F71}
{305CF822-D9CA-C4BE-7183-DD5447E36C7E} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{75715051-86CB-472A-BEFA-0E9AAA5EB24F} = {305CF822-D9CA-C4BE-7183-DD5447E36C7E}
EndGlobalSection
EndGlobal
+564
View File
@@ -0,0 +1,564 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildingBlocks", "BuildingBlocks", "{7266C4A9-E384-DC00-0EB6-47F6002CFBA7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.BuildingBlocks", "src\BuildingBlocks\Nashel.BuildingBlocks.csproj", "{18F5CA61-2478-40DB-A8C3-3CA10BA66F4A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Host", "Host", "{7226ABEA-787E-1D09-10EA-A2ECF996D6A6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Host", "src\Host\Nashel.Host.csproj", "{4D89232D-7F93-4DA5-9B00-E25A355561EF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Catalog.Infrastructure", "src\Modules\Catalog\Infrastructure\Nashel.Modules.Catalog.Infrastructure.csproj", "{F2C3F1F1-328F-4EA9-BC82-0AF252375E2A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Catalog.Application", "src\Modules\Catalog\Application\Nashel.Modules.Catalog.Application.csproj", "{C47D5462-DA34-4A5E-BCBF-642D702E1ECA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Catalog.Domain", "src\Modules\Catalog\Domain\Nashel.Modules.Catalog.Domain.csproj", "{4C3D1645-4D40-4313-ACB6-079FDD8AA1D2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Catalog.Presentation", "src\Modules\Catalog\Presentation\Nashel.Modules.Catalog.Presentation.csproj", "{937B457D-1C2A-41E3-B7BD-CDFA827E4F76}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Collaboration.Infrastructure", "src\Modules\Collaboration\Infrastructure\Nashel.Modules.Collaboration.Infrastructure.csproj", "{6A825C22-139D-4927-AA08-F9130D4F6845}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Collaboration.Application", "src\Modules\Collaboration\Application\Nashel.Modules.Collaboration.Application.csproj", "{8043FAEB-5AAE-4DF5-9A3F-EF0E3CA6DC89}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Collaboration.Domain", "src\Modules\Collaboration\Domain\Nashel.Modules.Collaboration.Domain.csproj", "{397ADF05-2B2A-497D-A94C-9C40C8C7F7AF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Collaboration.Presentation", "src\Modules\Collaboration\Presentation\Nashel.Modules.Collaboration.Presentation.csproj", "{01138FAD-D215-45D4-9D52-6A5875516557}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Geo.Infrastructure", "src\Modules\Geo\Infrastructure\Nashel.Modules.Geo.Infrastructure.csproj", "{15A81ACF-D93F-4039-9F67-8D3A96F57756}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Geo.Application", "src\Modules\Geo\Application\Nashel.Modules.Geo.Application.csproj", "{04D91154-1813-4666-93FD-01644023C766}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Geo.Domain", "src\Modules\Geo\Domain\Nashel.Modules.Geo.Domain.csproj", "{0A09480D-13BC-4C4A-863B-EB5A110F4436}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Geo.Presentation", "src\Modules\Geo\Presentation\Nashel.Modules.Geo.Presentation.csproj", "{059BD6B6-39E5-4558-85F7-2E35FEEA36D4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Identity.Infrastructure", "src\Modules\Identity\Infrastructure\Nashel.Modules.Identity.Infrastructure.csproj", "{33DFFF98-2E80-421C-B0A5-02C7C329B5C1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Identity.Application", "src\Modules\Identity\Application\Nashel.Modules.Identity.Application.csproj", "{88BE5612-F8D3-4E22-A1CE-53520F5B8D13}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Identity.Domain", "src\Modules\Identity\Domain\Nashel.Modules.Identity.Domain.csproj", "{733FC595-BBEF-4B1B-81A8-F3A952820250}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Identity.Presentation", "src\Modules\Identity\Presentation\Nashel.Modules.Identity.Presentation.csproj", "{9458707B-D600-4F2C-9FD9-9195F9EC69DC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Order.Infrastructure", "src\Modules\Order\Infrastructure\Nashel.Modules.Order.Infrastructure.csproj", "{996763EA-95DD-4BC7-8782-19DD630E4F75}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Order.Application", "src\Modules\Order\Application\Nashel.Modules.Order.Application.csproj", "{F9CD05EC-1FFF-4DCC-8806-2FDD9FAD3BBE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Order.Domain", "src\Modules\Order\Domain\Nashel.Modules.Order.Domain.csproj", "{A02E0244-9E52-4DCD-AF42-6FC90E03C4E1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Order.Presentation", "src\Modules\Order\Presentation\Nashel.Modules.Order.Presentation.csproj", "{5FC793DE-0C52-432C-95A9-174919C3E57C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Reputation.Infrastructure", "src\Modules\Reputation\Infrastructure\Nashel.Modules.Reputation.Infrastructure.csproj", "{6B7E10A1-CE95-4D71-A657-7303FEE7B52A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Reputation.Application", "src\Modules\Reputation\Application\Nashel.Modules.Reputation.Application.csproj", "{C6D6EB9D-D40E-4839-8F78-1024ED4964E1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Reputation.Domain", "src\Modules\Reputation\Domain\Nashel.Modules.Reputation.Domain.csproj", "{2010E82F-B42C-491F-AFD7-4486D4845737}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Reputation.Presentation", "src\Modules\Reputation\Presentation\Nashel.Modules.Reputation.Presentation.csproj", "{EEC2B03A-3172-4EC1-B7E3-8687E436072D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Identity.Tests", "src\Modules\Identity\Tests\Nashel.Modules.Identity.Tests.csproj", "{401E3B50-A6B8-4AA1-9DD3-41F9EC78FABD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Order", "Order", "{2C821288-5B9D-778E-74EE-053FE87F223E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{FB27511E-CBA6-E343-8CE0-54AAD98AFC9F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Order.Tests", "src\Modules\Order\Tests\Nashel.Modules.Order.Tests.csproj", "{DC1E99E0-6B92-4FBD-B74B-68D202961FB7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Reputation", "Reputation", "{305CF822-D9CA-C4BE-7183-DD5447E36C7E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{6FECF8C1-5BB0-731D-B0FE-7CF5A5513390}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Reputation.Tests", "src\Modules\Reputation\Tests\Nashel.Modules.Reputation.Tests.csproj", "{B496E978-09C5-4EFA-9A2A-6984A3F89F77}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Host.Tests", "tests\Nashel.Host.Tests\Nashel.Host.Tests.csproj", "{087C0E53-90E0-415A-A394-6E31E12EF7BF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Catalog", "Catalog", "{1E1EF545-4C18-AEE2-B997-7EF431518D3F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{77959D31-1EA8-27CA-126F-40C5E927F925}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Catalog.Tests", "src\Modules\Catalog\Tests\Nashel.Modules.Catalog.Tests.csproj", "{2C29F7A5-8927-4490-BC7A-50B6F5442EEF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Geo", "Geo", "{1F785BA6-BCCC-EB5A-C0AC-017D258EF3FD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{9CB3DAB5-483B-9BA7-DF84-97D903318F73}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Geo.Tests", "src\Modules\Geo\Tests\Nashel.Modules.Geo.Tests.csproj", "{06992B63-6D24-4785-8A7D-138E9AC6D38E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Collaboration", "Collaboration", "{0D6C7A58-55D3-AEBB-0CCB-984ED0F92F71}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{04D8AB37-CCCF-08EA-5F4C-67D97B5A3FDE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nashel.Modules.Collaboration.Tests", "src\Modules\Collaboration\Tests\Nashel.Modules.Collaboration.Tests.csproj", "{4786B22D-13B2-4CC5-8738-EAC9121EF4F0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Domain", "Domain", "{F7A07CC8-D014-3EFC-E9E0-4BA33974CD0E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{9B135FC8-DBD1-6BD7-B3C5-FE40E7BAF0CB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{18F5CA61-2478-40DB-A8C3-3CA10BA66F4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{18F5CA61-2478-40DB-A8C3-3CA10BA66F4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{18F5CA61-2478-40DB-A8C3-3CA10BA66F4A}.Debug|x64.ActiveCfg = Debug|Any CPU
{18F5CA61-2478-40DB-A8C3-3CA10BA66F4A}.Debug|x64.Build.0 = Debug|Any CPU
{18F5CA61-2478-40DB-A8C3-3CA10BA66F4A}.Debug|x86.ActiveCfg = Debug|Any CPU
{18F5CA61-2478-40DB-A8C3-3CA10BA66F4A}.Debug|x86.Build.0 = Debug|Any CPU
{18F5CA61-2478-40DB-A8C3-3CA10BA66F4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{18F5CA61-2478-40DB-A8C3-3CA10BA66F4A}.Release|Any CPU.Build.0 = Release|Any CPU
{18F5CA61-2478-40DB-A8C3-3CA10BA66F4A}.Release|x64.ActiveCfg = Release|Any CPU
{18F5CA61-2478-40DB-A8C3-3CA10BA66F4A}.Release|x64.Build.0 = Release|Any CPU
{18F5CA61-2478-40DB-A8C3-3CA10BA66F4A}.Release|x86.ActiveCfg = Release|Any CPU
{18F5CA61-2478-40DB-A8C3-3CA10BA66F4A}.Release|x86.Build.0 = Release|Any CPU
{4D89232D-7F93-4DA5-9B00-E25A355561EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4D89232D-7F93-4DA5-9B00-E25A355561EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4D89232D-7F93-4DA5-9B00-E25A355561EF}.Debug|x64.ActiveCfg = Debug|Any CPU
{4D89232D-7F93-4DA5-9B00-E25A355561EF}.Debug|x64.Build.0 = Debug|Any CPU
{4D89232D-7F93-4DA5-9B00-E25A355561EF}.Debug|x86.ActiveCfg = Debug|Any CPU
{4D89232D-7F93-4DA5-9B00-E25A355561EF}.Debug|x86.Build.0 = Debug|Any CPU
{4D89232D-7F93-4DA5-9B00-E25A355561EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4D89232D-7F93-4DA5-9B00-E25A355561EF}.Release|Any CPU.Build.0 = Release|Any CPU
{4D89232D-7F93-4DA5-9B00-E25A355561EF}.Release|x64.ActiveCfg = Release|Any CPU
{4D89232D-7F93-4DA5-9B00-E25A355561EF}.Release|x64.Build.0 = Release|Any CPU
{4D89232D-7F93-4DA5-9B00-E25A355561EF}.Release|x86.ActiveCfg = Release|Any CPU
{4D89232D-7F93-4DA5-9B00-E25A355561EF}.Release|x86.Build.0 = Release|Any CPU
{F2C3F1F1-328F-4EA9-BC82-0AF252375E2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2C3F1F1-328F-4EA9-BC82-0AF252375E2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2C3F1F1-328F-4EA9-BC82-0AF252375E2A}.Debug|x64.ActiveCfg = Debug|Any CPU
{F2C3F1F1-328F-4EA9-BC82-0AF252375E2A}.Debug|x64.Build.0 = Debug|Any CPU
{F2C3F1F1-328F-4EA9-BC82-0AF252375E2A}.Debug|x86.ActiveCfg = Debug|Any CPU
{F2C3F1F1-328F-4EA9-BC82-0AF252375E2A}.Debug|x86.Build.0 = Debug|Any CPU
{F2C3F1F1-328F-4EA9-BC82-0AF252375E2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2C3F1F1-328F-4EA9-BC82-0AF252375E2A}.Release|Any CPU.Build.0 = Release|Any CPU
{F2C3F1F1-328F-4EA9-BC82-0AF252375E2A}.Release|x64.ActiveCfg = Release|Any CPU
{F2C3F1F1-328F-4EA9-BC82-0AF252375E2A}.Release|x64.Build.0 = Release|Any CPU
{F2C3F1F1-328F-4EA9-BC82-0AF252375E2A}.Release|x86.ActiveCfg = Release|Any CPU
{F2C3F1F1-328F-4EA9-BC82-0AF252375E2A}.Release|x86.Build.0 = Release|Any CPU
{C47D5462-DA34-4A5E-BCBF-642D702E1ECA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C47D5462-DA34-4A5E-BCBF-642D702E1ECA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C47D5462-DA34-4A5E-BCBF-642D702E1ECA}.Debug|x64.ActiveCfg = Debug|Any CPU
{C47D5462-DA34-4A5E-BCBF-642D702E1ECA}.Debug|x64.Build.0 = Debug|Any CPU
{C47D5462-DA34-4A5E-BCBF-642D702E1ECA}.Debug|x86.ActiveCfg = Debug|Any CPU
{C47D5462-DA34-4A5E-BCBF-642D702E1ECA}.Debug|x86.Build.0 = Debug|Any CPU
{C47D5462-DA34-4A5E-BCBF-642D702E1ECA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C47D5462-DA34-4A5E-BCBF-642D702E1ECA}.Release|Any CPU.Build.0 = Release|Any CPU
{C47D5462-DA34-4A5E-BCBF-642D702E1ECA}.Release|x64.ActiveCfg = Release|Any CPU
{C47D5462-DA34-4A5E-BCBF-642D702E1ECA}.Release|x64.Build.0 = Release|Any CPU
{C47D5462-DA34-4A5E-BCBF-642D702E1ECA}.Release|x86.ActiveCfg = Release|Any CPU
{C47D5462-DA34-4A5E-BCBF-642D702E1ECA}.Release|x86.Build.0 = Release|Any CPU
{4C3D1645-4D40-4313-ACB6-079FDD8AA1D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4C3D1645-4D40-4313-ACB6-079FDD8AA1D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C3D1645-4D40-4313-ACB6-079FDD8AA1D2}.Debug|x64.ActiveCfg = Debug|Any CPU
{4C3D1645-4D40-4313-ACB6-079FDD8AA1D2}.Debug|x64.Build.0 = Debug|Any CPU
{4C3D1645-4D40-4313-ACB6-079FDD8AA1D2}.Debug|x86.ActiveCfg = Debug|Any CPU
{4C3D1645-4D40-4313-ACB6-079FDD8AA1D2}.Debug|x86.Build.0 = Debug|Any CPU
{4C3D1645-4D40-4313-ACB6-079FDD8AA1D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4C3D1645-4D40-4313-ACB6-079FDD8AA1D2}.Release|Any CPU.Build.0 = Release|Any CPU
{4C3D1645-4D40-4313-ACB6-079FDD8AA1D2}.Release|x64.ActiveCfg = Release|Any CPU
{4C3D1645-4D40-4313-ACB6-079FDD8AA1D2}.Release|x64.Build.0 = Release|Any CPU
{4C3D1645-4D40-4313-ACB6-079FDD8AA1D2}.Release|x86.ActiveCfg = Release|Any CPU
{4C3D1645-4D40-4313-ACB6-079FDD8AA1D2}.Release|x86.Build.0 = Release|Any CPU
{937B457D-1C2A-41E3-B7BD-CDFA827E4F76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{937B457D-1C2A-41E3-B7BD-CDFA827E4F76}.Debug|Any CPU.Build.0 = Debug|Any CPU
{937B457D-1C2A-41E3-B7BD-CDFA827E4F76}.Debug|x64.ActiveCfg = Debug|Any CPU
{937B457D-1C2A-41E3-B7BD-CDFA827E4F76}.Debug|x64.Build.0 = Debug|Any CPU
{937B457D-1C2A-41E3-B7BD-CDFA827E4F76}.Debug|x86.ActiveCfg = Debug|Any CPU
{937B457D-1C2A-41E3-B7BD-CDFA827E4F76}.Debug|x86.Build.0 = Debug|Any CPU
{937B457D-1C2A-41E3-B7BD-CDFA827E4F76}.Release|Any CPU.ActiveCfg = Release|Any CPU
{937B457D-1C2A-41E3-B7BD-CDFA827E4F76}.Release|Any CPU.Build.0 = Release|Any CPU
{937B457D-1C2A-41E3-B7BD-CDFA827E4F76}.Release|x64.ActiveCfg = Release|Any CPU
{937B457D-1C2A-41E3-B7BD-CDFA827E4F76}.Release|x64.Build.0 = Release|Any CPU
{937B457D-1C2A-41E3-B7BD-CDFA827E4F76}.Release|x86.ActiveCfg = Release|Any CPU
{937B457D-1C2A-41E3-B7BD-CDFA827E4F76}.Release|x86.Build.0 = Release|Any CPU
{6A825C22-139D-4927-AA08-F9130D4F6845}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6A825C22-139D-4927-AA08-F9130D4F6845}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6A825C22-139D-4927-AA08-F9130D4F6845}.Debug|x64.ActiveCfg = Debug|Any CPU
{6A825C22-139D-4927-AA08-F9130D4F6845}.Debug|x64.Build.0 = Debug|Any CPU
{6A825C22-139D-4927-AA08-F9130D4F6845}.Debug|x86.ActiveCfg = Debug|Any CPU
{6A825C22-139D-4927-AA08-F9130D4F6845}.Debug|x86.Build.0 = Debug|Any CPU
{6A825C22-139D-4927-AA08-F9130D4F6845}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6A825C22-139D-4927-AA08-F9130D4F6845}.Release|Any CPU.Build.0 = Release|Any CPU
{6A825C22-139D-4927-AA08-F9130D4F6845}.Release|x64.ActiveCfg = Release|Any CPU
{6A825C22-139D-4927-AA08-F9130D4F6845}.Release|x64.Build.0 = Release|Any CPU
{6A825C22-139D-4927-AA08-F9130D4F6845}.Release|x86.ActiveCfg = Release|Any CPU
{6A825C22-139D-4927-AA08-F9130D4F6845}.Release|x86.Build.0 = Release|Any CPU
{8043FAEB-5AAE-4DF5-9A3F-EF0E3CA6DC89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8043FAEB-5AAE-4DF5-9A3F-EF0E3CA6DC89}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8043FAEB-5AAE-4DF5-9A3F-EF0E3CA6DC89}.Debug|x64.ActiveCfg = Debug|Any CPU
{8043FAEB-5AAE-4DF5-9A3F-EF0E3CA6DC89}.Debug|x64.Build.0 = Debug|Any CPU
{8043FAEB-5AAE-4DF5-9A3F-EF0E3CA6DC89}.Debug|x86.ActiveCfg = Debug|Any CPU
{8043FAEB-5AAE-4DF5-9A3F-EF0E3CA6DC89}.Debug|x86.Build.0 = Debug|Any CPU
{8043FAEB-5AAE-4DF5-9A3F-EF0E3CA6DC89}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8043FAEB-5AAE-4DF5-9A3F-EF0E3CA6DC89}.Release|Any CPU.Build.0 = Release|Any CPU
{8043FAEB-5AAE-4DF5-9A3F-EF0E3CA6DC89}.Release|x64.ActiveCfg = Release|Any CPU
{8043FAEB-5AAE-4DF5-9A3F-EF0E3CA6DC89}.Release|x64.Build.0 = Release|Any CPU
{8043FAEB-5AAE-4DF5-9A3F-EF0E3CA6DC89}.Release|x86.ActiveCfg = Release|Any CPU
{8043FAEB-5AAE-4DF5-9A3F-EF0E3CA6DC89}.Release|x86.Build.0 = Release|Any CPU
{397ADF05-2B2A-497D-A94C-9C40C8C7F7AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{397ADF05-2B2A-497D-A94C-9C40C8C7F7AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{397ADF05-2B2A-497D-A94C-9C40C8C7F7AF}.Debug|x64.ActiveCfg = Debug|Any CPU
{397ADF05-2B2A-497D-A94C-9C40C8C7F7AF}.Debug|x64.Build.0 = Debug|Any CPU
{397ADF05-2B2A-497D-A94C-9C40C8C7F7AF}.Debug|x86.ActiveCfg = Debug|Any CPU
{397ADF05-2B2A-497D-A94C-9C40C8C7F7AF}.Debug|x86.Build.0 = Debug|Any CPU
{397ADF05-2B2A-497D-A94C-9C40C8C7F7AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{397ADF05-2B2A-497D-A94C-9C40C8C7F7AF}.Release|Any CPU.Build.0 = Release|Any CPU
{397ADF05-2B2A-497D-A94C-9C40C8C7F7AF}.Release|x64.ActiveCfg = Release|Any CPU
{397ADF05-2B2A-497D-A94C-9C40C8C7F7AF}.Release|x64.Build.0 = Release|Any CPU
{397ADF05-2B2A-497D-A94C-9C40C8C7F7AF}.Release|x86.ActiveCfg = Release|Any CPU
{397ADF05-2B2A-497D-A94C-9C40C8C7F7AF}.Release|x86.Build.0 = Release|Any CPU
{01138FAD-D215-45D4-9D52-6A5875516557}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{01138FAD-D215-45D4-9D52-6A5875516557}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01138FAD-D215-45D4-9D52-6A5875516557}.Debug|x64.ActiveCfg = Debug|Any CPU
{01138FAD-D215-45D4-9D52-6A5875516557}.Debug|x64.Build.0 = Debug|Any CPU
{01138FAD-D215-45D4-9D52-6A5875516557}.Debug|x86.ActiveCfg = Debug|Any CPU
{01138FAD-D215-45D4-9D52-6A5875516557}.Debug|x86.Build.0 = Debug|Any CPU
{01138FAD-D215-45D4-9D52-6A5875516557}.Release|Any CPU.ActiveCfg = Release|Any CPU
{01138FAD-D215-45D4-9D52-6A5875516557}.Release|Any CPU.Build.0 = Release|Any CPU
{01138FAD-D215-45D4-9D52-6A5875516557}.Release|x64.ActiveCfg = Release|Any CPU
{01138FAD-D215-45D4-9D52-6A5875516557}.Release|x64.Build.0 = Release|Any CPU
{01138FAD-D215-45D4-9D52-6A5875516557}.Release|x86.ActiveCfg = Release|Any CPU
{01138FAD-D215-45D4-9D52-6A5875516557}.Release|x86.Build.0 = Release|Any CPU
{15A81ACF-D93F-4039-9F67-8D3A96F57756}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{15A81ACF-D93F-4039-9F67-8D3A96F57756}.Debug|Any CPU.Build.0 = Debug|Any CPU
{15A81ACF-D93F-4039-9F67-8D3A96F57756}.Debug|x64.ActiveCfg = Debug|Any CPU
{15A81ACF-D93F-4039-9F67-8D3A96F57756}.Debug|x64.Build.0 = Debug|Any CPU
{15A81ACF-D93F-4039-9F67-8D3A96F57756}.Debug|x86.ActiveCfg = Debug|Any CPU
{15A81ACF-D93F-4039-9F67-8D3A96F57756}.Debug|x86.Build.0 = Debug|Any CPU
{15A81ACF-D93F-4039-9F67-8D3A96F57756}.Release|Any CPU.ActiveCfg = Release|Any CPU
{15A81ACF-D93F-4039-9F67-8D3A96F57756}.Release|Any CPU.Build.0 = Release|Any CPU
{15A81ACF-D93F-4039-9F67-8D3A96F57756}.Release|x64.ActiveCfg = Release|Any CPU
{15A81ACF-D93F-4039-9F67-8D3A96F57756}.Release|x64.Build.0 = Release|Any CPU
{15A81ACF-D93F-4039-9F67-8D3A96F57756}.Release|x86.ActiveCfg = Release|Any CPU
{15A81ACF-D93F-4039-9F67-8D3A96F57756}.Release|x86.Build.0 = Release|Any CPU
{04D91154-1813-4666-93FD-01644023C766}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{04D91154-1813-4666-93FD-01644023C766}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04D91154-1813-4666-93FD-01644023C766}.Debug|x64.ActiveCfg = Debug|Any CPU
{04D91154-1813-4666-93FD-01644023C766}.Debug|x64.Build.0 = Debug|Any CPU
{04D91154-1813-4666-93FD-01644023C766}.Debug|x86.ActiveCfg = Debug|Any CPU
{04D91154-1813-4666-93FD-01644023C766}.Debug|x86.Build.0 = Debug|Any CPU
{04D91154-1813-4666-93FD-01644023C766}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04D91154-1813-4666-93FD-01644023C766}.Release|Any CPU.Build.0 = Release|Any CPU
{04D91154-1813-4666-93FD-01644023C766}.Release|x64.ActiveCfg = Release|Any CPU
{04D91154-1813-4666-93FD-01644023C766}.Release|x64.Build.0 = Release|Any CPU
{04D91154-1813-4666-93FD-01644023C766}.Release|x86.ActiveCfg = Release|Any CPU
{04D91154-1813-4666-93FD-01644023C766}.Release|x86.Build.0 = Release|Any CPU
{0A09480D-13BC-4C4A-863B-EB5A110F4436}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0A09480D-13BC-4C4A-863B-EB5A110F4436}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A09480D-13BC-4C4A-863B-EB5A110F4436}.Debug|x64.ActiveCfg = Debug|Any CPU
{0A09480D-13BC-4C4A-863B-EB5A110F4436}.Debug|x64.Build.0 = Debug|Any CPU
{0A09480D-13BC-4C4A-863B-EB5A110F4436}.Debug|x86.ActiveCfg = Debug|Any CPU
{0A09480D-13BC-4C4A-863B-EB5A110F4436}.Debug|x86.Build.0 = Debug|Any CPU
{0A09480D-13BC-4C4A-863B-EB5A110F4436}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A09480D-13BC-4C4A-863B-EB5A110F4436}.Release|Any CPU.Build.0 = Release|Any CPU
{0A09480D-13BC-4C4A-863B-EB5A110F4436}.Release|x64.ActiveCfg = Release|Any CPU
{0A09480D-13BC-4C4A-863B-EB5A110F4436}.Release|x64.Build.0 = Release|Any CPU
{0A09480D-13BC-4C4A-863B-EB5A110F4436}.Release|x86.ActiveCfg = Release|Any CPU
{0A09480D-13BC-4C4A-863B-EB5A110F4436}.Release|x86.Build.0 = Release|Any CPU
{059BD6B6-39E5-4558-85F7-2E35FEEA36D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{059BD6B6-39E5-4558-85F7-2E35FEEA36D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{059BD6B6-39E5-4558-85F7-2E35FEEA36D4}.Debug|x64.ActiveCfg = Debug|Any CPU
{059BD6B6-39E5-4558-85F7-2E35FEEA36D4}.Debug|x64.Build.0 = Debug|Any CPU
{059BD6B6-39E5-4558-85F7-2E35FEEA36D4}.Debug|x86.ActiveCfg = Debug|Any CPU
{059BD6B6-39E5-4558-85F7-2E35FEEA36D4}.Debug|x86.Build.0 = Debug|Any CPU
{059BD6B6-39E5-4558-85F7-2E35FEEA36D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{059BD6B6-39E5-4558-85F7-2E35FEEA36D4}.Release|Any CPU.Build.0 = Release|Any CPU
{059BD6B6-39E5-4558-85F7-2E35FEEA36D4}.Release|x64.ActiveCfg = Release|Any CPU
{059BD6B6-39E5-4558-85F7-2E35FEEA36D4}.Release|x64.Build.0 = Release|Any CPU
{059BD6B6-39E5-4558-85F7-2E35FEEA36D4}.Release|x86.ActiveCfg = Release|Any CPU
{059BD6B6-39E5-4558-85F7-2E35FEEA36D4}.Release|x86.Build.0 = Release|Any CPU
{33DFFF98-2E80-421C-B0A5-02C7C329B5C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{33DFFF98-2E80-421C-B0A5-02C7C329B5C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{33DFFF98-2E80-421C-B0A5-02C7C329B5C1}.Debug|x64.ActiveCfg = Debug|Any CPU
{33DFFF98-2E80-421C-B0A5-02C7C329B5C1}.Debug|x64.Build.0 = Debug|Any CPU
{33DFFF98-2E80-421C-B0A5-02C7C329B5C1}.Debug|x86.ActiveCfg = Debug|Any CPU
{33DFFF98-2E80-421C-B0A5-02C7C329B5C1}.Debug|x86.Build.0 = Debug|Any CPU
{33DFFF98-2E80-421C-B0A5-02C7C329B5C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{33DFFF98-2E80-421C-B0A5-02C7C329B5C1}.Release|Any CPU.Build.0 = Release|Any CPU
{33DFFF98-2E80-421C-B0A5-02C7C329B5C1}.Release|x64.ActiveCfg = Release|Any CPU
{33DFFF98-2E80-421C-B0A5-02C7C329B5C1}.Release|x64.Build.0 = Release|Any CPU
{33DFFF98-2E80-421C-B0A5-02C7C329B5C1}.Release|x86.ActiveCfg = Release|Any CPU
{33DFFF98-2E80-421C-B0A5-02C7C329B5C1}.Release|x86.Build.0 = Release|Any CPU
{88BE5612-F8D3-4E22-A1CE-53520F5B8D13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{88BE5612-F8D3-4E22-A1CE-53520F5B8D13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{88BE5612-F8D3-4E22-A1CE-53520F5B8D13}.Debug|x64.ActiveCfg = Debug|Any CPU
{88BE5612-F8D3-4E22-A1CE-53520F5B8D13}.Debug|x64.Build.0 = Debug|Any CPU
{88BE5612-F8D3-4E22-A1CE-53520F5B8D13}.Debug|x86.ActiveCfg = Debug|Any CPU
{88BE5612-F8D3-4E22-A1CE-53520F5B8D13}.Debug|x86.Build.0 = Debug|Any CPU
{88BE5612-F8D3-4E22-A1CE-53520F5B8D13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{88BE5612-F8D3-4E22-A1CE-53520F5B8D13}.Release|Any CPU.Build.0 = Release|Any CPU
{88BE5612-F8D3-4E22-A1CE-53520F5B8D13}.Release|x64.ActiveCfg = Release|Any CPU
{88BE5612-F8D3-4E22-A1CE-53520F5B8D13}.Release|x64.Build.0 = Release|Any CPU
{88BE5612-F8D3-4E22-A1CE-53520F5B8D13}.Release|x86.ActiveCfg = Release|Any CPU
{88BE5612-F8D3-4E22-A1CE-53520F5B8D13}.Release|x86.Build.0 = Release|Any CPU
{733FC595-BBEF-4B1B-81A8-F3A952820250}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{733FC595-BBEF-4B1B-81A8-F3A952820250}.Debug|Any CPU.Build.0 = Debug|Any CPU
{733FC595-BBEF-4B1B-81A8-F3A952820250}.Debug|x64.ActiveCfg = Debug|Any CPU
{733FC595-BBEF-4B1B-81A8-F3A952820250}.Debug|x64.Build.0 = Debug|Any CPU
{733FC595-BBEF-4B1B-81A8-F3A952820250}.Debug|x86.ActiveCfg = Debug|Any CPU
{733FC595-BBEF-4B1B-81A8-F3A952820250}.Debug|x86.Build.0 = Debug|Any CPU
{733FC595-BBEF-4B1B-81A8-F3A952820250}.Release|Any CPU.ActiveCfg = Release|Any CPU
{733FC595-BBEF-4B1B-81A8-F3A952820250}.Release|Any CPU.Build.0 = Release|Any CPU
{733FC595-BBEF-4B1B-81A8-F3A952820250}.Release|x64.ActiveCfg = Release|Any CPU
{733FC595-BBEF-4B1B-81A8-F3A952820250}.Release|x64.Build.0 = Release|Any CPU
{733FC595-BBEF-4B1B-81A8-F3A952820250}.Release|x86.ActiveCfg = Release|Any CPU
{733FC595-BBEF-4B1B-81A8-F3A952820250}.Release|x86.Build.0 = Release|Any CPU
{9458707B-D600-4F2C-9FD9-9195F9EC69DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9458707B-D600-4F2C-9FD9-9195F9EC69DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9458707B-D600-4F2C-9FD9-9195F9EC69DC}.Debug|x64.ActiveCfg = Debug|Any CPU
{9458707B-D600-4F2C-9FD9-9195F9EC69DC}.Debug|x64.Build.0 = Debug|Any CPU
{9458707B-D600-4F2C-9FD9-9195F9EC69DC}.Debug|x86.ActiveCfg = Debug|Any CPU
{9458707B-D600-4F2C-9FD9-9195F9EC69DC}.Debug|x86.Build.0 = Debug|Any CPU
{9458707B-D600-4F2C-9FD9-9195F9EC69DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9458707B-D600-4F2C-9FD9-9195F9EC69DC}.Release|Any CPU.Build.0 = Release|Any CPU
{9458707B-D600-4F2C-9FD9-9195F9EC69DC}.Release|x64.ActiveCfg = Release|Any CPU
{9458707B-D600-4F2C-9FD9-9195F9EC69DC}.Release|x64.Build.0 = Release|Any CPU
{9458707B-D600-4F2C-9FD9-9195F9EC69DC}.Release|x86.ActiveCfg = Release|Any CPU
{9458707B-D600-4F2C-9FD9-9195F9EC69DC}.Release|x86.Build.0 = Release|Any CPU
{996763EA-95DD-4BC7-8782-19DD630E4F75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{996763EA-95DD-4BC7-8782-19DD630E4F75}.Debug|Any CPU.Build.0 = Debug|Any CPU
{996763EA-95DD-4BC7-8782-19DD630E4F75}.Debug|x64.ActiveCfg = Debug|Any CPU
{996763EA-95DD-4BC7-8782-19DD630E4F75}.Debug|x64.Build.0 = Debug|Any CPU
{996763EA-95DD-4BC7-8782-19DD630E4F75}.Debug|x86.ActiveCfg = Debug|Any CPU
{996763EA-95DD-4BC7-8782-19DD630E4F75}.Debug|x86.Build.0 = Debug|Any CPU
{996763EA-95DD-4BC7-8782-19DD630E4F75}.Release|Any CPU.ActiveCfg = Release|Any CPU
{996763EA-95DD-4BC7-8782-19DD630E4F75}.Release|Any CPU.Build.0 = Release|Any CPU
{996763EA-95DD-4BC7-8782-19DD630E4F75}.Release|x64.ActiveCfg = Release|Any CPU
{996763EA-95DD-4BC7-8782-19DD630E4F75}.Release|x64.Build.0 = Release|Any CPU
{996763EA-95DD-4BC7-8782-19DD630E4F75}.Release|x86.ActiveCfg = Release|Any CPU
{996763EA-95DD-4BC7-8782-19DD630E4F75}.Release|x86.Build.0 = Release|Any CPU
{F9CD05EC-1FFF-4DCC-8806-2FDD9FAD3BBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F9CD05EC-1FFF-4DCC-8806-2FDD9FAD3BBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F9CD05EC-1FFF-4DCC-8806-2FDD9FAD3BBE}.Debug|x64.ActiveCfg = Debug|Any CPU
{F9CD05EC-1FFF-4DCC-8806-2FDD9FAD3BBE}.Debug|x64.Build.0 = Debug|Any CPU
{F9CD05EC-1FFF-4DCC-8806-2FDD9FAD3BBE}.Debug|x86.ActiveCfg = Debug|Any CPU
{F9CD05EC-1FFF-4DCC-8806-2FDD9FAD3BBE}.Debug|x86.Build.0 = Debug|Any CPU
{F9CD05EC-1FFF-4DCC-8806-2FDD9FAD3BBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F9CD05EC-1FFF-4DCC-8806-2FDD9FAD3BBE}.Release|Any CPU.Build.0 = Release|Any CPU
{F9CD05EC-1FFF-4DCC-8806-2FDD9FAD3BBE}.Release|x64.ActiveCfg = Release|Any CPU
{F9CD05EC-1FFF-4DCC-8806-2FDD9FAD3BBE}.Release|x64.Build.0 = Release|Any CPU
{F9CD05EC-1FFF-4DCC-8806-2FDD9FAD3BBE}.Release|x86.ActiveCfg = Release|Any CPU
{F9CD05EC-1FFF-4DCC-8806-2FDD9FAD3BBE}.Release|x86.Build.0 = Release|Any CPU
{A02E0244-9E52-4DCD-AF42-6FC90E03C4E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A02E0244-9E52-4DCD-AF42-6FC90E03C4E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A02E0244-9E52-4DCD-AF42-6FC90E03C4E1}.Debug|x64.ActiveCfg = Debug|Any CPU
{A02E0244-9E52-4DCD-AF42-6FC90E03C4E1}.Debug|x64.Build.0 = Debug|Any CPU
{A02E0244-9E52-4DCD-AF42-6FC90E03C4E1}.Debug|x86.ActiveCfg = Debug|Any CPU
{A02E0244-9E52-4DCD-AF42-6FC90E03C4E1}.Debug|x86.Build.0 = Debug|Any CPU
{A02E0244-9E52-4DCD-AF42-6FC90E03C4E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A02E0244-9E52-4DCD-AF42-6FC90E03C4E1}.Release|Any CPU.Build.0 = Release|Any CPU
{A02E0244-9E52-4DCD-AF42-6FC90E03C4E1}.Release|x64.ActiveCfg = Release|Any CPU
{A02E0244-9E52-4DCD-AF42-6FC90E03C4E1}.Release|x64.Build.0 = Release|Any CPU
{A02E0244-9E52-4DCD-AF42-6FC90E03C4E1}.Release|x86.ActiveCfg = Release|Any CPU
{A02E0244-9E52-4DCD-AF42-6FC90E03C4E1}.Release|x86.Build.0 = Release|Any CPU
{5FC793DE-0C52-432C-95A9-174919C3E57C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5FC793DE-0C52-432C-95A9-174919C3E57C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FC793DE-0C52-432C-95A9-174919C3E57C}.Debug|x64.ActiveCfg = Debug|Any CPU
{5FC793DE-0C52-432C-95A9-174919C3E57C}.Debug|x64.Build.0 = Debug|Any CPU
{5FC793DE-0C52-432C-95A9-174919C3E57C}.Debug|x86.ActiveCfg = Debug|Any CPU
{5FC793DE-0C52-432C-95A9-174919C3E57C}.Debug|x86.Build.0 = Debug|Any CPU
{5FC793DE-0C52-432C-95A9-174919C3E57C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FC793DE-0C52-432C-95A9-174919C3E57C}.Release|Any CPU.Build.0 = Release|Any CPU
{5FC793DE-0C52-432C-95A9-174919C3E57C}.Release|x64.ActiveCfg = Release|Any CPU
{5FC793DE-0C52-432C-95A9-174919C3E57C}.Release|x64.Build.0 = Release|Any CPU
{5FC793DE-0C52-432C-95A9-174919C3E57C}.Release|x86.ActiveCfg = Release|Any CPU
{5FC793DE-0C52-432C-95A9-174919C3E57C}.Release|x86.Build.0 = Release|Any CPU
{6B7E10A1-CE95-4D71-A657-7303FEE7B52A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6B7E10A1-CE95-4D71-A657-7303FEE7B52A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B7E10A1-CE95-4D71-A657-7303FEE7B52A}.Debug|x64.ActiveCfg = Debug|Any CPU
{6B7E10A1-CE95-4D71-A657-7303FEE7B52A}.Debug|x64.Build.0 = Debug|Any CPU
{6B7E10A1-CE95-4D71-A657-7303FEE7B52A}.Debug|x86.ActiveCfg = Debug|Any CPU
{6B7E10A1-CE95-4D71-A657-7303FEE7B52A}.Debug|x86.Build.0 = Debug|Any CPU
{6B7E10A1-CE95-4D71-A657-7303FEE7B52A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6B7E10A1-CE95-4D71-A657-7303FEE7B52A}.Release|Any CPU.Build.0 = Release|Any CPU
{6B7E10A1-CE95-4D71-A657-7303FEE7B52A}.Release|x64.ActiveCfg = Release|Any CPU
{6B7E10A1-CE95-4D71-A657-7303FEE7B52A}.Release|x64.Build.0 = Release|Any CPU
{6B7E10A1-CE95-4D71-A657-7303FEE7B52A}.Release|x86.ActiveCfg = Release|Any CPU
{6B7E10A1-CE95-4D71-A657-7303FEE7B52A}.Release|x86.Build.0 = Release|Any CPU
{C6D6EB9D-D40E-4839-8F78-1024ED4964E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C6D6EB9D-D40E-4839-8F78-1024ED4964E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C6D6EB9D-D40E-4839-8F78-1024ED4964E1}.Debug|x64.ActiveCfg = Debug|Any CPU
{C6D6EB9D-D40E-4839-8F78-1024ED4964E1}.Debug|x64.Build.0 = Debug|Any CPU
{C6D6EB9D-D40E-4839-8F78-1024ED4964E1}.Debug|x86.ActiveCfg = Debug|Any CPU
{C6D6EB9D-D40E-4839-8F78-1024ED4964E1}.Debug|x86.Build.0 = Debug|Any CPU
{C6D6EB9D-D40E-4839-8F78-1024ED4964E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C6D6EB9D-D40E-4839-8F78-1024ED4964E1}.Release|Any CPU.Build.0 = Release|Any CPU
{C6D6EB9D-D40E-4839-8F78-1024ED4964E1}.Release|x64.ActiveCfg = Release|Any CPU
{C6D6EB9D-D40E-4839-8F78-1024ED4964E1}.Release|x64.Build.0 = Release|Any CPU
{C6D6EB9D-D40E-4839-8F78-1024ED4964E1}.Release|x86.ActiveCfg = Release|Any CPU
{C6D6EB9D-D40E-4839-8F78-1024ED4964E1}.Release|x86.Build.0 = Release|Any CPU
{2010E82F-B42C-491F-AFD7-4486D4845737}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2010E82F-B42C-491F-AFD7-4486D4845737}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2010E82F-B42C-491F-AFD7-4486D4845737}.Debug|x64.ActiveCfg = Debug|Any CPU
{2010E82F-B42C-491F-AFD7-4486D4845737}.Debug|x64.Build.0 = Debug|Any CPU
{2010E82F-B42C-491F-AFD7-4486D4845737}.Debug|x86.ActiveCfg = Debug|Any CPU
{2010E82F-B42C-491F-AFD7-4486D4845737}.Debug|x86.Build.0 = Debug|Any CPU
{2010E82F-B42C-491F-AFD7-4486D4845737}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2010E82F-B42C-491F-AFD7-4486D4845737}.Release|Any CPU.Build.0 = Release|Any CPU
{2010E82F-B42C-491F-AFD7-4486D4845737}.Release|x64.ActiveCfg = Release|Any CPU
{2010E82F-B42C-491F-AFD7-4486D4845737}.Release|x64.Build.0 = Release|Any CPU
{2010E82F-B42C-491F-AFD7-4486D4845737}.Release|x86.ActiveCfg = Release|Any CPU
{2010E82F-B42C-491F-AFD7-4486D4845737}.Release|x86.Build.0 = Release|Any CPU
{EEC2B03A-3172-4EC1-B7E3-8687E436072D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EEC2B03A-3172-4EC1-B7E3-8687E436072D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EEC2B03A-3172-4EC1-B7E3-8687E436072D}.Debug|x64.ActiveCfg = Debug|Any CPU
{EEC2B03A-3172-4EC1-B7E3-8687E436072D}.Debug|x64.Build.0 = Debug|Any CPU
{EEC2B03A-3172-4EC1-B7E3-8687E436072D}.Debug|x86.ActiveCfg = Debug|Any CPU
{EEC2B03A-3172-4EC1-B7E3-8687E436072D}.Debug|x86.Build.0 = Debug|Any CPU
{EEC2B03A-3172-4EC1-B7E3-8687E436072D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EEC2B03A-3172-4EC1-B7E3-8687E436072D}.Release|Any CPU.Build.0 = Release|Any CPU
{EEC2B03A-3172-4EC1-B7E3-8687E436072D}.Release|x64.ActiveCfg = Release|Any CPU
{EEC2B03A-3172-4EC1-B7E3-8687E436072D}.Release|x64.Build.0 = Release|Any CPU
{EEC2B03A-3172-4EC1-B7E3-8687E436072D}.Release|x86.ActiveCfg = Release|Any CPU
{EEC2B03A-3172-4EC1-B7E3-8687E436072D}.Release|x86.Build.0 = Release|Any CPU
{401E3B50-A6B8-4AA1-9DD3-41F9EC78FABD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{401E3B50-A6B8-4AA1-9DD3-41F9EC78FABD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{401E3B50-A6B8-4AA1-9DD3-41F9EC78FABD}.Debug|x64.ActiveCfg = Debug|Any CPU
{401E3B50-A6B8-4AA1-9DD3-41F9EC78FABD}.Debug|x64.Build.0 = Debug|Any CPU
{401E3B50-A6B8-4AA1-9DD3-41F9EC78FABD}.Debug|x86.ActiveCfg = Debug|Any CPU
{401E3B50-A6B8-4AA1-9DD3-41F9EC78FABD}.Debug|x86.Build.0 = Debug|Any CPU
{401E3B50-A6B8-4AA1-9DD3-41F9EC78FABD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{401E3B50-A6B8-4AA1-9DD3-41F9EC78FABD}.Release|Any CPU.Build.0 = Release|Any CPU
{401E3B50-A6B8-4AA1-9DD3-41F9EC78FABD}.Release|x64.ActiveCfg = Release|Any CPU
{401E3B50-A6B8-4AA1-9DD3-41F9EC78FABD}.Release|x64.Build.0 = Release|Any CPU
{401E3B50-A6B8-4AA1-9DD3-41F9EC78FABD}.Release|x86.ActiveCfg = Release|Any CPU
{401E3B50-A6B8-4AA1-9DD3-41F9EC78FABD}.Release|x86.Build.0 = Release|Any CPU
{DC1E99E0-6B92-4FBD-B74B-68D202961FB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DC1E99E0-6B92-4FBD-B74B-68D202961FB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DC1E99E0-6B92-4FBD-B74B-68D202961FB7}.Debug|x64.ActiveCfg = Debug|Any CPU
{DC1E99E0-6B92-4FBD-B74B-68D202961FB7}.Debug|x64.Build.0 = Debug|Any CPU
{DC1E99E0-6B92-4FBD-B74B-68D202961FB7}.Debug|x86.ActiveCfg = Debug|Any CPU
{DC1E99E0-6B92-4FBD-B74B-68D202961FB7}.Debug|x86.Build.0 = Debug|Any CPU
{DC1E99E0-6B92-4FBD-B74B-68D202961FB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DC1E99E0-6B92-4FBD-B74B-68D202961FB7}.Release|Any CPU.Build.0 = Release|Any CPU
{DC1E99E0-6B92-4FBD-B74B-68D202961FB7}.Release|x64.ActiveCfg = Release|Any CPU
{DC1E99E0-6B92-4FBD-B74B-68D202961FB7}.Release|x64.Build.0 = Release|Any CPU
{DC1E99E0-6B92-4FBD-B74B-68D202961FB7}.Release|x86.ActiveCfg = Release|Any CPU
{DC1E99E0-6B92-4FBD-B74B-68D202961FB7}.Release|x86.Build.0 = Release|Any CPU
{B496E978-09C5-4EFA-9A2A-6984A3F89F77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B496E978-09C5-4EFA-9A2A-6984A3F89F77}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B496E978-09C5-4EFA-9A2A-6984A3F89F77}.Debug|x64.ActiveCfg = Debug|Any CPU
{B496E978-09C5-4EFA-9A2A-6984A3F89F77}.Debug|x64.Build.0 = Debug|Any CPU
{B496E978-09C5-4EFA-9A2A-6984A3F89F77}.Debug|x86.ActiveCfg = Debug|Any CPU
{B496E978-09C5-4EFA-9A2A-6984A3F89F77}.Debug|x86.Build.0 = Debug|Any CPU
{B496E978-09C5-4EFA-9A2A-6984A3F89F77}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B496E978-09C5-4EFA-9A2A-6984A3F89F77}.Release|Any CPU.Build.0 = Release|Any CPU
{B496E978-09C5-4EFA-9A2A-6984A3F89F77}.Release|x64.ActiveCfg = Release|Any CPU
{B496E978-09C5-4EFA-9A2A-6984A3F89F77}.Release|x64.Build.0 = Release|Any CPU
{B496E978-09C5-4EFA-9A2A-6984A3F89F77}.Release|x86.ActiveCfg = Release|Any CPU
{B496E978-09C5-4EFA-9A2A-6984A3F89F77}.Release|x86.Build.0 = Release|Any CPU
{087C0E53-90E0-415A-A394-6E31E12EF7BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{087C0E53-90E0-415A-A394-6E31E12EF7BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{087C0E53-90E0-415A-A394-6E31E12EF7BF}.Debug|x64.ActiveCfg = Debug|Any CPU
{087C0E53-90E0-415A-A394-6E31E12EF7BF}.Debug|x64.Build.0 = Debug|Any CPU
{087C0E53-90E0-415A-A394-6E31E12EF7BF}.Debug|x86.ActiveCfg = Debug|Any CPU
{087C0E53-90E0-415A-A394-6E31E12EF7BF}.Debug|x86.Build.0 = Debug|Any CPU
{087C0E53-90E0-415A-A394-6E31E12EF7BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{087C0E53-90E0-415A-A394-6E31E12EF7BF}.Release|Any CPU.Build.0 = Release|Any CPU
{087C0E53-90E0-415A-A394-6E31E12EF7BF}.Release|x64.ActiveCfg = Release|Any CPU
{087C0E53-90E0-415A-A394-6E31E12EF7BF}.Release|x64.Build.0 = Release|Any CPU
{087C0E53-90E0-415A-A394-6E31E12EF7BF}.Release|x86.ActiveCfg = Release|Any CPU
{087C0E53-90E0-415A-A394-6E31E12EF7BF}.Release|x86.Build.0 = Release|Any CPU
{2C29F7A5-8927-4490-BC7A-50B6F5442EEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C29F7A5-8927-4490-BC7A-50B6F5442EEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C29F7A5-8927-4490-BC7A-50B6F5442EEF}.Debug|x64.ActiveCfg = Debug|Any CPU
{2C29F7A5-8927-4490-BC7A-50B6F5442EEF}.Debug|x64.Build.0 = Debug|Any CPU
{2C29F7A5-8927-4490-BC7A-50B6F5442EEF}.Debug|x86.ActiveCfg = Debug|Any CPU
{2C29F7A5-8927-4490-BC7A-50B6F5442EEF}.Debug|x86.Build.0 = Debug|Any CPU
{2C29F7A5-8927-4490-BC7A-50B6F5442EEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C29F7A5-8927-4490-BC7A-50B6F5442EEF}.Release|Any CPU.Build.0 = Release|Any CPU
{2C29F7A5-8927-4490-BC7A-50B6F5442EEF}.Release|x64.ActiveCfg = Release|Any CPU
{2C29F7A5-8927-4490-BC7A-50B6F5442EEF}.Release|x64.Build.0 = Release|Any CPU
{2C29F7A5-8927-4490-BC7A-50B6F5442EEF}.Release|x86.ActiveCfg = Release|Any CPU
{2C29F7A5-8927-4490-BC7A-50B6F5442EEF}.Release|x86.Build.0 = Release|Any CPU
{06992B63-6D24-4785-8A7D-138E9AC6D38E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{06992B63-6D24-4785-8A7D-138E9AC6D38E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{06992B63-6D24-4785-8A7D-138E9AC6D38E}.Debug|x64.ActiveCfg = Debug|Any CPU
{06992B63-6D24-4785-8A7D-138E9AC6D38E}.Debug|x64.Build.0 = Debug|Any CPU
{06992B63-6D24-4785-8A7D-138E9AC6D38E}.Debug|x86.ActiveCfg = Debug|Any CPU
{06992B63-6D24-4785-8A7D-138E9AC6D38E}.Debug|x86.Build.0 = Debug|Any CPU
{06992B63-6D24-4785-8A7D-138E9AC6D38E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{06992B63-6D24-4785-8A7D-138E9AC6D38E}.Release|Any CPU.Build.0 = Release|Any CPU
{06992B63-6D24-4785-8A7D-138E9AC6D38E}.Release|x64.ActiveCfg = Release|Any CPU
{06992B63-6D24-4785-8A7D-138E9AC6D38E}.Release|x64.Build.0 = Release|Any CPU
{06992B63-6D24-4785-8A7D-138E9AC6D38E}.Release|x86.ActiveCfg = Release|Any CPU
{06992B63-6D24-4785-8A7D-138E9AC6D38E}.Release|x86.Build.0 = Release|Any CPU
{4786B22D-13B2-4CC5-8738-EAC9121EF4F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4786B22D-13B2-4CC5-8738-EAC9121EF4F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4786B22D-13B2-4CC5-8738-EAC9121EF4F0}.Debug|x64.ActiveCfg = Debug|Any CPU
{4786B22D-13B2-4CC5-8738-EAC9121EF4F0}.Debug|x64.Build.0 = Debug|Any CPU
{4786B22D-13B2-4CC5-8738-EAC9121EF4F0}.Debug|x86.ActiveCfg = Debug|Any CPU
{4786B22D-13B2-4CC5-8738-EAC9121EF4F0}.Debug|x86.Build.0 = Debug|Any CPU
{4786B22D-13B2-4CC5-8738-EAC9121EF4F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4786B22D-13B2-4CC5-8738-EAC9121EF4F0}.Release|Any CPU.Build.0 = Release|Any CPU
{4786B22D-13B2-4CC5-8738-EAC9121EF4F0}.Release|x64.ActiveCfg = Release|Any CPU
{4786B22D-13B2-4CC5-8738-EAC9121EF4F0}.Release|x64.Build.0 = Release|Any CPU
{4786B22D-13B2-4CC5-8738-EAC9121EF4F0}.Release|x86.ActiveCfg = Release|Any CPU
{4786B22D-13B2-4CC5-8738-EAC9121EF4F0}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{18F5CA61-2478-40DB-A8C3-3CA10BA66F4A} = {7266C4A9-E384-DC00-0EB6-47F6002CFBA7}
{4D89232D-7F93-4DA5-9B00-E25A355561EF} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{F2C3F1F1-328F-4EA9-BC82-0AF252375E2A} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{C47D5462-DA34-4A5E-BCBF-642D702E1ECA} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{4C3D1645-4D40-4313-ACB6-079FDD8AA1D2} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{937B457D-1C2A-41E3-B7BD-CDFA827E4F76} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{6A825C22-139D-4927-AA08-F9130D4F6845} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{8043FAEB-5AAE-4DF5-9A3F-EF0E3CA6DC89} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{397ADF05-2B2A-497D-A94C-9C40C8C7F7AF} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{01138FAD-D215-45D4-9D52-6A5875516557} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{15A81ACF-D93F-4039-9F67-8D3A96F57756} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{04D91154-1813-4666-93FD-01644023C766} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{0A09480D-13BC-4C4A-863B-EB5A110F4436} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{059BD6B6-39E5-4558-85F7-2E35FEEA36D4} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{33DFFF98-2E80-421C-B0A5-02C7C329B5C1} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{88BE5612-F8D3-4E22-A1CE-53520F5B8D13} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{733FC595-BBEF-4B1B-81A8-F3A952820250} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{9458707B-D600-4F2C-9FD9-9195F9EC69DC} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{996763EA-95DD-4BC7-8782-19DD630E4F75} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{F9CD05EC-1FFF-4DCC-8806-2FDD9FAD3BBE} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{A02E0244-9E52-4DCD-AF42-6FC90E03C4E1} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{5FC793DE-0C52-432C-95A9-174919C3E57C} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{6B7E10A1-CE95-4D71-A657-7303FEE7B52A} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{C6D6EB9D-D40E-4839-8F78-1024ED4964E1} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{2010E82F-B42C-491F-AFD7-4486D4845737} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{EEC2B03A-3172-4EC1-B7E3-8687E436072D} = {7226ABEA-787E-1D09-10EA-A2ECF996D6A6}
{401E3B50-A6B8-4AA1-9DD3-41F9EC78FABD} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{2C821288-5B9D-778E-74EE-053FE87F223E} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{FB27511E-CBA6-E343-8CE0-54AAD98AFC9F} = {2C821288-5B9D-778E-74EE-053FE87F223E}
{DC1E99E0-6B92-4FBD-B74B-68D202961FB7} = {FB27511E-CBA6-E343-8CE0-54AAD98AFC9F}
{305CF822-D9CA-C4BE-7183-DD5447E36C7E} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{6FECF8C1-5BB0-731D-B0FE-7CF5A5513390} = {305CF822-D9CA-C4BE-7183-DD5447E36C7E}
{B496E978-09C5-4EFA-9A2A-6984A3F89F77} = {6FECF8C1-5BB0-731D-B0FE-7CF5A5513390}
{087C0E53-90E0-415A-A394-6E31E12EF7BF} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{1E1EF545-4C18-AEE2-B997-7EF431518D3F} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{77959D31-1EA8-27CA-126F-40C5E927F925} = {1E1EF545-4C18-AEE2-B997-7EF431518D3F}
{2C29F7A5-8927-4490-BC7A-50B6F5442EEF} = {77959D31-1EA8-27CA-126F-40C5E927F925}
{1F785BA6-BCCC-EB5A-C0AC-017D258EF3FD} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{9CB3DAB5-483B-9BA7-DF84-97D903318F73} = {1F785BA6-BCCC-EB5A-C0AC-017D258EF3FD}
{06992B63-6D24-4785-8A7D-138E9AC6D38E} = {9CB3DAB5-483B-9BA7-DF84-97D903318F73}
{0D6C7A58-55D3-AEBB-0CCB-984ED0F92F71} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{04D8AB37-CCCF-08EA-5F4C-67D97B5A3FDE} = {0D6C7A58-55D3-AEBB-0CCB-984ED0F92F71}
{4786B22D-13B2-4CC5-8738-EAC9121EF4F0} = {04D8AB37-CCCF-08EA-5F4C-67D97B5A3FDE}
{F7A07CC8-D014-3EFC-E9E0-4BA33974CD0E} = {0D6C7A58-55D3-AEBB-0CCB-984ED0F92F71}
{9B135FC8-DBD1-6BD7-B3C5-FE40E7BAF0CB} = {0D6C7A58-55D3-AEBB-0CCB-984ED0F92F71}
EndGlobalSection
EndGlobal
+112 -1
View File
@@ -1 +1,112 @@
# nashel-backend
# 🔮 Nashel — Backend API
> **ASP.NET Core · Modular Monolith · PostgreSQL (PostGIS) · Docker**
Серверная часть платформы **Nashel** — инновационного маркетплейса для поиска и найма профессиональных исполнителей. Бэкенд построен по принципу **Модульного Монолита**, что обеспечивает идеальный баланс между скоростью разработки и чистотой архитектуры с четкой доменной изоляцией.
---
## 🎯 Миссия Nashel
Мы создаем прозрачную экосистему, где мастера получают профессиональный инструментарий для ведения бизнеса, а клиенты — надежный сервис поиска по геолокации, реальным отзывам и защищенным сделкам.
**Ключевые преимущества:**
- **Гео-центричность:** Поиск исполнителей в радиусе на карте.
- **Интеллектуальный статус:** Проверка доступности мастера в реальном времени.
- **Безопасность:** Проработанный жизненный цикл заказа с системой споров.
- **Репутация:** Честная система отзывов, привязанная к реальным сделкам.
---
## 🧱 Архитектура и Технологии
Проект реализован как **Modular Monolith**. Каждый модуль — это изолированная единица со своей логикой, данными и API, взаимодействующая с другими через контракты BuildingBlocks.
### Технологический стек
- **Runtime:** .NET 8 / ASP.NET Core
- **Database:** PostgreSQL + **PostGIS** (гео-запросы)
- **ORM:** Entity Framework Core
- **Messaging:** MediatR (In-process commands/queries)
- **Security:** JWT Authentication, Role-based Access Control
- **Spatial:** NetTopologySuite
### Структура модуля
```
Modules/<Module>/
├── Domain/ # Сущности, Value Objects, Доменные события
├── Application/ # Use Cases (MediatR Handlers), DTOs, Mapping
├── Infrastructure/ # EF Core (Persistence), External Services
└── Presentation/ # Minimal API Endpoints
```
---
## 🚀 Реализованные Модули
| Модуль | Статус | Функционал |
|--------|--------|------------|
| **Identity** | ✅ Ready | Auth (JWT), Профили, Аватары (Base64), Расписание, Статусы доступности |
| **Catalog** | ✅ Ready | Управление услугами (CRUD), Multi-image (до 10 фото), Атрибуты |
| **Search** | ✅ Ready | Full-text search, сортировка по Geo-дистанции и релевантности |
| **Geo** | ✅ Ready | Расчет расстояний, Геозоны, Индексация координат |
| **Order** | ✅ Ready | Заказы (Direct/Public), SLA таймеры, Система споров (Disputes), Отклики |
| **Reputation** | ✅ Ready | Отзывы, Рейтинги (User/Offer), Дополнения к отзывам |
| **Collaboration**| ✅ Ready | HR-инструментарий: Найм, Наложение вето на расписание, Проверки |
---
## ⚙️ Ключевая Логика
### 1. Умная доступность (Smart Status)
Мастер может управлять своей доступностью двумя способами:
- **Расписание:** Настройка рабочих дней и часов.
- **Manual Toggle:** Ручное переключение статуса «Готов к заказу». Ручная активация имеет приоритет и действует до конца текущего дня, перекрывая стандартное расписание.
### 2. Жизненный цикл заказа (Order Flow)
Реализована сложная машина состояний:
1. **Создание:** Прямой заказ мастеру или публикация заявки в общий доступ.
2. **SLA:** Для прямых заказов действует 60-минутный таймер на принятие.
3. **Исполнение:** Статусы "В работе", "Выполнено", "Подтверждено".
4. **Споры (Disputes):** Многоэтапный процесс разрешения конфликтов (Открытие -> Ответ мастера -> Возражение клиента -> Принятие условий).
### 3. Поиск и Геолокация
- Поиск учитывает не только текст, но и **расстояние**.
- В приоритете — конкретные услуги. Если мастер не имеет услуг, он показывается как специалист.
- Интеграция с PostGIS позволяет делать сверхбыстрые выборки в радиусе.
---
## 🐳 Развертывание
### Docker (Рекомендуемо)
```bash
docker-compose up -d --build
```
Это запустит:
- Бэкенд (порт 5000)
- PostgreSQL + PostGIS (порт 5432)
### Локальный запуск
1. Установите PostgreSQL и расширение PostGIS.
2. Обновите строку подключения в `appsettings.json`.
3. Примените миграции:
```bash
dotnet ef database update -p src/Host -s src/Host
```
4. Запустите Host:
```bash
dotnet run --project src/Host
```
Swagger доступен по адресу: `http://localhost:5000/swagger`
---
## 🔮 Планы развития
- [ ] **Real-time:** Интеграция WebSockets для чатов и уведомлений.
- [ ] **Verification:** Модуль проверки документов исполнителей.
- [ ] **Finances:** Интеграция платежных шлюзов.
- [ ] **Analytics:** Сбор метрик просмотров и конверсий для мастеров.
- [ ] **Mobile SDK:** API для нативных мобильных приложений.
- [ ] **Notifications:** Push и Email уведомления о статусах заказов.
+2
View File
@@ -0,0 +1,2 @@
SET search_path TO identity;
SELECT "WorkingDays" FROM "WorkSchedules" LIMIT 3;
+61
View File
@@ -0,0 +1,61 @@
services:
migrations:
build:
context: .
dockerfile: Dockerfile.migrations
container_name: nashel-migrations
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ConnectionStrings__DefaultConnection=Host=db;Port=5432;Database=nashel;Username=postgres;Password=postgres
depends_on:
db:
condition: service_healthy
networks:
- nashel-network
app:
build:
context: .
dockerfile: Dockerfile
container_name: nashel-backend
ports:
- "5000:8080"
volumes:
- ./uploads:/app/wwwroot/uploads
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ConnectionStrings__DefaultConnection=Host=db;Port=5432;Database=nashel;Username=postgres;Password=postgres
depends_on:
db:
condition: service_healthy
migrations:
condition: service_completed_successfully
networks:
- nashel-network
db:
image: postgis/postgis:16-3.4
container_name: nashel-db
environment:
- POSTGRES_DB=nashel
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
ports:
- "5432:5432"
volumes:
- nashel-db-data:/var/lib/postgresql/data
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
interval: 5s
timeout: 5s
retries: 5
networks:
- nashel-network
volumes:
nashel-db-data:
networks:
nashel-network:
driver: bridge
+2
View File
@@ -0,0 +1,2 @@
SET search_path TO "identity";
SELECT * FROM "WorkSchedules";
@@ -0,0 +1,7 @@
namespace Nashel.BuildingBlocks.Application.Abstractions;
public interface ICurrentUserService
{
Guid? UserId { get; }
// Other claims if needed
}
@@ -0,0 +1,34 @@
using FluentValidation;
using MediatR;
namespace Nashel.BuildingBlocks.Application.Behaviors;
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
if (!_validators.Any())
{
return await next();
}
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();
if (failures.Count != 0)
{
throw new ValidationException(failures);
}
return await next();
}
}
@@ -0,0 +1,55 @@
namespace Nashel.BuildingBlocks.Application.Configurations;
/// <summary>
/// Настройки работы с медиа-файлами.
/// </summary>
public class MediaSettings
{
public const string SectionName = "MediaSettings";
/// <summary>Максимальный размер файла в байтах.</summary>
public long MaxFileSizeInBytes { get; set; }
/// <summary>Максимальное количество изображений для одной услуги.</summary>
public int MaxImagesPerOffer { get; set; }
/// <summary>Список допустимых расширений файлов.</summary>
public string[] AllowedExtensions { get; set; } = Array.Empty<string>();
}
/// <summary>
/// Настройки модуля заказов.
/// </summary>
public class OrderSettings
{
public const string SectionName = "OrderSettings";
/// <summary>Время на принятие прямого заказа (SLA) в минутах.</summary>
public int DirectOrderSlaMinutes { get; set; }
/// <summary>Время жизни публичной заявки в минутах.</summary>
public int PublicOrderSlaMinutes { get; set; }
/// <summary>Максимальное время на ответ в споре (в часах).</summary>
public int MaxDisputeResponseHours { get; set; }
/// <summary>Максимальное число компетенций у исполнителя.</summary>
public int MaxCompetenciesPerPerformer { get; set; }
}
/// <summary>
/// Настройки модуля Identity.
/// </summary>
public class IdentitySettings
{
public const string SectionName = "IdentitySettings";
/// <summary>Максимальная длина описания профиля.</summary>
public int MaxDescriptionLength { get; set; }
/// <summary>Минимальная длина описания для роли исполнителя.</summary>
public int MinPerformerDescriptionLength { get; set; }
/// <summary>Максимальное число компетенций.</summary>
public int MaxCompetencies { get; set; }
}
@@ -0,0 +1,54 @@
namespace Nashel.BuildingBlocks.Application.Exceptions;
/// <summary>
/// Базовый класс для всех доменных исключений и исключений приложения.
/// </summary>
public abstract class ApplicationExceptionBase : Exception
{
protected ApplicationExceptionBase(string message) : base(message) { }
}
/// <summary>
/// Исключение, выбрасываемое, когда сущность не найдена.
/// </summary>
public class NotFoundException : ApplicationExceptionBase
{
public NotFoundException(string name, object key)
: base($"Сущность \"{name}\" ({key}) не найдена.") { }
}
/// <summary>
/// Исключение валидации данных.
/// </summary>
public class ValidationException : ApplicationExceptionBase
{
public IDictionary<string, string[]> Errors { get; }
public ValidationException()
: base("Произошла одна или несколько ошибок валидации.")
{
Errors = new Dictionary<string, string[]>();
}
public ValidationException(IDictionary<string, string[]> errors)
: base("Произошла одна или несколько ошибок валидации.")
{
Errors = errors;
}
}
/// <summary>
/// Исключение, выбрасываемое при нарушении бизнес-правил или конфликтах.
/// </summary>
public class BusinessRuleException : ApplicationExceptionBase
{
public BusinessRuleException(string message) : base(message) { }
}
/// <summary>
/// Исключение отсутствия прав доступа.
/// </summary>
public class ForbiddenAccessException : ApplicationExceptionBase
{
public ForbiddenAccessException() : base("У вас недостаточно прав для выполнения этой операции.") { }
}
@@ -0,0 +1,31 @@
using System.Collections.Concurrent;
namespace Nashel.BuildingBlocks.Domain;
public interface IEntity { }
public abstract class Entity<TId> : IEntity
{
public TId Id { get; protected set; }
}
public interface IAggregateRoot { }
public abstract class AggregateRoot<TId> : Entity<TId>, IAggregateRoot
{
private readonly List<IDomainEvent> _domainEvents = new();
public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
public void AddDomainEvent(IDomainEvent domainEvent)
{
_domainEvents.Add(domainEvent);
}
public void ClearDomainEvents()
{
_domainEvents.Clear();
}
}
@@ -0,0 +1,9 @@
namespace Nashel.BuildingBlocks.Domain;
/// <summary>
/// Базовый класс для исключений доменного слоя.
/// </summary>
public abstract class DomainException : Exception
{
protected DomainException(string message) : base(message) { }
}
+13
View File
@@ -0,0 +1,13 @@
using MediatR;
namespace Nashel.BuildingBlocks.Domain;
public interface IDomainEvent : INotification
{
DateTime OccurredOn { get; }
}
public class BaseDomainEvent : IDomainEvent
{
public DateTime OccurredOn { get; protected set; } = DateTime.UtcNow;
}
+28
View File
@@ -0,0 +1,28 @@
namespace Nashel.BuildingBlocks.Domain;
public class Result<T>
{
public bool IsSuccess { get; }
public T? Value { get; }
public string? Error { get; }
protected Result(bool isSuccess, T? value, string? error)
{
IsSuccess = isSuccess;
Value = value;
Error = error;
}
public static Result<T> Success(T value) => new Result<T>(true, value, null);
public static Result<T> Failure(string error) => new Result<T>(false, default, error);
}
public class Result : Result<object>
{
protected Result(bool isSuccess, string? error) : base(isSuccess, null, error)
{
}
public static Result Success() => new Result(true, null);
public static new Result Failure(string error) => new Result(false, error);
}
+252
View File
@@ -0,0 +1,252 @@
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Nashel.BuildingBlocks.Application.Abstractions;
using Nashel.Modules.Catalog.Infrastructure.Persistence;
using Nashel.Modules.Geo.Infrastructure.Persistence;
using Nashel.Modules.Identity.Infrastructure.Persistence;
using Nashel.Modules.Reputation.Infrastructure.Persistence;
using NetTopologySuite.Geometries;
namespace Nashel.Host.Endpoints;
public static class SearchEndpoints
{
public static void MapSearchEndpoints(this IEndpointRouteBuilder app)
{
app.MapGet("/api/search", async (
GeoDbContext geoDb,
IdentityDbContext identityDb,
CatalogDbContext catalogDb,
ReputationDbContext reputationDb,
ICurrentUserService currentUserService,
CancellationToken ct,
[FromQuery] double lat = 0, // Убрали обязательность, чтобы опциональные работали нормально или перенесли в конец
[FromQuery] double lon = 0,
[FromQuery] string? q = null,
[FromQuery] double radius = 10000,
[FromQuery] bool inTitle = true,
[FromQuery] bool inDesc = true,
[FromQuery] bool inComp = true) =>
{
if (lat == 0 && lon == 0) return Results.BadRequest("Coordinates required");
var currentUserId = currentUserService.UserId;
// 1. Ищем ближайших мастеров через GeoDbContext (включая оффлайн, исключая себя)
var searchPoint = new Point(lon, lat) { SRID = 4326 };
var nearbyGeoQuery = geoDb.LiveStatuses
.Where(x => x.Location.IsWithinDistance(searchPoint, radius));
if (currentUserId.HasValue)
{
nearbyGeoQuery = nearbyGeoQuery.Where(x => x.Id != currentUserId.Value);
}
var nearbyGeoRaw = await nearbyGeoQuery
.Select(x => new { x.Id, x.State, Latitude = x.Location.Coordinate.Y, Longitude = x.Location.Coordinate.X, x.LastUpdatedAt })
.ToListAsync(ct);
var nearbyGeo = nearbyGeoRaw.Select(x => new
{
x.Id,
x.State,
x.Latitude,
x.Longitude,
x.LastUpdatedAt,
DistanceMeters = CalculateDistanceInMeters(lat, lon, x.Latitude, x.Longitude)
}).ToList();
var nearbyIds = nearbyGeo.Select(x => x.Id).ToList();
if (!nearbyIds.Any()) return Results.Ok(new List<object>());
// 2. Ищем совпадения по услугам (CatalogDbContext)
var offersQuery = catalogDb.Offers
.Where(o => nearbyIds.Contains(o.PerformerId) && o.IsActive && !o.IsDeleted);
var qLower = q?.ToLower();
if (!string.IsNullOrWhiteSpace(qLower))
{
// EF Core транслирует Contains в ILIKE
offersQuery = offersQuery.Where(o =>
(inTitle && o.Title.ToLower().Contains(qLower)) ||
(inDesc && o.Description != null && o.Description.ToLower().Contains(qLower)));
}
var matchedOffersRaw = await offersQuery
.Select(o => new { o.Id, o.PerformerId, o.Title, Description = o.Description ?? "", o.Price.Amount, o.Price.Currency, o.Images })
.ToListAsync(ct);
var matchedOffers = matchedOffersRaw.Select(o => new { o.Id, o.PerformerId, o.Title, o.Description, PriceAmount = o.Amount, PriceCurrency = "₽", ImageUrl = o.Images.FirstOrDefault() }).ToList();
var performersWithOffers = matchedOffers.Select(o => o.PerformerId).Distinct().ToList();
// 3. Ищем пользователей, их расписание и компетенции (IdentityDbContext)
var profilesQuery = identityDb.UserProfiles
.Include(p => p.WorkSchedule)
.Include(p => p.Competencies)
.Where(p => nearbyIds.Contains(p.Id));
if (!string.IsNullOrWhiteSpace(qLower) && inComp)
{
// Фильтр: либо есть нужные офферы, либо подходящие компетенции
profilesQuery = profilesQuery.Where(p =>
performersWithOffers.Contains(p.Id) ||
p.Competencies.Any(c => c.Name.ToLower().Contains(qLower)));
}
else if (!string.IsNullOrWhiteSpace(qLower))
{
// Поиск только по услугам, компетенции игнорируем
profilesQuery = profilesQuery.Where(p => performersWithOffers.Contains(p.Id));
}
var profiles = await profilesQuery.ToListAsync(ct);
var profileIds = profiles.Select(p => p.Id).ToList();
var accountsRaw = await identityDb.Accounts
.Where(a => profileIds.Contains(a.Id))
.Select(a => new { a.Id, a.Roles })
.ToListAsync(ct);
var accountsDict = accountsRaw.ToDictionary(a => a.Id, a => a.Roles.Select(r => r.ToString()).ToList());
// 4. Рейтинги (ReputationDbContext)
var performerRatings = await reputationDb.Reviews
.Where(r => profileIds.Contains(r.TargetId))
.GroupBy(r => r.TargetId)
.Select(g => new { TargetId = g.Key, Avg = g.Average(r => r.Rating), Count = g.Count() })
.ToDictionaryAsync(x => x.TargetId, x => new { x.Avg, x.Count }, ct);
var offerIds = matchedOffers.Select(o => o.Id).ToList();
var offerRatings = await reputationDb.Reviews
.Where(r => offerIds.Contains(r.OfferId))
.GroupBy(r => r.OfferId)
.Select(g => new { OfferId = g.Key, Avg = g.Average(r => r.Rating), Count = g.Count() })
.ToDictionaryAsync(x => x.OfferId, x => new { x.Avg, x.Count }, ct);
// 5. Сборка результата и логика "Умного статуса"
var currentDay = GetRussianDayOfWeek(DateTime.UtcNow.DayOfWeek);
var currentTime = DateTime.UtcNow.TimeOfDay;
var offersLookup = matchedOffers.GroupBy(o => o.PerformerId).ToDictionary(g => g.Key, g => g.ToList());
var result = profiles.Join(nearbyGeo, p => p.Id, g => g.Id, (p, g) =>
{
var names = new[] { p.FirstName, p.LastName }.Where(x => !string.IsNullOrWhiteSpace(x));
string fullName = string.Join(" ", names);
if (string.IsNullOrWhiteSpace(fullName))
fullName = p.CompanyName ?? "Мастер";
string finalStatus = CalculateSmartStatus(
g.State.ToString(), // Передаем реальный статус из Geo
g.LastUpdatedAt, // Передаем время последнего обновления
p.WorkSchedule?.IsAlwaysReady ?? false,
p.WorkSchedule?.WorkingDays,
currentDay,
currentTime
);
var matchedOffersList = offersLookup.TryGetValue(p.Id, out var oList)
? oList.Select(ox => (object)new
{
ox.Id,
ox.PerformerId,
ox.Title,
ox.Description,
ox.PriceAmount,
ox.PriceCurrency,
ox.ImageUrl,
Rating = offerRatings.TryGetValue(ox.Id, out var or) ? Math.Round(or.Avg, 1) : 0,
ReviewsCount = offerRatings.TryGetValue(ox.Id, out var orc) ? orc.Count : 0
}).ToList()
: new List<object>();
var roles = accountsDict.TryGetValue(p.Id, out var r) ? r : new List<string>();
string roleName = "Пользователь";
if (roles.Contains("Company")) roleName = "Компания";
else if (roles.Contains("Master")) roleName = "Мастер";
else if (roles.Contains("Candidate")) roleName = "Кандидат";
return new
{
PerformerId = p.Id,
Name = fullName,
AvatarUrl = p.AvatarUrl,
Latitude = g.Latitude,
Longitude = g.Longitude,
Status = finalStatus, // "Готов к заказу" или "Офлайн"
Distance = Math.Round(g.DistanceMeters),
Role = roleName,
Rating = performerRatings.TryGetValue(p.Id, out var pr) ? Math.Round(pr.Avg, 1) : 0,
ReviewsCount = performerRatings.TryGetValue(p.Id, out var prc) ? prc.Count : 0,
MatchedCompetencies = p.Competencies
.Where(c => string.IsNullOrWhiteSpace(qLower) || !inComp || c.Name.ToLower().Contains(qLower))
.Select(c => c.Name).ToList(),
MatchedOffers = matchedOffersList
};
});
return Results.Ok(result.OrderByDescending(x => x.Status == "Готов к заказу").ThenBy(x => x.Distance));
})
.WithName("GlobalSearch")
.WithOpenApi(operation => new(operation)
{
Summary = "Поиск мастеров и услуг (EF Core)",
Description = "Ищет мастеров по компетенциям и услугам с учетом геолокации и графиков работы."
})
.WithTags("Search");
}
private static string CalculateSmartStatus(string liveState, DateTime lastUpdatedAt, bool isAlwaysReady, string? workingDaysJson, string currentDay, TimeSpan currentTime)
{
if (liveState != "Available") return "Офлайн";
if (isAlwaysReady) return "Готов к заказу";
// Если пользователь вручную обновил статус 'Available' сегодня, он остается онлайн до конца дня,
// игнорируя расписание на сегодня. В следующий день снова начнет работать расписание.
if (lastUpdatedAt.Date == DateTime.UtcNow.Date) return "Готов к заказу";
if (string.IsNullOrEmpty(workingDaysJson)) return "Готов к заказу"; // Если расписания нет - считаем готовым
try
{
using var doc = JsonDocument.Parse(workingDaysJson);
if (!doc.RootElement.TryGetProperty(currentDay, out var dayArray) || dayArray.ValueKind != JsonValueKind.Array)
return "Офлайн"; // В этот день вообще не работает
foreach (var period in dayArray.EnumerateArray())
{
var startStr = period.GetProperty("start").GetString();
var endStr = period.GetProperty("end").GetString();
if (TimeSpan.TryParse(startStr, out var start) && TimeSpan.TryParse(endStr, out var end))
{
if (currentTime >= start && currentTime <= end) return "Готов к заказу";
}
}
}
catch { }
return "Офлайн"; // Если время не попало ни в один интервал - офлайн
}
private static string GetRussianDayOfWeek(DayOfWeek day) => day switch
{
DayOfWeek.Monday => "Понедельник",
DayOfWeek.Tuesday => "Вторник",
DayOfWeek.Wednesday => "Среда",
DayOfWeek.Thursday => "Четверг",
DayOfWeek.Friday => "Пятница",
DayOfWeek.Saturday => "Суббота",
DayOfWeek.Sunday => "Воскресенье",
_ => "Понедельник"
};
private static double CalculateDistanceInMeters(double lat1, double lon1, double lat2, double lon2)
{
var dLat = (lat2 - lat1) * Math.PI / 180.0;
var dLon = (lon2 - lon1) * Math.PI / 180.0;
var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) + Math.Cos(lat1 * Math.PI / 180.0) * Math.Cos(lat2 * Math.PI / 180.0) * Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
return 6371000.0 * 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
}
}
@@ -0,0 +1,102 @@
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Nashel.BuildingBlocks.Application.Exceptions;
namespace Nashel.Host.Infrastructure.Exceptions;
/// <summary>
/// Глобальный обработчик исключений для всей системы.
/// Перехватывает ошибки и возвращает их в стандарте ProblemDetails (RFC 7807).
/// </summary>
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger<GlobalExceptionHandler> _logger;
public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
{
_logger = logger;
}
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
_logger.LogError(exception, "Произошла необработанная ошибка: {Message}", exception.Message);
var (statusCode, title, detail, errors) = MapException(exception);
var problemDetails = new ProblemDetails
{
Status = statusCode,
Title = title,
Detail = detail,
Instance = $"{httpContext.Request.Method} {httpContext.Request.Path}"
};
if (errors != null)
{
problemDetails.Extensions["errors"] = errors;
}
httpContext.Response.StatusCode = statusCode;
await httpContext.Response.WriteAsJsonAsync(problemDetails, cancellationToken);
return true;
}
private static (int StatusCode, string Title, string Detail, IDictionary<string, string[]>? Errors) MapException(Exception exception)
{
return exception switch
{
ValidationException validationException => (
StatusCodes.Status400BadRequest,
"Ошибка валидации",
"Один или несколько параметров запроса не прошли проверку.",
validationException.Errors),
NotFoundException notFoundException => (
StatusCodes.Status404NotFound,
"Ресурс не найден",
notFoundException.Message,
null),
Nashel.BuildingBlocks.Domain.DomainException domainException => (
StatusCodes.Status409Conflict,
"Ошибка в бизнес-логике",
domainException.Message,
null),
BusinessRuleException businessRuleException => (
StatusCodes.Status409Conflict,
"Нарушение бизнес-правила",
businessRuleException.Message,
null),
ForbiddenAccessException => (
StatusCodes.Status403Forbidden,
"Доступ запрещен",
"У вас недостаточно прав для выполнения этой операции.",
null),
UnauthorizedAccessException => (
StatusCodes.Status401Unauthorized,
"Неавторизован",
"Требуется авторизация для доступа к этому ресурсу.",
null),
// Обработка стандартных ArgumentException как 400 Bad Request
ArgumentException argumentException => (
StatusCodes.Status400BadRequest,
"Некорректный запрос",
argumentException.Message,
null),
_ => (
StatusCodes.Status500InternalServerError,
"Внутренняя ошибка сервера",
"Произошла непредвиденная ошибка на сервере. Пожалуйста, попробуйте позже.",
null)
};
}
}
+35
View File
@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PublishAot>false</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.76.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.OpenApi" Version="1.6.14" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Modules\Catalog\Infrastructure\Nashel.Modules.Catalog.Infrastructure.csproj" />
<ProjectReference Include="..\Modules\Catalog\Presentation\Nashel.Modules.Catalog.Presentation.csproj" />
<ProjectReference Include="..\Modules\Collaboration\Infrastructure\Nashel.Modules.Collaboration.Infrastructure.csproj" />
<ProjectReference Include="..\Modules\Collaboration\Presentation\Nashel.Modules.Collaboration.Presentation.csproj" />
<ProjectReference Include="..\Modules\Geo\Infrastructure\Nashel.Modules.Geo.Infrastructure.csproj" />
<ProjectReference Include="..\Modules\Geo\Presentation\Nashel.Modules.Geo.Presentation.csproj" />
<ProjectReference Include="..\Modules\Identity\Infrastructure\Nashel.Modules.Identity.Infrastructure.csproj" />
<ProjectReference Include="..\Modules\Identity\Presentation\Nashel.Modules.Identity.Presentation.csproj" />
<ProjectReference Include="..\Modules\Order\Infrastructure\Nashel.Modules.Order.Infrastructure.csproj" />
<ProjectReference Include="..\Modules\Order\Presentation\Nashel.Modules.Order.Presentation.csproj" />
<ProjectReference Include="..\Modules\Reputation\Infrastructure\Nashel.Modules.Reputation.Infrastructure.csproj" />
<ProjectReference Include="..\Modules\Reputation\Presentation\Nashel.Modules.Reputation.Presentation.csproj" />
</ItemGroup>
</Project>
+6
View File
@@ -0,0 +1,6 @@
@Nashel.Host_HostAddress = http://localhost:5232
GET {{Nashel.Host_HostAddress}}/weatherforecast/
Accept: application/json
###
-13
View File
@@ -1,13 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.76.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0-preview.6.25358.103" />
</ItemGroup>
</Project>
-6
View File
@@ -1,6 +0,0 @@
@Nashel.RF.Host_HostAddress = http://localhost:5232
GET {{Nashel.RF.Host_HostAddress}}/weatherforecast/
Accept: application/json
###
+147 -27
View File
@@ -1,39 +1,159 @@
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Nashel.Host.Endpoints;
using Nashel.Modules.Catalog.Infrastructure;
using Nashel.Modules.Catalog.Presentation.Endpoints;
using Nashel.Modules.Collaboration.Infrastructure;
using Nashel.Modules.Collaboration.Presentation;
using Nashel.Modules.Geo.Infrastructure;
using Nashel.Modules.Geo.Presentation;
using Nashel.Modules.Identity.Infrastructure;
using Nashel.Modules.Identity.Presentation.Endpoints;
using Nashel.Modules.Order.Infrastructure;
using Nashel.Modules.Order.Presentation;
using Nashel.Modules.Reputation.Infrastructure;
using Nashel.Modules.Reputation.Presentation;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
// Настройка JSON: принимаем строковые значения enum ("Direct" вместо 0)
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
});
// Настройка Options
builder.Services.Configure<Nashel.BuildingBlocks.Application.Configurations.MediaSettings>(
builder.Configuration.GetSection(Nashel.BuildingBlocks.Application.Configurations.MediaSettings.SectionName));
builder.Services.Configure<Nashel.BuildingBlocks.Application.Configurations.OrderSettings>(
builder.Configuration.GetSection(Nashel.BuildingBlocks.Application.Configurations.OrderSettings.SectionName));
builder.Services.Configure<Nashel.BuildingBlocks.Application.Configurations.IdentitySettings>(
builder.Configuration.GetSection(Nashel.BuildingBlocks.Application.Configurations.IdentitySettings.SectionName));
// Добавление сервисов в контейнер.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
// Добавляем поддержку JWT Bearer (кнопка Authorize)
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "Введите только токен в поле ниже (без 'Bearer ')"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] {}
}
});
// Подключение XML-документации
var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml");
foreach (var xmlFile in xmlFiles)
{
options.IncludeXmlComments(xmlFile);
}
});
// Регистрация модуля Identity
builder.Services.AddIdentityModule(builder.Configuration);
// Регистрация модуля Catalog
builder.Services.AddCatalogModule(builder.Configuration);
// Регистрация модуля Geo
builder.Services.AddGeoModule(builder.Configuration);
// Регистрация модуля Collaboartion
builder.Services.AddCollaborationModule(builder.Configuration);
// Регистрация модуля Order
builder.Services.AddOrderModule(builder.Configuration);
// Регистрация модуля Reputation
builder.Services.AddReputationModule(builder.Configuration);
// Настройка аутентификации
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["JwtSettings:Issuer"],
ValidAudience = builder.Configuration["JwtSettings:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JwtSettings:Secret"] ?? "super_secret_key_change_me_please_this_is_for_development_only_12345"))
};
});
// Регистрация глобального обработчика исключений
builder.Services.AddExceptionHandler<Nashel.Host.Infrastructure.Exceptions.GlobalExceptionHandler>();
builder.Services.AddProblemDetails();
builder.Services.AddAuthorization();
builder.Services.AddCors(options =>
{
options.AddPolicy("FrontendPolicy", policy =>
{
policy.WithOrigins("http://localhost:3000", "http://127.0.0.1:3000", "http://localhost:3001", "http://127.0.0.1:3001")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
var app = builder.Build();
// Configure the HTTP request pipeline.
// Глобальная обработка ошибок в формате ProblemDetails
app.UseExceptionHandler();
// Настройка конвейера HTTP-запросов.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Nashel API v1");
});
}
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.UseCors("FrontendPolicy");
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast");
// Включение статических файлов для аватаров
app.UseStaticFiles();
// Включение аутентификации и авторизации
app.UseAuthentication();
app.UseAuthorization();
// Маппинг эндпоинтов
app.MapIdentityEndpoints();
app.MapCatalogEndpoints();
app.MapGeoEndpoints();
app.MapOrderEndpoints();
app.MapCollaborationEndpoints();
app.MapReputationEndpoints();
// Глобальный поиск
app.MapSearchEndpoints();
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
+2 -2
View File
@@ -5,10 +5,10 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5232",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
}
+26 -2
View File
@@ -5,5 +5,29 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Database=nashel;Username=postgres;Password=postgres"
},
"JwtSettings": {
"Secret": "super_secret_key_change_me_please_this_is_for_development_only_12345",
"Issuer": "Nashel",
"Audience": "Nashel"
},
"MediaSettings": {
"MaxFileSizeInBytes": 5242880,
"MaxImagesPerOffer": 10,
"AllowedExtensions": [ ".jpg", ".jpeg", ".png", ".webp" ]
},
"OrderSettings": {
"DirectOrderSlaMinutes": 60,
"PublicOrderSlaMinutes": 1440,
"MaxDisputeResponseHours": 72,
"MaxCompetenciesPerPerformer": 50
},
"IdentitySettings": {
"MaxDescriptionLength": 2048,
"MinPerformerDescriptionLength": 50,
"MaxCompetencies": 50
}
}
@@ -1,520 +0,0 @@
{
"format": 1,
"restore": {
"E:\\GIT\\mvp\\backend\\src\\Host\\Nashel.RF.Host.csproj": {}
},
"projects": {
"E:\\GIT\\mvp\\backend\\src\\Host\\Nashel.RF.Host.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "E:\\GIT\\mvp\\backend\\src\\Host\\Nashel.RF.Host.csproj",
"projectName": "Nashel.RF.Host",
"projectPath": "E:\\GIT\\mvp\\backend\\src\\Host\\Nashel.RF.Host.csproj",
"packagesPath": "C:\\Users\\HomePC\\.nuget\\packages\\",
"outputPath": "E:\\GIT\\mvp\\backend\\src\\Host\\obj\\",
"projectStyle": "PackageReference",
"fallbackFolders": [
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
],
"configFilePaths": [
"C:\\Users\\HomePC\\AppData\\Roaming\\NuGet\\NuGet.Config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
],
"originalTargetFrameworks": [
"net10.0"
],
"sources": {
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
"C:\\Program Files\\dotnet\\library-packs": {},
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net10.0": {
"targetAlias": "net10.0",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "all"
},
"SdkAnalysisLevel": "10.0.100"
},
"frameworks": {
"net10.0": {
"targetAlias": "net10.0",
"dependencies": {
"Grpc.AspNetCore": {
"target": "Package",
"version": "[2.76.0, )"
},
"Microsoft.AspNetCore.OpenApi": {
"target": "Package",
"version": "[10.0.0-preview.6.25358.103, )"
},
"Microsoft.DotNet.ILCompiler": {
"suppressParent": "All",
"target": "Package",
"version": "[10.0.0-preview.6.25358.103, )",
"autoReferenced": true
},
"Microsoft.NET.ILLink.Tasks": {
"suppressParent": "All",
"target": "Package",
"version": "[10.0.0-preview.6.25358.103, )",
"autoReferenced": true
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"downloadDependencies": [
{
"name": "runtime.win-x64.Microsoft.DotNet.ILCompiler",
"version": "[10.0.0-preview.6.25358.103, 10.0.0-preview.6.25358.103]"
}
],
"frameworkReferences": {
"Microsoft.AspNetCore.App": {
"privateAssets": "none"
},
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.100-preview.6.25358.103/PortableRuntimeIdentifierGraph.json",
"packagesToPrune": {
"Microsoft.AspNetCore": "(,10.0.32767]",
"Microsoft.AspNetCore.Antiforgery": "(,10.0.32767]",
"Microsoft.AspNetCore.App": "(,10.0.32767]",
"Microsoft.AspNetCore.Authentication": "(,10.0.32767]",
"Microsoft.AspNetCore.Authentication.Abstractions": "(,10.0.32767]",
"Microsoft.AspNetCore.Authentication.BearerToken": "(,10.0.32767]",
"Microsoft.AspNetCore.Authentication.Cookies": "(,10.0.32767]",
"Microsoft.AspNetCore.Authentication.Core": "(,10.0.32767]",
"Microsoft.AspNetCore.Authentication.OAuth": "(,10.0.32767]",
"Microsoft.AspNetCore.Authorization": "(,10.0.32767]",
"Microsoft.AspNetCore.Authorization.Policy": "(,10.0.32767]",
"Microsoft.AspNetCore.Components": "(,10.0.32767]",
"Microsoft.AspNetCore.Components.Authorization": "(,10.0.32767]",
"Microsoft.AspNetCore.Components.Endpoints": "(,10.0.32767]",
"Microsoft.AspNetCore.Components.Forms": "(,10.0.32767]",
"Microsoft.AspNetCore.Components.Server": "(,10.0.32767]",
"Microsoft.AspNetCore.Components.Web": "(,10.0.32767]",
"Microsoft.AspNetCore.Connections.Abstractions": "(,10.0.32767]",
"Microsoft.AspNetCore.CookiePolicy": "(,10.0.32767]",
"Microsoft.AspNetCore.Cors": "(,10.0.32767]",
"Microsoft.AspNetCore.Cryptography.Internal": "(,10.0.32767]",
"Microsoft.AspNetCore.Cryptography.KeyDerivation": "(,10.0.32767]",
"Microsoft.AspNetCore.DataProtection": "(,10.0.32767]",
"Microsoft.AspNetCore.DataProtection.Abstractions": "(,10.0.32767]",
"Microsoft.AspNetCore.DataProtection.Extensions": "(,10.0.32767]",
"Microsoft.AspNetCore.Diagnostics": "(,10.0.32767]",
"Microsoft.AspNetCore.Diagnostics.Abstractions": "(,10.0.32767]",
"Microsoft.AspNetCore.Diagnostics.HealthChecks": "(,10.0.32767]",
"Microsoft.AspNetCore.HostFiltering": "(,10.0.32767]",
"Microsoft.AspNetCore.Hosting": "(,10.0.32767]",
"Microsoft.AspNetCore.Hosting.Abstractions": "(,10.0.32767]",
"Microsoft.AspNetCore.Hosting.Server.Abstractions": "(,10.0.32767]",
"Microsoft.AspNetCore.Html.Abstractions": "(,10.0.32767]",
"Microsoft.AspNetCore.Http": "(,10.0.32767]",
"Microsoft.AspNetCore.Http.Abstractions": "(,10.0.32767]",
"Microsoft.AspNetCore.Http.Connections": "(,10.0.32767]",
"Microsoft.AspNetCore.Http.Connections.Common": "(,10.0.32767]",
"Microsoft.AspNetCore.Http.Extensions": "(,10.0.32767]",
"Microsoft.AspNetCore.Http.Features": "(,10.0.32767]",
"Microsoft.AspNetCore.Http.Results": "(,10.0.32767]",
"Microsoft.AspNetCore.HttpLogging": "(,10.0.32767]",
"Microsoft.AspNetCore.HttpOverrides": "(,10.0.32767]",
"Microsoft.AspNetCore.HttpsPolicy": "(,10.0.32767]",
"Microsoft.AspNetCore.Identity": "(,10.0.32767]",
"Microsoft.AspNetCore.Localization": "(,10.0.32767]",
"Microsoft.AspNetCore.Localization.Routing": "(,10.0.32767]",
"Microsoft.AspNetCore.Metadata": "(,10.0.32767]",
"Microsoft.AspNetCore.Mvc": "(,10.0.32767]",
"Microsoft.AspNetCore.Mvc.Abstractions": "(,10.0.32767]",
"Microsoft.AspNetCore.Mvc.ApiExplorer": "(,10.0.32767]",
"Microsoft.AspNetCore.Mvc.Core": "(,10.0.32767]",
"Microsoft.AspNetCore.Mvc.Cors": "(,10.0.32767]",
"Microsoft.AspNetCore.Mvc.DataAnnotations": "(,10.0.32767]",
"Microsoft.AspNetCore.Mvc.Formatters.Json": "(,10.0.32767]",
"Microsoft.AspNetCore.Mvc.Formatters.Xml": "(,10.0.32767]",
"Microsoft.AspNetCore.Mvc.Localization": "(,10.0.32767]",
"Microsoft.AspNetCore.Mvc.Razor": "(,10.0.32767]",
"Microsoft.AspNetCore.Mvc.RazorPages": "(,10.0.32767]",
"Microsoft.AspNetCore.Mvc.TagHelpers": "(,10.0.32767]",
"Microsoft.AspNetCore.Mvc.ViewFeatures": "(,10.0.32767]",
"Microsoft.AspNetCore.OutputCaching": "(,10.0.32767]",
"Microsoft.AspNetCore.RateLimiting": "(,10.0.32767]",
"Microsoft.AspNetCore.Razor": "(,10.0.32767]",
"Microsoft.AspNetCore.Razor.Runtime": "(,10.0.32767]",
"Microsoft.AspNetCore.RequestDecompression": "(,10.0.32767]",
"Microsoft.AspNetCore.ResponseCaching": "(,10.0.32767]",
"Microsoft.AspNetCore.ResponseCaching.Abstractions": "(,10.0.32767]",
"Microsoft.AspNetCore.ResponseCompression": "(,10.0.32767]",
"Microsoft.AspNetCore.Rewrite": "(,10.0.32767]",
"Microsoft.AspNetCore.Routing": "(,10.0.32767]",
"Microsoft.AspNetCore.Routing.Abstractions": "(,10.0.32767]",
"Microsoft.AspNetCore.Server.HttpSys": "(,10.0.32767]",
"Microsoft.AspNetCore.Server.IIS": "(,10.0.32767]",
"Microsoft.AspNetCore.Server.IISIntegration": "(,10.0.32767]",
"Microsoft.AspNetCore.Server.Kestrel": "(,10.0.32767]",
"Microsoft.AspNetCore.Server.Kestrel.Core": "(,10.0.32767]",
"Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes": "(,10.0.32767]",
"Microsoft.AspNetCore.Server.Kestrel.Transport.Quic": "(,10.0.32767]",
"Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets": "(,10.0.32767]",
"Microsoft.AspNetCore.Session": "(,10.0.32767]",
"Microsoft.AspNetCore.SignalR": "(,10.0.32767]",
"Microsoft.AspNetCore.SignalR.Common": "(,10.0.32767]",
"Microsoft.AspNetCore.SignalR.Core": "(,10.0.32767]",
"Microsoft.AspNetCore.SignalR.Protocols.Json": "(,10.0.32767]",
"Microsoft.AspNetCore.StaticAssets": "(,10.0.32767]",
"Microsoft.AspNetCore.StaticFiles": "(,10.0.32767]",
"Microsoft.AspNetCore.WebSockets": "(,10.0.32767]",
"Microsoft.AspNetCore.WebUtilities": "(,10.0.32767]",
"Microsoft.CSharp": "(,4.7.32767]",
"Microsoft.Extensions.Caching.Abstractions": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Caching.Memory": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Configuration": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Configuration.Abstractions": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Configuration.Binder": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Configuration.CommandLine": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Configuration.FileExtensions": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Configuration.Ini": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Configuration.Json": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Configuration.KeyPerFile": "(,10.0.32767]",
"Microsoft.Extensions.Configuration.UserSecrets": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Configuration.Xml": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.DependencyInjection": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.DependencyInjection.Abstractions": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Diagnostics": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Diagnostics.Abstractions": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Diagnostics.HealthChecks": "(,10.0.32767]",
"Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "(,10.0.32767]",
"Microsoft.Extensions.Features": "(,10.0.32767]",
"Microsoft.Extensions.FileProviders.Abstractions": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.FileProviders.Composite": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.FileProviders.Embedded": "(,10.0.32767]",
"Microsoft.Extensions.FileProviders.Physical": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.FileSystemGlobbing": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Hosting": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Hosting.Abstractions": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Http": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Identity.Core": "(,10.0.32767]",
"Microsoft.Extensions.Identity.Stores": "(,10.0.32767]",
"Microsoft.Extensions.Localization": "(,10.0.32767]",
"Microsoft.Extensions.Localization.Abstractions": "(,10.0.32767]",
"Microsoft.Extensions.Logging": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Logging.Abstractions": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Logging.Configuration": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Logging.Console": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Logging.Debug": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Logging.EventLog": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Logging.EventSource": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Logging.TraceSource": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.ObjectPool": "(,10.0.32767]",
"Microsoft.Extensions.Options": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Options.ConfigurationExtensions": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Options.DataAnnotations": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Primitives": "(,10.0.0-preview.6.25358.103]",
"Microsoft.Extensions.Validation": "(,10.0.32767]",
"Microsoft.Extensions.WebEncoders": "(,10.0.32767]",
"Microsoft.JSInterop": "(,10.0.32767]",
"Microsoft.Net.Http.Headers": "(,10.0.32767]",
"Microsoft.VisualBasic": "(,10.4.32767]",
"Microsoft.Win32.Primitives": "(,4.3.32767]",
"Microsoft.Win32.Registry": "(,5.0.32767]",
"runtime.any.System.Collections": "(,4.3.32767]",
"runtime.any.System.Diagnostics.Tools": "(,4.3.32767]",
"runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]",
"runtime.any.System.Globalization": "(,4.3.32767]",
"runtime.any.System.Globalization.Calendars": "(,4.3.32767]",
"runtime.any.System.IO": "(,4.3.32767]",
"runtime.any.System.Reflection": "(,4.3.32767]",
"runtime.any.System.Reflection.Extensions": "(,4.3.32767]",
"runtime.any.System.Reflection.Primitives": "(,4.3.32767]",
"runtime.any.System.Resources.ResourceManager": "(,4.3.32767]",
"runtime.any.System.Runtime": "(,4.3.32767]",
"runtime.any.System.Runtime.Handles": "(,4.3.32767]",
"runtime.any.System.Runtime.InteropServices": "(,4.3.32767]",
"runtime.any.System.Text.Encoding": "(,4.3.32767]",
"runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]",
"runtime.any.System.Threading.Tasks": "(,4.3.32767]",
"runtime.any.System.Threading.Timer": "(,4.3.32767]",
"runtime.aot.System.Collections": "(,4.3.32767]",
"runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]",
"runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]",
"runtime.aot.System.Globalization": "(,4.3.32767]",
"runtime.aot.System.Globalization.Calendars": "(,4.3.32767]",
"runtime.aot.System.IO": "(,4.3.32767]",
"runtime.aot.System.Reflection": "(,4.3.32767]",
"runtime.aot.System.Reflection.Extensions": "(,4.3.32767]",
"runtime.aot.System.Reflection.Primitives": "(,4.3.32767]",
"runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]",
"runtime.aot.System.Runtime": "(,4.3.32767]",
"runtime.aot.System.Runtime.Handles": "(,4.3.32767]",
"runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]",
"runtime.aot.System.Text.Encoding": "(,4.3.32767]",
"runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]",
"runtime.aot.System.Threading.Tasks": "(,4.3.32767]",
"runtime.aot.System.Threading.Timer": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]",
"runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]",
"runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]",
"runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]",
"runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]",
"runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]",
"runtime.unix.System.Console": "(,4.3.32767]",
"runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]",
"runtime.unix.System.IO.FileSystem": "(,4.3.32767]",
"runtime.unix.System.Net.Primitives": "(,4.3.32767]",
"runtime.unix.System.Net.Sockets": "(,4.3.32767]",
"runtime.unix.System.Private.Uri": "(,4.3.32767]",
"runtime.unix.System.Runtime.Extensions": "(,4.3.32767]",
"runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]",
"runtime.win.System.Console": "(,4.3.32767]",
"runtime.win.System.Diagnostics.Debug": "(,4.3.32767]",
"runtime.win.System.IO.FileSystem": "(,4.3.32767]",
"runtime.win.System.Net.Primitives": "(,4.3.32767]",
"runtime.win.System.Net.Sockets": "(,4.3.32767]",
"runtime.win.System.Runtime.Extensions": "(,4.3.32767]",
"runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]",
"runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]",
"runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]",
"runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.win7.System.Private.Uri": "(,4.3.32767]",
"runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]",
"System.AppContext": "(,4.3.32767]",
"System.Buffers": "(,5.0.32767]",
"System.Collections": "(,4.3.32767]",
"System.Collections.Concurrent": "(,4.3.32767]",
"System.Collections.Immutable": "(,10.0.0-preview.6.25358.103]",
"System.Collections.NonGeneric": "(,4.3.32767]",
"System.Collections.Specialized": "(,4.3.32767]",
"System.ComponentModel": "(,4.3.32767]",
"System.ComponentModel.Annotations": "(,4.3.32767]",
"System.ComponentModel.EventBasedAsync": "(,4.3.32767]",
"System.ComponentModel.Primitives": "(,4.3.32767]",
"System.ComponentModel.TypeConverter": "(,4.3.32767]",
"System.Console": "(,4.3.32767]",
"System.Data.Common": "(,4.3.32767]",
"System.Data.DataSetExtensions": "(,4.4.32767]",
"System.Diagnostics.Contracts": "(,4.3.32767]",
"System.Diagnostics.Debug": "(,4.3.32767]",
"System.Diagnostics.DiagnosticSource": "(,10.0.0-preview.6.25358.103]",
"System.Diagnostics.EventLog": "(,10.0.0-preview.6.25358.103]",
"System.Diagnostics.FileVersionInfo": "(,4.3.32767]",
"System.Diagnostics.Process": "(,4.3.32767]",
"System.Diagnostics.StackTrace": "(,4.3.32767]",
"System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]",
"System.Diagnostics.Tools": "(,4.3.32767]",
"System.Diagnostics.TraceSource": "(,4.3.32767]",
"System.Diagnostics.Tracing": "(,4.3.32767]",
"System.Drawing.Primitives": "(,4.3.32767]",
"System.Dynamic.Runtime": "(,4.3.32767]",
"System.Formats.Asn1": "(,10.0.0-preview.6.25358.103]",
"System.Formats.Cbor": "(,10.0.0-preview.6.25358.103]",
"System.Formats.Tar": "(,10.0.0-preview.6.25358.103]",
"System.Globalization": "(,4.3.32767]",
"System.Globalization.Calendars": "(,4.3.32767]",
"System.Globalization.Extensions": "(,4.3.32767]",
"System.IO": "(,4.3.32767]",
"System.IO.Compression": "(,4.3.32767]",
"System.IO.Compression.ZipFile": "(,4.3.32767]",
"System.IO.FileSystem": "(,4.3.32767]",
"System.IO.FileSystem.AccessControl": "(,4.4.32767]",
"System.IO.FileSystem.DriveInfo": "(,4.3.32767]",
"System.IO.FileSystem.Primitives": "(,4.3.32767]",
"System.IO.FileSystem.Watcher": "(,4.3.32767]",
"System.IO.IsolatedStorage": "(,4.3.32767]",
"System.IO.MemoryMappedFiles": "(,4.3.32767]",
"System.IO.Pipelines": "(,10.0.0-preview.6.25358.103]",
"System.IO.Pipes": "(,4.3.32767]",
"System.IO.Pipes.AccessControl": "(,5.0.32767]",
"System.IO.UnmanagedMemoryStream": "(,4.3.32767]",
"System.Linq": "(,4.3.32767]",
"System.Linq.AsyncEnumerable": "(,10.0.0-preview.6.25358.103]",
"System.Linq.Expressions": "(,4.3.32767]",
"System.Linq.Parallel": "(,4.3.32767]",
"System.Linq.Queryable": "(,4.3.32767]",
"System.Memory": "(,5.0.32767]",
"System.Net.Http": "(,4.3.32767]",
"System.Net.Http.Json": "(,10.0.0-preview.6.25358.103]",
"System.Net.NameResolution": "(,4.3.32767]",
"System.Net.NetworkInformation": "(,4.3.32767]",
"System.Net.Ping": "(,4.3.32767]",
"System.Net.Primitives": "(,4.3.32767]",
"System.Net.Requests": "(,4.3.32767]",
"System.Net.Security": "(,4.3.32767]",
"System.Net.ServerSentEvents": "(,10.0.0-preview.6.25358.103]",
"System.Net.Sockets": "(,4.3.32767]",
"System.Net.WebHeaderCollection": "(,4.3.32767]",
"System.Net.WebSockets": "(,4.3.32767]",
"System.Net.WebSockets.Client": "(,4.3.32767]",
"System.Numerics.Vectors": "(,5.0.32767]",
"System.ObjectModel": "(,4.3.32767]",
"System.Private.DataContractSerialization": "(,4.3.32767]",
"System.Private.Uri": "(,4.3.32767]",
"System.Reflection": "(,4.3.32767]",
"System.Reflection.DispatchProxy": "(,6.0.32767]",
"System.Reflection.Emit": "(,4.7.32767]",
"System.Reflection.Emit.ILGeneration": "(,4.7.32767]",
"System.Reflection.Emit.Lightweight": "(,4.7.32767]",
"System.Reflection.Extensions": "(,4.3.32767]",
"System.Reflection.Metadata": "(,10.0.0-preview.6.25358.103]",
"System.Reflection.Primitives": "(,4.3.32767]",
"System.Reflection.TypeExtensions": "(,4.3.32767]",
"System.Resources.Reader": "(,4.3.32767]",
"System.Resources.ResourceManager": "(,4.3.32767]",
"System.Resources.Writer": "(,4.3.32767]",
"System.Runtime": "(,4.3.32767]",
"System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]",
"System.Runtime.CompilerServices.VisualC": "(,4.3.32767]",
"System.Runtime.Extensions": "(,4.3.32767]",
"System.Runtime.Handles": "(,4.3.32767]",
"System.Runtime.InteropServices": "(,4.3.32767]",
"System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]",
"System.Runtime.Loader": "(,4.3.32767]",
"System.Runtime.Numerics": "(,4.3.32767]",
"System.Runtime.Serialization.Formatters": "(,4.3.32767]",
"System.Runtime.Serialization.Json": "(,4.3.32767]",
"System.Runtime.Serialization.Primitives": "(,4.3.32767]",
"System.Runtime.Serialization.Xml": "(,4.3.32767]",
"System.Security.AccessControl": "(,6.0.32767]",
"System.Security.Claims": "(,4.3.32767]",
"System.Security.Cryptography.Algorithms": "(,4.3.32767]",
"System.Security.Cryptography.Cng": "(,5.0.32767]",
"System.Security.Cryptography.Csp": "(,4.3.32767]",
"System.Security.Cryptography.Encoding": "(,4.3.32767]",
"System.Security.Cryptography.OpenSsl": "(,5.0.32767]",
"System.Security.Cryptography.Primitives": "(,4.3.32767]",
"System.Security.Cryptography.X509Certificates": "(,4.3.32767]",
"System.Security.Cryptography.Xml": "(,10.0.0-preview.6.25358.103]",
"System.Security.Principal": "(,4.3.32767]",
"System.Security.Principal.Windows": "(,5.0.32767]",
"System.Security.SecureString": "(,4.3.32767]",
"System.Text.Encoding": "(,4.3.32767]",
"System.Text.Encoding.CodePages": "(,10.0.0-preview.6.25358.103]",
"System.Text.Encoding.Extensions": "(,4.3.32767]",
"System.Text.Encodings.Web": "(,10.0.0-preview.6.25358.103]",
"System.Text.Json": "(,10.0.0-preview.6.25358.103]",
"System.Text.RegularExpressions": "(,4.3.32767]",
"System.Threading": "(,4.3.32767]",
"System.Threading.AccessControl": "(,10.0.0-preview.6.25358.103]",
"System.Threading.Channels": "(,10.0.0-preview.6.25358.103]",
"System.Threading.Overlapped": "(,4.3.32767]",
"System.Threading.RateLimiting": "(,10.0.0-preview.6.25358.103]",
"System.Threading.Tasks": "(,4.3.32767]",
"System.Threading.Tasks.Dataflow": "(,10.0.0-preview.6.25358.103]",
"System.Threading.Tasks.Extensions": "(,5.0.32767]",
"System.Threading.Tasks.Parallel": "(,4.3.32767]",
"System.Threading.Thread": "(,4.3.32767]",
"System.Threading.ThreadPool": "(,4.3.32767]",
"System.Threading.Timer": "(,4.3.32767]",
"System.ValueTuple": "(,4.5.32767]",
"System.Xml.ReaderWriter": "(,4.3.32767]",
"System.Xml.XDocument": "(,4.3.32767]",
"System.Xml.XmlDocument": "(,4.3.32767]",
"System.Xml.XmlSerializer": "(,4.3.32767]",
"System.Xml.XPath": "(,4.3.32767]",
"System.Xml.XPath.XDocument": "(,5.0.32767]"
}
}
}
}
}
}
@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\HomePC\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.15.0</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="C:\Users\HomePC\.nuget\packages\" />
<SourceRoot Include="C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages\" />
</ItemGroup>
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)microsoft.net.illink.tasks\10.0.0-preview.6.25358.103\build\Microsoft.NET.ILLink.Tasks.props" Condition="Exists('$(NuGetPackageRoot)microsoft.net.illink.tasks\10.0.0-preview.6.25358.103\build\Microsoft.NET.ILLink.Tasks.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.dotnet.ilcompiler\10.0.0-preview.6.25358.103\build\Microsoft.DotNet.ILCompiler.props" Condition="Exists('$(NuGetPackageRoot)microsoft.dotnet.ilcompiler\10.0.0-preview.6.25358.103\build\Microsoft.DotNet.ILCompiler.props')" />
<Import Project="$(NuGetPackageRoot)grpc.tools\2.76.0\build\Grpc.Tools.props" Condition="Exists('$(NuGetPackageRoot)grpc.tools\2.76.0\build\Grpc.Tools.props')" />
</ImportGroup>
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<PkgMicrosoft_NET_ILLink_Tasks Condition=" '$(PkgMicrosoft_NET_ILLink_Tasks)' == '' ">C:\Users\HomePC\.nuget\packages\microsoft.net.illink.tasks\10.0.0-preview.6.25358.103</PkgMicrosoft_NET_ILLink_Tasks>
<PkgMicrosoft_DotNet_ILCompiler Condition=" '$(PkgMicrosoft_DotNet_ILCompiler)' == '' ">C:\Users\HomePC\.nuget\packages\microsoft.dotnet.ilcompiler\10.0.0-preview.6.25358.103</PkgMicrosoft_DotNet_ILCompiler>
<PkgGrpc_Tools Condition=" '$(PkgGrpc_Tools)' == '' ">C:\Users\HomePC\.nuget\packages\grpc.tools\2.76.0</PkgGrpc_Tools>
</PropertyGroup>
</Project>
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)microsoft.aspnetcore.openapi\10.0.0-preview.6.25358.103\build\Microsoft.AspNetCore.OpenApi.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.aspnetcore.openapi\10.0.0-preview.6.25358.103\build\Microsoft.AspNetCore.OpenApi.targets')" />
<Import Project="$(NuGetPackageRoot)grpc.tools\2.76.0\build\Grpc.Tools.targets" Condition="Exists('$(NuGetPackageRoot)grpc.tools\2.76.0\build\Grpc.Tools.targets')" />
</ImportGroup>
</Project>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,76 @@
using System.Text.Json;
using MediatR;
using Nashel.BuildingBlocks.Domain;
using Nashel.Modules.Catalog.Domain.Aggregates;
using Nashel.Modules.Catalog.Domain.Repositories;
namespace Nashel.Modules.Catalog.Application.Commands;
/// <summary>
/// Команда создания категории.
/// </summary>
public record CreateCategoryCommand : IRequest<Result<Guid>>
{
/// <summary>
/// Название категории.
/// </summary>
public string Name { get; init; } = default!;
/// <summary>
/// URL-friendly идентификатор (слаг).
/// </summary>
public string Slug { get; init; } = default!;
/// <summary>
/// ID родительской категории (null для корневых).
/// </summary>
public Guid? ParentId { get; init; }
/// <summary>
/// Шаблон характеристик (JSON).
/// </summary>
public JsonDocument? AttributeSchema { get; init; }
public CreateCategoryCommand(string name, string slug, Guid? parentId, JsonDocument? attributeSchema)
{
Name = name;
Slug = slug;
ParentId = parentId;
AttributeSchema = attributeSchema;
}
public CreateCategoryCommand() { } // For deserialization
}
public class CreateCategoryCommandHandler : IRequestHandler<CreateCategoryCommand, Result<Guid>>
{
private readonly ICategoryRepository _repository;
public CreateCategoryCommandHandler(ICategoryRepository repository)
{
_repository = repository;
}
public async Task<Result<Guid>> Handle(CreateCategoryCommand request, CancellationToken cancellationToken)
{
if (request.ParentId.HasValue)
{
var parentCategory = await _repository.GetByIdAsync(request.ParentId.Value, cancellationToken);
if (parentCategory == null)
{
return Result<Guid>.Failure($"Родительская категория с ID {request.ParentId} не найдена.");
}
}
var category = new Category(
request.Name,
request.Slug,
request.ParentId,
request.AttributeSchema
);
await _repository.AddAsync(category, cancellationToken);
return Result<Guid>.Success(category.Id);
}
}
@@ -0,0 +1,83 @@
using System.Text.Json;
using MediatR;
using Nashel.BuildingBlocks.Application.Abstractions;
using Nashel.BuildingBlocks.Domain;
using Nashel.Modules.Catalog.Domain.Aggregates;
using Nashel.Modules.Catalog.Domain.Repositories;
using Nashel.Modules.Catalog.Domain.ValueObjects;
namespace Nashel.Modules.Catalog.Application.Commands;
/// <summary>
/// Команда создания услуги (оффера).
/// </summary>
public record CreateOfferCommand : IRequest<Result<Guid>>
{
public Guid CategoryId { get; init; }
/// <summary>
/// Заголовок объявления.
/// </summary>
public string Title { get; init; } = default!;
/// <summary>
/// Полное описание услуги.
/// </summary>
public string Description { get; init; } = default!;
/// <summary>
/// Цена услуги.
/// </summary>
public Price Price { get; init; } = default!;
/// <summary>
/// Характеристики услуги (JSON).
/// </summary>
public JsonDocument? Attributes { get; init; }
public List<string>? Images { get; init; }
public CreateOfferCommand(Guid categoryId, string title, string description, Price price, JsonDocument? attributes, List<string>? images)
{
CategoryId = categoryId;
Title = title;
Description = description;
Price = price;
Attributes = attributes;
Images = images;
}
public CreateOfferCommand() { }
}
public class CreateOfferCommandHandler : IRequestHandler<CreateOfferCommand, Result<Guid>>
{
private readonly IOfferRepository _repository;
private readonly ICurrentUserService _currentUserService;
public CreateOfferCommandHandler(IOfferRepository repository, ICurrentUserService currentUserService)
{
_repository = repository;
_currentUserService = currentUserService;
}
public async Task<Result<Guid>> Handle(CreateOfferCommand request, CancellationToken cancellationToken)
{
var userId = _currentUserService.UserId;
if (userId == null) return Result<Guid>.Failure("Неавторизован");
var offer = new Offer(
userId.Value,
request.CategoryId,
request.Title,
request.Description,
request.Price,
request.Attributes,
request.Images
);
await _repository.AddAsync(offer, cancellationToken);
return Result<Guid>.Success(offer.Id);
}
}
@@ -0,0 +1,32 @@
using MediatR;
using Nashel.BuildingBlocks.Application.Abstractions;
using Nashel.Modules.Catalog.Domain.Repositories;
namespace Nashel.Modules.Catalog.Application.Commands;
public record DeleteOfferCommand(Guid OfferId) : IRequest<bool>;
public class DeleteOfferCommandHandler : IRequestHandler<DeleteOfferCommand, bool>
{
private readonly IOfferRepository _repository;
private readonly ICurrentUserService _currentUser;
public DeleteOfferCommandHandler(IOfferRepository repository, ICurrentUserService currentUser)
{
_repository = repository;
_currentUser = currentUser;
}
public async Task<bool> Handle(DeleteOfferCommand request, CancellationToken cancellationToken)
{
var offer = await _repository.GetByIdAsync(request.OfferId, cancellationToken);
if (offer == null) throw new Exception("Offer not found");
if (offer.PerformerId != _currentUser.UserId)
throw new UnauthorizedAccessException("Not your offer");
offer.Delete();
await _repository.UpdateAsync(offer, cancellationToken);
return true;
}
}
@@ -0,0 +1,32 @@
using MediatR;
using Nashel.BuildingBlocks.Application.Abstractions;
using Nashel.Modules.Catalog.Domain.Repositories;
namespace Nashel.Modules.Catalog.Application.Commands;
public record ToggleOfferStatusCommand(Guid OfferId) : IRequest<bool>;
public class ToggleOfferStatusCommandHandler : IRequestHandler<ToggleOfferStatusCommand, bool>
{
private readonly IOfferRepository _repository;
private readonly ICurrentUserService _currentUser;
public ToggleOfferStatusCommandHandler(IOfferRepository repository, ICurrentUserService currentUser)
{
_repository = repository;
_currentUser = currentUser;
}
public async Task<bool> Handle(ToggleOfferStatusCommand request, CancellationToken cancellationToken)
{
var offer = await _repository.GetByIdAsync(request.OfferId, cancellationToken);
if (offer == null) throw new Exception("Offer not found");
if (offer.PerformerId != _currentUser.UserId)
throw new UnauthorizedAccessException("Not your offer");
offer.ToggleActive();
await _repository.UpdateAsync(offer, cancellationToken);
return offer.IsActive;
}
}
@@ -0,0 +1,48 @@
using System.Text.Json;
using MediatR;
using Nashel.BuildingBlocks.Application.Abstractions;
using Nashel.Modules.Catalog.Domain.Aggregates;
using Nashel.Modules.Catalog.Domain.Repositories;
using Nashel.Modules.Catalog.Domain.ValueObjects;
namespace Nashel.Modules.Catalog.Application.Commands;
public record UpdateOfferCommand(
Guid OfferId,
string Title,
string Description,
decimal PriceAmount,
int PriceType, // 0-Fixed, 1-Hourly, 2-Negotiable
Dictionary<string, string>? Attributes,
List<string>? Images) : IRequest<bool>;
public class UpdateOfferCommandHandler : IRequestHandler<UpdateOfferCommand, bool>
{
private readonly IOfferRepository _repository;
private readonly ICurrentUserService _currentUser;
public UpdateOfferCommandHandler(IOfferRepository repository, ICurrentUserService currentUser)
{
_repository = repository;
_currentUser = currentUser;
}
public async Task<bool> Handle(UpdateOfferCommand request, CancellationToken cancellationToken)
{
var offer = await _repository.GetByIdAsync(request.OfferId, cancellationToken);
if (offer == null) throw new Exception("Offer not found");
if (offer.PerformerId != _currentUser.UserId)
throw new UnauthorizedAccessException("Not your offer");
var price = new Price(request.PriceAmount, (Nashel.Modules.Catalog.Domain.Enums.OfferType)request.PriceType);
var jsonAttrs = request.Attributes != null && request.Attributes.Count > 0
? JsonDocument.Parse(JsonSerializer.Serialize(request.Attributes))
: null;
offer.Update(request.Title, request.Description, price, jsonAttrs, request.Images);
await _repository.UpdateAsync(offer, cancellationToken);
return true;
}
}
@@ -0,0 +1,57 @@
using MediatR;
using Microsoft.Extensions.Options;
using Nashel.BuildingBlocks.Application.Abstractions;
using Nashel.BuildingBlocks.Application.Configurations;
using Nashel.BuildingBlocks.Application.Exceptions;
namespace Nashel.Modules.Catalog.Application.Commands;
/// <summary>
/// Команда загрузки изображения услуги.
/// </summary>
public record UploadOfferImageCommand(byte[] Content, string FileName) : IRequest<string>;
public class UploadOfferImageCommandHandler : IRequestHandler<UploadOfferImageCommand, string>
{
private readonly ICurrentUserService _currentUserService;
private readonly MediaSettings _settings;
public UploadOfferImageCommandHandler(
ICurrentUserService currentUserService,
IOptions<MediaSettings> settings)
{
_currentUserService = currentUserService;
_settings = settings.Value;
}
public Task<string> Handle(UploadOfferImageCommand request, CancellationToken cancellationToken)
{
var userId = _currentUserService.UserId;
if (userId == null)
{
throw new UnauthorizedAccessException();
}
if (request.Content.Length > _settings.MaxFileSizeInBytes)
{
throw new BusinessRuleException($"Размер файла слишком велик. Максимум: {_settings.MaxFileSizeInBytes / 1024 / 1024} МБ.");
}
var extension = Path.GetExtension(request.FileName).ToLowerInvariant();
if (!_settings.AllowedExtensions.Contains(extension))
{
throw new BusinessRuleException($"Допустимые форматы: {string.Join(", ", _settings.AllowedExtensions)}.");
}
var base64String = Convert.ToBase64String(request.Content);
var mimeType = extension switch
{
".png" => "image/png",
".webp" => "image/webp",
_ => "image/jpeg"
};
return Task.FromResult($"data:{mimeType};base64,{base64String}");
}
}
@@ -0,0 +1,139 @@
using System.Text.Json;
using Nashel.Modules.Catalog.Domain.ValueObjects;
namespace Nashel.Modules.Catalog.Application.Common;
/// <summary>
/// DTO категории.
/// </summary>
public record CategoryDto
{
/// <summary>
/// Уникальный идентификатор категории.
/// </summary>
public Guid Id { get; init; }
/// <summary>
/// Название категории.
/// </summary>
public string Name { get; private set; } = default!;
/// <summary>
/// URL-совместимый идентификатор (slug).
/// </summary>
public string Slug { get; init; } = default!;
/// <summary>
/// Идентификатор родительской категории (null, если корневая).
/// </summary>
public Guid? ParentId { get; init; }
/// <summary>
/// JSON-схема характеристик, специфичных для категории.
/// </summary>
public JsonDocument? AttributeSchema { get; init; }
public CategoryDto(Guid id, string name, string slug, Guid? parentId, JsonDocument? attributeSchema)
{
Id = id;
Name = name;
Slug = slug;
ParentId = parentId;
AttributeSchema = attributeSchema;
}
public CategoryDto() { }
}
/// <summary>
/// DTO услуги/оффера.
/// </summary>
public record OfferDto
{
/// <summary>
/// Уникальный идентификатор услуги.
/// </summary>
public Guid Id { get; init; }
/// <summary>
/// Идентификатор владельца (исполнителя).
/// </summary>
public Guid PerformerId { get; init; }
/// <summary>
/// Идентификатор категории.
/// </summary>
public Guid CategoryId { get; init; }
/// <summary>
/// Заголовок услуги.
/// </summary>
public string Title { get; init; } = default!;
/// <summary>
/// Подробное описание услуги.
/// </summary>
public string Description { get; init; } = default!;
/// <summary>
/// Цена услуги.
/// </summary>
public Price Price { get; init; } = default!;
/// <summary>
/// JSON-объект с характеристиками услуги.
/// </summary>
public JsonDocument? Attributes { get; init; }
/// <summary>
/// Флаг активности услуги.
/// </summary>
public bool IsActive { get; init; }
/// <summary>
/// Изображения услуги.
/// </summary>
public List<string> Images { get; init; } = new();
/// <summary>
/// Имя исполнителя.
/// </summary>
public string? PerformerName { get; init; }
/// <summary>
/// Роль исполнителя (Мастер, Специалист и т.д.).
/// </summary>
public string? PerformerRole { get; init; }
/// <summary>
/// Средний рейтинг услуги.
/// </summary>
public double Rating { get; init; }
/// <summary>
/// Количество отзывов об услуге.
/// </summary>
public int ReviewsCount { get; init; }
public OfferDto(Guid id, Guid performerId, Guid categoryId, string title, string description, Price price, JsonDocument? attributes, bool isActive, List<string>? images = null, string? performerName = null, string? performerRole = null, double rating = 0, int reviewsCount = 0)
{
Id = id;
PerformerId = performerId;
CategoryId = categoryId;
Title = title;
Description = description;
Price = price;
Attributes = attributes;
IsActive = isActive;
if (images != null)
{
Images.AddRange(images);
}
PerformerName = performerName;
PerformerRole = performerRole;
Rating = rating;
ReviewsCount = reviewsCount;
}
public OfferDto() { }
}
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\Nashel.BuildingBlocks.csproj" />
<ProjectReference Include="..\Domain\Nashel.Modules.Catalog.Domain.csproj" />
<ProjectReference Include="..\..\Identity\Application\Nashel.Modules.Identity.Application.csproj" />
<ProjectReference Include="..\..\Reputation\Application\Nashel.Modules.Reputation.Application.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MediatR" Version="14.0.0" />
</ItemGroup>
<PropertyGroup>
<RootNamespace>Nashel.Modules.Catalog.Application</RootNamespace>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
</Project>
@@ -0,0 +1,41 @@
using System.Text.Json;
using MediatR;
using Nashel.BuildingBlocks.Domain;
using Nashel.Modules.Catalog.Application.Common;
using Nashel.Modules.Catalog.Domain.Repositories;
namespace Nashel.Modules.Catalog.Application.Queries;
/// <summary>
/// Запрос дерева категорий.
/// </summary>
public record GetCategoriesQuery() : IRequest<Result<List<CategoryDto>>>;
public class GetCategoriesQueryHandler : IRequestHandler<GetCategoriesQuery, Result<List<CategoryDto>>>
{
private readonly ICategoryRepository _repository;
public GetCategoriesQueryHandler(ICategoryRepository repository)
{
_repository = repository;
}
public async Task<Result<List<CategoryDto>>> Handle(GetCategoriesQuery request, CancellationToken cancellationToken)
{
var rawCategories = await _repository.GetAllAsync(cancellationToken);
// Преобразование в DTO (для дерева логика нужна сложнее, но пока плоский список для старта)
// Если нужно дерево: нужно иметь DTO с List<CategoryDto> Children
// Для MVP возвращаем плоский список, фронтенд сам строит дерево по ParentId
var list = rawCategories.Select(c => new CategoryDto(
c.Id,
c.Name,
c.Slug,
c.ParentId,
c.AttributeSchema
)).ToList();
return Result<List<CategoryDto>>.Success(list);
}
}
@@ -0,0 +1,46 @@
using MediatR;
using Nashel.BuildingBlocks.Application.Abstractions;
using Nashel.BuildingBlocks.Domain;
using Nashel.Modules.Catalog.Application.Common;
using Nashel.Modules.Catalog.Domain.Repositories;
namespace Nashel.Modules.Catalog.Application.Queries;
/// <summary>
/// Запрос получения услуг текущего пользователя.
/// </summary>
public record GetMyOffersQuery() : IRequest<Result<List<OfferDto>>>;
public class GetMyOffersQueryHandler : IRequestHandler<GetMyOffersQuery, Result<List<OfferDto>>>
{
private readonly IOfferRepository _repository;
private readonly ICurrentUserService _currentUserService;
public GetMyOffersQueryHandler(IOfferRepository repository, ICurrentUserService currentUserService)
{
_repository = repository;
_currentUserService = currentUserService;
}
public async Task<Result<List<OfferDto>>> Handle(GetMyOffersQuery request, CancellationToken cancellationToken)
{
var userId = _currentUserService.UserId;
if (userId == null) return Result<List<OfferDto>>.Failure("Неавторизован");
var offers = await _repository.GetByPerformerIdAsync(userId.Value, cancellationToken);
var list = offers.Select(offer => new OfferDto(
offer.Id,
offer.PerformerId,
offer.CategoryId,
offer.Title,
offer.Description ?? string.Empty,
offer.Price,
offer.Attributes,
offer.IsActive,
offer.Images
)).ToList();
return Result<List<OfferDto>>.Success(list);
}
}
@@ -0,0 +1,54 @@
using MediatR;
using Nashel.BuildingBlocks.Domain;
using Nashel.Modules.Catalog.Application.Common;
using Nashel.Modules.Catalog.Domain.Repositories;
namespace Nashel.Modules.Catalog.Application.Queries;
/// <summary>
/// Запрос деталей услуги по ID.
/// </summary>
public record GetOfferByIdQuery(Guid Id) : IRequest<Result<OfferDto>>;
public class GetOfferByIdQueryHandler : IRequestHandler<GetOfferByIdQuery, Result<OfferDto>>
{
private readonly IOfferRepository _repository;
private readonly IMediator _mediator;
public GetOfferByIdQueryHandler(IOfferRepository repository, IMediator mediator)
{
_repository = repository;
_mediator = mediator;
}
public async Task<Result<OfferDto>> Handle(GetOfferByIdQuery request, CancellationToken cancellationToken)
{
var offer = await _repository.GetByIdAsync(request.Id, cancellationToken);
if (offer == null)
{
throw new Nashel.BuildingBlocks.Application.Exceptions.NotFoundException("Offer", request.Id);
}
// Получаем данные исполнителя и рейтинги
var userDetailsResult = await _mediator.Send(new Nashel.Modules.Identity.Application.Queries.GetUserDetailsQuery(offer.PerformerId), cancellationToken);
var profileRatingResult = await _mediator.Send(new Nashel.Modules.Reputation.Application.Queries.GetProfileRatingQuery(offer.PerformerId), cancellationToken);
var dto = new OfferDto(
offer.Id,
offer.PerformerId,
offer.CategoryId,
offer.Title,
offer.Description ?? string.Empty,
offer.Price,
offer.Attributes,
offer.IsActive,
offer.Images,
performerName: (userDetailsResult != null && userDetailsResult.IsSuccess && userDetailsResult.Value != null) ? userDetailsResult.Value.FullName : "Неизвестный исполнитель",
performerRole: (userDetailsResult != null && userDetailsResult.IsSuccess && userDetailsResult.Value != null) ? userDetailsResult.Value.PrimaryRole : "Пользователь",
rating: profileRatingResult?.AverageRating ?? 0,
reviewsCount: profileRatingResult?.TotalReviews ?? 0
);
return Result<OfferDto>.Success(dto);
}
}
@@ -0,0 +1,50 @@
using System.Text.Json;
using System.Text.Json.Nodes;
namespace Nashel.Modules.Catalog.Domain.Aggregates;
/// <summary>
/// Категория услуг.
/// </summary>
public class Category
{
/// <summary>
/// Уникальный идентификатор.
/// </summary>
public Guid Id { get; private set; }
/// <summary>
/// Название категории.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// URL-friendly идентификатор (слаг).
/// </summary>
public string Slug { get; private set; }
/// <summary>
/// ID родительской категории (null для корневых).
/// </summary>
public Guid? ParentId { get; private set; }
/// <summary>
/// Шаблон характеристик (JSON).
/// </summary>
public JsonDocument? AttributeSchema { get; private set; }
// Конструктор по умолчанию для EF Core
private Category() { }
/// <summary>
/// Создает новую категорию.
/// </summary>
public Category(string name, string slug, Guid? parentId, JsonDocument? attributeSchema = null)
{
Id = Guid.NewGuid();
Name = name;
Slug = slug;
ParentId = parentId;
AttributeSchema = attributeSchema;
}
}
@@ -0,0 +1,114 @@
using System.Text.Json;
using Nashel.Modules.Catalog.Domain.ValueObjects;
namespace Nashel.Modules.Catalog.Domain.Aggregates;
/// <summary>
/// Услуга или оффер исполнителя.
/// </summary>
public class Offer
{
/// <summary>
/// Уникальный идентификатор оффера.
/// </summary>
public Guid Id { get; private set; }
/// <summary>
/// ID владельца (пользователя/исполнителя).
/// </summary>
public Guid PerformerId { get; private set; }
/// <summary>
/// ID услуги/категории.
/// </summary>
public Guid CategoryId { get; private set; }
/// <summary>
/// Заголовок объявления.
/// </summary>
public string Title { get; private set; }
/// <summary>
/// Полное описание.
/// </summary>
public string? Description { get; private set; }
/// <summary>
/// Цена (сумма, валюта, тип оплаты).
/// </summary>
public Price Price { get; private set; }
/// <summary>
/// Характеристики (JSONB).
/// </summary>
public JsonDocument? Attributes { get; private set; }
/// <summary>
/// Активно ли объявление.
/// </summary>
public bool IsActive { get; private set; }
/// <summary>
/// Удалено ли объявление (Soft Delete).
/// </summary>
public bool IsDeleted { get; private set; }
/// <summary>
/// Дата создания.
/// </summary>
public DateTimeOffset CreatedAt { get; private set; }
/// <summary>
/// Коллекция изображений (Base64 URL)
/// </summary>
public List<string> Images { get; private set; } = new();
// Конструктор по умолчанию для EF Core
private Offer() { }
/// <summary>
/// Создает новый оффер.
/// </summary>
public Offer(Guid performerId, Guid categoryId, string title, string description, Price price, JsonDocument? attributes = null, List<string>? images = null)
{
Id = Guid.NewGuid();
PerformerId = performerId;
CategoryId = categoryId;
Title = title;
Description = description;
Price = price;
Attributes = attributes;
IsActive = true;
IsDeleted = false;
CreatedAt = DateTimeOffset.UtcNow;
if (images != null)
{
Images.AddRange(images);
}
}
public void Update(string title, string description, Price price, JsonDocument? attributes, List<string>? images)
{
Title = title;
Description = description;
Price = price;
Attributes = attributes;
Images.Clear();
if (images != null)
{
Images.AddRange(images);
}
}
public void ToggleActive()
{
IsActive = !IsActive;
}
public void Delete()
{
IsDeleted = true;
IsActive = false;
}
}
@@ -0,0 +1,22 @@
namespace Nashel.Modules.Catalog.Domain.Enums;
/// <summary>
/// Тип оплаты услуги.
/// </summary>
public enum OfferType
{
/// <summary>
/// Фиксированная цена.
/// </summary>
Fixed,
/// <summary>
/// Почасовая оплата.
/// </summary>
Hourly,
/// <summary>
/// Договорная цена.
/// </summary>
Negotiable
}
@@ -1,18 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\..\BuildingBlocks\Nashel.RF.BuildingBlocks.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MediatR" Version="14.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
<ProjectReference Include="..\..\..\BuildingBlocks\Nashel.BuildingBlocks.csproj" />
</ItemGroup>
<PropertyGroup>
<RootNamespace>Nashel.Modules.Catalog.Domain</RootNamespace>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
</Project>
@@ -0,0 +1,10 @@
using Nashel.Modules.Catalog.Domain.Aggregates;
namespace Nashel.Modules.Catalog.Domain.Repositories;
public interface ICategoryRepository
{
Task<Category?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
Task<List<Category>> GetAllAsync(CancellationToken cancellationToken = default);
Task AddAsync(Category category, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,11 @@
using Nashel.Modules.Catalog.Domain.Aggregates;
namespace Nashel.Modules.Catalog.Domain.Repositories;
public interface IOfferRepository
{
Task<Offer?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
Task<List<Offer>> GetByPerformerIdAsync(Guid performerId, CancellationToken cancellationToken = default);
Task AddAsync(Offer offer, CancellationToken cancellationToken = default);
Task UpdateAsync(Offer offer, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,35 @@
using System.Text.Json.Serialization;
using Nashel.Modules.Catalog.Domain.Enums;
namespace Nashel.Modules.Catalog.Domain.ValueObjects;
/// <summary>
/// Значение цены услуги.
/// </summary>
public record Price
{
/// <summary>
/// Сумма.
/// </summary>
public decimal Amount { get; init; }
/// <summary>
/// Валюта (по умолчанию RUB).
/// </summary>
public string Currency { get; init; } = "RUB";
/// <summary>
/// Тип оплаты (фиксированная, почасовая, договорная).
/// </summary>
public OfferType Type { get; init; }
// Конструктор по умолчанию для EF Core и сериализации
public Price() { }
public Price(decimal amount, OfferType type, string currency = "RUB")
{
Amount = amount;
Type = type;
Currency = currency;
}
}
@@ -0,0 +1,34 @@
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Nashel.Modules.Catalog.Application.Commands;
using Nashel.Modules.Catalog.Domain.Repositories;
using Nashel.Modules.Catalog.Infrastructure.Persistence;
using Nashel.Modules.Catalog.Infrastructure.Repositories;
namespace Nashel.Modules.Catalog.Infrastructure;
public static class DependencyInjection
{
public static IServiceCollection AddCatalogModule(this IServiceCollection services, IConfiguration configuration)
{
// 1. Регистрация DbContext
services.AddDbContext<CatalogDbContext>(options =>
{
options.UseNpgsql(configuration.GetConnectionString("DefaultConnection"));
});
// 2. Репозитории
services.AddScoped<ICategoryRepository, CategoryRepository>();
services.AddScoped<IOfferRepository, OfferRepository>();
// 3. MediatR (сканируем сборку Application)
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(CreateCategoryCommand).Assembly);
});
return services;
}
}
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\Nashel.BuildingBlocks.csproj" />
<ProjectReference Include="..\Application\Nashel.Modules.Catalog.Application.csproj" />
<ProjectReference Include="..\Domain\Nashel.Modules.Catalog.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
</ItemGroup>
<PropertyGroup>
<RootNamespace>Nashel.Modules.Catalog.Infrastructure</RootNamespace>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
@@ -0,0 +1,55 @@
using Microsoft.EntityFrameworkCore;
using Nashel.Modules.Catalog.Domain.Aggregates;
namespace Nashel.Modules.Catalog.Infrastructure.Persistence;
public class CatalogDbContext : DbContext
{
public DbSet<Category> Categories { get; set; } = null!;
public DbSet<Offer> Offers { get; set; } = null!;
public CatalogDbContext(DbContextOptions<CatalogDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasPostgresExtension("postgis"); // Если нужно для чего-то еще, но вообще JSONB встроен.
// Но лучше просто не трогать расширения если не уверены.
// Для JSONB ничего особенного не нужно, кроме HasColumnType("jsonb").
modelBuilder.Entity<Category>(entity =>
{
entity.ToTable("Categories", "catalog");
entity.HasKey(e => e.Id);
entity.HasIndex(e => e.Slug).IsUnique();
// Self-referencing
entity.HasOne<Category>()
.WithMany() // Навигационное свойство Children не добавлено в доменную модель явно, но связь есть
.HasForeignKey(e => e.ParentId)
.OnDelete(DeleteBehavior.Restrict);
entity.Property(e => e.AttributeSchema)
.HasColumnType("jsonb");
});
modelBuilder.Entity<Offer>(entity =>
{
entity.ToTable("Offers", "catalog");
entity.HasKey(e => e.Id);
entity.Property(e => e.Attributes)
.HasColumnType("jsonb");
// GIN Index
entity.HasIndex(e => e.Attributes)
.HasMethod("gin");
entity.OwnsOne(e => e.Price, price =>
{
price.Property(p => p.Amount).HasColumnName("PriceAmount");
price.Property(p => p.Currency).HasColumnName("PriceCurrency").HasMaxLength(3);
price.Property(p => p.Type).HasColumnName("PriceType"); // Enum as int by default
});
});
}
}
@@ -0,0 +1,137 @@
// <auto-generated />
using System;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Nashel.Modules.Catalog.Infrastructure.Persistence;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Nashel.Modules.Catalog.Infrastructure.Persistence.Migrations
{
[DbContext(typeof(CatalogDbContext))]
[Migration("20260210114511_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis");
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Category", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<JsonDocument>("AttributeSchema")
.HasColumnType("jsonb");
b.Property<Guid?>("ParentId")
.HasColumnType("uuid");
b.Property<string>("Slug")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("ParentId");
b.HasIndex("Slug")
.IsUnique();
b.ToTable("Categories", "catalog");
});
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Offer", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<JsonDocument>("Attributes")
.HasColumnType("jsonb");
b.Property<Guid>("CategoryId")
.HasColumnType("uuid");
b.Property<string>("Description")
.HasColumnType("text");
b.Property<bool>("IsActive")
.HasColumnType("boolean");
b.Property<Guid>("OwnerId")
.HasColumnType("uuid");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("Attributes");
NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Attributes"), "gin");
b.ToTable("Offers", "catalog");
});
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Category", b =>
{
b.HasOne("Nashel.Modules.Catalog.Domain.Aggregates.Category", null)
.WithMany()
.HasForeignKey("ParentId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Offer", b =>
{
b.OwnsOne("Nashel.Modules.Catalog.Domain.ValueObjects.Price", "Price", b1 =>
{
b1.Property<Guid>("OfferId")
.HasColumnType("uuid");
b1.Property<decimal>("Amount")
.HasColumnType("numeric")
.HasColumnName("PriceAmount");
b1.Property<string>("Currency")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("PriceCurrency");
b1.Property<int>("Type")
.HasColumnType("integer")
.HasColumnName("PriceType");
b1.HasKey("OfferId");
b1.ToTable("Offers", "catalog");
b1.WithOwner()
.HasForeignKey("OfferId");
});
b.Navigation("Price")
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,98 @@
using System;
using System.Text.Json;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Nashel.Modules.Catalog.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.EnsureSchema(
name: "catalog");
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:PostgresExtension:postgis", ",,");
migrationBuilder.CreateTable(
name: "Categories",
schema: "catalog",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Title = table.Column<string>(type: "text", nullable: false),
Slug = table.Column<string>(type: "text", nullable: false),
ParentId = table.Column<Guid>(type: "uuid", nullable: true),
AttributeSchema = table.Column<JsonDocument>(type: "jsonb", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Categories", x => x.Id);
table.ForeignKey(
name: "FK_Categories_Categories_ParentId",
column: x => x.ParentId,
principalSchema: "catalog",
principalTable: "Categories",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "Offers",
schema: "catalog",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
OwnerId = table.Column<Guid>(type: "uuid", nullable: false),
CategoryId = table.Column<Guid>(type: "uuid", nullable: false),
Title = table.Column<string>(type: "text", nullable: false),
Description = table.Column<string>(type: "text", nullable: true),
PriceAmount = table.Column<decimal>(type: "numeric", nullable: false),
PriceCurrency = table.Column<string>(type: "character varying(3)", maxLength: 3, nullable: false),
PriceType = table.Column<int>(type: "integer", nullable: false),
Attributes = table.Column<JsonDocument>(type: "jsonb", nullable: true),
IsActive = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Offers", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_Categories_ParentId",
schema: "catalog",
table: "Categories",
column: "ParentId");
migrationBuilder.CreateIndex(
name: "IX_Categories_Slug",
schema: "catalog",
table: "Categories",
column: "Slug",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Offers_Attributes",
schema: "catalog",
table: "Offers",
column: "Attributes")
.Annotation("Npgsql:IndexMethod", "gin");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Categories",
schema: "catalog");
migrationBuilder.DropTable(
name: "Offers",
schema: "catalog");
}
}
}
@@ -0,0 +1,140 @@
// <auto-generated />
using System;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Nashel.Modules.Catalog.Infrastructure.Persistence;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Nashel.Modules.Catalog.Infrastructure.Persistence.Migrations
{
[DbContext(typeof(CatalogDbContext))]
[Migration("20260302095500_CatalogRefactor")]
partial class CatalogRefactor
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis");
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Category", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<JsonDocument>("AttributeSchema")
.HasColumnType("jsonb");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<Guid?>("ParentId")
.HasColumnType("uuid");
b.Property<string>("Slug")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("ParentId");
b.HasIndex("Slug")
.IsUnique();
b.ToTable("Categories", "catalog");
});
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Offer", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<JsonDocument>("Attributes")
.HasColumnType("jsonb");
b.Property<Guid>("CategoryId")
.HasColumnType("uuid");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.HasColumnType("text");
b.Property<bool>("IsActive")
.HasColumnType("boolean");
b.Property<Guid>("PerformerId")
.HasColumnType("uuid");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("Attributes");
NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Attributes"), "gin");
b.ToTable("Offers", "catalog");
});
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Category", b =>
{
b.HasOne("Nashel.Modules.Catalog.Domain.Aggregates.Category", null)
.WithMany()
.HasForeignKey("ParentId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Offer", b =>
{
b.OwnsOne("Nashel.Modules.Catalog.Domain.ValueObjects.Price", "Price", b1 =>
{
b1.Property<Guid>("OfferId")
.HasColumnType("uuid");
b1.Property<decimal>("Amount")
.HasColumnType("numeric")
.HasColumnName("PriceAmount");
b1.Property<string>("Currency")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("PriceCurrency");
b1.Property<int>("Type")
.HasColumnType("integer")
.HasColumnName("PriceType");
b1.HasKey("OfferId");
b1.ToTable("Offers", "catalog");
b1.WithOwner()
.HasForeignKey("OfferId");
});
b.Navigation("Price")
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,56 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Nashel.Modules.Catalog.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class CatalogRefactor : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "OwnerId",
schema: "catalog",
table: "Offers",
newName: "PerformerId");
migrationBuilder.RenameColumn(
name: "Title",
schema: "catalog",
table: "Categories",
newName: "Name");
migrationBuilder.AddColumn<DateTimeOffset>(
name: "CreatedAt",
schema: "catalog",
table: "Offers",
type: "timestamp with time zone",
nullable: false,
defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CreatedAt",
schema: "catalog",
table: "Offers");
migrationBuilder.RenameColumn(
name: "PerformerId",
schema: "catalog",
table: "Offers",
newName: "OwnerId");
migrationBuilder.RenameColumn(
name: "Name",
schema: "catalog",
table: "Categories",
newName: "Title");
}
}
}
@@ -0,0 +1,148 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Nashel.Modules.Catalog.Infrastructure.Persistence;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Nashel.Modules.Catalog.Infrastructure.Persistence.Migrations
{
[DbContext(typeof(CatalogDbContext))]
[Migration("20260306185654_AddOfferImagesAndSoftDelete")]
partial class AddOfferImagesAndSoftDelete
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis");
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Category", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<JsonDocument>("AttributeSchema")
.HasColumnType("jsonb");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<Guid?>("ParentId")
.HasColumnType("uuid");
b.Property<string>("Slug")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("ParentId");
b.HasIndex("Slug")
.IsUnique();
b.ToTable("Categories", "catalog");
});
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Offer", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<JsonDocument>("Attributes")
.HasColumnType("jsonb");
b.Property<Guid>("CategoryId")
.HasColumnType("uuid");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.HasColumnType("text");
b.PrimitiveCollection<List<string>>("Images")
.IsRequired()
.HasColumnType("text[]");
b.Property<bool>("IsActive")
.HasColumnType("boolean");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean");
b.Property<Guid>("PerformerId")
.HasColumnType("uuid");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("Attributes");
NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Attributes"), "gin");
b.ToTable("Offers", "catalog");
});
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Category", b =>
{
b.HasOne("Nashel.Modules.Catalog.Domain.Aggregates.Category", null)
.WithMany()
.HasForeignKey("ParentId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Offer", b =>
{
b.OwnsOne("Nashel.Modules.Catalog.Domain.ValueObjects.Price", "Price", b1 =>
{
b1.Property<Guid>("OfferId")
.HasColumnType("uuid");
b1.Property<decimal>("Amount")
.HasColumnType("numeric")
.HasColumnName("PriceAmount");
b1.Property<string>("Currency")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("PriceCurrency");
b1.Property<int>("Type")
.HasColumnType("integer")
.HasColumnName("PriceType");
b1.HasKey("OfferId");
b1.ToTable("Offers", "catalog");
b1.WithOwner()
.HasForeignKey("OfferId");
});
b.Navigation("Price")
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,45 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Nashel.Modules.Catalog.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class AddOfferImagesAndSoftDelete : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<List<string>>(
name: "Images",
schema: "catalog",
table: "Offers",
type: "text[]",
nullable: false,
defaultValueSql: "'{}'");
migrationBuilder.AddColumn<bool>(
name: "IsDeleted",
schema: "catalog",
table: "Offers",
type: "boolean",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Images",
schema: "catalog",
table: "Offers");
migrationBuilder.DropColumn(
name: "IsDeleted",
schema: "catalog",
table: "Offers");
}
}
}
@@ -0,0 +1,148 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Nashel.Modules.Catalog.Infrastructure.Persistence;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Nashel.Modules.Catalog.Infrastructure.Persistence.Migrations
{
[DbContext(typeof(CatalogDbContext))]
[Migration("20260310145100_BackendUpdate_Catalog")]
partial class BackendUpdate_Catalog
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis");
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Category", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<JsonDocument>("AttributeSchema")
.HasColumnType("jsonb");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<Guid?>("ParentId")
.HasColumnType("uuid");
b.Property<string>("Slug")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("ParentId");
b.HasIndex("Slug")
.IsUnique();
b.ToTable("Categories", "catalog");
});
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Offer", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<JsonDocument>("Attributes")
.HasColumnType("jsonb");
b.Property<Guid>("CategoryId")
.HasColumnType("uuid");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.HasColumnType("text");
b.PrimitiveCollection<List<string>>("Images")
.IsRequired()
.HasColumnType("text[]");
b.Property<bool>("IsActive")
.HasColumnType("boolean");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean");
b.Property<Guid>("PerformerId")
.HasColumnType("uuid");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("Attributes");
NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Attributes"), "gin");
b.ToTable("Offers", "catalog");
});
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Category", b =>
{
b.HasOne("Nashel.Modules.Catalog.Domain.Aggregates.Category", null)
.WithMany()
.HasForeignKey("ParentId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Offer", b =>
{
b.OwnsOne("Nashel.Modules.Catalog.Domain.ValueObjects.Price", "Price", b1 =>
{
b1.Property<Guid>("OfferId")
.HasColumnType("uuid");
b1.Property<decimal>("Amount")
.HasColumnType("numeric")
.HasColumnName("PriceAmount");
b1.Property<string>("Currency")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("PriceCurrency");
b1.Property<int>("Type")
.HasColumnType("integer")
.HasColumnName("PriceType");
b1.HasKey("OfferId");
b1.ToTable("Offers", "catalog");
b1.WithOwner()
.HasForeignKey("OfferId");
});
b.Navigation("Price")
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Nashel.Modules.Catalog.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class BackendUpdate_Catalog : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}
@@ -0,0 +1,145 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Nashel.Modules.Catalog.Infrastructure.Persistence;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Nashel.Modules.Catalog.Infrastructure.Persistence.Migrations
{
[DbContext(typeof(CatalogDbContext))]
partial class CatalogDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis");
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Category", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<JsonDocument>("AttributeSchema")
.HasColumnType("jsonb");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<Guid?>("ParentId")
.HasColumnType("uuid");
b.Property<string>("Slug")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("ParentId");
b.HasIndex("Slug")
.IsUnique();
b.ToTable("Categories", "catalog");
});
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Offer", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<JsonDocument>("Attributes")
.HasColumnType("jsonb");
b.Property<Guid>("CategoryId")
.HasColumnType("uuid");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.HasColumnType("text");
b.PrimitiveCollection<List<string>>("Images")
.IsRequired()
.HasColumnType("text[]");
b.Property<bool>("IsActive")
.HasColumnType("boolean");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean");
b.Property<Guid>("PerformerId")
.HasColumnType("uuid");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("Attributes");
NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Attributes"), "gin");
b.ToTable("Offers", "catalog");
});
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Category", b =>
{
b.HasOne("Nashel.Modules.Catalog.Domain.Aggregates.Category", null)
.WithMany()
.HasForeignKey("ParentId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("Nashel.Modules.Catalog.Domain.Aggregates.Offer", b =>
{
b.OwnsOne("Nashel.Modules.Catalog.Domain.ValueObjects.Price", "Price", b1 =>
{
b1.Property<Guid>("OfferId")
.HasColumnType("uuid");
b1.Property<decimal>("Amount")
.HasColumnType("numeric")
.HasColumnName("PriceAmount");
b1.Property<string>("Currency")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("character varying(3)")
.HasColumnName("PriceCurrency");
b1.Property<int>("Type")
.HasColumnType("integer")
.HasColumnName("PriceType");
b1.HasKey("OfferId");
b1.ToTable("Offers", "catalog");
b1.WithOwner()
.HasForeignKey("OfferId");
});
b.Navigation("Price")
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,34 @@
using Microsoft.EntityFrameworkCore;
using Nashel.Modules.Catalog.Domain.Aggregates;
using Nashel.Modules.Catalog.Domain.Repositories;
using Nashel.Modules.Catalog.Infrastructure.Persistence;
namespace Nashel.Modules.Catalog.Infrastructure.Repositories;
public class CategoryRepository : ICategoryRepository
{
private readonly CatalogDbContext _context;
public CategoryRepository(CatalogDbContext context)
{
_context = context;
}
public async Task<Category?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
{
return await _context.Categories
.Include(c => c.AttributeSchema) // Not needed as it's a property now, but sometimes needed if navigation property (JsonDocument is property)
.FirstOrDefaultAsync(c => c.Id == id, cancellationToken);
}
public async Task<List<Category>> GetAllAsync(CancellationToken cancellationToken = default)
{
return await _context.Categories.ToListAsync(cancellationToken);
}
public async Task AddAsync(Category category, CancellationToken cancellationToken = default)
{
await _context.Categories.AddAsync(category, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
}
}
@@ -0,0 +1,41 @@
using Microsoft.EntityFrameworkCore;
using Nashel.Modules.Catalog.Domain.Aggregates;
using Nashel.Modules.Catalog.Domain.Repositories;
using Nashel.Modules.Catalog.Infrastructure.Persistence;
namespace Nashel.Modules.Catalog.Infrastructure.Repositories;
public class OfferRepository : IOfferRepository
{
private readonly CatalogDbContext _context;
public OfferRepository(CatalogDbContext context)
{
_context = context;
}
public async Task<Offer?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
{
return await _context.Offers
.FirstOrDefaultAsync(o => o.Id == id, cancellationToken);
}
public async Task<List<Offer>> GetByPerformerIdAsync(Guid performerId, CancellationToken cancellationToken = default)
{
return await _context.Offers
.Where(o => o.PerformerId == performerId)
.ToListAsync(cancellationToken);
}
public async Task AddAsync(Offer offer, CancellationToken cancellationToken = default)
{
await _context.Offers.AddAsync(offer, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
}
public async Task UpdateAsync(Offer offer, CancellationToken cancellationToken = default)
{
_context.Offers.Update(offer);
await _context.SaveChangesAsync(cancellationToken);
}
}
@@ -0,0 +1,110 @@
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Nashel.Modules.Catalog.Application.Commands;
using Nashel.Modules.Catalog.Application.Queries;
namespace Nashel.Modules.Catalog.Presentation.Endpoints;
/// <summary>
/// API Каталога (Категории и Услуги).
/// </summary>
public static class CatalogEndpoints
{
public static void MapCatalogEndpoints(this IEndpointRouteBuilder app)
{
var catalogGroup = app.MapGroup("/api/catalog").WithTags("Catalog");
// --- Категории ---
catalogGroup.MapPost("/categories", async ([FromBody] CreateCategoryCommand command, ISender sender) =>
{
var result = await sender.Send(command);
return result.IsSuccess ? Results.Ok(result.Value) : Results.BadRequest(result.Error);
})
.WithName("CreateCategory")
.WithOpenApi(operation => new(operation) { Summary = "Создать категорию (Admin)", Description = "Создает новую категорию. Требуются права администратора." });
catalogGroup.MapGet("/categories", async (ISender sender) =>
{
var result = await sender.Send(new GetCategoriesQuery());
return result.IsSuccess ? Results.Ok(result.Value) : Results.BadRequest(result.Error);
})
.WithName("GetCategories")
.WithOpenApi(operation => new(operation) { Summary = "Получить все категории", Description = "Возвращает плоский список категорий с указанием ParentId для иерархии." });
// --- Услуги (Offers) ---
var protectedOffersGroup = catalogGroup.MapGroup("").RequireAuthorization();
protectedOffersGroup.MapGet("/offers/my", async (ISender sender) =>
{
var result = await sender.Send(new GetMyOffersQuery());
return result.IsSuccess ? Results.Ok(result.Value) : Results.BadRequest(result.Error);
})
.WithName("GetMyOffers")
.WithOpenApi(operation => new(operation) { Summary = "Получить мои услуги", Description = "Возвращает список услуг текущего пользователя." });
protectedOffersGroup.MapPost("/offers", async ([FromBody] CreateOfferCommand command, ISender sender) =>
{
var result = await sender.Send(command);
return result.IsSuccess ? Results.Ok(result.Value) : Results.BadRequest(result.Error);
})
.WithName("CreateOffer")
.WithOpenApi(operation => new(operation) { Summary = "Создать оффер/услугу", Description = "Создает новое предложение услуги в указанной категории." });
catalogGroup.MapGet("/offers/{id:guid}", async (Guid id, ISender sender) =>
{
var result = await sender.Send(new GetOfferByIdQuery(id));
return result.IsSuccess && result.Value is not null ? Results.Ok(result.Value) : Results.NotFound();
})
.WithName("GetOfferById")
.WithOpenApi(operation => new(operation) { Summary = "Получить детали услуги по ID", Description = "Возвращает полную информацию об услуге." });
protectedOffersGroup.MapPut("/offers/{id:guid}", async (Guid id, [FromBody] UpdateOfferPayload payload, ISender sender) =>
{
var command = new UpdateOfferCommand(id, payload.Title, payload.Description, payload.Price.Amount, payload.Price.Type, payload.Attributes, payload.Images);
await sender.Send(command);
return Results.NoContent();
})
.WithName("UpdateOffer")
.WithOpenApi(operation => new(operation) { Summary = "Обновить услугу" });
protectedOffersGroup.MapPatch("/offers/{id:guid}/toggle", async (Guid id, ISender sender) =>
{
var isActive = await sender.Send(new ToggleOfferStatusCommand(id));
return Results.Ok(new { IsActive = isActive });
})
.WithName("ToggleOfferStatus")
.WithOpenApi(operation => new(operation) { Summary = "Приостановить/активировать услугу" });
protectedOffersGroup.MapDelete("/offers/{id:guid}", async (Guid id, ISender sender) =>
{
await sender.Send(new DeleteOfferCommand(id));
return Results.NoContent();
})
.WithName("DeleteOffer")
.WithOpenApi(operation => new(operation) { Summary = "Удалить услугу" });
protectedOffersGroup.MapPost("/offers/image", async (IFormFile file, ISender sender) =>
{
using var ms = new MemoryStream();
await file.CopyToAsync(ms);
var url = await sender.Send(new UploadOfferImageCommand(ms.ToArray(), file.FileName));
return Results.Ok(new { url });
})
.WithName("UploadOfferImage")
.WithOpenApi(operation => new(operation) { Summary = "Загрузить изображение для услуги" })
.DisableAntiforgery();
}
}
public record UpdateOfferPayload(
string Title,
string Description,
PricePayload Price,
Dictionary<string, string>? Attributes,
List<string>? Images);
public record PricePayload(decimal Amount, int Type);
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Application\Nashel.Modules.Catalog.Application.csproj" />
</ItemGroup>
<PropertyGroup>
<RootNamespace>Nashel.Modules.Catalog.Presentation</RootNamespace>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.4" />
</ItemGroup>
</Project>
@@ -0,0 +1,48 @@
using FluentAssertions;
using NSubstitute;
using Nashel.Modules.Catalog.Application.Commands;
using Nashel.Modules.Catalog.Domain.Aggregates;
using Nashel.Modules.Catalog.Domain.Repositories;
using Xunit;
namespace Nashel.Modules.Catalog.Tests.Application;
public class CreateCategoryCommandHandlerTests
{
private readonly ICategoryRepository _categoryRepository;
private readonly CreateCategoryCommandHandler _handler;
public CreateCategoryCommandHandlerTests()
{
// Инициализация мока через NSubstitute
_categoryRepository = Substitute.For<ICategoryRepository>();
_handler = new CreateCategoryCommandHandler(_categoryRepository);
}
[Fact]
public async Task Handle_ShouldCreateCategory_WhenCommandIsValid()
{
// Arrange (Подготовка)
var command = new CreateCategoryCommand(
"Test Category",
"test-category",
null,
null
);
// Act (Действие)
var result = await _handler.Handle(command, CancellationToken.None);
// Assert (Проверка)
result.IsSuccess.Should().BeTrue();
result.Value.Should().NotBeEmpty();
// Проверяем вызов репозитория
await _categoryRepository.Received(1).AddAsync(Arg.Is<Category>(c =>
c.Name == command.Name &&
c.Slug == command.Slug &&
c.ParentId == command.ParentId &&
c.AttributeSchema == command.AttributeSchema
), Arg.Any<CancellationToken>());
}
}
@@ -0,0 +1,64 @@
using FluentAssertions;
using NSubstitute;
using Nashel.BuildingBlocks.Application.Abstractions;
using Nashel.Modules.Catalog.Application.Commands;
using Nashel.Modules.Catalog.Domain.Aggregates;
using Nashel.Modules.Catalog.Domain.Enums;
using Nashel.Modules.Catalog.Domain.Repositories;
using Nashel.Modules.Catalog.Domain.ValueObjects;
using System.Text.Json;
using Xunit;
namespace Nashel.Modules.Catalog.Tests.Application;
public class CreateOfferCommandHandlerTests
{
private readonly IOfferRepository _offerRepository;
private readonly ICurrentUserService _currentUserService;
private readonly CreateOfferCommandHandler _handler;
public CreateOfferCommandHandlerTests()
{
// Инициализация моков через NSubstitute
_offerRepository = Substitute.For<IOfferRepository>();
_currentUserService = Substitute.For<ICurrentUserService>();
_handler = new CreateOfferCommandHandler(_offerRepository, _currentUserService);
}
[Fact]
public async Task Handle_ShouldCreateOffer_WhenCommandIsValid()
{
// Arrange (Подготовка)
var userId = Guid.NewGuid();
var categoryId = Guid.NewGuid();
var command = new CreateOfferCommand(
categoryId,
"Test Offer",
"Test Description",
new Price(100, OfferType.Fixed, "RUB"),
JsonDocument.Parse("{}"),
null
);
_currentUserService.UserId.Returns(userId);
// Act (Действие)
var result = await _handler.Handle(command, CancellationToken.None);
// Assert (Проверка)
result.IsSuccess.Should().BeTrue();
result.Value.Should().NotBeEmpty();
// Проверяем, что метод AddAsync был вызван ровно один раз с правильными параметрами
await _offerRepository.Received(1).AddAsync(Arg.Is<Offer>(o =>
o.Title == command.Title &&
o.PerformerId == userId &&
o.CategoryId == command.CategoryId &&
o.Description == command.Description &&
o.Price.Amount == command.Price.Amount &&
o.Price.Type == command.Price.Type &&
o.Price.Currency == command.Price.Currency &&
o.Attributes == command.Attributes
), Arg.Any<CancellationToken>());
}
}
@@ -0,0 +1,68 @@
using FluentAssertions;
using NSubstitute;
using Nashel.Modules.Catalog.Application.Common;
using Nashel.Modules.Catalog.Application.Queries;
using Nashel.Modules.Catalog.Domain.Aggregates;
using Nashel.Modules.Catalog.Domain.Repositories;
using System.Text.Json;
using Xunit;
namespace Nashel.Modules.Catalog.Tests.Application;
public class GetCategoriesQueryHandlerTests
{
private readonly ICategoryRepository _categoryRepository;
private readonly GetCategoriesQueryHandler _handler;
public GetCategoriesQueryHandlerTests()
{
// Инициализация мока репозитория через NSubstitute
_categoryRepository = Substitute.For<ICategoryRepository>();
_handler = new GetCategoriesQueryHandler(_categoryRepository);
}
[Fact]
public async Task Handle_ShouldReturnCategories_WhenCategoriesExist()
{
// Arrange (Подготовка)
var categories = new List<Category>
{
new Category("Category 1", "category-1", null),
new Category("Category 2", "category-2", null)
};
_categoryRepository.GetAllAsync(Arg.Any<CancellationToken>())
.Returns(categories);
var query = new GetCategoriesQuery();
// Act (Действие)
var result = await _handler.Handle(query, CancellationToken.None);
// Assert (Проверка)
result.IsSuccess.Should().BeTrue();
result.Value.Should().NotBeNull();
result.Value.Should().HaveCount(2);
// Сверяем DTO с данными из доменных сущностей
result.Value.Should().ContainEquivalentOf(new { Name = "Category 1", Slug = "category-1" });
result.Value.Should().ContainEquivalentOf(new { Name = "Category 2", Slug = "category-2" });
}
[Fact]
public async Task Handle_ShouldReturnEmptyList_WhenNoCategoriesExist()
{
// Arrange (Подготовка)
_categoryRepository.GetAllAsync(Arg.Any<CancellationToken>())
.Returns(new List<Category>());
var query = new GetCategoriesQuery();
// Act (Действие)
var result = await _handler.Handle(query, CancellationToken.None);
// Assert (Проверка)
result.IsSuccess.Should().BeTrue();
result.Value.Should().NotBeNull();
result.Value.Should().BeEmpty();
}
}
@@ -0,0 +1,78 @@
using FluentAssertions;
using NSubstitute;
using MediatR;
using Nashel.Modules.Catalog.Application.Common;
using Nashel.Modules.Catalog.Application.Queries;
using Nashel.Modules.Catalog.Domain.Aggregates;
using Nashel.Modules.Catalog.Domain.Repositories;
using Nashel.Modules.Catalog.Domain.ValueObjects;
using Nashel.Modules.Catalog.Domain.Enums;
using Nashel.BuildingBlocks.Application.Exceptions;
using System.Text.Json;
using Xunit;
namespace Nashel.Modules.Catalog.Tests.Application;
public class GetOfferByIdQueryHandlerTests
{
private readonly IOfferRepository _offerRepository;
private readonly IMediator _mediator;
private readonly GetOfferByIdQueryHandler _handler;
public GetOfferByIdQueryHandlerTests()
{
_offerRepository = Substitute.For<IOfferRepository>();
_mediator = Substitute.For<IMediator>();
_handler = new GetOfferByIdQueryHandler(_offerRepository, _mediator);
}
[Fact]
public async Task Handle_ShouldReturnOffer_WhenOfferExists()
{
// Arrange (Подготовка)
var offer = new Offer(
Guid.NewGuid(),
Guid.NewGuid(),
"Test Offer",
"Test Description",
new Price(100, OfferType.Fixed, "RUB"),
JsonDocument.Parse("{}")
);
_offerRepository.GetByIdAsync(offer.Id, Arg.Any<CancellationToken>())
.Returns(offer);
// Настройка мока Mediator для внутренних запросов
_mediator.Send(Arg.Any<Nashel.Modules.Identity.Application.Queries.GetUserDetailsQuery>(), Arg.Any<CancellationToken>())
.Returns(Nashel.BuildingBlocks.Domain.Result<Nashel.Modules.Identity.Application.Queries.UserDetailsDto>.Success(
new Nashel.Modules.Identity.Application.Queries.UserDetailsDto(offer.PerformerId, "Исполнитель", null, "Мастер")));
_mediator.Send(Arg.Any<Nashel.Modules.Reputation.Application.Queries.GetProfileRatingQuery>(), Arg.Any<CancellationToken>())
.Returns(new Nashel.Modules.Reputation.Application.Queries.ProfileRatingDto(4.5, 10));
var query = new GetOfferByIdQuery(offer.Id);
// Act (Действие)
var result = await _handler.Handle(query, CancellationToken.None);
// Assert (Проверка)
result.IsSuccess.Should().BeTrue();
result.Value.Id.Should().Be(offer.Id);
result.Value.Title.Should().Be("Test Offer");
result.Value.Rating.Should().Be(4.5);
}
[Fact]
public async Task Handle_ShouldThrowNotFoundException_WhenOfferDoesNotExist()
{
// Arrange (Подготовка)
_offerRepository.GetByIdAsync(Arg.Any<Guid>(), Arg.Any<CancellationToken>())
.Returns((Offer?)null);
var query = new GetOfferByIdQuery(Guid.NewGuid());
// Act & Assert (Действие и Проверка)
var act = async () => await _handler.Handle(query, CancellationToken.None);
await act.Should().ThrowAsync<NotFoundException>();
}
}
@@ -0,0 +1,40 @@
using FluentAssertions;
using Nashel.Modules.Catalog.Domain.Aggregates;
using System.Text.Json;
using Xunit;
namespace Nashel.Modules.Catalog.Tests.Domain;
public class CategoryTests
{
[Fact]
public void Constructor_ShouldSetPropertiesCorrectly()
{
// Arrange (Подготовка)
var title = "Test Category";
var slug = "test-category";
var parentId = Guid.NewGuid();
var jsonDoc = JsonDocument.Parse("{}");
// Act (Действие)
var category = new Category(title, slug, parentId, jsonDoc);
// Assert (Проверка)
category.Id.Should().NotBeEmpty();
category.Name.Should().Be(title);
category.Slug.Should().Be(slug);
category.ParentId.Should().Be(parentId);
category.AttributeSchema.Should().NotBeNull();
}
[Fact]
public void Constructor_ShouldSetDefaultValues_WhenOptionalParametersAreNull()
{
// Act (Действие)
var category = new Category("Test", "test", null, null);
// Assert (Проверка)
category.ParentId.Should().BeNull();
category.AttributeSchema.Should().BeNull();
}
}
@@ -0,0 +1,36 @@
using FluentAssertions;
using Nashel.Modules.Catalog.Domain.Aggregates;
using Nashel.Modules.Catalog.Domain.ValueObjects;
using Nashel.Modules.Catalog.Domain.Enums;
using System.Text.Json;
using Xunit;
namespace Nashel.Modules.Catalog.Tests.Domain;
public class OfferTests
{
[Fact]
public void Constructor_ShouldSetPropertiesCorrectly()
{
// Arrange (Подготовка)
var performerId = Guid.NewGuid();
var categoryId = Guid.NewGuid();
var title = "Test Offer";
var description = "Test Description";
var price = new Price(100, OfferType.Fixed, "RUB");
var attributes = JsonDocument.Parse("{}");
// Act (Действие)
var offer = new Offer(performerId, categoryId, title, description, price, attributes);
// Assert (Проверка)
offer.Id.Should().NotBeEmpty();
offer.PerformerId.Should().Be(performerId);
offer.CategoryId.Should().Be(categoryId);
offer.Title.Should().Be(title);
offer.Description.Should().Be(description);
offer.Price.Should().BeEquivalentTo(price);
offer.Attributes.Should().NotBeNull();
offer.IsActive.Should().BeTrue();
}
}
@@ -0,0 +1,58 @@
using FluentAssertions;
using Nashel.Modules.Catalog.Domain.Enums;
using Nashel.Modules.Catalog.Domain.ValueObjects;
using Xunit;
namespace Nashel.Modules.Catalog.Tests.Domain;
public class PriceTests
{
[Fact]
public void Constructor_ShouldSetPropertiesCorrectly()
{
// Arrange (Подготовка)
var amount = 100m;
var type = OfferType.Fixed;
var currency = "USD";
// Act (Действие)
var price = new Price(amount, type, currency);
// Assert (Проверка)
price.Amount.Should().Be(amount);
price.Type.Should().Be(type);
price.Currency.Should().Be(currency);
}
[Fact]
public void Constructor_ShouldSetDefaultCurrency_WhenNotProvided()
{
// Act (Действие)
var price = new Price(100m, OfferType.Fixed);
// Assert (Проверка)
price.Currency.Should().Be("RUB");
}
[Fact]
public void Equality_ShouldBeTrue_WhenValuesAreSame()
{
// Arrange (Подготовка)
var price1 = new Price(100m, OfferType.Fixed, "USD");
var price2 = new Price(100m, OfferType.Fixed, "USD");
// Act & Assert (Действие и Проверка)
price1.Should().Be(price2);
}
[Fact]
public void Equality_ShouldBeFalse_WhenValuesAreDifferent()
{
// Arrange (Подготовка)
var price1 = new Price(100m, OfferType.Fixed, "USD");
var price2 = new Price(200m, OfferType.Fixed, "USD");
// Act & Assert (Действие и Проверка)
price1.Should().NotBe(price2);
}
}
@@ -0,0 +1,25 @@
<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.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Domain\Nashel.Modules.Catalog.Domain.csproj" />
<ProjectReference Include="..\Application\Nashel.Modules.Catalog.Application.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\Nashel.BuildingBlocks.csproj" />
</ItemGroup>
</Project>
@@ -1,714 +0,0 @@
{
"format": 1,
"restore": {
"E:\\GIT\\mvp\\backend\\src\\Modules\\Catalog\\Nashel.RF.Modules.Catalog.csproj": {}
},
"projects": {
"E:\\GIT\\mvp\\backend\\src\\BuildingBlocks\\Nashel.RF.BuildingBlocks.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "E:\\GIT\\mvp\\backend\\src\\BuildingBlocks\\Nashel.RF.BuildingBlocks.csproj",
"projectName": "Nashel.RF.BuildingBlocks",
"projectPath": "E:\\GIT\\mvp\\backend\\src\\BuildingBlocks\\Nashel.RF.BuildingBlocks.csproj",
"packagesPath": "C:\\Users\\HomePC\\.nuget\\packages\\",
"outputPath": "E:\\GIT\\mvp\\backend\\src\\BuildingBlocks\\obj\\",
"projectStyle": "PackageReference",
"fallbackFolders": [
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
],
"configFilePaths": [
"C:\\Users\\HomePC\\AppData\\Roaming\\NuGet\\NuGet.Config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
],
"originalTargetFrameworks": [
"net10.0"
],
"sources": {
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
"C:\\Program Files\\dotnet\\library-packs": {},
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net10.0": {
"targetAlias": "net10.0",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "all"
},
"SdkAnalysisLevel": "10.0.100"
},
"frameworks": {
"net10.0": {
"targetAlias": "net10.0",
"dependencies": {
"FluentValidation": {
"target": "Package",
"version": "[12.1.1, )"
},
"MediatR": {
"target": "Package",
"version": "[14.0.0, )"
},
"Microsoft.EntityFrameworkCore.Relational": {
"target": "Package",
"version": "[10.0.2, )"
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"frameworkReferences": {
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.100-preview.6.25358.103/PortableRuntimeIdentifierGraph.json",
"packagesToPrune": {
"Microsoft.CSharp": "(,4.7.32767]",
"Microsoft.VisualBasic": "(,10.4.32767]",
"Microsoft.Win32.Primitives": "(,4.3.32767]",
"Microsoft.Win32.Registry": "(,5.0.32767]",
"runtime.any.System.Collections": "(,4.3.32767]",
"runtime.any.System.Diagnostics.Tools": "(,4.3.32767]",
"runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]",
"runtime.any.System.Globalization": "(,4.3.32767]",
"runtime.any.System.Globalization.Calendars": "(,4.3.32767]",
"runtime.any.System.IO": "(,4.3.32767]",
"runtime.any.System.Reflection": "(,4.3.32767]",
"runtime.any.System.Reflection.Extensions": "(,4.3.32767]",
"runtime.any.System.Reflection.Primitives": "(,4.3.32767]",
"runtime.any.System.Resources.ResourceManager": "(,4.3.32767]",
"runtime.any.System.Runtime": "(,4.3.32767]",
"runtime.any.System.Runtime.Handles": "(,4.3.32767]",
"runtime.any.System.Runtime.InteropServices": "(,4.3.32767]",
"runtime.any.System.Text.Encoding": "(,4.3.32767]",
"runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]",
"runtime.any.System.Threading.Tasks": "(,4.3.32767]",
"runtime.any.System.Threading.Timer": "(,4.3.32767]",
"runtime.aot.System.Collections": "(,4.3.32767]",
"runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]",
"runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]",
"runtime.aot.System.Globalization": "(,4.3.32767]",
"runtime.aot.System.Globalization.Calendars": "(,4.3.32767]",
"runtime.aot.System.IO": "(,4.3.32767]",
"runtime.aot.System.Reflection": "(,4.3.32767]",
"runtime.aot.System.Reflection.Extensions": "(,4.3.32767]",
"runtime.aot.System.Reflection.Primitives": "(,4.3.32767]",
"runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]",
"runtime.aot.System.Runtime": "(,4.3.32767]",
"runtime.aot.System.Runtime.Handles": "(,4.3.32767]",
"runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]",
"runtime.aot.System.Text.Encoding": "(,4.3.32767]",
"runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]",
"runtime.aot.System.Threading.Tasks": "(,4.3.32767]",
"runtime.aot.System.Threading.Timer": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]",
"runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]",
"runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]",
"runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]",
"runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]",
"runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]",
"runtime.unix.System.Console": "(,4.3.32767]",
"runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]",
"runtime.unix.System.IO.FileSystem": "(,4.3.32767]",
"runtime.unix.System.Net.Primitives": "(,4.3.32767]",
"runtime.unix.System.Net.Sockets": "(,4.3.32767]",
"runtime.unix.System.Private.Uri": "(,4.3.32767]",
"runtime.unix.System.Runtime.Extensions": "(,4.3.32767]",
"runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]",
"runtime.win.System.Console": "(,4.3.32767]",
"runtime.win.System.Diagnostics.Debug": "(,4.3.32767]",
"runtime.win.System.IO.FileSystem": "(,4.3.32767]",
"runtime.win.System.Net.Primitives": "(,4.3.32767]",
"runtime.win.System.Net.Sockets": "(,4.3.32767]",
"runtime.win.System.Runtime.Extensions": "(,4.3.32767]",
"runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]",
"runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]",
"runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]",
"runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.win7.System.Private.Uri": "(,4.3.32767]",
"runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]",
"System.AppContext": "(,4.3.32767]",
"System.Buffers": "(,5.0.32767]",
"System.Collections": "(,4.3.32767]",
"System.Collections.Concurrent": "(,4.3.32767]",
"System.Collections.Immutable": "(,10.0.0-preview.6.25358.103]",
"System.Collections.NonGeneric": "(,4.3.32767]",
"System.Collections.Specialized": "(,4.3.32767]",
"System.ComponentModel": "(,4.3.32767]",
"System.ComponentModel.Annotations": "(,4.3.32767]",
"System.ComponentModel.EventBasedAsync": "(,4.3.32767]",
"System.ComponentModel.Primitives": "(,4.3.32767]",
"System.ComponentModel.TypeConverter": "(,4.3.32767]",
"System.Console": "(,4.3.32767]",
"System.Data.Common": "(,4.3.32767]",
"System.Data.DataSetExtensions": "(,4.4.32767]",
"System.Diagnostics.Contracts": "(,4.3.32767]",
"System.Diagnostics.Debug": "(,4.3.32767]",
"System.Diagnostics.DiagnosticSource": "(,10.0.0-preview.6.25358.103]",
"System.Diagnostics.FileVersionInfo": "(,4.3.32767]",
"System.Diagnostics.Process": "(,4.3.32767]",
"System.Diagnostics.StackTrace": "(,4.3.32767]",
"System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]",
"System.Diagnostics.Tools": "(,4.3.32767]",
"System.Diagnostics.TraceSource": "(,4.3.32767]",
"System.Diagnostics.Tracing": "(,4.3.32767]",
"System.Drawing.Primitives": "(,4.3.32767]",
"System.Dynamic.Runtime": "(,4.3.32767]",
"System.Formats.Asn1": "(,10.0.0-preview.6.25358.103]",
"System.Formats.Tar": "(,10.0.0-preview.6.25358.103]",
"System.Globalization": "(,4.3.32767]",
"System.Globalization.Calendars": "(,4.3.32767]",
"System.Globalization.Extensions": "(,4.3.32767]",
"System.IO": "(,4.3.32767]",
"System.IO.Compression": "(,4.3.32767]",
"System.IO.Compression.ZipFile": "(,4.3.32767]",
"System.IO.FileSystem": "(,4.3.32767]",
"System.IO.FileSystem.AccessControl": "(,4.4.32767]",
"System.IO.FileSystem.DriveInfo": "(,4.3.32767]",
"System.IO.FileSystem.Primitives": "(,4.3.32767]",
"System.IO.FileSystem.Watcher": "(,4.3.32767]",
"System.IO.IsolatedStorage": "(,4.3.32767]",
"System.IO.MemoryMappedFiles": "(,4.3.32767]",
"System.IO.Pipelines": "(,10.0.0-preview.6.25358.103]",
"System.IO.Pipes": "(,4.3.32767]",
"System.IO.Pipes.AccessControl": "(,5.0.32767]",
"System.IO.UnmanagedMemoryStream": "(,4.3.32767]",
"System.Linq": "(,4.3.32767]",
"System.Linq.AsyncEnumerable": "(,10.0.0-preview.6.25358.103]",
"System.Linq.Expressions": "(,4.3.32767]",
"System.Linq.Parallel": "(,4.3.32767]",
"System.Linq.Queryable": "(,4.3.32767]",
"System.Memory": "(,5.0.32767]",
"System.Net.Http": "(,4.3.32767]",
"System.Net.Http.Json": "(,10.0.0-preview.6.25358.103]",
"System.Net.NameResolution": "(,4.3.32767]",
"System.Net.NetworkInformation": "(,4.3.32767]",
"System.Net.Ping": "(,4.3.32767]",
"System.Net.Primitives": "(,4.3.32767]",
"System.Net.Requests": "(,4.3.32767]",
"System.Net.Security": "(,4.3.32767]",
"System.Net.ServerSentEvents": "(,10.0.0-preview.6.25358.103]",
"System.Net.Sockets": "(,4.3.32767]",
"System.Net.WebHeaderCollection": "(,4.3.32767]",
"System.Net.WebSockets": "(,4.3.32767]",
"System.Net.WebSockets.Client": "(,4.3.32767]",
"System.Numerics.Vectors": "(,5.0.32767]",
"System.ObjectModel": "(,4.3.32767]",
"System.Private.DataContractSerialization": "(,4.3.32767]",
"System.Private.Uri": "(,4.3.32767]",
"System.Reflection": "(,4.3.32767]",
"System.Reflection.DispatchProxy": "(,6.0.32767]",
"System.Reflection.Emit": "(,4.7.32767]",
"System.Reflection.Emit.ILGeneration": "(,4.7.32767]",
"System.Reflection.Emit.Lightweight": "(,4.7.32767]",
"System.Reflection.Extensions": "(,4.3.32767]",
"System.Reflection.Metadata": "(,10.0.0-preview.6.25358.103]",
"System.Reflection.Primitives": "(,4.3.32767]",
"System.Reflection.TypeExtensions": "(,4.3.32767]",
"System.Resources.Reader": "(,4.3.32767]",
"System.Resources.ResourceManager": "(,4.3.32767]",
"System.Resources.Writer": "(,4.3.32767]",
"System.Runtime": "(,4.3.32767]",
"System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]",
"System.Runtime.CompilerServices.VisualC": "(,4.3.32767]",
"System.Runtime.Extensions": "(,4.3.32767]",
"System.Runtime.Handles": "(,4.3.32767]",
"System.Runtime.InteropServices": "(,4.3.32767]",
"System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]",
"System.Runtime.Loader": "(,4.3.32767]",
"System.Runtime.Numerics": "(,4.3.32767]",
"System.Runtime.Serialization.Formatters": "(,4.3.32767]",
"System.Runtime.Serialization.Json": "(,4.3.32767]",
"System.Runtime.Serialization.Primitives": "(,4.3.32767]",
"System.Runtime.Serialization.Xml": "(,4.3.32767]",
"System.Security.AccessControl": "(,6.0.32767]",
"System.Security.Claims": "(,4.3.32767]",
"System.Security.Cryptography.Algorithms": "(,4.3.32767]",
"System.Security.Cryptography.Cng": "(,5.0.32767]",
"System.Security.Cryptography.Csp": "(,4.3.32767]",
"System.Security.Cryptography.Encoding": "(,4.3.32767]",
"System.Security.Cryptography.OpenSsl": "(,5.0.32767]",
"System.Security.Cryptography.Primitives": "(,4.3.32767]",
"System.Security.Cryptography.X509Certificates": "(,4.3.32767]",
"System.Security.Principal": "(,4.3.32767]",
"System.Security.Principal.Windows": "(,5.0.32767]",
"System.Security.SecureString": "(,4.3.32767]",
"System.Text.Encoding": "(,4.3.32767]",
"System.Text.Encoding.CodePages": "(,10.0.0-preview.6.25358.103]",
"System.Text.Encoding.Extensions": "(,4.3.32767]",
"System.Text.Encodings.Web": "(,10.0.0-preview.6.25358.103]",
"System.Text.Json": "(,10.0.0-preview.6.25358.103]",
"System.Text.RegularExpressions": "(,4.3.32767]",
"System.Threading": "(,4.3.32767]",
"System.Threading.Channels": "(,10.0.0-preview.6.25358.103]",
"System.Threading.Overlapped": "(,4.3.32767]",
"System.Threading.Tasks": "(,4.3.32767]",
"System.Threading.Tasks.Dataflow": "(,10.0.0-preview.6.25358.103]",
"System.Threading.Tasks.Extensions": "(,5.0.32767]",
"System.Threading.Tasks.Parallel": "(,4.3.32767]",
"System.Threading.Thread": "(,4.3.32767]",
"System.Threading.ThreadPool": "(,4.3.32767]",
"System.Threading.Timer": "(,4.3.32767]",
"System.ValueTuple": "(,4.5.32767]",
"System.Xml.ReaderWriter": "(,4.3.32767]",
"System.Xml.XDocument": "(,4.3.32767]",
"System.Xml.XmlDocument": "(,4.3.32767]",
"System.Xml.XmlSerializer": "(,4.3.32767]",
"System.Xml.XPath": "(,4.3.32767]",
"System.Xml.XPath.XDocument": "(,5.0.32767]"
}
}
}
},
"E:\\GIT\\mvp\\backend\\src\\Modules\\Catalog\\Nashel.RF.Modules.Catalog.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "E:\\GIT\\mvp\\backend\\src\\Modules\\Catalog\\Nashel.RF.Modules.Catalog.csproj",
"projectName": "Nashel.RF.Modules.Catalog",
"projectPath": "E:\\GIT\\mvp\\backend\\src\\Modules\\Catalog\\Nashel.RF.Modules.Catalog.csproj",
"packagesPath": "C:\\Users\\HomePC\\.nuget\\packages\\",
"outputPath": "E:\\GIT\\mvp\\backend\\src\\Modules\\Catalog\\obj\\",
"projectStyle": "PackageReference",
"fallbackFolders": [
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
],
"configFilePaths": [
"C:\\Users\\HomePC\\AppData\\Roaming\\NuGet\\NuGet.Config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
],
"originalTargetFrameworks": [
"net10.0"
],
"sources": {
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
"C:\\Program Files\\dotnet\\library-packs": {},
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net10.0": {
"targetAlias": "net10.0",
"projectReferences": {
"E:\\GIT\\mvp\\backend\\src\\BuildingBlocks\\Nashel.RF.BuildingBlocks.csproj": {
"projectPath": "E:\\GIT\\mvp\\backend\\src\\BuildingBlocks\\Nashel.RF.BuildingBlocks.csproj"
}
}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "all"
},
"SdkAnalysisLevel": "10.0.100"
},
"frameworks": {
"net10.0": {
"targetAlias": "net10.0",
"dependencies": {
"MediatR": {
"target": "Package",
"version": "[14.0.0, )"
},
"Npgsql.EntityFrameworkCore.PostgreSQL": {
"target": "Package",
"version": "[10.0.0, )"
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"frameworkReferences": {
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.100-preview.6.25358.103/PortableRuntimeIdentifierGraph.json",
"packagesToPrune": {
"Microsoft.CSharp": "(,4.7.32767]",
"Microsoft.VisualBasic": "(,10.4.32767]",
"Microsoft.Win32.Primitives": "(,4.3.32767]",
"Microsoft.Win32.Registry": "(,5.0.32767]",
"runtime.any.System.Collections": "(,4.3.32767]",
"runtime.any.System.Diagnostics.Tools": "(,4.3.32767]",
"runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]",
"runtime.any.System.Globalization": "(,4.3.32767]",
"runtime.any.System.Globalization.Calendars": "(,4.3.32767]",
"runtime.any.System.IO": "(,4.3.32767]",
"runtime.any.System.Reflection": "(,4.3.32767]",
"runtime.any.System.Reflection.Extensions": "(,4.3.32767]",
"runtime.any.System.Reflection.Primitives": "(,4.3.32767]",
"runtime.any.System.Resources.ResourceManager": "(,4.3.32767]",
"runtime.any.System.Runtime": "(,4.3.32767]",
"runtime.any.System.Runtime.Handles": "(,4.3.32767]",
"runtime.any.System.Runtime.InteropServices": "(,4.3.32767]",
"runtime.any.System.Text.Encoding": "(,4.3.32767]",
"runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]",
"runtime.any.System.Threading.Tasks": "(,4.3.32767]",
"runtime.any.System.Threading.Timer": "(,4.3.32767]",
"runtime.aot.System.Collections": "(,4.3.32767]",
"runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]",
"runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]",
"runtime.aot.System.Globalization": "(,4.3.32767]",
"runtime.aot.System.Globalization.Calendars": "(,4.3.32767]",
"runtime.aot.System.IO": "(,4.3.32767]",
"runtime.aot.System.Reflection": "(,4.3.32767]",
"runtime.aot.System.Reflection.Extensions": "(,4.3.32767]",
"runtime.aot.System.Reflection.Primitives": "(,4.3.32767]",
"runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]",
"runtime.aot.System.Runtime": "(,4.3.32767]",
"runtime.aot.System.Runtime.Handles": "(,4.3.32767]",
"runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]",
"runtime.aot.System.Text.Encoding": "(,4.3.32767]",
"runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]",
"runtime.aot.System.Threading.Tasks": "(,4.3.32767]",
"runtime.aot.System.Threading.Timer": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]",
"runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]",
"runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]",
"runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]",
"runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]",
"runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]",
"runtime.unix.System.Console": "(,4.3.32767]",
"runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]",
"runtime.unix.System.IO.FileSystem": "(,4.3.32767]",
"runtime.unix.System.Net.Primitives": "(,4.3.32767]",
"runtime.unix.System.Net.Sockets": "(,4.3.32767]",
"runtime.unix.System.Private.Uri": "(,4.3.32767]",
"runtime.unix.System.Runtime.Extensions": "(,4.3.32767]",
"runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]",
"runtime.win.System.Console": "(,4.3.32767]",
"runtime.win.System.Diagnostics.Debug": "(,4.3.32767]",
"runtime.win.System.IO.FileSystem": "(,4.3.32767]",
"runtime.win.System.Net.Primitives": "(,4.3.32767]",
"runtime.win.System.Net.Sockets": "(,4.3.32767]",
"runtime.win.System.Runtime.Extensions": "(,4.3.32767]",
"runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]",
"runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]",
"runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]",
"runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.win7.System.Private.Uri": "(,4.3.32767]",
"runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]",
"System.AppContext": "(,4.3.32767]",
"System.Buffers": "(,5.0.32767]",
"System.Collections": "(,4.3.32767]",
"System.Collections.Concurrent": "(,4.3.32767]",
"System.Collections.Immutable": "(,10.0.0-preview.6.25358.103]",
"System.Collections.NonGeneric": "(,4.3.32767]",
"System.Collections.Specialized": "(,4.3.32767]",
"System.ComponentModel": "(,4.3.32767]",
"System.ComponentModel.Annotations": "(,4.3.32767]",
"System.ComponentModel.EventBasedAsync": "(,4.3.32767]",
"System.ComponentModel.Primitives": "(,4.3.32767]",
"System.ComponentModel.TypeConverter": "(,4.3.32767]",
"System.Console": "(,4.3.32767]",
"System.Data.Common": "(,4.3.32767]",
"System.Data.DataSetExtensions": "(,4.4.32767]",
"System.Diagnostics.Contracts": "(,4.3.32767]",
"System.Diagnostics.Debug": "(,4.3.32767]",
"System.Diagnostics.DiagnosticSource": "(,10.0.0-preview.6.25358.103]",
"System.Diagnostics.FileVersionInfo": "(,4.3.32767]",
"System.Diagnostics.Process": "(,4.3.32767]",
"System.Diagnostics.StackTrace": "(,4.3.32767]",
"System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]",
"System.Diagnostics.Tools": "(,4.3.32767]",
"System.Diagnostics.TraceSource": "(,4.3.32767]",
"System.Diagnostics.Tracing": "(,4.3.32767]",
"System.Drawing.Primitives": "(,4.3.32767]",
"System.Dynamic.Runtime": "(,4.3.32767]",
"System.Formats.Asn1": "(,10.0.0-preview.6.25358.103]",
"System.Formats.Tar": "(,10.0.0-preview.6.25358.103]",
"System.Globalization": "(,4.3.32767]",
"System.Globalization.Calendars": "(,4.3.32767]",
"System.Globalization.Extensions": "(,4.3.32767]",
"System.IO": "(,4.3.32767]",
"System.IO.Compression": "(,4.3.32767]",
"System.IO.Compression.ZipFile": "(,4.3.32767]",
"System.IO.FileSystem": "(,4.3.32767]",
"System.IO.FileSystem.AccessControl": "(,4.4.32767]",
"System.IO.FileSystem.DriveInfo": "(,4.3.32767]",
"System.IO.FileSystem.Primitives": "(,4.3.32767]",
"System.IO.FileSystem.Watcher": "(,4.3.32767]",
"System.IO.IsolatedStorage": "(,4.3.32767]",
"System.IO.MemoryMappedFiles": "(,4.3.32767]",
"System.IO.Pipelines": "(,10.0.0-preview.6.25358.103]",
"System.IO.Pipes": "(,4.3.32767]",
"System.IO.Pipes.AccessControl": "(,5.0.32767]",
"System.IO.UnmanagedMemoryStream": "(,4.3.32767]",
"System.Linq": "(,4.3.32767]",
"System.Linq.AsyncEnumerable": "(,10.0.0-preview.6.25358.103]",
"System.Linq.Expressions": "(,4.3.32767]",
"System.Linq.Parallel": "(,4.3.32767]",
"System.Linq.Queryable": "(,4.3.32767]",
"System.Memory": "(,5.0.32767]",
"System.Net.Http": "(,4.3.32767]",
"System.Net.Http.Json": "(,10.0.0-preview.6.25358.103]",
"System.Net.NameResolution": "(,4.3.32767]",
"System.Net.NetworkInformation": "(,4.3.32767]",
"System.Net.Ping": "(,4.3.32767]",
"System.Net.Primitives": "(,4.3.32767]",
"System.Net.Requests": "(,4.3.32767]",
"System.Net.Security": "(,4.3.32767]",
"System.Net.ServerSentEvents": "(,10.0.0-preview.6.25358.103]",
"System.Net.Sockets": "(,4.3.32767]",
"System.Net.WebHeaderCollection": "(,4.3.32767]",
"System.Net.WebSockets": "(,4.3.32767]",
"System.Net.WebSockets.Client": "(,4.3.32767]",
"System.Numerics.Vectors": "(,5.0.32767]",
"System.ObjectModel": "(,4.3.32767]",
"System.Private.DataContractSerialization": "(,4.3.32767]",
"System.Private.Uri": "(,4.3.32767]",
"System.Reflection": "(,4.3.32767]",
"System.Reflection.DispatchProxy": "(,6.0.32767]",
"System.Reflection.Emit": "(,4.7.32767]",
"System.Reflection.Emit.ILGeneration": "(,4.7.32767]",
"System.Reflection.Emit.Lightweight": "(,4.7.32767]",
"System.Reflection.Extensions": "(,4.3.32767]",
"System.Reflection.Metadata": "(,10.0.0-preview.6.25358.103]",
"System.Reflection.Primitives": "(,4.3.32767]",
"System.Reflection.TypeExtensions": "(,4.3.32767]",
"System.Resources.Reader": "(,4.3.32767]",
"System.Resources.ResourceManager": "(,4.3.32767]",
"System.Resources.Writer": "(,4.3.32767]",
"System.Runtime": "(,4.3.32767]",
"System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]",
"System.Runtime.CompilerServices.VisualC": "(,4.3.32767]",
"System.Runtime.Extensions": "(,4.3.32767]",
"System.Runtime.Handles": "(,4.3.32767]",
"System.Runtime.InteropServices": "(,4.3.32767]",
"System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]",
"System.Runtime.Loader": "(,4.3.32767]",
"System.Runtime.Numerics": "(,4.3.32767]",
"System.Runtime.Serialization.Formatters": "(,4.3.32767]",
"System.Runtime.Serialization.Json": "(,4.3.32767]",
"System.Runtime.Serialization.Primitives": "(,4.3.32767]",
"System.Runtime.Serialization.Xml": "(,4.3.32767]",
"System.Security.AccessControl": "(,6.0.32767]",
"System.Security.Claims": "(,4.3.32767]",
"System.Security.Cryptography.Algorithms": "(,4.3.32767]",
"System.Security.Cryptography.Cng": "(,5.0.32767]",
"System.Security.Cryptography.Csp": "(,4.3.32767]",
"System.Security.Cryptography.Encoding": "(,4.3.32767]",
"System.Security.Cryptography.OpenSsl": "(,5.0.32767]",
"System.Security.Cryptography.Primitives": "(,4.3.32767]",
"System.Security.Cryptography.X509Certificates": "(,4.3.32767]",
"System.Security.Principal": "(,4.3.32767]",
"System.Security.Principal.Windows": "(,5.0.32767]",
"System.Security.SecureString": "(,4.3.32767]",
"System.Text.Encoding": "(,4.3.32767]",
"System.Text.Encoding.CodePages": "(,10.0.0-preview.6.25358.103]",
"System.Text.Encoding.Extensions": "(,4.3.32767]",
"System.Text.Encodings.Web": "(,10.0.0-preview.6.25358.103]",
"System.Text.Json": "(,10.0.0-preview.6.25358.103]",
"System.Text.RegularExpressions": "(,4.3.32767]",
"System.Threading": "(,4.3.32767]",
"System.Threading.Channels": "(,10.0.0-preview.6.25358.103]",
"System.Threading.Overlapped": "(,4.3.32767]",
"System.Threading.Tasks": "(,4.3.32767]",
"System.Threading.Tasks.Dataflow": "(,10.0.0-preview.6.25358.103]",
"System.Threading.Tasks.Extensions": "(,5.0.32767]",
"System.Threading.Tasks.Parallel": "(,4.3.32767]",
"System.Threading.Thread": "(,4.3.32767]",
"System.Threading.ThreadPool": "(,4.3.32767]",
"System.Threading.Timer": "(,4.3.32767]",
"System.ValueTuple": "(,4.5.32767]",
"System.Xml.ReaderWriter": "(,4.3.32767]",
"System.Xml.XDocument": "(,4.3.32767]",
"System.Xml.XmlDocument": "(,4.3.32767]",
"System.Xml.XmlSerializer": "(,4.3.32767]",
"System.Xml.XPath": "(,4.3.32767]",
"System.Xml.XPath.XDocument": "(,5.0.32767]"
}
}
}
}
}
}
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\HomePC\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.15.0</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="C:\Users\HomePC\.nuget\packages\" />
<SourceRoot Include="C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages\" />
</ItemGroup>
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore\10.0.2\buildTransitive\net10.0\Microsoft.EntityFrameworkCore.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore\10.0.2\buildTransitive\net10.0\Microsoft.EntityFrameworkCore.props')" />
</ImportGroup>
</Project>
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\10.0.2\buildTransitive\net8.0\Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\10.0.2\buildTransitive\net8.0\Microsoft.Extensions.Logging.Abstractions.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.options\10.0.2\buildTransitive\net8.0\Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options\10.0.2\buildTransitive\net8.0\Microsoft.Extensions.Options.targets')" />
</ImportGroup>
</Project>
File diff suppressed because it is too large Load Diff
@@ -1,31 +0,0 @@
{
"version": 2,
"dgSpecHash": "5oOdKeOvG+M=",
"success": true,
"projectFilePath": "E:\\GIT\\mvp\\backend\\src\\Modules\\Catalog\\Nashel.RF.Modules.Catalog.csproj",
"expectedPackageFiles": [
"C:\\Users\\HomePC\\.nuget\\packages\\fluentvalidation\\12.1.1\\fluentvalidation.12.1.1.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\mediatr\\14.0.0\\mediatr.14.0.0.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\mediatr.contracts\\2.0.1\\mediatr.contracts.2.0.1.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\microsoft.entityframeworkcore\\10.0.2\\microsoft.entityframeworkcore.10.0.2.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\microsoft.entityframeworkcore.abstractions\\10.0.2\\microsoft.entityframeworkcore.abstractions.10.0.2.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\microsoft.entityframeworkcore.analyzers\\10.0.2\\microsoft.entityframeworkcore.analyzers.10.0.2.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\microsoft.entityframeworkcore.relational\\10.0.2\\microsoft.entityframeworkcore.relational.10.0.2.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\microsoft.extensions.caching.abstractions\\10.0.2\\microsoft.extensions.caching.abstractions.10.0.2.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\microsoft.extensions.caching.memory\\10.0.2\\microsoft.extensions.caching.memory.10.0.2.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\microsoft.extensions.configuration.abstractions\\10.0.2\\microsoft.extensions.configuration.abstractions.10.0.2.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\microsoft.extensions.dependencyinjection\\10.0.2\\microsoft.extensions.dependencyinjection.10.0.2.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\microsoft.extensions.dependencyinjection.abstractions\\10.0.2\\microsoft.extensions.dependencyinjection.abstractions.10.0.2.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\microsoft.extensions.logging\\10.0.2\\microsoft.extensions.logging.10.0.2.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\microsoft.extensions.logging.abstractions\\10.0.2\\microsoft.extensions.logging.abstractions.10.0.2.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\microsoft.extensions.options\\10.0.2\\microsoft.extensions.options.10.0.2.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\microsoft.extensions.primitives\\10.0.2\\microsoft.extensions.primitives.10.0.2.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\microsoft.identitymodel.abstractions\\8.14.0\\microsoft.identitymodel.abstractions.8.14.0.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\microsoft.identitymodel.jsonwebtokens\\8.14.0\\microsoft.identitymodel.jsonwebtokens.8.14.0.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\microsoft.identitymodel.logging\\8.14.0\\microsoft.identitymodel.logging.8.14.0.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\microsoft.identitymodel.tokens\\8.14.0\\microsoft.identitymodel.tokens.8.14.0.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\npgsql\\10.0.0\\npgsql.10.0.0.nupkg.sha512",
"C:\\Users\\HomePC\\.nuget\\packages\\npgsql.entityframeworkcore.postgresql\\10.0.0\\npgsql.entityframeworkcore.postgresql.10.0.0.nupkg.sha512"
],
"logs": []
}
@@ -0,0 +1,34 @@
using MediatR;
using Nashel.Modules.Collaboration.Domain.Entities;
using Nashel.Modules.Collaboration.Domain.Repositories;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Nashel.Modules.Collaboration.Application.Commands
{
/// <summary>
/// Команда найма сотрудника (создание контракта).
/// </summary>
public record HireEmployeeCommand(Guid EmployerId, Guid EmployeeId, string Role) : IRequest<Guid>;
public class HireEmployeeHandler : IRequestHandler<HireEmployeeCommand, Guid>
{
private readonly IContractRepository _repository;
public HireEmployeeHandler(IContractRepository repository)
{
_repository = repository;
}
public async Task<Guid> Handle(HireEmployeeCommand request, CancellationToken cancellationToken)
{
// Здесь можно добавить проверку, не существует ли уже активный контракт между этими сторонами
// Но пока просто создаем новый.
var contract = Contract.Create(request.EmployerId, request.EmployeeId, request.Role);
await _repository.AddAsync(contract, cancellationToken);
return contract.Id;
}
}
}
@@ -0,0 +1,38 @@
using MediatR;
using Nashel.Modules.Collaboration.Domain.Repositories;
using Nashel.Modules.Collaboration.Domain.ValueObjects;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Nashel.Modules.Collaboration.Application.Commands
{
/// <summary>
/// Команда установки вето (блокировки) на контракт.
/// </summary>
public record SetVetoCommand(Guid ContractId, DateTime Start, DateTime End) : IRequest;
public class SetVetoHandler : IRequestHandler<SetVetoCommand>
{
private readonly IContractRepository _repository;
public SetVetoHandler(IContractRepository repository)
{
_repository = repository;
}
public async Task Handle(SetVetoCommand request, CancellationToken cancellationToken)
{
var contract = await _repository.GetByIdAsync(request.ContractId, cancellationToken);
if (contract == null)
{
throw new ApplicationException($"Contract with ID {request.ContractId} not found.");
}
var range = new DateRange(request.Start, request.End);
contract.ImposeVeto(range);
await _repository.UpdateAsync(contract, cancellationToken);
}
}
}
@@ -1,18 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\..\BuildingBlocks\Nashel.RF.BuildingBlocks.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\Nashel.BuildingBlocks.csproj" />
<ProjectReference Include="..\Domain\Nashel.Modules.Collaboration.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MediatR" Version="14.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
</ItemGroup>
<PropertyGroup>
<RootNamespace>Nashel.Modules.Collaboration.Application</RootNamespace>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
</Project>
@@ -0,0 +1,30 @@
using MediatR;
using Nashel.Modules.Collaboration.Domain.Services;
using Nashel.Modules.Collaboration.Domain.ValueObjects;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Nashel.Modules.Collaboration.Application.Queries
{
/// <summary>
/// Проверка доступности сотрудника (Query).
/// </summary>
public record CheckAvailabilityQuery(Guid EmployeeId, DateTime Start, DateTime End) : IRequest<bool>;
public class CheckAvailabilityHandler : IRequestHandler<CheckAvailabilityQuery, bool>
{
private readonly ScheduleConflictService _conflictService;
public CheckAvailabilityHandler(ScheduleConflictService conflictService)
{
_conflictService = conflictService;
}
public async Task<bool> Handle(CheckAvailabilityQuery request, CancellationToken cancellationToken)
{
var range = new DateRange(request.Start, request.End);
return await _conflictService.CheckAvailability(request.EmployeeId, range, cancellationToken);
}
}
}
@@ -0,0 +1,80 @@
using Nashel.BuildingBlocks.Domain;
using Nashel.Modules.Collaboration.Domain.Enums;
using Nashel.Modules.Collaboration.Domain.ValueObjects;
namespace Nashel.Modules.Collaboration.Domain.Entities;
/// <summary>
/// Контракт найма между Компанией и Сотрудником (Мастером).
/// </summary>
public class Contract : AggregateRoot<Guid>
{
public Guid EmployerId { get; private set; }
public Guid EmployeeId { get; private set; }
public string Role { get; private set; }
public ContractStatus Status { get; private set; }
private readonly List<VetoLock> _vetoLocks = new();
public IReadOnlyCollection<VetoLock> VetoLocks => _vetoLocks.AsReadOnly();
private Contract() { }
private Contract(Guid id, Guid employerId, Guid employeeId, string role)
{
Id = id;
EmployerId = employerId;
EmployeeId = employeeId;
Role = role;
Status = ContractStatus.Active;
}
public static Contract Create(Guid employerId, Guid employeeId, string role)
{
return new Contract(Guid.NewGuid(), employerId, employeeId, role);
}
/// <summary>
/// Накладывает вето (блокировку) на указанный период.
/// </summary>
public void ImposeVeto(DateRange period)
{
if (Status == ContractStatus.Terminated)
{
throw new InvalidOperationException("Нельзя наложить вето на расторгнутый контракт.");
}
// Проверяем пересечение с существующими блокировками в этом же контракте (опционально, но логично)
var hasOverlap = _vetoLocks.Any(v => v.Period.Overlaps(period));
if (hasOverlap)
{
// Здесь можно бросать ошибку или просто игнорировать/мерджить.
// Для упрощения: бросаем ошибку, чтобы фронт знал о дублировании.
throw new InvalidOperationException("Период вето пересекается с уже существующей блокировкой в этом контракте.");
}
_vetoLocks.Add(new VetoLock(Id, period));
}
/// <summary>
/// Снимает вето (блокировку) на указанный период.
/// Логика удаления по полному совпадению.
/// </summary>
public void RemoveVeto(DateRange period)
{
var existingLock = _vetoLocks.FirstOrDefault(v => v.Period == period);
if (existingLock != null)
{
_vetoLocks.Remove(existingLock);
}
}
/// <summary>
/// Завершает контракт.
/// </summary>
public void Terminate()
{
Status = ContractStatus.Terminated;
_vetoLocks.Clear(); // При расторжении контракта все блокировки снимаются?
// Допустим, да, так как работодатель больше не имеет прав.
}
}
@@ -0,0 +1,23 @@
using Nashel.BuildingBlocks.Domain;
using Nashel.Modules.Collaboration.Domain.Enums;
using Nashel.Modules.Collaboration.Domain.ValueObjects;
namespace Nashel.Modules.Collaboration.Domain.Entities;
/// <summary>
/// Сущестность блокировки (Вето) на доступность сотрудника.
/// </summary>
public class VetoLock : Entity<Guid>
{
public DateRange Period { get; private set; }
public Guid ContractId { get; private set; }
private VetoLock() { }
public VetoLock(Guid contractId, DateRange period)
{
Id = Guid.NewGuid();
ContractId = contractId;
Period = period;
}
}
@@ -0,0 +1,17 @@
namespace Nashel.Modules.Collaboration.Domain.Enums;
/// <summary>
/// Статус контракта найма.
/// </summary>
public enum ContractStatus
{
/// <summary>
/// Контракт активен.
/// </summary>
Active,
/// <summary>
/// Контракт расторгнут.
/// </summary>
Terminated
}
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\Nashel.BuildingBlocks.csproj" />
</ItemGroup>
<PropertyGroup>
<RootNamespace>Nashel.Modules.Collaboration.Domain</RootNamespace>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Nashel.Modules.Collaboration.Domain.Entities;
namespace Nashel.Modules.Collaboration.Domain.Repositories
{
public interface IContractRepository
{
Task<Contract?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
Task<List<Contract>> GetAllByEmployeeIdAsync(Guid employeeId, CancellationToken cancellationToken = default);
Task AddAsync(Contract contract, CancellationToken cancellationToken = default);
Task UpdateAsync(Contract contract, CancellationToken cancellationToken = default);
}
}
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Nashel.Modules.Collaboration.Domain.Enums;
using Nashel.Modules.Collaboration.Domain.Repositories;
using Nashel.Modules.Collaboration.Domain.ValueObjects;
namespace Nashel.Modules.Collaboration.Domain.Services
{
public class ScheduleConflictService
{
private readonly IContractRepository _repository;
public ScheduleConflictService(IContractRepository repository)
{
_repository = repository;
}
/// <summary>
/// Проверяет доступность сотрудника, анализируя все активные контракты на наличие блокировок.
/// </summary>
/// <returns>True - если сотрудник свободен, False - если заблокирован хотя бы одним Вето.</returns>
public async Task<bool> CheckAvailability(Guid employeeId, DateRange requestedPeriod, CancellationToken ct = default)
{
var contracts = await _repository.GetAllByEmployeeIdAsync(employeeId, ct);
// Фильтруем только активные контракты
var activeContracts = contracts.Where(c => c.Status == ContractStatus.Active);
foreach (var contract in activeContracts)
{
// Если есть хоть одно пересекающееся вето - сотрудник занят
if (contract.VetoLocks.Any(veto => veto.Period.Overlaps(requestedPeriod)))
{
return false;
}
}
return true;
}
}
}
@@ -0,0 +1,21 @@
using Nashel.BuildingBlocks.Domain;
namespace Nashel.Modules.Collaboration.Domain.ValueObjects;
/// <summary>
/// Диапазон дат.
/// </summary>
public record DateRange(DateTime Start, DateTime End)
{
private DateRange() : this(default, default) { } // For EF Core
public bool Overlaps(DateRange other)
{
return Start < other.End && End > other.Start;
}
public bool Includes(DateTime date)
{
return date >= Start && date <= End;
}
}
@@ -0,0 +1,35 @@
using System;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Nashel.Modules.Collaboration.Domain.Repositories;
using Nashel.Modules.Collaboration.Domain.Services;
using Nashel.Modules.Collaboration.Infrastructure.Persistence;
using Nashel.Modules.Collaboration.Infrastructure.Persistence.Repositories;
namespace Nashel.Modules.Collaboration.Infrastructure
{
public static class DependencyInjection
{
public static IServiceCollection AddCollaborationModule(this IServiceCollection services, IConfiguration configuration)
{
var connectionString = configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<CollaborationDbContext>(options =>
options.UseNpgsql(connectionString));
services.AddScoped<IContractRepository, ContractRepository>();
services.AddScoped<ScheduleConflictService>();
// Register MediatR
services.AddMediatR(cfg =>
{
// Register handlers from Application layer
cfg.RegisterServicesFromAssembly(Assembly.Load("Nashel.Modules.Collaboration.Application"));
});
return services;
}
}
}
@@ -0,0 +1,104 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Nashel.Modules.Collaboration.Infrastructure.Persistence;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Nashel.Modules.Collaboration.Infrastructure.Migrations
{
[DbContext(typeof(CollaborationDbContext))]
[Migration("20260211111004_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("collaboration")
.HasAnnotation("ProductVersion", "10.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Nashel.Modules.Collaboration.Domain.Entities.Contract", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("EmployeeId")
.HasColumnType("uuid");
b.Property<Guid>("EmployerId")
.HasColumnType("uuid");
b.Property<string>("Role")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Contracts", "collaboration");
});
modelBuilder.Entity("Nashel.Modules.Collaboration.Domain.Entities.Contract", b =>
{
b.OwnsMany("Nashel.Modules.Collaboration.Domain.Entities.VetoLock", "VetoLocks", b1 =>
{
b1.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b1.Property<Guid>("ContractId")
.HasColumnType("uuid");
b1.HasKey("Id");
b1.HasIndex("ContractId");
b1.ToTable("VetoLocks", "collaboration");
b1.WithOwner()
.HasForeignKey("ContractId");
b1.OwnsOne("Nashel.Modules.Collaboration.Domain.ValueObjects.DateRange", "Period", b2 =>
{
b2.Property<Guid>("VetoLockId")
.HasColumnType("uuid");
b2.Property<DateTime>("End")
.HasColumnType("timestamp with time zone")
.HasColumnName("VetoEnd");
b2.Property<DateTime>("Start")
.HasColumnType("timestamp with time zone")
.HasColumnName("VetoStart");
b2.HasKey("VetoLockId");
b2.ToTable("VetoLocks", "collaboration");
b2.WithOwner()
.HasForeignKey("VetoLockId");
});
b1.Navigation("Period")
.IsRequired();
});
b.Navigation("VetoLocks");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,74 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Nashel.Modules.Collaboration.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.EnsureSchema(
name: "collaboration");
migrationBuilder.CreateTable(
name: "Contracts",
schema: "collaboration",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
EmployerId = table.Column<Guid>(type: "uuid", nullable: false),
EmployeeId = table.Column<Guid>(type: "uuid", nullable: false),
Role = table.Column<string>(type: "text", nullable: false),
Status = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Contracts", x => x.Id);
});
migrationBuilder.CreateTable(
name: "VetoLocks",
schema: "collaboration",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
VetoStart = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
VetoEnd = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
ContractId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_VetoLocks", x => x.Id);
table.ForeignKey(
name: "FK_VetoLocks_Contracts_ContractId",
column: x => x.ContractId,
principalSchema: "collaboration",
principalTable: "Contracts",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_VetoLocks_ContractId",
schema: "collaboration",
table: "VetoLocks",
column: "ContractId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "VetoLocks",
schema: "collaboration");
migrationBuilder.DropTable(
name: "Contracts",
schema: "collaboration");
}
}
}
@@ -0,0 +1,104 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Nashel.Modules.Collaboration.Infrastructure.Persistence;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Nashel.Modules.Collaboration.Infrastructure.Migrations
{
[DbContext(typeof(CollaborationDbContext))]
[Migration("20260310145106_BackendUpdate_Collaboration")]
partial class BackendUpdate_Collaboration
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("collaboration")
.HasAnnotation("ProductVersion", "10.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Nashel.Modules.Collaboration.Domain.Entities.Contract", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("EmployeeId")
.HasColumnType("uuid");
b.Property<Guid>("EmployerId")
.HasColumnType("uuid");
b.Property<string>("Role")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Contracts", "collaboration");
});
modelBuilder.Entity("Nashel.Modules.Collaboration.Domain.Entities.Contract", b =>
{
b.OwnsMany("Nashel.Modules.Collaboration.Domain.Entities.VetoLock", "VetoLocks", b1 =>
{
b1.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b1.Property<Guid>("ContractId")
.HasColumnType("uuid");
b1.HasKey("Id");
b1.HasIndex("ContractId");
b1.ToTable("VetoLocks", "collaboration");
b1.WithOwner()
.HasForeignKey("ContractId");
b1.OwnsOne("Nashel.Modules.Collaboration.Domain.ValueObjects.DateRange", "Period", b2 =>
{
b2.Property<Guid>("VetoLockId")
.HasColumnType("uuid");
b2.Property<DateTime>("End")
.HasColumnType("timestamp with time zone")
.HasColumnName("VetoEnd");
b2.Property<DateTime>("Start")
.HasColumnType("timestamp with time zone")
.HasColumnName("VetoStart");
b2.HasKey("VetoLockId");
b2.ToTable("VetoLocks", "collaboration");
b2.WithOwner()
.HasForeignKey("VetoLockId");
});
b1.Navigation("Period")
.IsRequired();
});
b.Navigation("VetoLocks");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Nashel.Modules.Collaboration.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class BackendUpdate_Collaboration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}
@@ -0,0 +1,101 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Nashel.Modules.Collaboration.Infrastructure.Persistence;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Nashel.Modules.Collaboration.Infrastructure.Migrations
{
[DbContext(typeof(CollaborationDbContext))]
partial class CollaborationDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("collaboration")
.HasAnnotation("ProductVersion", "10.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Nashel.Modules.Collaboration.Domain.Entities.Contract", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("EmployeeId")
.HasColumnType("uuid");
b.Property<Guid>("EmployerId")
.HasColumnType("uuid");
b.Property<string>("Role")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Contracts", "collaboration");
});
modelBuilder.Entity("Nashel.Modules.Collaboration.Domain.Entities.Contract", b =>
{
b.OwnsMany("Nashel.Modules.Collaboration.Domain.Entities.VetoLock", "VetoLocks", b1 =>
{
b1.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b1.Property<Guid>("ContractId")
.HasColumnType("uuid");
b1.HasKey("Id");
b1.HasIndex("ContractId");
b1.ToTable("VetoLocks", "collaboration");
b1.WithOwner()
.HasForeignKey("ContractId");
b1.OwnsOne("Nashel.Modules.Collaboration.Domain.ValueObjects.DateRange", "Period", b2 =>
{
b2.Property<Guid>("VetoLockId")
.HasColumnType("uuid");
b2.Property<DateTime>("End")
.HasColumnType("timestamp with time zone")
.HasColumnName("VetoEnd");
b2.Property<DateTime>("Start")
.HasColumnType("timestamp with time zone")
.HasColumnName("VetoStart");
b2.HasKey("VetoLockId");
b2.ToTable("VetoLocks", "collaboration");
b2.WithOwner()
.HasForeignKey("VetoLockId");
});
b1.Navigation("Period")
.IsRequired();
});
b.Navigation("VetoLocks");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\Nashel.BuildingBlocks.csproj" />
<ProjectReference Include="..\Application\Nashel.Modules.Collaboration.Application.csproj" />
<ProjectReference Include="..\Domain\Nashel.Modules.Collaboration.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
</ItemGroup>
<PropertyGroup>
<RootNamespace>Nashel.Modules.Collaboration.Infrastructure</RootNamespace>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
@@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore;
using Nashel.Modules.Collaboration.Domain.Entities;
namespace Nashel.Modules.Collaboration.Infrastructure.Persistence
{
public class CollaborationDbContext : DbContext
{
public DbSet<Contract> Contracts { get; set; }
public CollaborationDbContext(DbContextOptions<CollaborationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("collaboration");
modelBuilder.ApplyConfigurationsFromAssembly(typeof(CollaborationDbContext).Assembly);
base.OnModelCreating(modelBuilder);
}
}
}
@@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Nashel.Modules.Collaboration.Domain.Entities;
namespace Nashel.Modules.Collaboration.Infrastructure.Persistence.Configurations
{
public class ContractConfiguration : IEntityTypeConfiguration<Contract>
{
public void Configure(EntityTypeBuilder<Contract> builder)
{
builder.HasKey(c => c.Id);
builder.Property(c => c.Role).IsRequired();
builder.Property(c => c.Status).HasConversion<string>();
// Настройка VetoLocks как Owned Collection (или таблицы связи)
// Вариант с отдельной таблицей "VetoLocks"
builder.OwnsMany(c => c.VetoLocks, a =>
{
a.ToTable("VetoLocks");
a.HasKey("Id"); // Теневое свойство или явное
a.WithOwner().HasForeignKey("ContractId");
a.OwnsOne(v => v.Period, p =>
{
p.Property(d => d.Start).HasColumnName("VetoStart");
p.Property(d => d.End).HasColumnName("VetoEnd");
});
});
}
}
}
@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Nashel.Modules.Collaboration.Domain.Entities;
using Nashel.Modules.Collaboration.Domain.Repositories;
namespace Nashel.Modules.Collaboration.Infrastructure.Persistence.Repositories
{
public class ContractRepository : IContractRepository
{
private readonly CollaborationDbContext _context;
public ContractRepository(CollaborationDbContext context)
{
_context = context;
}
public async Task AddAsync(Contract contract, CancellationToken cancellationToken = default)
{
await _context.Contracts.AddAsync(contract, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
}
public async Task<List<Contract>> GetAllByEmployeeIdAsync(Guid employeeId, CancellationToken cancellationToken = default)
{
return await _context.Contracts
.Include(c => c.VetoLocks)
.Where(c => c.EmployeeId == employeeId)
.ToListAsync(cancellationToken);
}
public async Task<Contract?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
{
return await _context.Contracts
.Include(c => c.VetoLocks)
.FirstOrDefaultAsync(c => c.Id == id, cancellationToken);
}
public async Task UpdateAsync(Contract contract, CancellationToken cancellationToken = default)
{
_context.Contracts.Update(contract);
await _context.SaveChangesAsync(cancellationToken);
}
}
}
@@ -0,0 +1,94 @@
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Nashel.Modules.Collaboration.Application.Commands;
using Nashel.Modules.Collaboration.Application.Queries;
namespace Nashel.Modules.Collaboration.Presentation
{
public static class CollaborationEndpoints
{
public static void MapCollaborationEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/api/hr").WithTags("Collaboration (HR)");
// POST /api/hr/hire
group.MapPost("/hire", async ([FromBody] HireEmployeeRequest request, ISender sender) =>
{
var command = new HireEmployeeCommand(request.EmployerId, request.EmployeeId, request.Role);
var contractId = await sender.Send(command);
return Results.Ok(contractId);
})
.WithName("HireEmployee")
.WithOpenApi(operation => new(operation) { Summary = "Нанять сотрудника", Description = "Создает контракт между работодателем и сотрудником." });
// POST /api/hr/veto
group.MapPost("/veto", async ([FromBody] SetVetoRequest request, ISender sender) =>
{
var command = new SetVetoCommand(request.ContractId, request.Start, request.End);
await sender.Send(command);
return Results.Ok();
})
.WithName("SetVeto")
.WithOpenApi(operation => new(operation) { Summary = "Наложить вето", Description = "Блокирует расписание сотрудника на указанный период." });
// GET /api/hr/check-availability
group.MapGet("/check-availability", async (
[FromQuery] Guid employeeId,
[FromQuery] DateTime start,
[FromQuery] DateTime end,
ISender sender) =>
{
var query = new CheckAvailabilityQuery(employeeId, start, end);
var isAvailable = await sender.Send(query);
return Results.Ok(new { IsAvailable = isAvailable });
})
.WithName("CheckAvailability")
.WithOpenApi(operation => new(operation) { Summary = "Проверить доступность", Description = "Проверяет, свободен ли сотрудник в указанный период (нет ли вето)." });
}
}
/// <summary>
/// Запрос на найм сотрудника.
/// </summary>
public record HireEmployeeRequest
{
/// <summary>
/// ID работодателя.
/// </summary>
public Guid EmployerId { get; init; }
/// <summary>
/// ID сотрудника.
/// </summary>
public Guid EmployeeId { get; init; }
/// <summary>
/// Роль сотрудника (прораб, мастер и т.д.).
/// </summary>
public string Role { get; init; } = default!;
}
/// <summary>
/// Запрос на установку вето.
/// </summary>
public record SetVetoRequest
{
/// <summary>
/// ID контракта.
/// </summary>
public Guid ContractId { get; init; }
/// <summary>
/// Начало периода блокировки.
/// </summary>
public DateTime Start { get; init; }
/// <summary>
/// Конец периода блокировки.
/// </summary>
public DateTime End { get; init; }
}
}

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