F1 — каноническая модель + FileStore/Searcher; адаптеры REST и WebDAV #35

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

Epic: #34

F1 — каноническая модель + FileStore/Searcher; адаптеры REST и WebDAV

Цель

Ввести единую внутреннюю абстракцию доступа к файлам и перевести на неё уже
существующие бэкенды (REST и WebDAV), не ломая публичный API.

Артефакты (плоский пакет, без подпакетов)

  • file_core.go:
    • type Kind int (File/Folder).
    • type Entry struct { ID, ParentID, Title string; Kind Kind; Size int64; MIME string; Created, Modified time.Time; Version int; Provider string }.
    • type FileStore interface { Name() string; List(ctx, parentID) ([]Entry, error); Stat(ctx,id) (Entry,error); CreateFolder(ctx,parentID,title) (Entry,error); Upload(ctx,parentID,title string, r io.Reader) (Entry,error); Download(ctx,id string, w io.Writer) (int64,error); Move(ctx, ids []string, parentID) error; Copy(...) error; Rename(ctx,id,title) error; Delete(ctx, ids []string) error }.
    • type SearchQuery struct { Text string; InContent bool; FolderID string; Extensions []string; Limit int }.
    • type SearchHit struct { Entry; Score float64; Highlight string; Path []string }.
    • type Searcher interface { Search(ctx, SearchQuery) ([]SearchHit, error); Name() string }.
  • file_rest.go — restStore поверх существующих методов files.go
    (ListFolder, GetFile, CreateFolder, UploadToFolder, UpdateFile,
    DownloadFile, MoveFiles, RenameFile, DeleteFiles).
  • file_dav.go — davStore поверх files_webdav.go (ListDavFolder,
    CreateDavFolder, UploadDavFile, DownloadDavFile, MoveDavItems,
    CopyDavItems, RenameDavFile/RenameDavFolder, DeleteDavItems).
  • Доступ из *Client: c.FileStore(backend) или c.Files() (фасад решается в F4).

