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 вне бэкендов.
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 ./... — зелёные.
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`).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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/*— читать через интерфейс.отдельного решения.
Критерии
FileEntry/DavFileв CLI нетечёт наружу без нужды.
oo dav ...,oo projects files ...сохраняют поведение (--output json).go build/vet/testиgo test -tags=integration ./...зелёные.F4 — отчёт: единый файловый фасад + миграция CLI/TUI
PR: #43
Ветка
feat/file-facade#38· коммитba29738(basemain,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)сохранён.
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)
file_facade_test.go(фейковыеFileStore, без сети):TestFileClientIsFileStore,TestClientFilesPrefersRESTForReadsAndWrites,TestFileClientPostgresTakesReadPriority,TestFileClientRegisterStoreNormalizesName,TestFileClientReadFallsBackOnlyOnTransient,TestFileClientReadStopsOnPermanentError,TestFileClientWriteUsesWriteBackend,TestFileClientWriteWithoutBackend,TestFileClientSearchSelection— PASS.go build ./... && go vet ./... && go test -race ./...— зелёные.TestIntegrationFileStores(REST + DAV, create/upload/list/stat/download/move/copy/rename/delete) — PASS.
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-кред) — ОК.no leaks found.Блокеры
Нет. Известный чужой долг (существует на
main, в рамках F4 не чинил):go vet -tags=integration ./...падает наcmd/office/fetch/task_integration_test.go:60(loader.TaskFields undefined).