# cms-admin-assets-root-folders-seed ## Goal Для каждого `Sites.SiteId` в DB `MoreThenCms` где нет соответствующего root `Folders` row с `Discriminator='AssetsFolder'`, `LoweredPath='/'`, `OwnerId=` — добавить недостающий root row. Это unblock'нёт admin `/admin/assets//getList?path=` который сейчас crash'ит 500 NullReferenceException на пустых assets для 15+ sites (включая `emspb.ru`, `pilorama98.ru`, `labtools.pro`, `kupimknigi.spb.ru`, `sestech.ru`, etc.). ## Root cause `MoreThenCms.Admin.ViewModels.Builders.AssetsJsonViewModelBuilder.Build` (`AssetsJsonViewModelBuilder.cs:22`) делает `new JValue(model.ParentPath)` БЕЗ null-check на model. `_assetsFoldersService.GetFolderByPath(ctx, ownerId, '')` возвращает null когда root folder не существует. Это происходит для sites которые **никогда не открывали admin assets UI** (root создаётся lazy при first upload, видимо). Frontend код (`AssetsAppFunc.cs:66-86`) делает proper null-check → HTTP 404. Только admin view-model builder упустил. Двух-уровневое решение: - **Краткосрочно (этой task'и):** seed missing root rows в DB. Один INSERT per site. Risk: minimal (lookup existing row pattern; используем same shape). - **Долгосрочно (отдельный task):** patch `AssetsJsonViewModelBuilder.Build` → null-guard → empty response. Требует recompile DLL `MoreThenCms.Admin.dll` (нет полного build env, рискованно). ## Key files - `MoreThenCms\Assets\Services\AssetsFoldersService.cs:47-54` — `GetFolderByPath` query (returns null if not found). - `MoreThenCms.Web\ViewModels\Builders\AssetsJsonViewModelBuilder.cs:22` — точка падения. - `MoreThenCms.Web\Admin\Controllers\AssetsController.cs:44-54` — вызов GetList. - DB: `[MoreThenCms].[dbo].[Folders]` (polymorphic, Discriminator='AssetsFolder' для assets). ## Discovery (2026-05-19) - 15 sites без root AssetsFolder подтверждены через `SELECT s.SiteId, s.PrimaryDomain, ... FROM Sites s LEFT JOIN ...`. См. полный список в Decisions. - Sample existing root row: `FolderId=1591A38C-..., OwnerId=7EB313DD-F289-..., Path='/', LoweredPath='/', DateCreated=2017-03-13 07:10:15, CreatedById=E4CC416B-...`. Тот же shape надо воспроизвести для missing. - Затрагивает: emspb.ru (`96EBC481-...`), pilorama98.ru (`37E67FC4-...`), labtools.pro, kupimknigi.spb.ru, sestech.ru, aquamax.spb.ru, artmone.pro, priemka-kvartiry.ru, profund.spb.ru, ics-artmaterials.com, _voda-indigo.ru, plus 4 sites с NULL `PrimaryDomain`. ## План 1. **Dry-run**: `SELECT COUNT(*) FROM Sites s WHERE NOT EXISTS (root for s.SiteId)` — должно быть = 15 (sanity check). 2. **Seed**: INSERT root row per missing site. Один transaction, no rollback unless count mismatch. 3. **Verify**: повторить original probe — root count should be 0 missing. 4. **Test**: hit `https://emspb.snolla.com/admin/assets/96EBC481-.../getList?path=` (через docker exec без auth → 302→login без crash). Затем user-side browser test. ## SQL ```sql DECLARE @CreatedById uniqueidentifier = ( SELECT TOP 1 CreatedById FROM Folders WHERE LoweredPath='/' AND Discriminator='AssetsFolder' ); INSERT INTO Folders (FolderId, OwnerId, Path, LoweredPath, DateCreated, UtcDateCreated, CreatedById, Discriminator) SELECT NEWID(), s.SiteId, '/', '/', SYSDATETIME(), SYSUTCDATETIME(), @CreatedById, 'AssetsFolder' FROM Sites s WHERE NOT EXISTS ( SELECT 1 FROM Folders f WHERE f.OwnerId = s.SiteId AND f.LoweredPath = '/' AND f.Discriminator = 'AssetsFolder' ); ``` ## Atomic revert Сохранить список inserted FolderId через `OUTPUT INSERTED.FolderId` в temp file. Если что: ```sql DELETE FROM Folders WHERE FolderId IN (); ``` ## Open questions - [ ] Аналогичная проблема для ImagesFolder/StylesheetsFolder/ScriptsFolder в admin Themes UI? Проверить. - [ ] DLL patch `AssetsJsonViewModelBuilder.Build` нужен в долгую — отдельная task. Сейчас обходим seed'ом. ## Decisions log - 2026-05-19: вынес из `cms-port-leak-fix` (там был open issue #8 на emspb). User подтвердил воспроизводимость на pilorama98 → не site-specific, общая data issue. - 2026-05-19: **DB seed применён** — 15 rows inserted (single transaction, idempotent NOT EXISTS). Inserted FolderId/OwnerId captured в `cms-admin-assets-root-folders-seed.inserted-rows.txt`. Server-side verified (probe вернул 302→login вместо 500). User browser-verified на pilorama98/emspb — admin assets открывается, empty list без ошибок. - 2026-05-19: **DLL recompile отложен** — `AssetsJsonViewModelBuilder.Build` нужен null-guard как defensive code, но требует build env (нет gitea/build/registry на recovery host'е). Ждёт [[vds-kzntsv-bootstrap]] для восстановления pipeline'а. На текущем seed-only fix'е admin assets работает для всех 15 sites. ## Completed steps - [x] Diagnostic: stack trace из Application event log → `AssetsJsonViewModelBuilder.cs:22` NullRef on `model.ParentPath`. Source code analysis показал отсутствие null-check (vs proper null-check во frontend `AssetsAppFunc.cs:66-86`). - [x] DB probe schema: `Folders` table polymorphic с Discriminator, columns FolderId/OwnerId/Path/LoweredPath/DateCreated/CreatedById обязательные. - [x] Dry-run: 15 sites без root AssetsFolder (включая emspb/pilorama98/labtools.pro/kupimknigi/sestech/etc), 4 sites с NULL PrimaryDomain тоже в списке. - [x] **SQL seed** через `docker exec mssql sqlcmd` — INSERT 15 rows с `OUTPUT INSERTED.FolderId, INSERTED.OwnerId INTO @Inserted`, output saved в `.tasks/cms-admin-assets-root-folders-seed.inserted-rows.txt`. - [x] Verify: missing-root count = 0. Probe `wget --header='Host: emspb.snolla.com'` на getList URL → 302 Found (auth redirect — normal), нет больше 500. - [x] User browser-verified pilorama98 admin assets открывается. - [x] Wiki ingest: новый concept `cms-admin-assets-root-folder-seed.md`, snapshot issue #8 → resolved, `cms-server-port-leak-fix` sibling-link, log.md + index.md обновлены. ## Notes - Связано: [[cms-server-port-leak-fix]] (предыдущая task'а закрыта; admin URLs теперь без `:8089`); [[recovery-architecture-snapshot]] (issue #8 — resolved); [[vds-kzntsv-bootstrap]] (build env для долгосрочного DLL fix'а).