refactor(core): Native AOT - JSON source generators, AOT-safe models and API client
- Add NozJsonContext / NozJsonContextPretty source generator contexts - Replace all reflection-based JSON with source-generated overloads - Add NotesCacheFile record for persistent note cache - Add date field to NoteInfo, CancellationToken to all API methods - LocalConfig uses source-generated serialization (compact + indented)
This commit is contained in:
parent
765ff7a976
commit
b76896dc7a
6 changed files with 61 additions and 38 deletions
|
|
@ -4,6 +4,7 @@ using System.Text;
|
|||
using System.Text.Json;
|
||||
using NozCli.Core.Config;
|
||||
using NozCli.Core.Models;
|
||||
using NozCli.Core.Serialization;
|
||||
|
||||
namespace NozCli.Core.Client;
|
||||
|
||||
|
|
@ -16,16 +17,15 @@ public class NozApiClient : IDisposable
|
|||
_http = new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri(cfg.ServerUrl.TrimEnd('/') + "/"),
|
||||
// Enforce TLS — reject plain HTTP at the OS level
|
||||
DefaultRequestVersion = new Version(2, 0),
|
||||
};
|
||||
_http.DefaultRequestHeaders.Authorization =
|
||||
new AuthenticationHeaderValue("Bearer", cfg.Token);
|
||||
}
|
||||
|
||||
public async Task<ServerCapabilities> GetConfigAsync()
|
||||
public async Task<ServerCapabilities> GetConfigAsync(CancellationToken ct = default)
|
||||
{
|
||||
var res = await _http.GetFromJsonAsync<JsonElement>("api/cli/config");
|
||||
var res = await _http.GetFromJsonAsync("api/cli/config", NozJsonContext.Default.JsonElement, ct);
|
||||
return new ServerCapabilities(
|
||||
Server: res.GetProperty("server").GetString()!,
|
||||
Enabled: res.GetProperty("enabled").GetBoolean(),
|
||||
|
|
@ -36,9 +36,9 @@ public class NozApiClient : IDisposable
|
|||
);
|
||||
}
|
||||
|
||||
public async Task<List<NoteInfo>> GetNotesAsync()
|
||||
public async Task<List<NoteInfo>> GetNotesAsync(CancellationToken ct = default)
|
||||
{
|
||||
var res = await _http.GetFromJsonAsync<JsonElement>("api/cli/notes");
|
||||
var res = await _http.GetFromJsonAsync("api/cli/notes", NozJsonContext.Default.JsonElement, ct);
|
||||
var notes = res.GetProperty("notes");
|
||||
return notes.EnumerateArray().Select(n => new NoteInfo(
|
||||
Slug: n.GetProperty("slug").GetString()!,
|
||||
|
|
@ -47,13 +47,14 @@ public class NozApiClient : IDisposable
|
|||
Topic: n.GetProperty("topic").GetString()!,
|
||||
Lang: n.GetProperty("lang").GetString()!,
|
||||
Private: n.GetProperty("private").GetBoolean(),
|
||||
Hash: n.TryGetProperty("hash", out var h) ? h.GetString() : null
|
||||
Hash: n.TryGetProperty("hash", out var h) ? h.GetString() : null,
|
||||
Date: n.TryGetProperty("date", out var d) ? d.GetString() : null
|
||||
)).ToList();
|
||||
}
|
||||
|
||||
public async Task<NoteContent> GetNoteAsync(string slug)
|
||||
public async Task<NoteContent> GetNoteAsync(string slug, CancellationToken ct = default)
|
||||
{
|
||||
var res = await _http.GetFromJsonAsync<JsonElement>($"api/cli/notes/{slug}");
|
||||
var res = await _http.GetFromJsonAsync($"api/cli/notes/{slug}", NozJsonContext.Default.JsonElement, ct);
|
||||
return new NoteContent(
|
||||
Slug: res.GetProperty("slug").GetString()!,
|
||||
Markdown: res.GetProperty("markdown").GetString()!,
|
||||
|
|
@ -61,25 +62,25 @@ public class NozApiClient : IDisposable
|
|||
);
|
||||
}
|
||||
|
||||
public async Task<string> PutNoteAsync(string slug, string markdown)
|
||||
public async Task<string> PutNoteAsync(string slug, string markdown, CancellationToken ct = default)
|
||||
{
|
||||
var body = JsonSerializer.Serialize(new { markdown });
|
||||
var body = JsonSerializer.Serialize(new PushBody(markdown), NozJsonContext.Default.PushBody);
|
||||
var content = new StringContent(body, Encoding.UTF8, "application/json");
|
||||
var res = await _http.PutAsync($"api/cli/notes/{slug}", content);
|
||||
var res = await _http.PutAsync($"api/cli/notes/{slug}", content, ct);
|
||||
res.EnsureSuccessStatusCode();
|
||||
var json = await res.Content.ReadFromJsonAsync<JsonElement>();
|
||||
var json = await res.Content.ReadFromJsonAsync(NozJsonContext.Default.JsonElement, ct);
|
||||
return json.GetProperty("hash").GetString()!;
|
||||
}
|
||||
|
||||
public async Task DeleteNoteAsync(string slug)
|
||||
public async Task DeleteNoteAsync(string slug, CancellationToken ct = default)
|
||||
{
|
||||
var res = await _http.DeleteAsync($"api/cli/notes/{slug}");
|
||||
var res = await _http.DeleteAsync($"api/cli/notes/{slug}", ct);
|
||||
res.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
public async Task RebuildIndexAsync()
|
||||
public async Task RebuildIndexAsync(CancellationToken ct = default)
|
||||
{
|
||||
var res = await _http.PostAsync("api/cli/rebuild", null);
|
||||
var res = await _http.PostAsync("api/cli/rebuild", null, ct);
|
||||
res.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
using System.Text.Json;
|
||||
using NozCli.Core.Serialization;
|
||||
|
||||
namespace NozCli.Core.Config;
|
||||
|
||||
public class LocalConfig
|
||||
{
|
||||
public string ServerUrl { get; set; } = string.Empty;
|
||||
public string Token { get; set; } = string.Empty;
|
||||
public string ServerUrl { get; set; } = string.Empty;
|
||||
public string Token { get; set; } = string.Empty;
|
||||
/// Default editor (nano, vim, vi, notepad …) — used by 'edit' and 'new'
|
||||
public string? Editor { get; set; } = null;
|
||||
/// Editor for the -c flag (code, zed, kate …) — used by 'edit -c' and 'new -c'
|
||||
public string? CodeEditor { get; set; } = null;
|
||||
|
||||
// ~/.config/noz/config.json
|
||||
private static readonly string ConfigPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
".config", "noz", "config.json"
|
||||
|
|
@ -17,24 +21,17 @@ public class LocalConfig
|
|||
{
|
||||
if (!File.Exists(ConfigPath)) return null;
|
||||
var json = File.ReadAllText(ConfigPath);
|
||||
return JsonSerializer.Deserialize<LocalConfig>(json, JsonOptions);
|
||||
return JsonSerializer.Deserialize(json, NozJsonContext.Default.LocalConfig);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(ConfigPath)!);
|
||||
// 0600 — only owner can read
|
||||
var json = JsonSerializer.Serialize(this, JsonOptions);
|
||||
var json = JsonSerializer.Serialize(this, NozJsonContextPretty.Default.LocalConfig);
|
||||
File.WriteAllText(ConfigPath, json);
|
||||
if (!OperatingSystem.IsWindows())
|
||||
File.SetUnixFileMode(ConfigPath, UnixFileMode.UserRead | UnixFileMode.UserWrite);
|
||||
}
|
||||
|
||||
public static bool Exists() => File.Exists(ConfigPath);
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
namespace NozCli.Core.Models;
|
||||
|
||||
public record NoteInfo(
|
||||
string Slug,
|
||||
string Title,
|
||||
string Status,
|
||||
string Topic,
|
||||
string Lang,
|
||||
bool Private,
|
||||
string? Hash
|
||||
string Slug,
|
||||
string Title,
|
||||
string Status,
|
||||
string Topic,
|
||||
string Lang,
|
||||
bool Private,
|
||||
string? Hash,
|
||||
string? Date = null
|
||||
);
|
||||
|
||||
public record NoteContent(
|
||||
|
|
|
|||
6
NozCli.Core/Models/NotesCacheFile.cs
Normal file
6
NozCli.Core/Models/NotesCacheFile.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
namespace NozCli.Core.Models;
|
||||
|
||||
public record NotesCacheFile(
|
||||
DateTimeOffset LastSynced,
|
||||
List<NoteInfo> Notes
|
||||
);
|
||||
|
|
@ -6,8 +6,5 @@
|
|||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Json" Version="10.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
21
NozCli.Core/Serialization/NozJsonContext.cs
Normal file
21
NozCli.Core/Serialization/NozJsonContext.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using NozCli.Core.Config;
|
||||
using NozCli.Core.Models;
|
||||
|
||||
namespace NozCli.Core.Serialization;
|
||||
|
||||
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower)]
|
||||
[JsonSerializable(typeof(LocalConfig))]
|
||||
[JsonSerializable(typeof(PushBody))]
|
||||
[JsonSerializable(typeof(JsonElement))]
|
||||
[JsonSerializable(typeof(NotesCacheFile))]
|
||||
[JsonSerializable(typeof(List<NoteInfo>))]
|
||||
[JsonSerializable(typeof(NoteInfo))]
|
||||
public partial class NozJsonContext : JsonSerializerContext { }
|
||||
|
||||
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower, WriteIndented = true)]
|
||||
[JsonSerializable(typeof(LocalConfig))]
|
||||
internal partial class NozJsonContextPretty : JsonSerializerContext { }
|
||||
|
||||
public record PushBody(string Markdown);
|
||||
Loading…
Reference in a new issue