adding gitea service
This commit is contained in:
165
Mindforge.API/Services/GiteaService.cs
Normal file
165
Mindforge.API/Services/GiteaService.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Mindforge.API.Exceptions;
|
||||
using Mindforge.API.Models;
|
||||
using Mindforge.API.Services.Interfaces;
|
||||
|
||||
namespace Mindforge.API.Services
|
||||
{
|
||||
public class GiteaService : IGiteaService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly string _baseUrl;
|
||||
private readonly string _owner;
|
||||
private readonly string _repo;
|
||||
private readonly string _token;
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
public GiteaService(HttpClient httpClient, IConfiguration configuration)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
|
||||
var repoUrl = configuration["GITEA_REPO_URL"];
|
||||
if (string.IsNullOrEmpty(repoUrl))
|
||||
throw new InvalidOperationException("GITEA_REPO_URL is not set in configuration.");
|
||||
|
||||
_token = configuration["GITEA_ACCESS_TOKEN"]
|
||||
?? throw new InvalidOperationException("GITEA_ACCESS_TOKEN is not set in configuration.");
|
||||
|
||||
// Parse: https://host/owner/repo or https://host/owner/repo.git
|
||||
var normalizedUrl = repoUrl.TrimEnd('/').TrimEnd(".git".ToCharArray());
|
||||
var uri = new Uri(normalizedUrl);
|
||||
_baseUrl = $"{uri.Scheme}://{uri.Host}{(uri.IsDefaultPort ? "" : $":{uri.Port}")}";
|
||||
var parts = uri.AbsolutePath.Trim('/').Split('/');
|
||||
_owner = parts[0];
|
||||
_repo = parts[1];
|
||||
}
|
||||
|
||||
public string GetRepositoryName() => _repo;
|
||||
|
||||
public async Task<List<FileTreeNode>> GetFileTreeAsync()
|
||||
{
|
||||
// Get the master branch to obtain the latest commit SHA
|
||||
var branchJson = await GetApiAsync($"/api/v1/repos/{_owner}/{_repo}/branches/master");
|
||||
var branch = JsonSerializer.Deserialize<GiteaBranch>(branchJson, JsonOptions)
|
||||
?? throw new InvalidOperationException("Failed to parse branch response from Gitea.");
|
||||
|
||||
var treeSha = branch.Commit.Id;
|
||||
|
||||
// Fetch the full recursive tree
|
||||
var treeJson = await GetApiAsync($"/api/v1/repos/{_owner}/{_repo}/git/trees/{treeSha}?recursive=true");
|
||||
var treeResponse = JsonSerializer.Deserialize<GiteaTreeResponse>(treeJson, JsonOptions)
|
||||
?? throw new InvalidOperationException("Failed to parse tree response from Gitea.");
|
||||
|
||||
return BuildTree(treeResponse.Tree);
|
||||
}
|
||||
|
||||
public async Task<string> GetFileContentAsync(string path)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get,
|
||||
$"{_baseUrl}/api/v1/repos/{_owner}/{_repo}/raw/{path}?ref=master");
|
||||
request.Headers.Add("Authorization", $"token {_token}");
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
throw new UserException($"File not found in repository: {path}");
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
|
||||
private async Task<string> GetApiAsync(string endpoint)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, $"{_baseUrl}{endpoint}");
|
||||
request.Headers.Add("Authorization", $"token {_token}");
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
|
||||
private static List<FileTreeNode> BuildTree(List<GiteaTreeItem> items)
|
||||
{
|
||||
var root = new List<FileTreeNode>();
|
||||
var folderMap = new Dictionary<string, FileTreeNode>();
|
||||
|
||||
// Only include .md files and their parent folders
|
||||
var mdFiles = items.Where(i => i.Type == "blob" && i.Path.EndsWith(".md")).ToList();
|
||||
var neededFolders = new HashSet<string>();
|
||||
foreach (var file in mdFiles)
|
||||
{
|
||||
var dir = System.IO.Path.GetDirectoryName(file.Path)?.Replace('\\', '/');
|
||||
while (!string.IsNullOrEmpty(dir))
|
||||
{
|
||||
neededFolders.Add(dir);
|
||||
dir = System.IO.Path.GetDirectoryName(dir)?.Replace('\\', '/');
|
||||
}
|
||||
}
|
||||
|
||||
// Create folder nodes
|
||||
foreach (var item in items.Where(i => i.Type == "tree" && neededFolders.Contains(i.Path)).OrderBy(i => i.Path))
|
||||
{
|
||||
var node = new FileTreeNode
|
||||
{
|
||||
Name = System.IO.Path.GetFileName(item.Path),
|
||||
Path = item.Path,
|
||||
Type = "folder",
|
||||
Children = []
|
||||
};
|
||||
folderMap[item.Path] = node;
|
||||
}
|
||||
|
||||
// Place file and folder nodes into parent
|
||||
var allNodes = mdFiles.Select(i => new FileTreeNode
|
||||
{
|
||||
Name = System.IO.Path.GetFileName(i.Path),
|
||||
Path = i.Path,
|
||||
Type = "file"
|
||||
}).Concat(folderMap.Values).OrderBy(n => n.Path);
|
||||
|
||||
foreach (var node in allNodes)
|
||||
{
|
||||
var parentPath = System.IO.Path.GetDirectoryName(node.Path)?.Replace('\\', '/');
|
||||
if (string.IsNullOrEmpty(parentPath))
|
||||
root.Add(node);
|
||||
else if (folderMap.TryGetValue(parentPath, out var parent))
|
||||
parent.Children!.Add(node);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
// ---- Gitea API DTOs ----
|
||||
|
||||
private class GiteaBranch
|
||||
{
|
||||
[JsonPropertyName("commit")]
|
||||
public GiteaCommit Commit { get; set; } = new();
|
||||
}
|
||||
|
||||
private class GiteaCommit
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; } = "";
|
||||
}
|
||||
|
||||
private class GiteaTreeResponse
|
||||
{
|
||||
[JsonPropertyName("tree")]
|
||||
public List<GiteaTreeItem> Tree { get; set; } = [];
|
||||
}
|
||||
|
||||
private class GiteaTreeItem
|
||||
{
|
||||
[JsonPropertyName("path")]
|
||||
public string Path { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } = ""; // "blob" | "tree"
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Mindforge.API/Services/Interfaces/IGiteaService.cs
Normal file
11
Mindforge.API/Services/Interfaces/IGiteaService.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Mindforge.API.Models;
|
||||
|
||||
namespace Mindforge.API.Services.Interfaces
|
||||
{
|
||||
public interface IGiteaService
|
||||
{
|
||||
Task<List<FileTreeNode>> GetFileTreeAsync();
|
||||
Task<string> GetFileContentAsync(string path);
|
||||
string GetRepositoryName();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user