using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text; using System.Text.Json; using NozCli.Core.Config; using NozCli.Core.Models; using NozCli.Core.Serialization; namespace NozCli.Core.Client; public class NozApiClient : IDisposable { private readonly HttpClient _http; public NozApiClient(LocalConfig cfg) { _http = new HttpClient { BaseAddress = new Uri(cfg.ServerUrl.TrimEnd('/') + "/"), DefaultRequestVersion = new Version(2, 0), }; _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", cfg.Token); } public async Task GetConfigAsync(CancellationToken ct = default) { 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(), AllowPush: res.GetProperty("allow_push").GetBoolean(), AllowDelete: res.GetProperty("allow_delete").GetBoolean(), AllowVault: res.GetProperty("allow_vault").GetBoolean(), AllowRebuild: res.GetProperty("allow_rebuild").GetBoolean() ); } public async Task> GetNotesAsync(CancellationToken ct = default) { 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()!, Title: n.GetProperty("title").GetString()!, Status: n.GetProperty("status").GetString()!, 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, Date: n.TryGetProperty("date", out var d) ? d.GetString() : null )).ToList(); } public async Task GetNoteAsync(string slug, CancellationToken ct = default) { 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()!, Hash: res.GetProperty("hash").GetString()! ); } public async Task PutNoteAsync(string slug, string markdown, CancellationToken ct = default) { 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, ct); res.EnsureSuccessStatusCode(); var json = await res.Content.ReadFromJsonAsync(NozJsonContext.Default.JsonElement, ct); return json.GetProperty("hash").GetString()!; } public async Task DeleteNoteAsync(string slug, CancellationToken ct = default) { var res = await _http.DeleteAsync($"api/cli/notes/{slug}", ct); res.EnsureSuccessStatusCode(); } public async Task> GetTemplatesAsync(CancellationToken ct = default) { var res = await _http.GetFromJsonAsync("api/cli/templates", NozJsonContext.Default.JsonElement, ct); return res.GetProperty("templates").EnumerateArray().Select(t => new TemplateInfo( Name: t.GetProperty("name").GetString()!, Description: t.TryGetProperty("description", out var d) ? d.GetString() : null )).ToList(); } public async Task GetTemplateAsync(string name, CancellationToken ct = default) { var res = await _http.GetAsync($"api/cli/templates/{Uri.EscapeDataString(name)}", ct); if (res.StatusCode == System.Net.HttpStatusCode.NotFound) return null; res.EnsureSuccessStatusCode(); var json = await res.Content.ReadFromJsonAsync(NozJsonContext.Default.JsonElement, ct); return new TemplateContent( Name: json.GetProperty("name").GetString()!, Description: json.TryGetProperty("description", out var d) ? d.GetString() : null, Body: json.GetProperty("body").GetString()! ); } public async Task PutFolderIndexAsync( string folderPath, string title, string? description = null, CancellationToken ct = default) { var body = JsonSerializer.Serialize(new FolderBody(title, description), NozJsonContext.Default.FolderBody); var content = new StringContent(body, Encoding.UTF8, "application/json"); var res = await _http.PutAsync($"api/cli/folders/{folderPath}", content, ct); res.EnsureSuccessStatusCode(); } public async Task GetGraphAsync(CancellationToken ct = default) { var res = await _http.GetFromJsonAsync("api/cli/graph", NozJsonContext.Default.JsonElement, ct); var nodes = res.GetProperty("nodes").EnumerateArray().Select(n => new GraphNode( Id: n.GetProperty("id").GetString()!, Title: n.GetProperty("title").GetString()!, Topic: n.GetProperty("topic").GetString()!, Status: n.GetProperty("status").GetString()! )).ToList(); var edges = res.GetProperty("edges").EnumerateArray().Select(e => new GraphEdge( Source: e.GetProperty("source").GetString()!, Target: e.GetProperty("target").GetString()! )).ToList(); return new GraphData(nodes, edges); } public async Task> SearchAsync( string query, string? topic = null, int limit = 20, CancellationToken ct = default) { var url = $"api/cli/search?q={Uri.EscapeDataString(query)}&limit={limit}"; if (topic is not null) url += $"&topic={Uri.EscapeDataString(topic)}"; var res = await _http.GetFromJsonAsync(url, NozJsonContext.Default.JsonElement, ct); return res.GetProperty("hits").EnumerateArray().Select(h => new SearchHit( Slug: h.GetProperty("slug").GetString()!, Title: h.GetProperty("title").GetString()!, Description: h.TryGetProperty("description", out var d) ? d.GetString() : null, Topic: h.GetProperty("topic").GetString()!, Status: h.GetProperty("status").GetString()!, Lang: h.GetProperty("lang").GetString()! )).ToList(); } public async Task RebuildIndexAsync(CancellationToken ct = default) { var res = await _http.PostAsync("api/cli/rebuild", null, ct); res.EnsureSuccessStatusCode(); } public void Dispose() => _http.Dispose(); }