docs(.wiki,.tasks): cms-admin-assets-root-folders-seed — DB seed 15 missing root rows
Admin /admin/assets/<siteId>/getList?path= крашился 500 NullReferenceException в AssetsJsonViewModelBuilder.cs:22 (model.ParentPath на null) для 15 sites без root AssetsFolder в Folders table (emspb.ru, pilorama98.ru, labtools.pro, kupimknigi.spb.ru, sestech.ru, aquamax.spb.ru, artmone.pro, priemka-kvartiry.ru, profund.spb.ru, ics-artmaterials.com, _voda-indigo.ru + 4 sites с NULL Domain). Root создавался lazy при first upload — sites которые никогда не использовали admin assets UI остались без root. Frontend (AssetsAppFunc.cs:66-86) делает proper null-check → 404, только admin view-model builder упустил. Fix: idempotent SQL seed (WHERE NOT EXISTS), 15 rows inserted. Inserted FolderId/OwnerId captured в .tasks/...inserted-rows.txt для atomic revert. Browser-verified user'ом на pilorama98/emspb. Долгосрочный TODO: null-guard в AssetsJsonViewModelBuilder.Build (CMS code), требует recompile DLL — отложено до восстановления build env (vds-kzntsv-bootstrap). snapshot open issue #8 → resolved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
92
cms-admin-assets-root-folder-seed.md
Normal file
92
cms-admin-assets-root-folder-seed.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
---
|
||||||
|
title: Admin assets crash — missing root AssetsFolder seed
|
||||||
|
type: concept
|
||||||
|
tags: [cms, admin, data-seed, mssql, gotcha, null-check]
|
||||||
|
sources: [../sources/iis-host-migration-2026-05-19.md]
|
||||||
|
updated: 2026-05-19
|
||||||
|
---
|
||||||
|
|
||||||
|
# CMS admin assets crash — root AssetsFolder seed
|
||||||
|
|
||||||
|
`AssetsJsonViewModelBuilder.cs:22` (admin /admin/assets/.../getList) падает NullReferenceException на любом site, где **root row `Folders` (Discriminator='AssetsFolder', LoweredPath='/', OwnerId=SiteId)** отсутствует. Root создаётся lazy (при first asset upload через admin), и sites которые никогда не использовали admin assets UI — без root.
|
||||||
|
|
||||||
|
Связано: [[cms-server-port-leak-fix]] (нашли при verification после port-leak fix), [[recovery-architecture-snapshot]] (был open issue #8).
|
||||||
|
|
||||||
|
## Симптом
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /admin/assets/<siteId>/getList?path=
|
||||||
|
→ 500 NullReferenceException
|
||||||
|
|
||||||
|
[NullReferenceException]
|
||||||
|
MoreThenCms.Admin.ViewModels.Builders.AssetsJsonViewModelBuilder.Build(...)
|
||||||
|
AssetsJsonViewModelBuilder.cs:22
|
||||||
|
MoreThenCms.Web.Admin.Controllers.AssetsController.GetList(Guid ownerId, String path)
|
||||||
|
AssetsController.cs:46
|
||||||
|
```
|
||||||
|
|
||||||
|
В `AssetsController.GetList` (line 46):
|
||||||
|
```csharp
|
||||||
|
var model = _assetsViewModelBuilder.Build(
|
||||||
|
_assetsFoldersService.GetFolderByPath(ExecutionContext, ownerId, path), ...);
|
||||||
|
```
|
||||||
|
|
||||||
|
`GetFolderByPath` (`MoreThenCms\Assets\Services\AssetsFoldersService.cs:47-54`) делает `repo.Find(p => p.LoweredPath == path && p.OwnerId == ownerId)` — возвращает **null** если row нет. `Build` дальше делает `new JValue(model.ParentPath)` без null-check → crash.
|
||||||
|
|
||||||
|
Frontend код (`AssetsAppFunc.cs:66-86`) делает proper null-check → HTTP 404. Только admin view-model builder упустил.
|
||||||
|
|
||||||
|
## Кого затрагивает (snapshot 2026-05-19)
|
||||||
|
|
||||||
|
После recovery: **15 sites без root** (из ~50). Sites где admin assets когда-либо открывался → root есть. Остальные:
|
||||||
|
- emspb.ru, pilorama98.ru, labtools.pro, kupimknigi.spb.ru, sestech.ru, aquamax.spb.ru, artmone.pro, priemka-kvartiry.ru, profund.spb.ru, ics-artmaterials.com, _voda-indigo.ru
|
||||||
|
- 4 sites с NULL `PrimaryDomain` (legacy/test data)
|
||||||
|
|
||||||
|
Проверочный запрос:
|
||||||
|
```sql
|
||||||
|
SELECT s.SiteId, s.PrimaryDomain,
|
||||||
|
CASE WHEN EXISTS (SELECT 1 FROM Folders f
|
||||||
|
WHERE f.OwnerId = s.SiteId
|
||||||
|
AND f.LoweredPath = '/'
|
||||||
|
AND f.Discriminator = 'AssetsFolder')
|
||||||
|
THEN 'OK' ELSE 'NO ROOT' END AS RootStatus
|
||||||
|
FROM Sites s
|
||||||
|
ORDER BY RootStatus, s.PrimaryDomain;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fix (применён 2026-05-19)
|
||||||
|
|
||||||
|
DB seed — один INSERT для всех missing roots, идемпотентный (`WHERE NOT EXISTS`):
|
||||||
|
|
||||||
|
```sql
|
||||||
|
DECLARE @CreatedById uniqueidentifier = 'E4CC416B-5D13-4757-8EFE-03CBEA15B18C'; -- existing admin user
|
||||||
|
DECLARE @Inserted TABLE (FolderId uniqueidentifier, OwnerId uniqueidentifier);
|
||||||
|
|
||||||
|
INSERT INTO Folders (FolderId, OwnerId, Path, LoweredPath,
|
||||||
|
DateCreated, UtcDateCreated, CreatedById, Discriminator)
|
||||||
|
OUTPUT INSERTED.FolderId, INSERTED.OwnerId INTO @Inserted
|
||||||
|
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');
|
||||||
|
|
||||||
|
SELECT FolderId, OwnerId FROM @Inserted; -- save for atomic revert
|
||||||
|
```
|
||||||
|
|
||||||
|
15 rows вставлены. Inserted FolderId/OwnerId сохранены в `.tasks/cms-admin-assets-root-folders-seed.inserted-rows.txt` для atomic revert (`DELETE FROM Folders WHERE FolderId IN (...)`).
|
||||||
|
|
||||||
|
После: `/admin/assets/<siteId>/getList?path=` возвращает HTTP 302 → /login для unauth (normal auth path), вместо 500. Authorized users видят empty assets list.
|
||||||
|
|
||||||
|
## Долгосрочный TODO (не сделано — нет рабочего build env)
|
||||||
|
|
||||||
|
`AssetsJsonViewModelBuilder.Build` должна null-guard'иться — возвращать empty JObject ({parentPath:"", path:"/", folders:[], files:[]}) когда model null. Это **defensive code**, плюс auto-create root в `GetFolderByPath` для root path (как обычно делают админ-репозитории).
|
||||||
|
|
||||||
|
Требует recompile `MoreThenCms.Admin.dll` — отложено до восстановления полного build pipeline (gitea/build/registry — см. [[vds-kzntsv-bootstrap]] task).
|
||||||
|
|
||||||
|
## Возможно затрагивает ещё (open для следующей сессии)
|
||||||
|
|
||||||
|
`Folders` table — polymorphic: `AssetsFolder`, `ImagesFolder` (OwnerId=ThemeId), `ScriptsFolder` (ThemeId), `StylesheetsFolder` (ThemeId). Аналогичная null-falling логика может быть в admin views для Themes (Stylesheets/Scripts/Images). Не проверено, не reported user'ом.
|
||||||
|
|
||||||
|
Если будет — same pattern: `SELECT s.SiteId vs Folders root по соответствующему Discriminator` + seed.
|
||||||
@@ -169,6 +169,6 @@ Site `Web.config` MOJET содержать `<rewrite><allowedServerVariables>`
|
|||||||
- URL Rewrite 2.1 MSI installed.
|
- URL Rewrite 2.1 MSI installed.
|
||||||
- Затрагивает **все 11 cms hosts** (общий site `snolla`): emspb, labtools, labtoolspro, pilorama98, tandemmebel, kupimknigi, maljarka, sestech, isc-artmaterials, rimiz, plus snolla.com sub-domains.
|
- Затрагивает **все 11 cms hosts** (общий site `snolla`): emspb, labtools, labtoolspro, pilorama98, tandemmebel, kupimknigi, maljarka, sestech, isc-artmaterials, rimiz, plus snolla.com sub-domains.
|
||||||
|
|
||||||
## Известное ограничение (НЕ закрыто этим fix'ом)
|
## Sibling fix (отдельная task, тот же день)
|
||||||
|
|
||||||
`/admin/assets/<guid>/getList?path=` на `emspb.snolla.com` admin → 500 NullReferenceException в `AssetsJsonViewModelBuilder.cs:22` (`model.ParentPath` на null). User подтвердил «только этот site». `_assetsFoldersService.GetFolderByPath(ctx, ownerId, '')` возвращает null. Скорее всего связано с пустыми settings `<fileStorageClients><add name="assets">` (basePath/siteUrl/sitePath = ""). **Отдельный bug**, не в scope этого fix'а — отдельная task если будет приоритет.
|
После port-leak fix'а surfaced `/admin/assets/<guid>/getList → 500 NullReferenceException` на admin assets. Оказался **не site-specific** — общий для 15 sites без root AssetsFolder в DB. Решён DB seed'ом, см. [[cms-admin-assets-root-folder-seed]].
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ ACL: `IIS AppPool\<site>:(OI)(CI)M` рекурсивно на каждом site
|
|||||||
1. ~~**X-Forwarded headers** не передаются от traefik в IIS → CMS делает redirect с `:4443` в URL.~~ → **RESOLVED 2026-05-19 вечер** через URL Rewrite 2.1 + `<serverVariables>` rule на host IIS. Также закрыл утечку `:8089` в admin SPA после attempt 2 миграции. См. [[cms-server-port-leak-fix]].
|
1. ~~**X-Forwarded headers** не передаются от traefik в IIS → CMS делает redirect с `:4443` в URL.~~ → **RESOLVED 2026-05-19 вечер** через URL Rewrite 2.1 + `<serverVariables>` rule на host IIS. Также закрыл утечку `:8089` в admin SPA после attempt 2 миграции. См. [[cms-server-port-leak-fix]].
|
||||||
2. **rimiz.ru → 404**. CMS-side, не инфра.
|
2. **rimiz.ru → 404**. CMS-side, не инфра.
|
||||||
3. **ics-artmaterials.com → 404**. Аналогично — CMS-side (`www.ics-artmaterials.com → 301 → ics-artmaterials.com → 404`).
|
3. **ics-artmaterials.com → 404**. Аналогично — CMS-side (`www.ics-artmaterials.com → 301 → ics-artmaterials.com → 404`).
|
||||||
8. **emspb.snolla.com `/admin/assets/<guid>/getList` → 500 NullReferenceException** в `AssetsJsonViewModelBuilder.cs:22` (`model` null). User подтвердил «только этот site». Не в scope port-leak fix; CMS-side bug в storage/Site mapping. Открыт.
|
8. ~~**emspb.snolla.com `/admin/assets/<guid>/getList` → 500 NullReferenceException**.~~ → **RESOLVED 2026-05-19 вечер** через DB seed root AssetsFolder rows для 15 sites без них (включая emspb, pilorama98, и др.). Симптом был НЕ site-specific — общий для всех sites которые никогда не использовали admin assets UI. См. [[cms-admin-assets-root-folder-seed]]. Долгосрочный TODO — null-guard в `AssetsJsonViewModelBuilder.Build` (требует recompile DLL, отложено до восстановления build env).
|
||||||
4. **acme.json HTTP-01 renewal** будет фейлить для доменов с DNS не на нашем IP → переезд на DNS-01 через REGRU до истечения сертификатов (~3 месяца).
|
4. **acme.json HTTP-01 renewal** будет фейлить для доменов с DNS не на нашем IP → переезд на DNS-01 через REGRU до истечения сертификатов (~3 месяца).
|
||||||
5. **C:\inetpub\logs\** растёт — нужна ротация.
|
5. **C:\inetpub\logs\** растёт — нужна ротация.
|
||||||
6. **docker.sock provider в traefik** не работает — но не блокирует (всё через file-provider). См. [[traefik-on-windows-docker-desktop]] Pitfall 3.
|
6. **docker.sock provider в traefik** не работает — но не блокирует (всё через file-provider). См. [[traefik-on-windows-docker-desktop]] Pitfall 3.
|
||||||
|
|||||||
Reference in New Issue
Block a user