от src избавился

This commit is contained in:
gorbenko.pavel
2026-08-12 12:19:04 +05:00
parent 509f26de7b
commit 4b83d666f1
71 changed files with 67 additions and 1135 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
run: dotnet restore backend/Tnsu.ApprovalRoute.sln
- name: Publish (local)
run: dotnet publish backend/src/Tnsu.ApprovalRoute.Api/Tnsu.ApprovalRoute.Api.csproj -c Release -o "$LOCAL_PUBLISH"
run: dotnet publish backend/Tnsu.ApprovalRoute.Api/Tnsu.ApprovalRoute.Api.csproj -c Release -o "$LOCAL_PUBLISH"
- name: Install cifs-utils
run: |
+5 -6
View File
@@ -22,11 +22,10 @@ backend/
Directory.Build.props # общие настройки таргета (net10.0, nullable, etc.)
Directory.Packages.props # централизованные версии пакетов
Tnsu.ApprovalRoute.sln
src/
Tnsu.ApprovalRoute.Domain/ # сущности и enum'ы домена
Tnsu.ApprovalRoute.Application/ # сервисы, DTO, валидаторы
Tnsu.ApprovalRoute.Infrastructure/ # EF Core DbContext, миграции, клиент tnsu-portal
Tnsu.ApprovalRoute.Api/ # ASP.NET Core Controllers, Program.cs, авторизация
Tnsu.ApprovalRoute.Domain/ # сущности и enum'ы домена
Tnsu.ApprovalRoute.Application/ # сервисы, DTO, валидаторы
Tnsu.ApprovalRoute.Infrastructure/ # EF Core DbContext, миграции, клиент tnsu-portal
Tnsu.ApprovalRoute.Api/ # ASP.NET Core Controllers, Program.cs, авторизация
tests/
Tnsu.ApprovalRoute.IntegrationTests/
frontend/
@@ -61,7 +60,7 @@ npm run build # heft test + package-solution --production
```bash
cd backend
dotnet run --project src/Tnsu.ApprovalRoute.Api
dotnet run --project Tnsu.ApprovalRoute.Api
```
Требуется PostgreSQL (строка подключения — `ConnectionStrings:Postgres` в `appsettings.json`).
+21
View File
@@ -0,0 +1,21 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY Directory.Build.props Directory.Packages.props ./
COPY Tnsu.ApprovalRoute.Domain/Tnsu.ApprovalRoute.Domain.csproj Tnsu.ApprovalRoute.Domain/
COPY Tnsu.ApprovalRoute.Application/Tnsu.ApprovalRoute.Application.csproj Tnsu.ApprovalRoute.Application/
COPY Tnsu.ApprovalRoute.Infrastructure/Tnsu.ApprovalRoute.Infrastructure.csproj Tnsu.ApprovalRoute.Infrastructure/
COPY Tnsu.ApprovalRoute.Api/Tnsu.ApprovalRoute.Api.csproj Tnsu.ApprovalRoute.Api/
RUN dotnet restore Tnsu.ApprovalRoute.Api/Tnsu.ApprovalRoute.Api.csproj
COPY Tnsu.ApprovalRoute.Domain/ Tnsu.ApprovalRoute.Domain/
COPY Tnsu.ApprovalRoute.Application/ Tnsu.ApprovalRoute.Application/
COPY Tnsu.ApprovalRoute.Infrastructure/ Tnsu.ApprovalRoute.Infrastructure/
COPY Tnsu.ApprovalRoute.Api/ Tnsu.ApprovalRoute.Api/
RUN dotnet publish Tnsu.ApprovalRoute.Api/Tnsu.ApprovalRoute.Api.csproj -c Release -o /app/publish /p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
COPY --from=build /app/publish .
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "Tnsu.ApprovalRoute.Api.dll"]
+4 -10
View File
@@ -3,15 +3,13 @@ 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}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tnsu.ApprovalRoute.Domain", "Tnsu.ApprovalRoute.Domain\Tnsu.ApprovalRoute.Domain.csproj", "{67C6FECB-26BF-4A7E-AC40-574F4D88EEAC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tnsu.ApprovalRoute.Domain", "src\Tnsu.ApprovalRoute.Domain\Tnsu.ApprovalRoute.Domain.csproj", "{67C6FECB-26BF-4A7E-AC40-574F4D88EEAC}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tnsu.ApprovalRoute.Application", "Tnsu.ApprovalRoute.Application\Tnsu.ApprovalRoute.Application.csproj", "{2EEA333B-16C7-460E-BD07-AB1F32D6A9A9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tnsu.ApprovalRoute.Application", "src\Tnsu.ApprovalRoute.Application\Tnsu.ApprovalRoute.Application.csproj", "{2EEA333B-16C7-460E-BD07-AB1F32D6A9A9}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tnsu.ApprovalRoute.Infrastructure", "Tnsu.ApprovalRoute.Infrastructure\Tnsu.ApprovalRoute.Infrastructure.csproj", "{46EF4247-8B98-4CC9-A981-186C8D24163C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tnsu.ApprovalRoute.Infrastructure", "src\Tnsu.ApprovalRoute.Infrastructure\Tnsu.ApprovalRoute.Infrastructure.csproj", "{46EF4247-8B98-4CC9-A981-186C8D24163C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tnsu.ApprovalRoute.Api", "src\Tnsu.ApprovalRoute.Api\Tnsu.ApprovalRoute.Api.csproj", "{4697B342-BE47-435B-B854-75F9277AD769}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tnsu.ApprovalRoute.Api", "Tnsu.ApprovalRoute.Api\Tnsu.ApprovalRoute.Api.csproj", "{4697B342-BE47-435B-B854-75F9277AD769}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
EndProject
@@ -92,10 +90,6 @@ Global
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{67C6FECB-26BF-4A7E-AC40-574F4D88EEAC} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{2EEA333B-16C7-460E-BD07-AB1F32D6A9A9} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{46EF4247-8B98-4CC9-A981-186C8D24163C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{4697B342-BE47-435B-B854-75F9277AD769} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{F5E2BA0E-7978-414C-98D4-D3C91C162FDE} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
EndGlobalSection
EndGlobal
-9
View File
@@ -1,9 +0,0 @@
{
"ExpandedNodes": [
"",
"\\Tnsu.ApprovalRoute.Api",
"\\Tnsu.ApprovalRoute.Application"
],
"SelectedNode": "\\Tnsu.ApprovalRoute.Application",
"PreviewInSolutionExplorer": false
}
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
@@ -1,37 +0,0 @@
{
"Version": 1,
"WorkspaceRootPath": "C:\\Users\\trishina.mariya\\Documents\\Projects\\tnsu-approval-route\\backend\\src\\",
"Documents": [
{
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\trishina.mariya\\Documents\\Projects\\tnsu-approval-route\\backend\\src\\Tnsu.ApprovalRoute.Api\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}",
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Tnsu.ApprovalRoute.Api\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
}
],
"DocumentGroupContainers": [
{
"Orientation": 0,
"VerticalTabListWidth": 256,
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": 0,
"Children": [
{
"$type": "Document",
"DocumentIndex": 0,
"Title": "appsettings.json",
"DocumentMoniker": "C:\\Users\\trishina.mariya\\Documents\\Projects\\tnsu-approval-route\\backend\\src\\Tnsu.ApprovalRoute.Api\\appsettings.json",
"RelativeDocumentMoniker": "Tnsu.ApprovalRoute.Api\\appsettings.json",
"ToolTip": "C:\\Users\\trishina.mariya\\Documents\\Projects\\tnsu-approval-route\\backend\\src\\Tnsu.ApprovalRoute.Api\\appsettings.json",
"RelativeToolTip": "Tnsu.ApprovalRoute.Api\\appsettings.json",
"ViewState": "AgIAAAAAAAAAAAAAAAAAABUAAAAXAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|",
"WhenOpened": "2026-07-28T05:52:49.686Z",
"EditorCaption": ""
}
]
}
]
}
]
}
@@ -1,37 +0,0 @@
{
"Version": 1,
"WorkspaceRootPath": "C:\\Users\\trishina.mariya\\Documents\\Projects\\tnsu-approval-route\\backend\\src\\",
"Documents": [
{
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\trishina.mariya\\Documents\\Projects\\tnsu-approval-route\\backend\\src\\Tnsu.ApprovalRoute.Api\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}",
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Tnsu.ApprovalRoute.Api\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
}
],
"DocumentGroupContainers": [
{
"Orientation": 0,
"VerticalTabListWidth": 256,
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": 0,
"Children": [
{
"$type": "Document",
"DocumentIndex": 0,
"Title": "appsettings.json",
"DocumentMoniker": "C:\\Users\\trishina.mariya\\Documents\\Projects\\tnsu-approval-route\\backend\\src\\Tnsu.ApprovalRoute.Api\\appsettings.json",
"RelativeDocumentMoniker": "Tnsu.ApprovalRoute.Api\\appsettings.json",
"ToolTip": "C:\\Users\\trishina.mariya\\Documents\\Projects\\tnsu-approval-route\\backend\\src\\Tnsu.ApprovalRoute.Api\\appsettings.json",
"RelativeToolTip": "Tnsu.ApprovalRoute.Api\\appsettings.json",
"ViewState": "AgIAAAAAAAAAAAAAAAAAABUAAAAXAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|",
"WhenOpened": "2026-07-28T05:52:49.686Z",
"EditorCaption": ""
}
]
}
]
}
]
}
@@ -1,18 +0,0 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY Directory.Build.props Directory.Packages.props ./
COPY src/Tnsu.ApprovalRoute.Domain/Tnsu.ApprovalRoute.Domain.csproj src/Tnsu.ApprovalRoute.Domain/
COPY src/Tnsu.ApprovalRoute.Application/Tnsu.ApprovalRoute.Application.csproj src/Tnsu.ApprovalRoute.Application/
COPY src/Tnsu.ApprovalRoute.Infrastructure/Tnsu.ApprovalRoute.Infrastructure.csproj src/Tnsu.ApprovalRoute.Infrastructure/
COPY src/Tnsu.ApprovalRoute.Api/Tnsu.ApprovalRoute.Api.csproj src/Tnsu.ApprovalRoute.Api/
RUN dotnet restore src/Tnsu.ApprovalRoute.Api/Tnsu.ApprovalRoute.Api.csproj
COPY src/ src/
RUN dotnet publish src/Tnsu.ApprovalRoute.Api/Tnsu.ApprovalRoute.Api.csproj -c Release -o /app/publish /p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
COPY --from=build /app/publish .
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "Tnsu.ApprovalRoute.Api.dll"]
@@ -5,8 +5,8 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Tnsu.ApprovalRoute.Infrastructure\Tnsu.ApprovalRoute.Infrastructure.csproj" />
<ProjectReference Include="..\..\src\Tnsu.ApprovalRoute.Application\Tnsu.ApprovalRoute.Application.csproj" />
<ProjectReference Include="..\..\Tnsu.ApprovalRoute.Infrastructure\Tnsu.ApprovalRoute.Infrastructure.csproj" />
<ProjectReference Include="..\..\Tnsu.ApprovalRoute.Application\Tnsu.ApprovalRoute.Application.csproj" />
</ItemGroup>
<ItemGroup>
+1 -1
View File
@@ -22,7 +22,7 @@ services:
api:
build:
context: ./backend
dockerfile: src/Tnsu.ApprovalRoute.Api/Dockerfile
dockerfile: Tnsu.ApprovalRoute.Api/Dockerfile
container_name: tnsu-approval-route-api
restart: unless-stopped
depends_on:
@@ -62,6 +62,12 @@ export function AsyncSearchableSelect<T>({
// Растущий счётчик запросов: если ответ пришёл не от последнего запроса
// (например, пользователь допечатал ещё символ) — просто игнорируем его.
const requestIdRef = React.useRef(0);
// getValue нужен внутри runSearch (дедуп при доклейке страницы), но в deps его
// класть нельзя: пропс приходит инлайновой стрелкой и меняет ссылку на каждом
// рендере — runSearch пересоздавался бы постоянно и перезапускал эффект
// бесконечной прокрутки. Держим последнюю версию в ref.
const getValueRef = React.useRef(getValue);
getValueRef.current = getValue;
const runSearch = React.useCallback((q: string, targetPage: number, append: boolean) => {
const requestId = ++requestIdRef.current;
@@ -71,7 +77,16 @@ export function AsyncSearchableSelect<T>({
search(q, targetPage)
.then(result => {
if (requestIdRef.current !== requestId) return; // устарел
setItems(prev => (append ? [...prev, ...result.items] : result.items));
// Дедуп при доклейке: соседние страницы могут пересекаться, если индекс
// переупорядочился между запросами (StartRow смещает окно по РАНГУ, а не
// по устойчивому курсору). Дубликат здесь = два пункта с одинаковым
// React-ключом, то есть заявка в списке дважды. Внутри одной страницы
// дубли отсекает уже источник (см. searchRequests в requestManageService).
setItems(prev => {
if (!append) return result.items;
const shown = new Set(prev.map(i => getValueRef.current(i)));
return [...prev, ...result.items.filter(i => !shown.has(getValueRef.current(i)))];
});
setHasMore(result.hasMore);
setPage(targetPage);
})
@@ -17,7 +17,7 @@ import {
} from '../models';
// Тонкий 1:1 фасад над контроллерами бэкенда:
// backend/src/Tnsu.ApprovalRoute.Api/Controllers/{RequestTypes,TemplateRoutes,TemplateRouteSteps}Controller.cs
// backend/Tnsu.ApprovalRoute.Api/Controllers/{RequestTypes,TemplateRoutes,TemplateRouteSteps}Controller.cs
export class ApprovalRouteApi {
constructor(private readonly http: IHttpClient) {}
@@ -152,7 +152,22 @@ export class RequestManageService {
});
const rawRows = (result.PrimarySearchResults ?? []) as Array<{ ListItemID?: string }>;
const ids = rawRows.map(r => Number(r.ListItemID)).filter(id => !isNaN(id) && id > 0);
// Дедуп по Id обязателен: TrimDuplicates: false выше выключает штатную
// дедупликацию Search (она иногда схлопывает нужные нам заявки), и тогда
// индекс может отдать один и тот же элемент несколькими хитами
// (переиндексация, несколько «версий» в crawl). Без этого фильтра хиты
// превращались в пункты комбобокса один-в-один, и заявка показывалась в
// списке дважды — с одинаковым React-ключом. Та же причина и то же лечение,
// что в request-portal (features/myRequests/myRequests.service.ts/hitsToEntries).
const seen = new Set<number>();
const ids = rawRows
.map(r => Number(r.ListItemID))
.filter(id => !isNaN(id) && id > 0)
.filter(id => {
if (seen.has(id)) return false;
seen.add(id);
return true;
});
if (ids.length === 0) return { items: [], hasMore: false };
const list = sp.web.getList(requestType.listId);