DI2 внедрение, таск 1: серверная интеграция Data-Insight2 (server/finance-di2)
- Копия DI2-сервера в server/finance-di2/ с правками: schema/db-client/cache/table-config/override-tables/google-auth/audit-agent/routes - db-client — обёртка над существующим пулом server/finance/db-client (второй пул не создаётся, добавлен экспорт isConnectionError) - ai-config.ts — чистое IO конфигов из ai-agent.ts без Telegram-поллинга; роуты /api/ai/toggle и /api/ai/status удалены, без compression/startAuditScheduler/autoStartIfEnabled - Обёртка registerDi2Routes с auth-gate (authenticateToken + finance.manage), регистрация строго перед registerFinanceRoutes - Статика /di2 из dist/public-di2 перед веткой vite/static (dev и prod) - Фикс предсуществующего бага DI2: buildGlobalExclusionConditions без cats в /api/profitability
This commit is contained in:
21
.superpowers/sdd/di2-embed/progress.md
Normal file
21
.superpowers/sdd/di2-embed/progress.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# SDD ledger — plan: внедрение Data-Insight2 в iistwin (субапп /di2)
|
||||
|
||||
BASE (start): a60a75e
|
||||
|
||||
## Решения (Rulings)
|
||||
- R1: DI2 внедряется целиком «как есть» (пользователь, 2026-09-05): отдельный SPA-субапп на `/di2` + серверные роуты DI2 регистрируются ДО старого модуля финансов и перехватывают общие пути (/api/income/*, /api/debt/* и т.д.). Старый модуль НЕ удаляем до сравнения пользователем. Стоит ошибки: дублирование 68 роутов — лечится удалением старого модуля позже.
|
||||
- R2: ai-agent.ts (Telegram-поллинг бот) НЕ переносим. Вместо него — `ai-config.ts` с чистыми функциями IO (loadAiConfig/saveAiConfig/loadAiPlanningConfig/saveAiPlanningConfig/getAiLog/saveLogEntry), вырезанными из ai-agent.ts. Роуты /api/ai/toggle и /api/ai/status удаляем (ссылаются на polling), autoStartIfEnabled() не вызываем. Остальное AI (config, planning-config/toggle, log, models, planning/ai-analyze) сохраняем. Стоит: кнопки «вкл бота» в настройках AI DI2 не будет.
|
||||
- R3: startAuditScheduler() из DI2 не запускаем (у iistwin уже есть свой аудит-шедулер server/finance/audit.ts; двойные тяжёлые запросы во внешнюю БД не нужны). Ручной /api/audit/run и /api/audit/history DI2 сохраняем. Стоит: расписание аудита DI2 не активно — при желании включить позже одной строкой.
|
||||
- R4: compression не переносим (не зависимость iistwin, пользы мало за reverse proxy). Стоит: ничего.
|
||||
- R5: Вместо отдельного пула DI2 db-client делегирует к пулу iistwin `server/finance/db-client.ts` (один пул, тот же файл data/db-config.json). Стоит ошибки в делегировании — проверить тестом подключения.
|
||||
- R6: Google-redirect DI2 переписать с REPLIT_DOMAINS на env APP_BASE_URL || https://iistwin.ru (путь /api/auth/google/callback оставить, конфликтов нет).
|
||||
|
||||
## Preflight scan
|
||||
- Task 1 (server) и Task 2 (client) трогают общий package.json (deps node-cron / scripts build) — поручаем оба изменения Task 1, Task 2 не трогает package.json... УТОЧНЕНИЕ: build-скрипт di2 логичнее в Task 2; конфликт один (строка scripts.build) — правило: Task 2 редактирует scripts.build, Task 1 только добавляет node-cron в dependencies. Порядок: Task 1 → Task 2 (resume агента).
|
||||
- Проверка spec возможна только после обоих тасков (build проходит только с клиентом). Ревью единое по всему диапазону BASE..HEAD.
|
||||
|
||||
## Заметки
|
||||
- 68 общих путей DI2/iistwin — DI2 регистрируется первым, старые становятся теневыми (намеренно).
|
||||
- client-di2 вне include корневого tsconfig → отдельный tsconfig, check-скрипт `tsc -p client-di2/tsconfig.json`.
|
||||
- wouter 3.10 поддерживает `<Router base>` — base="/di2".
|
||||
- DI2 tsconfig идентичен корневому iistwin по строгости (strict: true) — копируется с заменой путей алиасов.
|
||||
59
.superpowers/sdd/di2-embed/task-1-brief.md
Normal file
59
.superpowers/sdd/di2-embed/task-1-brief.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Task 1 — Серверная интеграция Data-Insight2
|
||||
|
||||
Цель: перенести серверную часть DI2 (`C:\1Ai\1iistwin\tmp\Data-Insight2\server`) в `server/finance-di2/` репозитория `C:\1Ai\1iistwin\1Project`, обёрнуть в авторизацию iistwin и зарегистрировать роуты ДО старого модуля финансов. Клиент НЕ входит в задачу (Task 2), но добавь зависимость `node-cron` (DI2 audit-agent.ts импортирует cron вверху файла; планировщик мы не запускаем, но импорт должен резолвиться). НЕ меняй `scripts.build` в package.json (это Task 2).
|
||||
|
||||
## Источник (только чтение)
|
||||
- `C:\1Ai\1iistwin\tmp\Data-Insight2\server\`: routes.ts (6387 строк), cache.ts, table-config.ts, override-tables.ts, google-auth.ts, audit-agent.ts (477 строк), db-client.ts, storage.ts (5 строк).
|
||||
- `C:\1Ai\1iistwin\tmp\Data-Insight2\shared\schema.ts` (366 строк, чистые типы/константы, без drizzle).
|
||||
|
||||
## Что создать в 1Project
|
||||
|
||||
1. **`server/finance-di2/`** — копии файлов DI2 с правками:
|
||||
- `schema.ts` ← копия `tmp/Data-Insight2/shared/schema.ts` (типы и константы).
|
||||
- `db-client.ts` — НЕ копировать DI2-версию. Написать обёртку, которая импортирует из `../finance/db-client` (iistwin): `query, testConnection, getConfig, setConfig, isConnectionError` и экспортирует их же + `getPoolClient` (проверь, что `server/finance/db-client.ts` экспортирует все эти имена; если `getPoolClient` отсутствует — добавь его туда, а не дублируй пул: открой файл и посмотри, есть ли там pool — экспортируй существующий). Формат конфига (data/db-config.json) идентичен — это тот же код по происхождению.
|
||||
- `cache.ts`, `table-config.ts`, `override-tables.ts` — копии без изменений (проверь их импорты: они должны ссылаться на `./db-client` и `./schema` — исправь алиасы).
|
||||
- `google-auth.ts` — копия с правкой `getRedirectUri()`: вместо `process.env.REPLIT_DOMAINS` использовать `process.env.APP_BASE_URL || "https://iistwin.ru"`, redirect `${APP_BASE_URL}/api/auth/google/callback`. Протокол всегда https.
|
||||
- `ai-config.ts` — НОВЫЙ файл: вырезать из `tmp/Data-Insight2/server/ai-agent.ts` только чистые функции IO без Telegram/поллинга: `loadAiConfig`, `saveAiConfig`, `loadAiPlanningConfig`, `saveAiPlanningConfig` (файлы data/ai-config.json, data/ai-planning-config.json), а также `getAiLog`, `saveLogEntry` (посмотри их реализацию в ai-agent.ts — обычно append в json/jsonl; перенеси как есть, включая нужные интерфейсы AiConfig/AiPlanningConfig). Без cron, без fetch, без Telegram.
|
||||
- `audit-agent.ts` — копия, но удалить `startAuditScheduler` и его вызовы (cronJob и импорт node-cron не нужны, если импорт только для этой функции — проверь: если cron используется только в startAuditScheduler, удали импорт и функцию целиком; тогда node-cron в package.json НЕ нужен — не добавляй). Оставить `runIntegrityAudit`, `getAuditHistory`.
|
||||
- `routes.ts` — копия с такими правками:
|
||||
- Импорт `dbConnectionSchema` с `./schema` (не `@shared/schema`).
|
||||
- Удалить `import compression from "compression"` и `app.use(compression());` (строка ~1142).
|
||||
- Строку 1371 `const {...} = await import("./ai-agent")` заменить на импорт из `./ai-config`: нужны только `loadAiConfig, saveAiConfig, loadAiPlanningConfig, saveAiPlanningConfig, getAiLog, saveLogEntry`.
|
||||
- Удалить роуты `app.post("/api/ai/toggle", ...)` и `app.get("/api/ai/status", ...)` (используют startPolling/stopPolling/isPolling).
|
||||
- Удалить вызов `autoStartIfEnabled();` (~строка 1508).
|
||||
- В конце: удалить `startAuditScheduler();` (строка 6384), оставить `scheduleTreeRebuild();`.
|
||||
- Проверь, что в routes.ts больше нет ссылок на ai-agent, compression, node-cron (grep).
|
||||
- Проверь остальные импорты вверху routes.ts: `./storage` (5-строчный файл — перенеси тоже, если импортируется), `./override-tables`, `./table-config`, `./cache`, `./google-auth`, `./audit-agent` — все становятся `./xxx` внутри finance-di2 (уже так).
|
||||
- `index.ts` — НОВЫЙ файл-обёртка `registerDi2Routes(app: Express)`:
|
||||
- Создать `express.Router()`.
|
||||
- Middleware на роутере: если `req.path` начинается с одного из префиксов DI2 — пропустить через `authenticateToken` затем `requirePermission('finance.manage')` (импорт из `../middleware/auth.middleware`), иначе `next()`.
|
||||
- Префиксы: `/api/income`, `/api/income-categories`, `/api/expenses`, `/api/expense-categories`, `/api/debt`, `/api/dashboard`, `/api/profitability`, `/api/heatmap`, `/api/planning`, `/api/salary`, `/api/audit`, `/api/calculations`, `/api/connection`, `/api/db`, `/api/table-config`, `/api/table-settings`, `/api/overrides`, `/api/tags`, `/api/tag-assignments`, `/api/cache`, `/api/refresh-all`, `/api/admin`, `/api/auth/google`, `/api/ai`, `/api/telegram`.
|
||||
- Затем вызвать DI2 `registerRoutes(httpServer, router as any)` — передав `null as any` вместо httpServer (он используется только в `return httpServer` в конце). Функция async — await. Затем `app.use(router)` — но НЕ сmount-префиксом, роуты абсолютные.
|
||||
- ВАЖНО порядок middleware: `router.use(authGate)` должен стоять ПЕРЕД регистрацией роутов DI2, и все DI2-роуты регистрируются на этом же роутере.
|
||||
2. **`server/routes/index.ts`**: добавить `await registerDi2Routes(app);` строкой НЕПОСРЕДСТВЕННО ПЕРЕД `await registerFinanceRoutes(app);` (строка 113). Импорт добавь рядом с импортом registerFinanceRoutes (строка 33).
|
||||
3. **`server/index.ts`**: перед вызовом `serveStatic(app)` (или где там статика подключается в prod-ветке — найди по `serveStatic(`) добавить:
|
||||
```ts
|
||||
// Data-Insight2 sub-app (финансы v2)
|
||||
const di2Dist = path.resolve(import.meta.dirname, "public-di2");
|
||||
if (fs.existsSync(di2Dist)) {
|
||||
app.use("/di2", express.static(di2Dist));
|
||||
app.use("/di2", (_req, res) => res.sendFile(path.resolve(di2Dist, "index.html")));
|
||||
}
|
||||
```
|
||||
(проверь, какие импорты path/fs уже есть в index.ts; позицию выбери так, чтобы это было ДО catch-all `serveStatic`, но ПОСЛЕ регистрации API-роутов). В dev-режиме (setupVite) тоже должен работать: setupVite вешает `app.use("*")` — проверь порядок: если setupVite вызывается в dev ПОСЛЕ нашего блока, то /di2/* уйдёт в main index.html. Чтобы dev тоже работал, добавь этот блок ПЕРЕД веткой `if (process.env.NODE_ENV === "development") setupVite(...) else serveStatic(app)` (найди эту ветку и вставь до неё). Проверь наличие fs/path импортов.
|
||||
4. **package.json**: если audit-agent оставил импорт cron — добавь `node-cron` в dependencies (проверь версию из DI2 package.json). Если импорт удалён — не добавляй. `scripts.build` НЕ трогать.
|
||||
|
||||
## Проверки (обязательно выполнить и приложить вывод)
|
||||
1. `grep -rn "ai-agent\|compression\|REPLIT" server/finance-di2/` — пусто.
|
||||
2. `npx tsc --noEmit` (корневой check) — без ошибок в server/finance-di2 (ошибки в других местах, если были до тебя, не считаются; проверь baseline сначала: `git stash` не нужен — просто сравни с ошибками на BASE, если есть новые — чини).
|
||||
3. `npm run build` должен пройти (клиентская часть di2 ещё не создана — это нормально, build собирает только main client; сборка di2 будет в Task 2). Если build падает из-за твоих изменений — чини.
|
||||
4. Быстрый smoke-тест сервера без Docker: запустить `npm run dev` не надо (трогает БД продакшена локально? нет — локальный dev стучится в локальную БД; НЕ запускай дев-сервер). Достаточно typecheck+build.
|
||||
|
||||
## Контракт отчёта
|
||||
Напиши файл `C:\1Ai\1iistwin\1Project\.superpowers\sdd\di2-embed\task-1-report.md`: что скопировано, какие правки, вывод grep-проверок, вывод tsc/build (последние строки), открытые вопросы. Верни: статус, список коммитов, одну строку по проверкам, опасения.
|
||||
|
||||
## Жёсткие ограничения
|
||||
- Код/комментарии — на русском (существующие DI2-комментарии не переводить обратно, оставь как есть).
|
||||
- Не трогай `server/finance/*` кроме случая, когда нужно экспортировать getPoolClient (добавь экспорт существующего пула, не создавай второй пул).
|
||||
- Не коммить изменения в tmp/Data-Insight2.
|
||||
- Pre-push hook прогоняет check+build — коммить в main.
|
||||
41
.superpowers/sdd/di2-embed/task-1-report.md
Normal file
41
.superpowers/sdd/di2-embed/task-1-report.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Task 1 — Серверная интеграция Data-Insight2: отчёт
|
||||
|
||||
## Статус: DONE
|
||||
|
||||
## Что сделано
|
||||
|
||||
### Новая папка `server/finance-di2/`
|
||||
- `schema.ts` — копия `tmp/Data-Insight2/shared/schema.ts` (чистые типы/константы, zod).
|
||||
- `db-client.ts` — **НЕ копия DI2**, а обёртка поверх `server/finance/db-client.ts` (iistwin): реэкспорт `query, testConnection, setConfig, isConnectionError, getPoolClient` + `getConfig()` с приведением к DI2-совместимой форме (iistwin-версия возвращает конфиг без пароля и может вернуть `null`; обёртка возвращает `DbConnectionConfig` с пустым паролем, как ожидает DI2-код). Второй пул не создаётся.
|
||||
- `cache.ts`, `table-config.ts`, `override-tables.ts` — копии без изменений (алиасы `@shared/schema` → `./schema` исправлены sed'ом в table-config.ts и routes.ts; override-tables/cache и так используют `./db-client`).
|
||||
- `google-auth.ts` — копия, `getRedirectUri()` переделан: `process.env.APP_BASE_URL || "https://iistwin.ru"`, redirect `${APP_BASE_URL}/api/auth/google/callback`, протокол всегда https. `REPLIT_DOMAINS` удалён.
|
||||
- `ai-config.ts` — НОВЫЙ файл: чистое IO из DI2 `ai-agent.ts` — `loadAiConfig/saveAiConfig` (data/ai-config.json), `loadAiPlanningConfig/saveAiPlanningConfig` (data/ai-planning-config.json), `getAiLog/saveLogEntry` (data/ai-query-log.json) + интерфейсы `AiConfig/AiPlanningConfig/AiLogEntry` и дефолтные промпты. Без cron, fetch, Telegram.
|
||||
- `audit-agent.ts` — копия без `import cron from "node-cron"`, без `cronJob` и без `startAuditScheduler()` (конец файла). Оставлены `runIntegrityAudit`, `getAuditHistory`. **node-cron в package.json НЕ добавлен** (импорт удалён целиком — cron использовался только в startAuditScheduler).
|
||||
- `routes.ts` — копия (6387 → 6359 строк) с правками:
|
||||
- удалён `import compression from "compression"` и `app.use(compression());`;
|
||||
- `dbConnectionSchema` импортируется из `./schema`;
|
||||
- динамический `await import("./ai-agent")` заменён статическим импортом `./ai-config` (только 6 IO-функций);
|
||||
- из `POST /api/ai/config` удалены вызовы `startPolling()/stopPolling()` (использовали Telegram-поллинг);
|
||||
- удалены роуты `POST /api/ai/toggle` и `GET /api/ai/status`;
|
||||
- удалён вызов `autoStartIfEnabled();`;
|
||||
- удалён вызов `startAuditScheduler();` в конце (оставлен `scheduleTreeRebuild();`);
|
||||
- **фикс предсуществующего бага DI2**: `buildGlobalExclusionConditions(incParams)` в `/api/profitability` и соседнем роуте вызывались без первого аргумента `cats` — под tsx в DI2 это молча возвращало `[]` (исключения категорий в рентабельности не применялись). Добавлен `loadCategories()` первым аргументом (строки ~2974 и ~3080). Поведение изменилось: глобальные excluded-категории теперь реально исключаются из рентабельности — как и задумано.
|
||||
- `index.ts` — НОВЫЙ файл-обёртка `registerDi2Routes(app)`: `express.Router()`, middleware auth-gate (`authenticateToken` → `requirePermission('finance.manage')`) по 26 префиксам из брифа, стоящий ПЕРЕД регистрацией DI2-роутов на том же роутере; `await registerRoutes(null as any, router as any)`; `app.use(router)` без mount-префикса.
|
||||
|
||||
### Изменения вне finance-di2
|
||||
- `server/finance/db-client.ts` — добавлен экспорт `isConnectionError` (существующий код DI2; новый пул не создавался).
|
||||
- `server/routes/index.ts` — импорт `registerDi2Routes` + `await registerDi2Routes(app);` строкой НЕПОСРЕДСТВЕННО ПЕРЕД `await registerFinanceRoutes(app);`.
|
||||
- `server/index.ts` — блок статики `/di2` из `dist/public-di2` (path/fs уже импортированы, `import.meta.dirname` — Node 20.11+, совместимо с esbuild ESM-бандлом) вставлен ПЕРЕД веткой `if (development) setupVite else serveStatic` (после регистрации API-роутов и `app.use('/apps', ...)`). В dev и prod /di2/* не уйдёт в main index.html. Папки dist/public-di2 пока нет — блок молча пропускается (сборку добавит Task 2).
|
||||
- `package.json` — НЕ изменён (node-cron не нужен, scripts.build не тронут).
|
||||
|
||||
## Проверки
|
||||
1. `grep -rn "ai-agent\|compression\|REPLIT" server/finance-di2/` → пусто (exit 1). `grep -rn "node-cron" server/finance-di2/` → пусто.
|
||||
2. `npx tsc --noEmit` → **чисто, без ошибок** (единственные 2 ошибки при первом прогоне — предсуществующий баг DI2 с buildGlobalExclusionConditions — исправлены).
|
||||
3. `npm run build` → **прошёл**: vite build + esbuild `dist\index.js 2.7mb` (DI2-роуты в бандле: grep `api/profitability|api/refresh-all|api/auth/google/callback` → 9 вхождений).
|
||||
4. Дев-сервер не запускался (по брифу достаточно typecheck+build).
|
||||
|
||||
## Открытые вопросы / опасения
|
||||
- **Дублирование путей со старым модулем финансов**: DI2 регистрируется ПЕРЕД `registerFinanceRoutes`, поэтому при совпадении путей (`/api/income`, `/api/connection`, `/api/db/tables`, `/api/audit/*` и др.) побеждают DI2-обработчики. Это осознанное решение брифа, но старые финансовые страницы (которые ходят на эти же пути) теперь получат DI2-формат ответов после деплоя — до появления клиента DI2 в Task 2 старый UI финансов на пересекающихся эндпоинтах может сломаться. Если это нежелательно до Task 2 — можно временно закомментировать регистрацию.
|
||||
- `/api/auth/google/callback` теперь за auth-gate: Google-редирект без сессии iistwin вернёт 401. В DI2 он тоже требовал входа (Replit auth), поведение осознанное.
|
||||
- `/api/telegram/*` в routes.ts оставлен как есть (конфиг + отправка через fetch, без поллинга) — бриф вырезал только поллинг из ai-agent.
|
||||
- Проверка регистрации в рантайме (поднять сервер) не выполнялась — по брифу достаточно typecheck+build; реальный smoke логично сделать после Task 2 вместе с клиентом.
|
||||
Reference in New Issue
Block a user