This commit is contained in:
2026-07-14 13:21:45 +07:00
commit 4333d69125
8 changed files with 1375 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
# 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).