< Summary

Information
Class: OzonOrdersWeb.Areas.Admin.Controllers.PagesController
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Admin/Controllers/PagesController.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 102
Coverable lines: 102
Total lines: 183
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 24
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
Index()0%620%
Walk()0%2040%
SyncFromCode()100%210%
EnsurePagesUpToDateAsync()0%2040%
Create()100%210%
Create()0%2040%
Edit()0%620%
Edit()0%2040%
Delete()0%2040%
Apply(...)100%210%
PopulateParents()0%620%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Admin/Controllers/PagesController.cs

#LineLine coverage
 1using Microsoft.AspNetCore.Mvc;
 2using Microsoft.AspNetCore.Mvc.Rendering;
 3using Microsoft.EntityFrameworkCore;
 4using Microsoft.Extensions.Caching.Memory;
 5using OzonDomains.Models.Authorization;
 6using OzonOrdersWeb.Areas.Admin.Models;
 7using OzonOrdersWeb.Authorization.Discovery;
 8using OzonOrdersWeb.Authorization.Seed;
 9using OzonRepositories.Context;
 10using Services.Authorization.Services;
 11
 12namespace OzonOrdersWeb.Areas.Admin.Controllers;
 13
 14/// <summary>
 15/// CRUD страниц (реестр маршрутов MVC, используемых для проверки прав и построения меню)
 16/// плюс синхронизация с кодом через PageDiscoveryService.
 17/// </summary>
 18public class PagesController : AdminAreaControllerBase
 19{
 20    private readonly ApplicationDbContext _db;
 21    private readonly IPageDiscoveryService _discovery;
 22    private readonly IPermissionService _permissions;
 23    private readonly IMemoryCache _cache;
 24
 025    public PagesController(ApplicationDbContext db, IPageDiscoveryService discovery, IPermissionService permissions, IMe
 26    {
 027        _db = db;
 028        _discovery = discovery;
 029        _permissions = permissions;
 030        _cache = cache;
 031    }
 32
 33    public async Task<IActionResult> Index(CancellationToken ct)
 34    {
 035        ViewData["Title"] = "Страницы";
 036        ViewData["Breadcrumb"] = new List<(string, string?)> { ("Админка", Url.Action("Index", "Home")), ("Страницы", nu
 37
 038        await EnsurePagesUpToDateAsync(ct);
 39
 040        var all = await _db.AppPages.AsNoTracking().OrderBy(p => p.SortOrder).ThenBy(p => p.DisplayName).ToListAsync(ct)
 041        var rows = new List<PageListItemVm>();
 42        void Walk(int? parentId, int depth)
 43        {
 044            foreach (var p in all.Where(x => x.ParentId == parentId).OrderBy(x => x.SortOrder))
 45            {
 046                rows.Add(new PageListItemVm
 047                {
 048                    Id = p.Id, ParentId = p.ParentId, Depth = depth,
 049                    Code = p.Code, DisplayName = p.DisplayName,
 050                    Area = p.Area, Controller = p.Controller, Action = p.Action,
 051                    IsMenuItem = p.IsMenuItem, IsActive = p.IsActive, Icon = p.Icon, SortOrder = p.SortOrder,
 052                });
 053                Walk(p.Id, depth + 1);
 54            }
 055        }
 056        Walk(null, 0);
 57
 058        ViewBag.DiscoveredNew = (await _discovery.DiscoverAsync(ct)).Where(d => !d.ExistsInDb).ToList();
 059        return View(rows);
 060    }
 61
 62    [HttpPost]
 63    [ValidateAntiForgeryToken]
 64    public async Task<IActionResult> SyncFromCode(CancellationToken ct)
 65    {
 066        var added = await _discovery.SyncAllAsync(ct);
 067        var titles = await _discovery.RefreshDisplayNamesFromViewsAsync(ct);
 068        var hierarchyChanges = await Studio2AppPageHierarchySync.EnsureAsync(_db, _cache, ct);
 069        _permissions.InvalidateAll();
 070        TempData["SuccessMessage"] =
 071            $"Синхронизация завершена. Добавлено страниц: {added}. Заголовки из .cshtml обновлены: {titles}. Иерархия/ме
 072        return RedirectToAction(nameof(Index));
 073    }
 74
 75    private async Task EnsurePagesUpToDateAsync(CancellationToken ct)
 76    {
 077        var added = await _discovery.SyncAllAsync(ct);
 078        var hierarchyChanges = await Studio2AppPageHierarchySync.EnsureAsync(_db, _cache, ct);
 079        if (added > 0 || hierarchyChanges > 0)
 80        {
 081            _permissions.InvalidateAll();
 82        }
 083    }
 84
 85    [HttpGet]
 86    public async Task<IActionResult> Create(CancellationToken ct)
 87    {
 088        ViewData["Title"] = "Новая страница";
 089        await PopulateParents(null, ct);
 090        return View("Edit", new PageEditVm { IsActive = true, IsMenuItem = true });
 091    }
 92
 93    [HttpPost]
 94    [ValidateAntiForgeryToken]
 95    public async Task<IActionResult> Create(PageEditVm model, CancellationToken ct)
 96    {
 097        if (await _db.AppPages.AnyAsync(p => p.Code == model.Code, ct))
 98        {
 099            ModelState.AddModelError(nameof(model.Code), "Страница с таким кодом уже существует");
 100        }
 0101        if (!ModelState.IsValid) { await PopulateParents(model.ParentId, ct); return View("Edit", model); }
 102
 0103        _db.AppPages.Add(Apply(new AppPage(), model));
 0104        await _db.SaveChangesAsync(ct);
 0105        _permissions.InvalidateAll();
 0106        TempData["SuccessMessage"] = "Страница создана";
 0107        return RedirectToAction(nameof(Index));
 0108    }
 109
 110    [HttpGet]
 111    public async Task<IActionResult> Edit(int id, CancellationToken ct)
 112    {
 0113        var p = await _db.AppPages.FirstOrDefaultAsync(x => x.Id == id, ct);
 0114        if (p == null) return NotFound();
 0115        ViewData["Title"] = $"Страница: {p.DisplayName}";
 0116        await PopulateParents(p.ParentId, ct, exclude: p.Id);
 0117        return View(new PageEditVm
 0118        {
 0119            Id = p.Id, Code = p.Code, DisplayName = p.DisplayName,
 0120            Area = p.Area, Controller = p.Controller, Action = p.Action,
 0121            ParentId = p.ParentId, SortOrder = p.SortOrder, Icon = p.Icon,
 0122            IsMenuItem = p.IsMenuItem, IsActive = p.IsActive,
 0123        });
 0124    }
 125
 126    [HttpPost]
 127    [ValidateAntiForgeryToken]
 128    public async Task<IActionResult> Edit(int id, PageEditVm model, CancellationToken ct)
 129    {
 0130        var p = await _db.AppPages.FirstOrDefaultAsync(x => x.Id == id, ct);
 0131        if (p == null) return NotFound();
 0132        if (!ModelState.IsValid) { await PopulateParents(model.ParentId, ct, exclude: p.Id); return View(model); }
 0133        Apply(p, model);
 0134        p.UpdatedAt = DateTime.UtcNow;
 0135        await _db.SaveChangesAsync(ct);
 0136        _permissions.InvalidateAll();
 0137        TempData["SuccessMessage"] = "Изменения сохранены";
 0138        return RedirectToAction(nameof(Index));
 0139    }
 140
 141    [HttpPost]
 142    [ValidateAntiForgeryToken]
 143    public async Task<IActionResult> Delete(int id, CancellationToken ct)
 144    {
 0145        var p = await _db.AppPages.FirstOrDefaultAsync(x => x.Id == id, ct);
 0146        if (p == null) return NotFound();
 0147        if (await _db.AppPages.AnyAsync(x => x.ParentId == id, ct))
 148        {
 0149            TempData["ErrorMessage"] = "Сначала удалите дочерние страницы";
 0150            return RedirectToAction(nameof(Index));
 151        }
 0152        _db.AppPages.Remove(p);
 0153        await _db.SaveChangesAsync(ct);
 0154        _permissions.InvalidateAll();
 0155        TempData["SuccessMessage"] = "Страница удалена";
 0156        return RedirectToAction(nameof(Index));
 0157    }
 158
 159    private static AppPage Apply(AppPage target, PageEditVm vm)
 160    {
 0161        target.Code = vm.Code.Trim();
 0162        target.DisplayName = vm.DisplayName.Trim();
 0163        target.Area = vm.Area;
 0164        target.Controller = vm.Controller.Trim();
 0165        target.Action = vm.Action.Trim();
 0166        target.ParentId = vm.ParentId;
 0167        target.SortOrder = vm.SortOrder;
 0168        target.Icon = vm.Icon;
 0169        target.IsMenuItem = vm.IsMenuItem;
 0170        target.IsActive = vm.IsActive;
 0171        return target;
 172    }
 173
 174    private async Task PopulateParents(int? selected, CancellationToken ct, int? exclude = null)
 175    {
 0176        var list = await _db.AppPages
 0177            .Where(p => exclude == null || p.Id != exclude)
 0178            .OrderBy(p => p.DisplayName)
 0179            .Select(p => new { p.Id, p.DisplayName })
 0180            .ToListAsync(ct);
 0181        ViewBag.Parents = new SelectList(list, "Id", "DisplayName", selected);
 0182    }
 183}