adding gitea service
This commit is contained in:
40
Mindforge.API/Controllers/RepositoryController.cs
Normal file
40
Mindforge.API/Controllers/RepositoryController.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Mindforge.API.Services.Interfaces;
|
||||
|
||||
namespace Mindforge.API.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/v1/repository")]
|
||||
public class RepositoryController : ControllerBase
|
||||
{
|
||||
private readonly IGiteaService _giteaService;
|
||||
|
||||
public RepositoryController(IGiteaService giteaService)
|
||||
{
|
||||
_giteaService = giteaService;
|
||||
}
|
||||
|
||||
[HttpGet("info")]
|
||||
public IActionResult GetInfo()
|
||||
{
|
||||
return Ok(new { name = _giteaService.GetRepositoryName() });
|
||||
}
|
||||
|
||||
[HttpGet("tree")]
|
||||
public async Task<IActionResult> GetTree()
|
||||
{
|
||||
var tree = await _giteaService.GetFileTreeAsync();
|
||||
return Ok(tree);
|
||||
}
|
||||
|
||||
[HttpGet("file")]
|
||||
public async Task<IActionResult> GetFile([FromQuery] string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return BadRequest(new { error = "path query parameter is required." });
|
||||
|
||||
var content = await _giteaService.GetFileContentAsync(path);
|
||||
return Ok(new { path, content });
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Mindforge.API/Models/FileTreeNode.cs
Normal file
10
Mindforge.API/Models/FileTreeNode.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Mindforge.API.Models
|
||||
{
|
||||
public class FileTreeNode
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public string Path { get; set; } = "";
|
||||
public string Type { get; set; } = ""; // "file" | "folder"
|
||||
public List<FileTreeNode>? Children { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,9 @@ using Mindforge.API.Services.Interfaces;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Ensure environment variables are loaded into IConfiguration
|
||||
builder.Configuration.AddEnvironmentVariables();
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddControllers();
|
||||
builder.Logging.AddConsole();
|
||||
@@ -37,6 +40,7 @@ builder.Services.AddScoped<ILlmApiProvider, GeminiApiProvider>();
|
||||
builder.Services.AddScoped<IAgentService, AgentService>();
|
||||
builder.Services.AddScoped<IFileService, FileService>();
|
||||
builder.Services.AddScoped<IFlashcardService, FlashcardService>();
|
||||
builder.Services.AddScoped<IGiteaService, GiteaService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
@@ -68,4 +72,17 @@ if (string.IsNullOrEmpty(geminiKey))
|
||||
app.Logger.LogWarning("GEMINI_API_KEY not found in configuration.");
|
||||
}
|
||||
|
||||
var giteaRepoUrl = builder.Configuration["GITEA_REPO_URL"];
|
||||
var giteaAccessToken = builder.Configuration["GITEA_ACCESS_TOKEN"];
|
||||
|
||||
if (string.IsNullOrEmpty(giteaRepoUrl))
|
||||
{
|
||||
app.Logger.LogWarning("GITEA_REPO_URL not found in configuration. Repository features will not work.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(giteaAccessToken))
|
||||
{
|
||||
app.Logger.LogWarning("GITEA_ACCESS_TOKEN not found in configuration. Repository features will not work.");
|
||||
}
|
||||
|
||||
app.Run();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -5,5 +5,9 @@
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
"AllowedHosts": "*",
|
||||
"OPENAI_API_KEY": "",
|
||||
"GEMINI_API_KEY": "",
|
||||
"GITEA_REPO_URL": "",
|
||||
"GITEA_ACCESS_TOKEN": ""
|
||||
}
|
||||
|
||||
@@ -30,6 +30,16 @@ spec:
|
||||
secretKeyRef:
|
||||
name: mindforge-secrets
|
||||
key: GEMINI_API_KEY
|
||||
- name: GITEA_REPO_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mindforge-secrets
|
||||
key: GITEA_REPO_URL
|
||||
- name: GITEA_ACCESS_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mindforge-secrets
|
||||
key: GITEA_ACCESS_TOKEN
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
|
||||
Reference in New Issue
Block a user