Принцип разделения интерфейсов (ISP)
internal/api/store_interfaces.go: 7 сегрегированных интерфейсов, заменяющих монолит из 100 методов Store.EmailReader (31 метод), EmailWriter (41), AccountStore (17), FolderStore (8), EntityStore(шаблоны/метки/правила/контакты/группы/пользователи/комментарии), AdminStore (администрирование/ИИ/MCP/вебхуки/Telegram), SystemStore (миграции/FTS/очередь/задачи).Store сохранён для обратной совместимости.internal/api/handlers.go: Handler теперь предоставляет сегрегированные поля (Emails, Writer, Accounts, Folders, Entities, Admin, System) вместе с унаследованным Store.Принцип единственной ответственности (SRP) — Разделение монолитов хранилищ
internal/store/postgres/: storage.go 3980 → 561 строка. Новые файлы: emails.go (2020), accounts.go (387), org.go (396), integrations.go (440), folders.go (137), labels_tags.go (82), attachments.go (55).internal/store/sqlite/: зеркальное разделение (4386 → 465 строк).internal/store/shared/crypto.go: выделены EncryptPassword / DecryptPassword, общие для обеих реализаций.WithTx() добавлен в postgres.Storage и sqlite.Storage для атомарных транзакций между файлами.DRY — Устранение дублирования
internal/mime/qp.go: унифицирован DecodeQuotedPrintable — удалены 3 дублирующие реализации (api, sync, мёртвый код в handlers.go).internal/api/email_action_handlers.go: хелпер toggleEmail — 3 функции переключения заменены обёртками с одним аргументом.internal/store/postgres/email_columns.go: константы emailColumns / emailColumnsPrefixed — 10 дублированных списков столбцов → 2 константы.OCP — Registry Pattern
internal/api/email_action_registry.go: emailActions map (11 actions) registered in init(). HandleEmail switch-case replaced with O(1) map lookup. Thread-safe: write-once in init(), read-only at request time.Внедрение зависимости (DIP)
internal/sync/interfaces.go: CASStore interface (1 method) + AIProvider interface (3 methods) defined in consuming package.Fetcher и Manager теперь зависят от интерфейсов вместо конкретных *attachment.CASStorage и *ai.Gateway. Тестируемы без реальной файловой системы или ИИ.YAGNI Cleanup
internalDecodeQP + unhexDigit (dead code, −32 lines).BulkToggleFlagEmails из интерфейса + обе реализации + mock (−28 lines).sqlite.Storage.Query/QueryRow/Exec — прямой SQL-доступ не в контракте (−15 lines).storage.go.bak* (−775KB).internal/sentry/ (new package): Init(), IsEnabled(), Flush(), CaptureException(), Recover(), Go() — все no-ops, когда SENTRY_DSN не задан, нулевая нагрузка.cmd/server/main.go: sentry.Init() после настройки логов, defer sentry.Flush() при завершении, CaptureException в HTTP panic recovery.internal/api/errors.go: WriteInternalError → sentry.CaptureException(err) — все ошибки 500 сообщаются с контекстом запроса.internal/sync/imap_connect.go: reportProvisionalSyncError → sentry.CaptureException(err) — IMAP/ошибки аутентификации отслеживаются для каждой учетной записи.@sentry/nextjs): sentry.client.config.ts + sentry.server.config.ts + sentry.edge.config.ts — условная инициализация через NEXT_PUBLIC_SENTRY_DSN.next.config.ts: withSentryConfig() — условное обертывание, загрузка source maps во время сборки (SENTRY_AUTH_TOKEN).src/app/[locale]/error.tsx + src/app/global-error.tsx — Sentry.captureException(error) на ошибках SSR/CSR.sentry.client.config.ts: tracePropagationTargets: [/^\/api/] — автоматически передаетsentry-trace заголовок на бэкенд, связывая ошибки фронтенда с трейсами бэкенда.SENTRY_DSN / SENTRY_ENVIRONMENT / NEXT_PUBLIC_SENTRY_DSN. ФронтендDockerfile принимаетSENTRY_ORG / SENTRY_PROJECT / SENTRY_AUTH_TOKEN как ARG-параметры сборки для загрузки source map.internal/api/middleware/auth.go: extractToken() приоритет изменен — заголовокAuthorization (localStorage) теперь проверяетсядо httpOnly cookie.localStorage и отправляет его через заголовокAuthorization.