Читать файлы/папки напрямую из БД Community Server (PostgreSQL) — быстро, без
HTTP-слоя — и представить как FileStore (read-only).
Research (сначала)
Определить фактическую БД OnlyOffice в VM onlyoffice-v2 (PG или MySQL?) и
доступность: внутри VM (localhost:5432) → снаружи нужен туннель/expose
(SSH-порт VM открыт: 127.0.0.1:32). Зафиксировать в docs/.
file_pg.go: pgStore (read-only) на database/sql + github.com/jackc/pgx/v5/stdlib
(или pgxpool) — реализует List/Stat/Download (по object key MinIO/storage)
и, опционально, Searcher по имени.
Guard: только SELECT; явный запрет write-методов (ErrReadOnly).
Конфиг: ONLYOFFICE_DSN (или ONLYOFFICE_PG_*), в .env.example только имена.
Download через тот же MinIO-путь, что storage_fallback.go (переиспользовать).
Integration (//go:build integration, skip без DSN): list/stat/download
реального файла из дерева проекта, сверка с REST-результатом.
Read-only: write-методы возвращают ErrReadOnly.
go build/vet/test зелёные; секретов нет.
Epic: #34
## F2 — PostgreSQL-бэкенд (read-only, «изнутри»)
### Цель
Читать файлы/папки напрямую из БД Community Server (PostgreSQL) — быстро, без
HTTP-слоя — и представить как `FileStore` (read-only).
### Research (сначала)
- Определить фактическую БД OnlyOffice в VM `onlyoffice-v2` (PG или MySQL?) и
доступность: внутри VM (`localhost:5432`) → снаружи нужен туннель/expose
(SSH-порт VM открыт: `127.0.0.1:32`). Зафиксировать в `docs/`.
- Схема: `files_file`, `files_folder`, `files_folder_tree`, `files_security`,
`tenants` (`tenant_id`), поля (id, parent, title, content_length, modified_on,
version, folder_id, create_by, ...).
### Реализация
- `file_pg.go`: `pgStore` (read-only) на `database/sql` + `github.com/jackc/pgx/v5/stdlib`
(или `pgxpool`) — реализует `List`/`Stat`/`Download` (по object key MinIO/`storage`)
и, опционально, `Searcher` по имени.
- Guard: только SELECT; явный запрет write-методов (`ErrReadOnly`).
- Конфиг: `ONLYOFFICE_DSN` (или `ONLYOFFICE_PG_*`), в `.env.example` только имена.
- `Download` через тот же MinIO-путь, что `storage_fallback.go` (переиспользовать).
### Тесты/критерии
- [ ] Unit: SQL-билдеры, маппинг строк → `Entry` (без БД).
- [ ] Integration (`//go:build integration`, skip без DSN): list/stat/download
реального файла из дерева проекта, сверка с REST-результатом.
- [ ] Read-only: write-методы возвращают `ErrReadOnly`.
- [ ] `go build/vet/test` зелёные; секретов нет.
eSlider
added the feature label 2026-09-16 17:21:13 +01:00
F2 — отчёт: read-only SQL-бэкенд файлов (Community Server DB)
PR: #45
Ветка feat/pg-store#36 · коммит bf3ef02 (base main).
Не мержу и не закрываю.
Research — БД оказалась MySQL, не PostgreSQL (блокера нет)
Проверено live на VM onlyoffice-v2 (SSH 127.0.0.1:32):
Community Server работает на MySQL 8.0, хост 127.0.0.1:3306 внутри VM,
база onlyoffice; конфиг /etc/onlyoffice/communityserver/appsettings.production.json
(providerName: MySql.Data.MySqlClient). Активные коннекты mono → :3306.
PostgreSQL 16 в той же VM — наш контур office-assistant: база onlyoffice,
схема edw_docs (роли edw/edw_ro), files_file там нет. К OnlyOffice
отношения не имеет.
Портал хранит файлы в S3/MinIO (бакет office, хост 192.168.188.10:9000);
DiscStorage — только мелочь.
Вывод: цель F2 (прямой SQL-доступ к БД Community Server) достижима, но движок —
MySQL. Issue/эпик предполагали PG. Реализовал database/sql с выбором драйвера
по DSN: mysql live, pgx для PostgreSQL (если портал перенесут). Оба пути
проверены живьём (pgx — подключением к edw_ro).
ControlMaster=no / ControlPath=none обязательны (иначе forward уходит в
persistent master из ~/.ssh/config).
Реализация
file_pg.go: pgStore (read-only) на database/sql; драйверы github.com/go-sql-driver/mysql и github.com/jackc/pgx/v5/stdlib. List/Stat/Download, Search по имени (LIKE), Name() = mysql|postgres.
Write-методы → ErrReadOnly. current_version = 1 — только текущая версия.
Download — REST не используется: ключ объекта 00/00/<tenant>/files/folder_<shard>/file_<id>/v<version>/content.<ext>, shard = (id/1000+1)*1000 (файл 3727 → folder_4000, НЕ folder_id; проверено
live по MinIO). Стриминг — через новый общий downloadMinioObject в storage_fallback.go (переиспользование, без дублей).
Integration file_pg_integration_test.go (//go:build integration, skip без ONLYOFFICE_DSN): TestIntegrationPGStore — PASS live (backend mysql): Stat/List сверены с REST (c.Files()), Download совпал побайтово с REST,
write-методы дают ErrReadOnly.
pgx-путь: NewPGStore c EDW_RO_DSN → Name()=postgres, ping OK (live).
go build ./... && go vet ./... && go test -race ./... — зелёные.
gitleaks — no leaks found. Секретов в коде нет.
Блокеры
Нет. Замечание для PO: issue/эпик называют бэкенд «PostgreSQL», фактически
источник — MySQL. Если нужен строго PG-only — скажите, уберу mysql-ветку; но
тогда live-данных для integration нет (в PG files_file отсутствует).
## F2 — отчёт: read-only SQL-бэкенд файлов (Community Server DB)
PR: https://git.produktor.io/eSlider/go-onlyoffice/pulls/45
Ветка `feat/pg-store#36` · коммит `bf3ef02` (base `main`).
Не мержу и не закрываю.
### Research — БД оказалась MySQL, не PostgreSQL (блокера нет)
Проверено live на VM `onlyoffice-v2` (SSH `127.0.0.1:32`):
- Community Server работает на **MySQL 8.0**, хост `127.0.0.1:3306` внутри VM,
база `onlyoffice`; конфиг `/etc/onlyoffice/communityserver/appsettings.production.json`
(`providerName: MySql.Data.MySqlClient`). Активные коннекты `mono → :3306`.
- Таблицы: `files_file` (8987 строк, PK `tenant_id,id,version`), `files_folder`
(553), `files_folder_tree` (2455), `files_security`; тенанты — `tenants_tenants`
(таблицы `tenants` нет). Поля: `id`, `version`, `version_group`,
`current_version` (1 = текущая версия), `folder_id`, `title`, `content_length`,
`create_on`, `modified_on`, `tenant_id`.
- PostgreSQL 16 в той же VM — **наш** контур office-assistant: база `onlyoffice`,
схема `edw_docs` (роли `edw`/`edw_ro`), `files_file` там нет. К OnlyOffice
отношения не имеет.
- Портал хранит файлы в **S3/MinIO** (бакет `office`, хост `192.168.188.10:9000`);
DiscStorage — только мелочь.
Вывод: цель F2 (прямой SQL-доступ к БД Community Server) достижима, но движок —
MySQL. Issue/эпик предполагали PG. Реализовал `database/sql` с выбором драйвера
по DSN: **mysql** live, **pgx** для PostgreSQL (если портал перенесут). Оба пути
проверены живьём (pgx — подключением к `edw_ro`).
### Туннель (MySQL)
```bash
ssh -f -N -o ControlMaster=no -o ControlPath=none -p 32 -i ~/.ssh/id_ed25519 \
-L 13306:127.0.0.1:3306 root@127.0.0.1
# DSN: root:<pw>@tcp(127.0.0.1:13306)/onlyoffice?parseTime=true
```
`ControlMaster=no / ControlPath=none` обязательны (иначе forward уходит в
persistent master из `~/.ssh/config`).
### Реализация
- `file_pg.go`: `pgStore` (read-only) на `database/sql`; драйверы
`github.com/go-sql-driver/mysql` и `github.com/jackc/pgx/v5/stdlib`.
`List`/`Stat`/`Download`, `Search` по имени (`LIKE`), `Name()` = `mysql`|`postgres`.
Write-методы → `ErrReadOnly`. `current_version = 1` — только текущая версия.
- `Download` — REST не используется: ключ объекта
`00/00/<tenant>/files/folder_<shard>/file_<id>/v<version>/content.<ext>`,
`shard = (id/1000+1)*1000` (файл 3727 → `folder_4000`, НЕ `folder_id`; проверено
live по MinIO). Стриминг — через новый общий `downloadMinioObject` в
`storage_fallback.go` (переиспользование, без дублей).
- Конфиг: `ONLYOFFICE_DSN`, `ONLYOFFICE_PG_DRIVER`, `ONLYOFFICE_PG_TENANT`
(fallback `ONLYOFFICE_TENANT`), `ONLYOFFICE_PG_{HOST,PORT,USER,PASSWORD,DBNAME,SSLMODE}`.
`.env.example` — только имена.
- Док: `docs/community-server-db.md` (схема, туннель, env, ключ MinIO, грабли).
### Тесты (имена + PASS)
- Unit `file_pg_test.go`: `TestRebind`, `TestCSPObjectKey`,
`TestPGDriverDetection`, `TestNormalizeSQLDSNMySQL`, `TestFileRowToEntry`,
`TestFolderRowToEntry`, `TestPGStoreWriteMethodsReadOnly`, `TestPGStoreName`,
`TestPGStoreStatRejectsNonNumeric` — PASS.
- Integration `file_pg_integration_test.go` (`//go:build integration`, skip без
`ONLYOFFICE_DSN`): `TestIntegrationPGStore` — **PASS live** (backend `mysql`):
`Stat`/`List` сверены с REST (`c.Files()`), `Download` совпал побайтово с REST,
write-методы дают `ErrReadOnly`.
- pgx-путь: `NewPGStore` c `EDW_RO_DSN` → `Name()=postgres`, ping OK (live).
- `go build ./... && go vet ./... && go test -race ./...` — зелёные.
gitleaks — `no leaks found`. Секретов в коде нет.
### Блокеры
Нет. Замечание для PO: issue/эпик называют бэкенд «PostgreSQL», фактически
источник — MySQL. Если нужен строго PG-only — скажите, уберу mysql-ветку; но
тогда live-данных для integration нет (в PG `files_file` отсутствует).
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
F2 — PostgreSQL-бэкенд (read-only, «изнутри»)
Цель
Читать файлы/папки напрямую из БД Community Server (PostgreSQL) — быстро, без
HTTP-слоя — и представить как
FileStore(read-only).Research (сначала)
onlyoffice-v2(PG или MySQL?) идоступность: внутри VM (
localhost:5432) → снаружи нужен туннель/expose(SSH-порт VM открыт:
127.0.0.1:32). Зафиксировать вdocs/.files_file,files_folder,files_folder_tree,files_security,tenants(tenant_id), поля (id, parent, title, content_length, modified_on,version, folder_id, create_by, ...).
Реализация
file_pg.go:pgStore(read-only) наdatabase/sql+github.com/jackc/pgx/v5/stdlib(или
pgxpool) — реализуетList/Stat/Download(по object key MinIO/storage)и, опционально,
Searcherпо имени.ErrReadOnly).ONLYOFFICE_DSN(илиONLYOFFICE_PG_*), в.env.exampleтолько имена.Downloadчерез тот же MinIO-путь, чтоstorage_fallback.go(переиспользовать).Тесты/критерии
Entry(без БД).//go:build integration, skip без DSN): list/stat/downloadреального файла из дерева проекта, сверка с REST-результатом.
ErrReadOnly.go build/vet/testзелёные; секретов нет.F2 — отчёт: read-only SQL-бэкенд файлов (Community Server DB)
PR: #45
Ветка
feat/pg-store#36· коммитbf3ef02(basemain).Не мержу и не закрываю.
Research — БД оказалась MySQL, не PostgreSQL (блокера нет)
Проверено live на VM
onlyoffice-v2(SSH127.0.0.1:32):127.0.0.1:3306внутри VM,база
onlyoffice; конфиг/etc/onlyoffice/communityserver/appsettings.production.json(
providerName: MySql.Data.MySqlClient). Активные коннектыmono → :3306.files_file(8987 строк, PKtenant_id,id,version),files_folder(553),
files_folder_tree(2455),files_security; тенанты —tenants_tenants(таблицы
tenantsнет). Поля:id,version,version_group,current_version(1 = текущая версия),folder_id,title,content_length,create_on,modified_on,tenant_id.onlyoffice,схема
edw_docs(ролиedw/edw_ro),files_fileтам нет. К OnlyOfficeотношения не имеет.
office, хост192.168.188.10:9000);DiscStorage — только мелочь.
Вывод: цель F2 (прямой SQL-доступ к БД Community Server) достижима, но движок —
MySQL. Issue/эпик предполагали PG. Реализовал
database/sqlс выбором драйверапо DSN: mysql live, pgx для PostgreSQL (если портал перенесут). Оба пути
проверены живьём (pgx — подключением к
edw_ro).Туннель (MySQL)
ControlMaster=no / ControlPath=noneобязательны (иначе forward уходит вpersistent master из
~/.ssh/config).Реализация
file_pg.go:pgStore(read-only) наdatabase/sql; драйверыgithub.com/go-sql-driver/mysqlиgithub.com/jackc/pgx/v5/stdlib.List/Stat/Download,Searchпо имени (LIKE),Name()=mysql|postgres.Write-методы →
ErrReadOnly.current_version = 1— только текущая версия.Download— REST не используется: ключ объекта00/00/<tenant>/files/folder_<shard>/file_<id>/v<version>/content.<ext>,shard = (id/1000+1)*1000(файл 3727 →folder_4000, НЕfolder_id; провереноlive по MinIO). Стриминг — через новый общий
downloadMinioObjectвstorage_fallback.go(переиспользование, без дублей).ONLYOFFICE_DSN,ONLYOFFICE_PG_DRIVER,ONLYOFFICE_PG_TENANT(fallback
ONLYOFFICE_TENANT),ONLYOFFICE_PG_{HOST,PORT,USER,PASSWORD,DBNAME,SSLMODE}..env.example— только имена.docs/community-server-db.md(схема, туннель, env, ключ MinIO, грабли).Тесты (имена + PASS)
file_pg_test.go:TestRebind,TestCSPObjectKey,TestPGDriverDetection,TestNormalizeSQLDSNMySQL,TestFileRowToEntry,TestFolderRowToEntry,TestPGStoreWriteMethodsReadOnly,TestPGStoreName,TestPGStoreStatRejectsNonNumeric— PASS.file_pg_integration_test.go(//go:build integration, skip безONLYOFFICE_DSN):TestIntegrationPGStore— PASS live (backendmysql):Stat/Listсверены с REST (c.Files()),Downloadсовпал побайтово с REST,write-методы дают
ErrReadOnly.NewPGStorecEDW_RO_DSN→Name()=postgres, ping OK (live).go build ./... && go vet ./... && go test -race ./...— зелёные.gitleaks —
no leaks found. Секретов в коде нет.Блокеры
Нет. Замечание для PO: issue/эпик называют бэкенд «PostgreSQL», фактически
источник — MySQL. Если нужен строго PG-only — скажите, уберу mysql-ветку; но
тогда live-данных для integration нет (в PG
files_fileотсутствует).