< Summary

Information
Class: OzonOrdersWeb.WebServices.Orders.Grid.OrdersGridUserSettingsService
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/Orders/Grid/OrdersGridUserSettingsService.cs
Line coverage
91%
Covered lines: 45
Uncovered lines: 4
Coverable lines: 49
Total lines: 124
Line coverage: 91.8%
Branch coverage
84%
Covered branches: 22
Total branches: 26
Branch coverage: 84.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
IsAllowedKey(...)100%66100%
.ctor(...)100%11100%
GetAllAsync()100%11100%
SaveAsync()80%212087.5%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/Orders/Grid/OrdersGridUserSettingsService.cs

#LineLine coverage
 1using System.Text.RegularExpressions;
 2using Microsoft.EntityFrameworkCore;
 3using OzonDomains.Models;
 4using OzonRepositories.Context;
 5
 6namespace OzonOrdersWeb.WebServices.Orders.Grid;
 7
 8public interface IOrdersGridUserSettingsService
 9{
 10    /// <summary>Все настройки пользователя: key → JSON.</summary>
 11    Task<Dictionary<string, string>> GetAllAsync(string userId, CancellationToken cancellationToken);
 12
 13    /// <summary>Сохранить (создать/обновить) одну настройку; пустое значение удаляет её.</summary>
 14    /// <summary>
 15    /// Сохранить значение; <paramref name="version"/> — версия клиента (монотонно растущая на ключ).
 16    /// Возвращает false, если в базе уже более новая версия (запрос устарел и проигнорирован).
 17    /// </summary>
 18    Task<bool> SaveAsync(string userId, string key, string? json, long version, CancellationToken cancellationToken);
 19}
 20
 21/// <summary>
 22/// Настройки грида заказов по пользователю: раскладка колонок, плотность/шрифт, последний набор фильтров,
 23/// сохранённые представления. Хранятся в <c>OrdersGridUserSettings</c> как JSON по ключу.
 24/// </summary>
 25public sealed class OrdersGridUserSettingsService : IOrdersGridUserSettingsService
 26{
 127    public static readonly IReadOnlySet<string> AllowedKeys = new HashSet<string>(StringComparer.Ordinal)
 128    {
 129        "columnState", "columnStateTwoLine", "viewPrefs", "lastView", "presets", "computedColumns", "columnLayout"
 130    };
 31
 32    /// <summary>
 33    /// Ключи грида транзакций (/Studio2/TransactionsGrid): общий слой поверх настроек OrdersGrid —
 34    /// <c>tx.columnState</c>, <c>tx.columnStateTwoLine</c>, <c>tx.columnLayout</c>, <c>tx.offersColumnState</c>;
 35    /// вариант с <c>.&lt;slug&gt;</c> остаётся допустимым для чтения старых пользовательских настроек.
 36    /// </summary>
 137    private static readonly Regex TransactionGridKey = new(
 138        @"^tx\.(columnState|columnStateTwoLine|viewPrefs|columnLayout|offersColumnState)(\.[a-z0-9-]{1,40})?$",
 139        RegexOptions.Compiled | RegexOptions.CultureInvariant);
 40
 41    private const int MaxValueLength = 256 * 1024;
 42
 43    public static bool IsAllowedKey(string? key)
 2744        => !string.IsNullOrEmpty(key)
 2745           && (AllowedKeys.Contains(key) || TransactionGridKey.IsMatch(key) || BitrixStock.BitrixSyncGridSettings.IsAllo
 46
 47    private readonly ApplicationDbContext _context;
 48
 449    public OrdersGridUserSettingsService(ApplicationDbContext context)
 50    {
 451        _context = context;
 452    }
 53
 54    public async Task<Dictionary<string, string>> GetAllAsync(string userId, CancellationToken cancellationToken)
 55    {
 56        // Пустое Value — «надгробие» удалённой настройки (строка хранит Version, чтобы отклонять опоздавшие записи).
 657        return await _context.OrdersGridUserSettings
 658            .AsNoTracking()
 659            .Where(s => s.UserId == userId && s.Value != string.Empty)
 1660            .ToDictionaryAsync(s => s.Key, s => s.Value, cancellationToken);
 661    }
 62
 63    public async Task<bool> SaveAsync(string userId, string key, string? json, long version, CancellationToken cancellat
 64    {
 1165        if (!IsAllowedKey(key))
 66        {
 067            throw new ArgumentException($"Неизвестный ключ настройки: {key}", nameof(key));
 68        }
 69
 1170        if (json is { Length: > MaxValueLength })
 71        {
 072            throw new ArgumentException("Слишком большое значение настройки.", nameof(json));
 73        }
 74
 1175        var existing = await _context.OrdersGridUserSettings
 1176            .FirstOrDefaultAsync(s => s.UserId == userId && s.Key == key, cancellationToken);
 77
 78        // Optimistic concurrency по версии клиента: запрос со старой версией пришёл позже нового — не затираем.
 1179        if (existing != null && version > 0 && version < existing.Version)
 80        {
 281            return false;
 82        }
 83
 984        if (string.IsNullOrWhiteSpace(json))
 85        {
 86            // Не удаляем строку физически: иначе опоздавший запрос с другой вкладки/устройства не увидит версию
 87            // и воссоздаст удалённую настройку. Оставляем надгробие с пустым Value и актуальной версией.
 188            if (existing != null)
 89            {
 190                existing.Value = string.Empty;
 191                existing.UpdatedAt = DateTime.UtcNow;
 192                existing.Version = Math.Max(version, existing.Version);
 93            }
 094            else if (version > 0)
 95            {
 096                _context.OrdersGridUserSettings.Add(new OrdersGridUserSetting { UserId = userId, Key = key, Value = stri
 97            }
 98
 199            await _context.SaveChangesAsync(cancellationToken);
 1100            return true;
 101        }
 102
 8103        if (existing == null)
 104        {
 5105            _context.OrdersGridUserSettings.Add(new OrdersGridUserSetting
 5106            {
 5107                UserId = userId,
 5108                Key = key,
 5109                Value = json,
 5110                UpdatedAt = DateTime.UtcNow,
 5111                Version = version
 5112            });
 113        }
 114        else
 115        {
 3116            existing.Value = json;
 3117            existing.UpdatedAt = DateTime.UtcNow;
 3118            existing.Version = Math.Max(version, existing.Version);
 119        }
 120
 8121        await _context.SaveChangesAsync(cancellationToken);
 8122        return true;
 11123    }
 124}