Files
Teamup/src/Modules/TeamUp.Modules.Skills/Endpoints/SkillsEndpoints.cs
T

109 lines
3.7 KiB
C#
Raw Normal View History

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.EntityFrameworkCore;
using TeamUp.Modules.Skills.Domain;
using TeamUp.Modules.Skills.Indexing;
using TeamUp.Modules.Skills.Persistence;
using TeamUp.Modules.Skills.Sync;
using TeamUp.SharedKernel.Modularity;
namespace TeamUp.Modules.Skills.Endpoints;
internal static class SkillsEndpoints
{
public static void Map(IEndpointRouteBuilder endpoints)
{
var group = endpoints.MapGroup("/api/skills").WithTags("Skills");
group.MapGet("/ping", () => TypedResults.Ok(new ModulePing("skills")));
group.MapGet("/", ListSkills).RequireAuthorization();
group.MapGet("/{key}", GetSkill).RequireAuthorization();
group.MapPost("/index", IndexSkill).RequireAuthorization();
group.MapPost("/sync", Sync).RequireAuthorization();
group.MapPost("/webhook/gitea", Webhook).AllowAnonymous();
}
private static async Task<IResult> Sync(SkillSyncService sync, CancellationToken ct) =>
Results.Ok(new SyncResult(await sync.SyncAsync(ct)));
// Gitea push webhook → re-sync the source. M2 re-indexes the whole source (idempotent);
// signature verification + changed-file-only sync via the job queue land later.
private static async Task<IResult> Webhook(SkillSyncService sync, CancellationToken ct) =>
Results.Ok(new SyncResult(await sync.SyncAsync(ct)));
private static async Task<IResult> ListSkills(
string? role, string? visibility, SkillsDbContext db, CancellationToken ct)
{
var query = db.Skills.AsQueryable();
if (!string.IsNullOrWhiteSpace(role))
{
query = query.Where(s => s.Roles.Contains(role));
}
if (Enum.TryParse<SkillVisibility>(visibility, ignoreCase: true, out var vis))
{
query = query.Where(s => s.Visibility == vis);
}
var skills = await query
.OrderBy(s => s.SkillKey)
.ThenByDescending(s => s.Version)
.ToListAsync(ct);
return Results.Ok(skills.Select(ToSummary).ToList());
}
private static async Task<IResult> GetSkill(string key, SkillsDbContext db, CancellationToken ct)
{
var versions = await db.Skills
.Where(s => s.SkillKey == key)
.OrderByDescending(s => s.Version)
.ToListAsync(ct);
return versions.Count == 0
? Results.NotFound()
: Results.Ok(versions.Select(ToDetail).ToList());
}
private static async Task<IResult> IndexSkill(IndexRequest request, SkillIndexer indexer, CancellationToken ct)
{
if (string.IsNullOrWhiteSpace(request.Content))
{
return Results.BadRequest("content is required.");
}
try
{
var skill = await indexer.IndexAsync(
request.Content, request.SourceRepo, request.SourcePath, request.SourceCommit, ct);
return Results.Ok(ToDetail(skill));
}
catch (FormatException ex)
{
return Results.BadRequest(ex.Message);
}
}
private static SkillSummary ToSummary(Skill skill) => new(
skill.SkillKey,
skill.Name,
skill.Version,
skill.Summary,
skill.Roles,
skill.Visibility.ToString(),
skill.MinTier.ToString(),
skill.Status.ToString(),
skill.Actions.Select(a => new ActionDto(a.Name, a.Risk.ToString())).ToList());
private static SkillDetail ToDetail(Skill skill) => new(
ToSummary(skill),
skill.Inputs,
skill.Outputs,
skill.Tools,
skill.Context,
skill.GoldenTests.Count,
skill.Body);
}