> For the complete documentation index, see [llms.txt](https://docs.ipcheck.ing/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ipcheck.ing/developer/ru/architecture/project-structure.md).

# Структура проекта

Как организован репозиторий: один репозиторий, два процесса, три уровня.

MyIP — это единый репозиторий, который поставляет **два процесса Node** и содержит **три слоя кода**. Больше ничего. Когда вы держите эту картину в голове, у каждого файла в дереве есть очевидное место.

## Два процесса

| Процесс            | Файл                 | Порт по умолчанию         | Задача                                                                                                   |
| ------------------ | -------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------- |
| Статический сервер | `frontend-server.js` | `18966` (`FRONTEND_PORT`) | Раздаёт собранную SPA из `dist/`, проксирует `/api` запросы на бэкенд, обрабатывает fallback истории SPA |
| API-сервер         | `backend-server.js`  | `11966` (`BACKEND_PORT`)  | Приложение Express 5 — каждый `/api/*` маршрут, проверки, ограничение частоты, офлайн-наборы данных      |

`pnpm start` запускает оба с помощью `concurrently`. В production они обычно управляются pm2 (`ecosystem.config.cjs` определяет `myip-frontend` и `myip-backend`) или Docker-образом, который запускает `npm start` внутри одного контейнера и открывает только `18966`.

```mermaid
flowchart LR
    B["Браузер"]
    F["frontend-server.js :18966<br/>статический dist/ + fallback SPA"]
    A["backend-server.js :11966<br/>API Express 5"]
    U["Внешние провайдеры<br/>ipinfo.io, ip-api.com, RIPEstat, OONI"]
    D["Локальные наборы данных<br/>MaxMind mmdb, CAIDA as2org / as-rel, индекс PeeringDB, реестры IEEE MAC"]

    B -->|"GET / , /tools/whois , /assets/*"| F
    B -->|"GET /api/*"| F
    F -->|"http-proxy-middleware"| A
    A -->|"fetchUpstream, таймаут 8 с"| U
    A --> D
```

{% hint style="info" %}
Только порт фронтенда должен быть доступен из интернета. Бэкенд слушает на `11966` для прокси; держите его на том же хосте или в частной сети. См. [Обратный прокси и домены](/developer/ru/getting-started/reverse-proxy-and-domains.md).
{% endhint %}

## Три слоя кода

| Каталог            | Где выполняется    | Содержимое                                                                                                                                                                                                                                                                                                                                                                           |
| ------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `frontend/`        | Браузер            | SPA на Vue 3 — компоненты, роутер, хранилище Pinia, локали, реестр инструментов                                                                                                                                                                                                                                                                                                      |
| `api/` + `server/` | Node               | Бэкенд. `api/` содержит по одному модулю обработчика Express на каждый маршрут, и ничего больше; `server/` содержит код только для бэкенда, общий для этих обработчиков: проверки, логгер, middleware edge-cache, клиенты внешних сервисов, опросчик статуса сервиса и — в `server/datasets/` — движок офлайн-наборов данных с его читателями MaxMind / CAIDA / PeeringDB / IEEE MAC |
| `common/`          | Node **и** браузер | Только код, который также импортируют фронтенд или сборка: валидаторы, обёртка fetch, IP-арифметика, реестр локалей, схема отчёта. Здесь также хранятся загруженные каталоги наборов данных (`*-db/`)                                                                                                                                                                                |

`common/` — единственный слой, пересекающий границу, поэтому модуль попадает туда только если его также импортируют фронтенд или сборка (`valid-ip.js`, `fetch-with-timeout.js`, `report-schema.js`, `dns-record-types.js`, `ip-math.js`, `mac-input.js`, `locale-registry.js`, …). Эти модули остаются без `fs` и `process` доступа, никогда не импортируют из `server/`, и переэкспортируются через тонкие мосты в `frontend/utils/` поэтому код приложения продолжает импортировать `@/utils/...`. Задачи только для Node живут в `server/` — например, User-Agent для внешних запросов формируется в `server/upstream-ua.js` (который читает `package.json` с диска) и внедряется в `common/fetch-with-timeout.js` при запуске.

## Аннотированное дерево

```
.
├── backend-server.js       Приложение Express: таблица маршрутов, порядок middleware, TTL кэша для каждого маршрута
├── frontend-server.js      Статический сервер + прокси /api + fallback истории SPA
├── sentry-instrument.js    Инициализация Sentry для бэкенда, загружается через `node --import`
├── ecosystem.config.cjs    определения процессов pm2 (передаёт флаг --import)
├── index.html              Точка входа Vite / оболочка SPA
├── vite.config.js          Конфиг сборки: алиасы, ручные чанки, dev-прокси
├── Dockerfile              Сборка в два этапа (см. ниже)
│
├── frontend/               SPA на Vue 3  → см. Frontend
│   ├── App.vue             Тонкая оболочка: Nav, глобальные диалоги + <router-view> с keep-alive
│   ├── main.js             Инициализация + динамическая инициализация, зависящая от env
│   ├── store.js            Основное хранилище Pinia
│   ├── router/             Таблица маршрутов + устаревший редирект /?tool= + хуки перехода между страницами
│   ├── data/               Статические реестры: инструменты, разделы, достижения, IP-базы данных, настройки подключения по умолчанию и отобранные списки импорта, таблицы персон, проверки блокировки рекламы
│   │   └── banners/        Данные баннеров разделов на этапе развёртывания — в gitignore (см. Section Banners)
│   ├── components/         Home / ToolPage / Nav / sections / advanced-tools / report / widgets / ui
│   ├── composables/        Логика `useXxx`, учитывающая Vue
│   ├── utils/              Независимые от фреймворка помощники: общая инфраструктура приложения в корне (шина событий, шина команд, …)
│   │   ├── ip/             Формирование и валидация данных IP / ASN (включая мосты к common/)
│   │   ├── report/         Конвейер для общего отчёта
│   │   ├── features/       Чистая логика одной функции (ip-calc.js, bulk-lookup-extract.js, mtr-parse.js, pulse-*.js, …)
│   │   └── getips/ · dnsleaks/ · persona/   Модули проверок для каждого источника
│   └── locales/            По одному паку на зарегистрированную локаль + подпаки по запросу
│
├── api/                    По одному обработчику на маршрут  → см. Backend
│
├── server/                 Код только для бэкенда
│   ├── guards.js           Middleware для проверки параметров
│   ├── cache-control.js    middleware edge-cache cacheable()
│   ├── logger.js           синглтон pino
│   ├── ip-timezone.js      Координаты → зона IANA + middleware withTimeZone()
│   ├── offline-data.js     Шлюз 503 при запуске для маршрутов, данные которых ещё загружаются
│   ├── as-relationships.js Количество upstream / peer / customer, согласующееся с графом
│   ├── asn-profile.js      Составление /api/asn-profile: сроки и статусы
│   ├── bulk-lookup-sources.js  Локальные источники данных для /api/bulk-lookup, выбираемые BULK_LOOKUP_SOURCE
│   ├── rdap.js             Клиент RDAP для доменов, IP-адресов и номеров AS
│   ├── asrank.js           Клиент CAIDA AS Rank
│   ├── service-status-*.js Список провайдеров, опросчик, преобразование ответа
│   └── datasets/           Движок офлайн-наборов данных и его читатели
│       ├── datasets.js         Строки офлайн-наборов данных: GeoLite2, as2org, as-rel, PeeringDB, IEEE MAC
│       ├── dataset-updater.js  Один движок: загрузка при запуске, ежедневное обновление, блокировка, публикация, наблюдатель
│       ├── maxmind-service.js  Локальные читатели GeoLite2 + поиск
│       ├── as-org-db.js        Поиск CAIDA AS → организация
│       ├── as-rel-db.js        Связи CAIDA AS (граф p2c + p2p)
│       ├── peeringdb-distill.js Потоковый дамп PeeringDB → индекс по ASN
│       ├── peeringdb-db.js     Локальный поиск по индексу PeeringDB
│       └── oui-db.js           Локальный поиск по реестру IEEE MAC (MAC Lookup)
│
├── common/                 Код, который также импортируют фронтенд или сборка
│   ├── valid-ip.js         Валидаторы IP / доменов
│   ├── fetch-with-timeout.js  fetchWithTimeout (5 с) / fetchUpstream (8 с)
│   ├── ip-math.js          Арифметика IP / CIDR на BigInt (IP Calculator + проверка принадлежности RDAP)
│   ├── dns-record-types.js Типы DNS-записей, на которые отвечает резолвер
│   ├── mac-input.js        Разбор запросов по MAC-адресу / префиксу
│   ├── locale-registry.js  Языки интерфейса и все производные от них сопоставления
│   ├── locale-pack.js      Помощники для структуры locale-pack + удаление на этапе сборки
│   ├── report-schema.js    Схема общего отчёта, валидатор и маскирование IP
│   ├── maxmind-db/         GeoLite2-City.mmdb · GeoLite2-ASN.mmdb
│   ├── as-org-db/          as-org2info.txt
│   ├── as-rel-db/          as-rel2.txt
│   ├── peeringdb-db/       peeringdb-index.json
│   └── oui-db/             oui.csv · mam.csv · oui36.csv · iab.csv · cid.csv
│
├── tests/                  Спецификации для Node test runner (`node --test`)
└── dist/                   Результат сборки (генерируется, не коммитится)
```

## Как проходит запрос

**Загрузка страницы.** Браузер запрашивает `frontend-server.js` URL.

1. `/api/*` сначала перехватывается middleware прокси и пересылается в `http://localhost:11966/api`.
2. Иначе `express.static` пытается отдать реальный файл из `dist/`, применяя `Cache-Control` заголовок для каждого класса ресурсов — `dist/assets/**` и `dist/fonts/**` получают год и ещё `immutable` (Vite добавляет к ним хэш содержимого), изображения в корне — 7 дней, `index.html` и `manifest.webmanifest` ноль в браузерах, но 24 часа на edge, всё остальное — час.
3. Если совпадений по файлу нет, fallback истории SPA возвращает `index.html` чтобы vue-router мог разрешить клиентский маршрут вроде `/tools/whois`. Fallback намеренно узкий: `GET` только, `Accept: text/html` только, и никогда для пути, чей последний сегмент содержит точку — отсутствующий `/assets/x.js` должен вернуть 404, а не HTML-ответ.

**Вызов API.** Внутри бэкенда запрос проходит цепочку middleware в `backend-server.js` — необязательные `pino-http`, rate limiter, slow-down, JSON-парсер тела, the `no-store` по умолчанию, глобальную проверку referer, затем проверки параметров для каждого маршрута и обработчик. Обработчик обычно делает один вызов во внешний сервис через `fetchUpstream`; исключение — `/api/asn-profile`, который собирает несколько источников параллельно, каждый со своим сроком. Подробности в [Бэкенд](/developer/ru/architecture/backend.md).

{% hint style="warning" %}
`backend-server.js` также монтирует `express.static('./dist')`. Это удобно для конфигураций, где бэкенд открыт напрямую; обычный путь по-прежнему браузер → frontend-сервер → прокси.
{% endhint %}

## Конвейер сборки

`pnpm build` запускает Vite, который выдаёт `dist/`:

* `@` разрешается в `frontend/`.
* `manualChunks` разбивает тяжёлые зависимости на четыре чанка — `vendor` (vue / vue-router / vue-i18n), `chart` (chart.js), `speedtest` (@cloudflare/speedtest) и `browser-detect` (thumbmarkjs / ua-parser-js) — и выносит вспомогательные модули для источников IP и авторизации в `utils-getips` / `utils-auth`.
* Шрифты попадают в `dist/fonts/`, всё остальное получает content-hash в `dist/assets/`.
* Карты исходников генерируются только когда `SENTRY_AUTH_TOKEN` установлен, как `hidden` карты, и удаляются из `dist/` после загрузки — см. [Мониторинг ошибок](/developer/ru/configuration/error-monitoring.md).

Docker собирается в два этапа. На этапе сборки устанавливается с помощью `pnpm install --frozen-lockfile` и выполняется `pnpm run build`; на production-этапе копируются только `node_modules`, `package.json`, `dist/`, два серверных файла, `sentry-instrument.js`, `api/`, `server/` и `common/`. Никаких toolchain, никакого шага установки во время выполнения. См. [Развёртывание с Docker](/developer/ru/getting-started/deploy-with-docker.md).

## Режим разработки

`pnpm dev` запускает Vite и бэкенд вместе. Vite раздаёт SPA на `FRONTEND_PORT` самом себе (без `dist/`, без `frontend-server.js`) и проксирует `/api` в `BACKEND_PORT` — поэтому форма URL идентична production. Бэкенд работает под `nodemon` с `--import ./sentry-instrument.js`, что не делает ничего без backend DSN. Подробности настройки в [Окружение разработки](/developer/ru/development/dev-environment.md).

## Где вносить изменения

| Вы хотите…                                          | Перейдите к                                                                                                                     |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Добавить инструмент в интерфейс                     | `frontend/data/tools.js` — см. [Добавление нового инструмента](/developer/ru/development/adding-a-new-tool.md)                  |
| Добавить маршрут API                                | Новый файл в `api/`, подключённый в `backend-server.js`                                                                         |
| Добавить помощник, который использует только бэкенд | `server/` (читатель набора данных помещается в `server/datasets/`)                                                              |
| Добавить валидатор, который тоже нужен браузеру     | `common/`, а также мост в `frontend/utils/`                                                                                     |
| Изменить текст                                      | `frontend/locales/` — см. [i18n](/developer/ru/development/i18n.md)                                                             |
| Показать промо- или спонсорский баннер под разделом | Файл данных в `frontend/data/banners/` во время сборки — см. [Баннеры разделов](/developer/ru/configuration/section-banners.md) |
| Добавить тест                                       | `tests/` — см. [Тестирование](/developer/ru/development/testing.md)                                                             |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.ipcheck.ing/developer/ru/architecture/project-structure.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