Правила

  • Никаких подпакетов; никаких синтетических моков OnlyOffice — только
    интеграционные тесты (//go:build integration) против живого инстанса.
  • Существующие публичные методы сохранить (обратная совместимость).
  • Запись — через DoRetry.

Тесты/критерии

  • Unit: конверсия FileEntry/DavFile → Entry (чистые функции), Kind.
  • Integration: REST и DAV проходят общий набор операций над тестовой папкой
    (создать/upload/list/stat/download/rename/move/copy/delete), папка убирается.
  • go build ./... && go vet ./... && go test ./... зелёные;
    go test -tags=integration ./... зелёные на живом OO.
  • Публичные сигнатуры files*.go не изменены.
Epic: #34 ## F1 — каноническая модель + `FileStore`/`Searcher`; адаптеры REST и WebDAV ### Цель Ввести единую внутреннюю абстракцию доступа к файлам и перевести на неё уже существующие бэкенды (REST и WebDAV), не ломая публичный API. ### Артефакты (плоский пакет, без подпакетов) - `file_core.go`: - `type Kind int` (`File`/`Folder`). - `type Entry struct { ID, ParentID, Title string; Kind Kind; Size int64; MIME string; Created, Modified time.Time; Version int; Provider string }`. - `type FileStore interface { Name() string; List(ctx, parentID) ([]Entry, error); Stat(ctx,id) (Entry,error); CreateFolder(ctx,parentID,title) (Entry,error); Upload(ctx,parentID,title string, r io.Reader) (Entry,error); Download(ctx,id string, w io.Writer) (int64,error); Move(ctx, ids []string, parentID) error; Copy(...) error; Rename(ctx,id,title) error; Delete(ctx, ids []string) error }`. - `type SearchQuery struct { Text string; InContent bool; FolderID string; Extensions []string; Limit int }`. - `type SearchHit struct { Entry; Score float64; Highlight string; Path []string }`. - `type Searcher interface { Search(ctx, SearchQuery) ([]SearchHit, error); Name() string }`. - `file_rest.go` — `restStore` поверх существующих методов `files.go` (`ListFolder`, `GetFile`, `CreateFolder`, `UploadToFolder`, `UpdateFile`, `DownloadFile`, `MoveFiles`, `RenameFile`, `DeleteFiles`). - `file_dav.go` — `davStore` поверх `files_webdav.go` (`ListDavFolder`, `CreateDavFolder`, `UploadDavFile`, `DownloadDavFile`, `MoveDavItems`, `CopyDavItems`, `RenameDavFile`/`RenameDavFolder`, `DeleteDavItems`). - Доступ из `*Client`: `c.FileStore(backend)` или `c.Files()` (фасад решается в F4). ### Правила - Никаких подпакетов; никаких синтетических моков OnlyOffice — только интеграционные тесты (`//go:build integration`) против живого инстанса. - Существующие публичные методы сохранить (обратная совместимость). - Запись — через `DoRetry`. ### Тесты/критерии - [ ] Unit: конверсия `FileEntry`/`DavFile` → `Entry` (чистые функции), `Kind`. - [ ] Integration: REST и DAV проходят общий набор операций над тестовой папкой (создать/upload/list/stat/download/rename/move/copy/delete), папка убирается. - [ ] `go build ./... && go vet ./... && go test ./...` зелёные; `go test -tags=integration ./...` зелёные на живом OO. - [ ] Публичные сигнатуры `files*.go` не изменены.
eSlider added the refactor label 2026-09-16 17:21:12 +01:00
Author
Owner

F1 — отчёт: каноническая модель + FileStore/Searcher; адаптеры REST и DAV

PR: #40
Ветка feat/file-store#35 · коммит 94951fc (base main).
Не мержу и не закрываю.

Что сделано

  • file_core.go:
    • type Kind int (File/Folder) + String().
    • type Entry struct { ID, ParentID, Title string; Kind Kind; Size int64; MIME string; Created, Modified time.Time; Version int; Provider string }.
    • type FileStore interface { Name; List; Stat; CreateFolder; Upload; Download; Move; Copy; Rename; Delete }.
    • type SearchQuery, type SearchHit, type Searcher.
    • Доступ: c.FileStore(backend) (по умолчанию REST, dav/webdav — DAV) и c.Files(). Полноценный фасад — F4 #38, сигнатуры существующих методов не тронуты.
    • Чистые конверсии: FileEntryToEntry, DavFileToEntry, DavFolderToEntry.
  • file_rest.go — restStore поверх files.go: ListFolder, GetFile,
    CreateFolder, UploadToFolder, DownloadFile, MoveFiles, RenameFile,
    DeleteFiles; Copy — через CopyDavItems (в files.go копирования нет).
    Upload (io.Reader) спулится в temp-файл, т.к. UploadToFolder path-based.
  • file_dav.go — davStore поверх files_webdav.go: ListDavFolder,
    CreateDavFolder, UploadDavFile, DownloadDavFile, MoveDavItems,
    CopyDavItems, RenameDavFile/RenameDavFolder, DeleteDavItems.
    Иды для fileops классифицируются Stat (папки/файлы).
  • Все API-вызовы адаптеров — через DoRetry (retryStoreOp,
    DefaultRetryPolicy).
  • Фикс MoveFiles (нужен restStore.Move): fileops/move отдаёт envelope
    с массивом операций, а старый код через putJSONObject разворачивал его и
    затем fileopsError искал уже срезанный response → всегда
    response missing "response", хотя перенос проходил. Теперь как
    MoveDavItems: putJSON + fileopsError, сигнатура не изменена.

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

Unit — go build ./... && go vet ./... && go test ./... зелёные:

  • TestFileEntryToEntry — PASS
  • TestFileEntryToEntryNil — PASS
  • TestFileEntryToEntrySizeFormats — PASS
  • TestDavFileToEntry — PASS
  • TestDavFolderToEntry — PASS
  • TestEntriesFromFolderMap — PASS
  • TestEntriesFromFolderMapNil — PASS
  • TestKindString — PASS
  • TestClientFileStoreSelection — PASS

Integration (//go:build integration, живой OO
https://office.pro-dukt.de) — go test -tags=integration -run TestIntegrationFileStores -v .:

  • TestIntegrationFileStores/rest — PASS
  • TestIntegrationFileStores/dav — PASS

Общий набор на тестовой папке проекта: create folder / upload / list / stat /
download / move / copy / rename / delete, папки убираются. Учтена
асинхронность fileops (move/copy/delete видны с задержкой) — тест опрашивает
результат. Прогон повторялся, стабильно зелёный.

Замечания / блокеры

  • Полный go test -tags=integration ./... на main уже красный в
    cmd/office/fetch (loader.TaskFields undefined,
    task_integration_test.go:60) — не связано с этой задачей, не трогал.
  • При массовом прогоне подряд портал отдаёт 429 на auth
    (invalid character '<' / 429 Too Many Requests), из-за чего часть
    старых integration-тестов падает; поодиночке/с паузами проходят. Лечится
    паузами/DoRetry, вне рамок F1.
  • Секреты не печатались, gitleaks (git + working tree) — clean.
## F1 — отчёт: каноническая модель + `FileStore`/`Searcher`; адаптеры REST и DAV PR: https://git.produktor.io/eSlider/go-onlyoffice/pulls/40 Ветка `feat/file-store#35` · коммит `94951fc` (base `main`). Не мержу и не закрываю. ### Что сделано - `file_core.go`: - `type Kind int` (`File`/`Folder`) + `String()`. - `type Entry struct { ID, ParentID, Title string; Kind Kind; Size int64; MIME string; Created, Modified time.Time; Version int; Provider string }`. - `type FileStore interface { Name; List; Stat; CreateFolder; Upload; Download; Move; Copy; Rename; Delete }`. - `type SearchQuery`, `type SearchHit`, `type Searcher`. - Доступ: `c.FileStore(backend)` (по умолчанию REST, `dav`/`webdav` — DAV) и `c.Files()`. Полноценный фасад — F4 #38, сигнатуры существующих методов не тронуты. - Чистые конверсии: `FileEntryToEntry`, `DavFileToEntry`, `DavFolderToEntry`. - `file_rest.go` — `restStore` поверх `files.go`: `ListFolder`, `GetFile`, `CreateFolder`, `UploadToFolder`, `DownloadFile`, `MoveFiles`, `RenameFile`, `DeleteFiles`; `Copy` — через `CopyDavItems` (в `files.go` копирования нет). `Upload` (io.Reader) спулится в temp-файл, т.к. `UploadToFolder` path-based. - `file_dav.go` — `davStore` поверх `files_webdav.go`: `ListDavFolder`, `CreateDavFolder`, `UploadDavFile`, `DownloadDavFile`, `MoveDavItems`, `CopyDavItems`, `RenameDavFile`/`RenameDavFolder`, `DeleteDavItems`. Иды для fileops классифицируются `Stat` (папки/файлы). - Все API-вызовы адаптеров — через `DoRetry` (`retryStoreOp`, `DefaultRetryPolicy`). - **Фикс `MoveFiles`** (нужен `restStore.Move`): `fileops/move` отдаёт envelope с массивом операций, а старый код через `putJSONObject` разворачивал его и затем `fileopsError` искал уже срезанный `response` → всегда `response missing "response"`, хотя перенос проходил. Теперь как `MoveDavItems`: `putJSON` + `fileopsError`, сигнатура не изменена. ### Тесты (имена + PASS) Unit — `go build ./... && go vet ./... && go test ./...` зелёные: - `TestFileEntryToEntry` — PASS - `TestFileEntryToEntryNil` — PASS - `TestFileEntryToEntrySizeFormats` — PASS - `TestDavFileToEntry` — PASS - `TestDavFolderToEntry` — PASS - `TestEntriesFromFolderMap` — PASS - `TestEntriesFromFolderMapNil` — PASS - `TestKindString` — PASS - `TestClientFileStoreSelection` — PASS Integration (`//go:build integration`, живой OO `https://office.pro-dukt.de`) — `go test -tags=integration -run TestIntegrationFileStores -v .`: - `TestIntegrationFileStores/rest` — PASS - `TestIntegrationFileStores/dav` — PASS Общий набор на тестовой папке проекта: create folder / upload / list / stat / download / move / copy / rename / delete, папки убираются. Учтена асинхронность `fileops` (move/copy/delete видны с задержкой) — тест опрашивает результат. Прогон повторялся, стабильно зелёный. ### Замечания / блокеры - Полный `go test -tags=integration ./...` на main уже красный в `cmd/office/fetch` (`loader.TaskFields undefined`, `task_integration_test.go:60`) — не связано с этой задачей, не трогал. - При массовом прогоне подряд портал отдаёт `429` на auth (`invalid character '<'` / `429 Too Many Requests`), из-за чего часть старых integration-тестов падает; поодиночке/с паузами проходят. Лечится паузами/`DoRetry`, вне рамок F1. - Секреты не печатались, gitleaks (git + working tree) — clean.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: eSlider/go-onlyoffice#35