Forge
csharp3407750f
1using AgentForge;
2using AgentForge.Abstractions;
3using AgentForge.Plugin.Sdk;
4using AgentForge.Contracts;
5using AgentForge.Data;
6using AgentForge.Data.Entities;
7using AgentForge.Models;
8using AgentForge.Services;
9
10namespace AgentForge.Plugin.Org.Web.Endpoints;
11
12internal static class ForgeOrgRepoGroupEndpoints
13{
14 internal static RouteGroupBuilder MapOrgRepoGroupEndpoints(this RouteGroupBuilder api)
15 {
16 api.MapGet("/orgs/{orgSlug}/repo-groups", ListGroups);
17 api.MapPost("/orgs/{orgSlug}/repo-groups", CreateGroup);
18 api.MapGet($"/orgs/{{orgSlug}}/repo-groups/{ForgeRepoGroupRouting.Pattern}", GetGroup);
19 api.MapGet($"/orgs/{{orgSlug}}/repo-groups/{ForgeRepoGroupRouting.Pattern}/repos", ListGroupRepos);
20 return api;
21 }
22
23 private static IResult ListGroups(string orgSlug, ForgeRepository repository)
24 {
25 var org = repository.GetOrgBySlug(orgSlug)
26 ?? throw new ForgeApiException(404, $"Organization '{orgSlug}' not found.");
27 var groups = repository.ListRepoGroups(org.Id);
28 var paths = repository.BuildRepoGroupPathLookup(org.Id);
29 var items = groups
30 .Select(g => ToResponse(g, org.Slug, repository.CountReposInGroup(g.Id), paths))
31 .ToList();
32 return Results.Ok(new RepoGroupListResponse(items.Count, items));
33 }
34
35 private static IResult CreateGroup(
36 string orgSlug,
37 CreateRepoGroupRequest request,
38 HttpContext context,
39 ForgeRepository repository,
40 ForgeRepoAccessService access)
41 {
42 var org = repository.GetOrgBySlug(orgSlug)
43 ?? throw new ForgeApiException(404, $"Organization '{orgSlug}' not found.");
44 access.EnsureOrgCatalogAdmin(org.Id, access.Resolve(context));
45 if (!ForgeSlug.IsValid(request.Slug))
46 throw new ForgeApiException(400, "Invalid group slug. Use lowercase slug: a-z, 0-9, hyphens.");
47 var slug = ForgeSlug.Normalize(request.Slug);
48
49 string? parentGroupId = null;
50 if (!string.IsNullOrWhiteSpace(request.Parent))
51 {
52 var parentSegments = ForgeRepoGroupPath.ParseSegments(request.Parent);
53 if (parentSegments.Count == 0 || parentSegments.Any(s => !ForgeSlug.IsValid(s)))
54 throw new ForgeApiException(400, "Invalid parent group path.");
55 var parent = repository.GetRepoGroupByOrgAndPath(org.Id, request.Parent)
56 ?? throw new ForgeApiException(404, $"Parent repository group '{request.Parent}' not found.");
57 parentGroupId = parent.Id;
58 }
59
60 if (repository.RepoGroupSlugExistsInParent(org.Id, slug, parentGroupId))
61 throw new ForgeApiException(409, $"Repository group '{slug}' already exists under this parent.");
62
63 RepoVisibility? defaultVisibility = null;
64 if (!string.IsNullOrWhiteSpace(request.DefaultVisibility))
65 {
66 if (!RepoVisibilityExtensions.TryParseSlug(request.DefaultVisibility, out var parsed))
67 throw new ForgeApiException(400, "defaultVisibility must be public or private.");
68 defaultVisibility = parsed;
69 }
70
71 var entity = repository.InsertRepoGroup(
72 org.Id,
73 slug,
74 request.DisplayName?.Trim() ?? slug,
75 request.Description?.Trim() ?? string.Empty,
76 defaultVisibility,
77 parentGroupId);
78 var paths = repository.BuildRepoGroupPathLookup(org.Id);
79 var path = paths[entity.Id];
80 return Results.Created(
81 $"/api/v1/orgs/{orgSlug}/repo-groups/{ForgeRepoGroupPath.ToUrlPath(path)}",
82 ToResponse(entity, org.Slug, 0, paths));
83 }
84
85 private static IResult GetGroup(string orgSlug, string groupPath, ForgeRepository repository)
86 {
87 var org = repository.GetOrgBySlug(orgSlug)
88 ?? throw new ForgeApiException(404, $"Organization '{orgSlug}' not found.");
89 var normalizedPath = ForgeRepoGroupPath.NormalizeRoute(groupPath);
90 var group = repository.GetRepoGroupByOrgAndPath(org.Id, normalizedPath)
91 ?? throw new ForgeApiException(404, $"Repository group '{normalizedPath}' not found.");
92 var paths = repository.BuildRepoGroupPathLookup(org.Id);
93 return Results.Ok(ToResponse(group, org.Slug, repository.CountReposInGroup(group.Id), paths));
94 }
95
96 private static IResult ListGroupRepos(
97 string orgSlug,
98 string groupPath,
99 ForgeRepository repository,
100 GitRepoService git,
101 ForgeRepoAccessService access,
102 HttpContext http)
103 {
104 var org = repository.GetOrgBySlug(orgSlug)
105 ?? throw new ForgeApiException(404, $"Organization '{orgSlug}' not found.");
106 var normalizedPath = ForgeRepoGroupPath.NormalizeRoute(groupPath);
107 var group = repository.GetRepoGroupByOrgAndPath(org.Id, normalizedPath)
108 ?? throw new ForgeApiException(404, $"Repository group '{normalizedPath}' not found.");
109 var principal = access.Resolve(http);
110 var repos = access.FilterReadable(repository.ListReposInGroup(group.Id), principal)
111 .Select(repo => ForgePluginResponses.ToRepoResponse(repo, git, repository))
112 .ToList();
113 return Results.Ok(new RepoListResponse(repos.Count, repos));
114 }
115
116 private static RepoGroupResponse ToResponse(
117 ForgeRepoGroupEntity group,
118 string orgSlug,
119 int repoCount,
120 IReadOnlyDictionary<string, string> paths)
121 {
122 paths.TryGetValue(group.Id, out var path);
123 path ??= group.Slug;
124 string? parentPath = null;
125 if (group.ParentGroupId is not null && paths.TryGetValue(group.ParentGroupId, out var resolvedParent))
126 parentPath = resolvedParent;
127
128 return new RepoGroupResponse(
129 group.Slug,
130 group.DisplayName,
131 group.Description,
132 repoCount,
133 group.CreatedAt,
134 orgSlug,
135 group.DefaultVisibility?.ToSlug(),
136 path,
137 parentPath);
138 }
139}
140
View only · write via MCP/CIDE