Files
2026-07-14 13:21:45 +07:00

88 lines
7.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).