F4 — фасад «один клиент для файлов» + миграция CLI/TUI #38

Closed
opened 2026-09-16 17:21:06 +01:00 by eSlider · 1 comment
Owner

Epic: #34

F4 — фасад «один клиент для файлов» + миграция CLI/TUI

Цель

Дать единый способ работать с файлами через *Client, скрыть выбор бэкенда, и
перевести cmd/oo и cmd/office на интерфейс, убрав дублирование.

Реализация

  • file_facade.go (file_client.go): type FileClient struct { stores map[string]FileStore; searchers map[string]Searcher }
    • Client.Files() *FileClient; Read()/Write()/Search() выбирают бэкенд
      (REST/DAV — запись; PG — быстрые чтения; ES — контент-поиск) с fallback.
  • Миграция:
    • cmd/oo/dav.go, projects_files.go — через FileStore (сохранить вывод/флаги).
    • cmd/office/fetch/* — читать через интерфейс.
  • Депрекейт-пометки на старые методы, где заменены; публичный API не удалять без
    отдельного решения.

Критерии

  • Один интерфейс для REST/DAV/PG/ES; прямой FileEntry/DavFile в CLI не
    течёт наружу без нужды.
  • oo dav ..., oo projects files ... сохраняют поведение (--output json).
  • go build/vet/test и go test -tags=integration ./... зелёные.
  • Не осталось продублированной логики list/upload/download вне бэкендов.
Epic: #34 ## F4 — фасад «один клиент для файлов» + миграция CLI/TUI ### Цель Дать единый способ работать с файлами через `*Client`, скрыть выбор бэкенда, и перевести `cmd/oo` и `cmd/office` на интерфейс, убрав дублирование. ### Реализация - `file_facade.go` (`file_client.go`): `type FileClient struct { stores map[string]FileStore; searchers map[string]Searcher }` - `Client.Files() *FileClient`; `Read()`/`Write()`/`Search()` выбирают бэкенд (REST/DAV — запись; PG — быстрые чтения; ES — контент-поиск) с fallback. - Миграция: - `cmd/oo/dav.go`, `projects_files.go` — через `FileStore` (сохранить вывод/флаги). - `cmd/office/fetch/*` — читать через интерфейс. - Депрекейт-пометки на старые методы, где заменены; публичный API не удалять без отдельного решения. ### Критерии - [ ] Один интерфейс для REST/DAV/PG/ES; прямой `FileEntry`/`DavFile` в CLI не течёт наружу без нужды. - [ ] `oo dav ...`, `oo projects files ...` сохраняют поведение (`--output json`). - [ ] `go build/vet/test` и `go test -tags=integration ./...` зелёные. - [ ] Не осталось продублированной логики list/upload/download вне бэкендов.
eSlider added the refactor label 2026-09-16 17:21:14 +01:00
Author
Owner

F4 — отчёт: единый файловый фасад + миграция CLI/TUI

PR: #43
Ветка feat/file-facade#38 · коммит ba29738 (base main, 72c7cd5).
Не мержу и не закрываю.

Что сделано

file_facade.go — один клиент для файлов

  • type FileClient struct { stores map[string]FileStore; searchers map[string]Searcher }
    плюс порядки выбора readOrder/writeOrder/searchOrder.
  • Read() — PG (если зарегистрирован) → REST → DAV; Write() — REST → DAV;
    Search() — ES (или ошибка с подсказкой ONLYOFFICE_ES_URL).
  • Чтения (List/Stat) с fallback на следующий бэкенд только на transient
    (429/502/503/504); постоянная ошибка — финальная. Download без fallback:
    повторная запись в io.Writer дала бы дубль байт.
  • RegisterStore/RegisterSearcher — точка подключения PG (F2 #36) и др.
  • Client.Files() *FileClient; *FileClient реализует FileStore, поэтому
    старый вызов Files() как REST-store не ломается. Client.FileStore(name)
    сохранён.
  • ES-сирчер регистрируется автоматически, если задан ONLYOFFICE_ES_URL.

Каноническая модель (file_core.go)

  • Entry дополнен Updated (нативная строка бэкенда) и FilesCount/
    FoldersCount (только папки) — чтобы dav ls --output json отдавал те же
    значения, что API (updated в формате 2026-09-15T15:52:58.0000000+02:00).
  • DavFolderToEntry/DavFileToEntry/FileEntryToEntry их заполняют.

Миграция на интерфейс

  • cmd/oo/dav.go: ls|mkdir|rm|move|copy|rename-file|rename-folder|download
    → c.FileStore(ProviderDAV) (FileStore). @root (виртуальные секции) и
    fileops оставлены на типизированных вызовах — у них нет аналога в
    FileStore. Вывод/флаги, включая --output json, сохранены.
  • cmd/oo/projects_files.go: download, rename, delete → c.Files().
    list оставлен на GetProjectFiles (project-scoped endpoint с
    filesCount/foldersCount), upload — на upsert-методах; дублирующей
    логики list/upload/download в CLI нет.
  • cmd/office/fetch/*: Loader получил Files FileStore (дефолт REST);
    preview (Stat+Download), download и delete файла идут через интерфейс.
  • oo search: теперь c.Files().Search() (фасад→ES), .env грузится через
    bootstrap.LoadEnv(); OO-креды по-прежнему не нужны.
  • Депрекейт-пометки // Deprecated: на transport-методы, которые абстрагирует
    FileStore (GetFile, DownloadFile, RenameFile, DeleteFiles,
    ListFolder, MoveFiles, UploadToFolder, DAV-CRUD). Публичный API не
    удалён.

Тесты (имена + PASS)

  • Unit file_facade_test.go (фейковые FileStore, без сети):
    TestFileClientIsFileStore, TestClientFilesPrefersRESTForReadsAndWrites,
    TestFileClientPostgresTakesReadPriority,
    TestFileClientRegisterStoreNormalizesName,
    TestFileClientReadFallsBackOnlyOnTransient,
    TestFileClientReadStopsOnPermanentError,
    TestFileClientWriteUsesWriteBackend, TestFileClientWriteWithoutBackend,
    TestFileClientSearchSelection — PASS.
  • go build ./... && go vet ./... && go test -race ./... — зелёные.
  • Integration live (SSH-креды Edelweiss OO): TestIntegrationFileStores
    (REST + DAV, create/upload/list/stat/download/move/copy/rename/delete) — PASS.
  • Smoke live: oo dav ls 647 и -o json (counts/updated как раньше),
    oo dav ls @root, oo projects files list 4 (+json),
    projects files download 22457, dav download 3858,
    oo search Rechnung (фасад→ES через туннель, без OO-кред) — ОК.
  • gitleaks — no leaks found.

Блокеры

Нет. Известный чужой долг (существует на main, в рамках F4 не чинил):
go vet -tags=integration ./... падает на
cmd/office/fetch/task_integration_test.go:60 (loader.TaskFields undefined).

## F4 — отчёт: единый файловый фасад + миграция CLI/TUI PR: https://git.produktor.io/eSlider/go-onlyoffice/pulls/43 Ветка `feat/file-facade#38` · коммит `ba29738` (base `main`, 72c7cd5). Не мержу и не закрываю. ### Что сделано **`file_facade.go` — один клиент для файлов** - `type FileClient struct { stores map[string]FileStore; searchers map[string]Searcher }` плюс порядки выбора `readOrder`/`writeOrder`/`searchOrder`. - `Read()` — PG (если зарегистрирован) → REST → DAV; `Write()` — REST → DAV; `Search()` — ES (или ошибка с подсказкой `ONLYOFFICE_ES_URL`). - Чтения (`List`/`Stat`) с fallback на следующий бэкенд **только** на transient (429/502/503/504); постоянная ошибка — финальная. `Download` без fallback: повторная запись в `io.Writer` дала бы дубль байт. - `RegisterStore`/`RegisterSearcher` — точка подключения PG (F2 #36) и др. - `Client.Files() *FileClient`; `*FileClient` реализует `FileStore`, поэтому старый вызов `Files()` как REST-store не ломается. `Client.FileStore(name)` сохранён. - ES-сирчер регистрируется автоматически, если задан `ONLYOFFICE_ES_URL`. **Каноническая модель (`file_core.go`)** - `Entry` дополнен `Updated` (нативная строка бэкенда) и `FilesCount`/ `FoldersCount` (только папки) — чтобы `dav ls --output json` отдавал те же значения, что API (`updated` в формате `2026-09-15T15:52:58.0000000+02:00`). - `DavFolderToEntry`/`DavFileToEntry`/`FileEntryToEntry` их заполняют. **Миграция на интерфейс** - `cmd/oo/dav.go`: `ls|mkdir|rm|move|copy|rename-file|rename-folder|download` → `c.FileStore(ProviderDAV)` (`FileStore`). `@root` (виртуальные секции) и `fileops` оставлены на типизированных вызовах — у них нет аналога в `FileStore`. Вывод/флаги, включая `--output json`, сохранены. - `cmd/oo/projects_files.go`: `download`, `rename`, `delete` → `c.Files()`. `list` оставлен на `GetProjectFiles` (project-scoped endpoint с `filesCount`/`foldersCount`), `upload` — на upsert-методах; дублирующей логики list/upload/download в CLI нет. - `cmd/office/fetch/*`: `Loader` получил `Files FileStore` (дефолт REST); preview (`Stat`+`Download`), download и delete файла идут через интерфейс. - `oo search`: теперь `c.Files().Search()` (фасад→ES), `.env` грузится через `bootstrap.LoadEnv()`; OO-креды по-прежнему не нужны. - Депрекейт-пометки `// Deprecated:` на transport-методы, которые абстрагирует `FileStore` (`GetFile`, `DownloadFile`, `RenameFile`, `DeleteFiles`, `ListFolder`, `MoveFiles`, `UploadToFolder`, DAV-CRUD). Публичный API не удалён. ### Тесты (имена + PASS) - Unit `file_facade_test.go` (фейковые `FileStore`, без сети): `TestFileClientIsFileStore`, `TestClientFilesPrefersRESTForReadsAndWrites`, `TestFileClientPostgresTakesReadPriority`, `TestFileClientRegisterStoreNormalizesName`, `TestFileClientReadFallsBackOnlyOnTransient`, `TestFileClientReadStopsOnPermanentError`, `TestFileClientWriteUsesWriteBackend`, `TestFileClientWriteWithoutBackend`, `TestFileClientSearchSelection` — PASS. - `go build ./... && go vet ./... && go test -race ./...` — зелёные. - Integration live (SSH-креды Edelweiss OO): `TestIntegrationFileStores` (REST + DAV, create/upload/list/stat/download/move/copy/rename/delete) — PASS. - Smoke live: `oo dav ls 647` и `-o json` (counts/updated как раньше), `oo dav ls @root`, `oo projects files list 4` (+json), `projects files download 22457`, `dav download 3858`, `oo search Rechnung` (фасад→ES через туннель, без OO-кред) — ОК. - gitleaks — `no leaks found`. ### Блокеры Нет. Известный чужой долг (существует на `main`, в рамках F4 не чинил): `go vet -tags=integration ./...` падает на `cmd/office/fetch/task_integration_test.go:60` (`loader.TaskFields undefined`).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: eSlider/go-onlyoffice#38