# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Что это `mcimport` — однобинарный инструмент (Go, только stdlib) для массового создания почтовых ящиков в mailcow из CSV через его REST API. Веб-морда встроена в бинарь (`//go:embed index.html`). Пайплайн пользователя: загрузка CSV → предпросмотр с валидацией → потоковый импорт с журналом. ## Команды ```bash # Сборка статического бинаря CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o mcimport . # Локальный запуск (UI на http://localhost:8080) MAILCOW_URL=https://mail.dom.ru ./mcimport # Docker (порт публикуется только на 127.0.0.1 — ходить через SSH-туннель) docker compose up -d --build go vet ./... gofmt -l . # список неотформатированных файлов; -w для правки ``` Тестов в проекте нет. `go.mod` не имеет внешних зависимостей — только stdlib, поэтому `go mod tidy`/`go get` при обычной работе не требуются. ## Архитектура Весь backend — один файл `main.go`, весь frontend — один `index.html` (встроен). Состояние на сервере не хранится: инструмент **stateless**, API-ключ и пароли не пишутся на диск и не логируются. Четыре HTTP-эндпоинта (`net/http` ServeMux с method-роутингом Go 1.22): - `GET /api/config` — отдаёт дефолты в UI (base URL, есть ли ключ в env, дефолт-квота). - `GET /api/example` — отдаёт образец CSV (`exampleCSV`) как вложение для скачивания. - `POST /api/test` — проверка связи: читает домены и ящики, подтверждает ключ и allow-list. - `POST /api/preview` — разбор CSV (multipart), валидация, обогащение против живого mailcow. Form-поле `domain` включает режим override (см. ниже). В ответе — `quotaWarnings` (проверка доменной квоты). - `POST /api/import` — создание ящиков, **потоковый NDJSON** (по строке результата на ящик, с `flusher.Flush()`). Три конвейерных этапа обработки строки CSV, каждый меняет `Row.Status` (`create`→`warn`→`exists`→`error`) и флаг `Importable`: 1. `parseCSV` → `validateRow` — структурная проверка без сети (email, пароль, дубликаты в файле). 2. `enrichRow` — сверка с живым mailcow: несуществующий домен → `error`, существующий ящик → `exists` (пропуск). Работает только если заданы креды. 3. `checkDomainQuota` — воспроизводит проверку mailcow: свободная квота домена = `max_quota_for_domain` − Σ(выделенных квот существующих ящиков). Если Σ квот импортируемых строк домена превышает свободную — возвращает `quotaWarn` (в МиБ). UI показывает предупреждение и **блокирует** импорт до подъёма квоты домена (домены с квотой 0 = безлимит пропускаются). 4. `handleImport` — POST на `/api/v1/add/mailbox`, ответ разбирается `parseMailcowMsg`. Используется **только** `add` — существующие ящики никогда не модифицируются (идемпотентность). ### Ключевые соглашения - **Разрешение кредов** — `resolveCreds`: значения из тела запроса приоритетнее env (`MAILCOW_URL`/`MAILCOW_API_KEY`). Ключ можно задать через env и не вводить в UI. - **Ответы API mailcow** имеют форму `[{"type": "...", "msg": ...}]`; `type != "success"` считается провалом. `msg` бывает строкой или массивом — разворачивается в `flattenMsg`. - **CSV-парсинг** терпимый: авто-определение разделителя (`sniffDelimiter`: `,`/`;`/таб; на практике используется `;`), снятие BOM, `LazyQuotes`, RU-алиасы заголовков (`headerAliases` + `normHeader` игнорирует регистр/пробелы/подчёркивания). `email` можно заменить парой `localpart`+`domain`. - **Режим override-домена** (`parseCSV(..., overrideDomain)`): если домен выбран в UI и передан form-полем `domain`, колонка логина (`email`/`login`/`localpart`) трактуется как localpart без домена, email собирается как `localpart@overrideDomain`. Полный email в значении терпится — берётся часть до `@` с пометкой в `Notes`. Домен нормализуется в lowercase в `handlePreview`. - Ошибки пользователю — **на русском**; комментарии в коде — на английском. Держать этот сплит. - `INSECURE_SKIP_VERIFY=true` отключает проверку TLS (для внутреннего CA mailcow). - Дефолт квоты — константа `defaultQuotaMiB` (10240 МиБ). Одна точка правды; дублируется в `docker-compose.yml`, `README.md` и `value` поля `#quota` в `index.html`. - **Единицы квот**: `fetchDomains`/`fetchMailboxes` возвращают квоты в **байтах** (как отдаёт mailcow), `Row.Quota` — в **МиБ** (строка). `checkDomainQuota` сводит всё к байтам, `quotaWarn` наружу — в МиБ. UI форматирует МиБ→ГБ (`gb()`). - **Повтор в UI без перезагрузки файла** (`index.html`): `previewRows`/`importResults` держатся в памяти. «Повторить проверку» = повторный `runPreview()` (файл в `fileInput` сохраняется). «Повторить неудавшиеся» = `runImport()` по строкам с `ok:false` из `importResults` — идемпотентно, т.к. упавшие ящики не создались, успешные не трогаются. При добавлении полей ящика править **три места синхронно**: заголовки/алиасы CSV (`headerAliases`), структуру `Row` + `validateRow`, и `payload` в `handleImport`. ## Требования на стороне mailcow - API-ключ с правами **Read-Write** (`Configuration → Access → API`). - IP, с которого ходит mcimport, добавлен в `API_ALLOW_FROM` — иначе отказ доступа (401/403).