mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 12:41:06 -04:00
Schema v13 adds shared_settings and shared_setting_revisions behind /api/settings: one revision-guarded JSON document per kind and key, with every revision (deletions included) kept with who saved it. Recreating a deleted document continues its revision count. OpenNest.Data gains the client (RemoteSharedSettings), a synchronous store that keeps the last copy read for offline use and refuses writes while the server is unreachable (SharedSettingsStore), a machine provider over it, and JSON parse/serialize entry points for clamp settings and nest defaults. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
122 lines
4.7 KiB
C#
122 lines
4.7 KiB
C#
using System.Text.Json;
|
|
|
|
namespace OpenNest.Data;
|
|
|
|
/// <summary>
|
|
/// One settings document every workstation shares through the nest server
|
|
/// (<c>/api/settings</c>): a machine configuration, a post's settings, the clamp rail or the
|
|
/// nest defaults. <see cref="Json"/> is the document exactly as its owner serializes it;
|
|
/// <see cref="Revision"/> guards every change.
|
|
/// </summary>
|
|
public sealed class SharedSettingRecord
|
|
{
|
|
public string Kind { get; set; } = "";
|
|
|
|
public string Key { get; set; } = "";
|
|
|
|
public int Revision { get; set; }
|
|
|
|
public string Json { get; set; } = "";
|
|
|
|
public DateTime SavedAt { get; set; }
|
|
|
|
public string SavedBy { get; set; } = "";
|
|
}
|
|
|
|
/// <summary>The body of a shared settings create or update.</summary>
|
|
public sealed class SharedSettingWrite
|
|
{
|
|
public string Json { get; set; } = "";
|
|
|
|
public string SavedBy { get; set; } = "";
|
|
}
|
|
|
|
/// <summary>The kinds of shared settings and the keys of the single-document kinds.</summary>
|
|
public static class SharedSettingKinds
|
|
{
|
|
/// <summary>A <see cref="MachineConfig"/>, keyed by its id.</summary>
|
|
public const string Machine = "machine";
|
|
|
|
/// <summary>A post processor's settings, keyed by the post's type name.</summary>
|
|
public const string Post = "post";
|
|
|
|
/// <summary>The <see cref="ClampSettings"/> new nests start from, under <see cref="DefaultKey"/>.</summary>
|
|
public const string Clamps = "clamps";
|
|
|
|
/// <summary>The <see cref="NestDefaults"/> new nests start from, under <see cref="DefaultKey"/>.</summary>
|
|
public const string NestDefaults = "nest-defaults";
|
|
|
|
/// <summary>The key of the clamp settings and nest defaults documents.</summary>
|
|
public const string DefaultKey = "default";
|
|
|
|
public static IReadOnlyList<string> All { get; } = [Machine, Post, Clamps, NestDefaults];
|
|
|
|
public static bool IsKnown(string? kind) => kind is not null && All.Contains(kind);
|
|
}
|
|
|
|
/// <summary>Validation shared by the nest server and its clients.</summary>
|
|
public static class SharedSettingRules
|
|
{
|
|
public const int MaxKeyLength = 200;
|
|
|
|
public const int MaxSavedByLength = 200;
|
|
|
|
/// <summary>
|
|
/// A settings document's limit in characters: far above the largest post configuration, and
|
|
/// small enough that the escaped document fits the server's request limit.
|
|
/// </summary>
|
|
public const int MaxJsonLength = 512 * 1024;
|
|
|
|
/// <summary>
|
|
/// The key as stored: a machine id in lower-case "D" form, any other key as given. Null when
|
|
/// the key is not valid for the kind.
|
|
/// </summary>
|
|
public static string? NormalizeKey(string? kind, string? key)
|
|
{
|
|
if (!SharedSettingKinds.IsKnown(kind) || string.IsNullOrEmpty(key) || key.Length > MaxKeyLength)
|
|
return null;
|
|
if (kind == SharedSettingKinds.Machine)
|
|
return Guid.TryParse(key, out var id) && id != Guid.Empty ? id.ToString("D") : null;
|
|
if (kind is SharedSettingKinds.Clamps or SharedSettingKinds.NestDefaults)
|
|
return key == SharedSettingKinds.DefaultKey ? key : null;
|
|
return key.All(c => char.IsAsciiLetterOrDigit(c) || c is '.' or '_' or '-' or '+') ? key : null;
|
|
}
|
|
|
|
/// <summary>Why the document cannot be stored under the kind and key, or null when it can.</summary>
|
|
public static string? GetError(string? kind, string? key, SharedSettingWrite? write)
|
|
{
|
|
if (!SharedSettingKinds.IsKnown(kind))
|
|
return $"Unknown settings kind '{kind}'.";
|
|
var normalized = NormalizeKey(kind, key);
|
|
if (normalized is null)
|
|
return $"Invalid {kind} settings key.";
|
|
if (write is null || string.IsNullOrEmpty(write.Json))
|
|
return "The settings document is required.";
|
|
if (write.Json.Length > MaxJsonLength)
|
|
return "The settings document is too large.";
|
|
if ((write.SavedBy ?? "").Length > MaxSavedByLength)
|
|
return "savedBy is too long.";
|
|
|
|
try
|
|
{
|
|
using var document = JsonDocument.Parse(write.Json);
|
|
if (document.RootElement.ValueKind != JsonValueKind.Object)
|
|
return "The settings document must be a JSON object.";
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
return "The settings document is not valid JSON.";
|
|
}
|
|
|
|
if (kind == SharedSettingKinds.Machine)
|
|
{
|
|
MachineConfig? machine;
|
|
try { machine = JsonSerializer.Deserialize<MachineConfig>(write.Json, LocalJsonProvider.JsonOptions); }
|
|
catch (JsonException) { return "The machine configuration is not valid."; }
|
|
if (machine is null || machine.Id.ToString("D") != normalized)
|
|
return "The machine configuration's id does not match its key.";
|
|
}
|
|
return null;
|
|
}
|
|
}
|