Files
admin/cms-admin-assets-root-folder-seed.md
vitya ec9177dddb 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>
2026-05-19 21:21:13 +03:00

93 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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.