noz-cli/NozCli.Core/Client/NozApiClient.cs
kryptomrx f45b31defa feat: graph and search API client with PS analysis cmdlets
Get-NozGraph, Find-NozNote, Measure-NozGarden, Set-NozNoteStatus,
Get-NozOrphans — graph edges, Meilisearch search, garden stats,
frontmatter status patch, orphan detection from inbound link analysis.
2026-05-17 00:27:45 +02:00

151 lines
6.9 KiB
C#

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<ServerCapabilities> 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<List<NoteInfo>> 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<NoteContent> 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<string> 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<List<TemplateInfo>> 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<TemplateContent?> 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<GraphData> 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<List<SearchHit>> 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();
}