| 1 | using AgentForge; |
| 2 | using AgentForge.Abstractions; |
| 3 | using AgentForge.Contracts; |
| 4 | using AgentForge.Data; |
| 5 | using AgentForge.Data.Entities; |
| 6 | using AgentForge.Models; |
| 7 | using AgentForge.Plugin.Sdk; |
| 8 | using AgentForge.Services; |
| 9 | using Microsoft.AspNetCore.Builder; |
| 10 | using Microsoft.AspNetCore.Http; |
| 11 | using Microsoft.AspNetCore.Routing; |
| 12 | |
| 13 | namespace AgentForge.Plugin.Organization.UserManagement.Endpoints; |
| 14 | |
| 15 | internal static class ForgeOrgMemberEndpoints |
| 16 | { |
| 17 | internal static RouteGroupBuilder MapOrgMemberEndpoints(this RouteGroupBuilder api) |
| 18 | { |
| 19 | api.MapGet("/orgs/{slug}/members", ListMembers); |
| 20 | api.MapPost("/orgs/{slug}/members", AddMember); |
| 21 | api.MapPatch("/orgs/{slug}/members/{login}", UpdateMember); |
| 22 | api.MapDelete("/orgs/{slug}/members/{login}", RemoveMember); |
| 23 | return api; |
| 24 | } |
| 25 | |
| 26 | private static IResult ListMembers( |
| 27 | string slug, |
| 28 | HttpContext context, |
| 29 | ForgeRepository repository, |
| 30 | ForgeRepoAccessService access) |
| 31 | { |
| 32 | var org = repository.GetOrgBySlug(slug) |
| 33 | ?? throw new ForgeApiException(404, $"Organization '{slug}' not found."); |
| 34 | access.EnsureOrgCatalogAdmin(org.Id, access.Resolve(context)); |
| 35 | |
| 36 | var members = repository.ListOrgMembers(org.Id) |
| 37 | .Select(pair => ToResponse(pair.Identity, pair.Member)) |
| 38 | .ToList(); |
| 39 | return Results.Ok(new OrgMemberListResponse(members.Count, members)); |
| 40 | } |
| 41 | |
| 42 | private static IResult AddMember( |
| 43 | string slug, |
| 44 | AddOrgMemberRequest request, |
| 45 | HttpContext context, |
| 46 | ForgeRepository repository, |
| 47 | ForgeRepoAccessService access) |
| 48 | { |
| 49 | var org = repository.GetOrgBySlug(slug) |
| 50 | ?? throw new ForgeApiException(404, $"Organization '{slug}' not found."); |
| 51 | access.EnsureOrgCatalogAdmin(org.Id, access.Resolve(context)); |
| 52 | if (string.IsNullOrWhiteSpace(request.Provider) || string.IsNullOrWhiteSpace(request.Login)) |
| 53 | throw new ForgeApiException(400, "provider and login required."); |
| 54 | |
| 55 | var identity = repository.FindIdentityByOAuthLogin(request.Provider.Trim(), request.Login.Trim()) |
| 56 | ?? throw new ForgeApiException(404, "Identity not found. User must sign in via OAuth first."); |
| 57 | |
| 58 | var role = ForgeOrgRoleExtensions.TryParseSlug(request.Role, out var parsed) |
| 59 | ? parsed |
| 60 | : ForgeOrgRole.Member; |
| 61 | |
| 62 | var hadMembership = repository.IsOrgMember(org.Id, identity.Id); |
| 63 | var member = repository.UpsertOrgMember(org.Id, identity.Id, role); |
| 64 | var response = ToResponse(identity, member); |
| 65 | |
| 66 | return hadMembership |
| 67 | ? Results.Ok(response) |
| 68 | : Results.Created($"/api/v1/orgs/{slug}/members/{identity.Login}", response); |
| 69 | } |
| 70 | |
| 71 | private static IResult UpdateMember( |
| 72 | string slug, |
| 73 | string login, |
| 74 | UpdateOrgMemberRequest request, |
| 75 | HttpContext context, |
| 76 | ForgeRepository repository, |
| 77 | ForgeRepoAccessService access) |
| 78 | { |
| 79 | var org = repository.GetOrgBySlug(slug) |
| 80 | ?? throw new ForgeApiException(404, $"Organization '{slug}' not found."); |
| 81 | access.EnsureOrgCatalogAdmin(org.Id, access.Resolve(context)); |
| 82 | |
| 83 | var identity = repository.FindIdentityByOAuthLogin("github", login.Trim()) |
| 84 | ?? throw new ForgeApiException(404, "Identity not found."); |
| 85 | |
| 86 | if (!repository.IsOrgMember(org.Id, identity.Id)) |
| 87 | throw new ForgeApiException(404, "Organization member not found."); |
| 88 | |
| 89 | if (!ForgeOrgRoleExtensions.TryParseSlug(request.Role, out var role)) |
| 90 | throw new ForgeApiException(400, "role required (member | admin | owner)."); |
| 91 | |
| 92 | var member = repository.UpsertOrgMember(org.Id, identity.Id, role); |
| 93 | return Results.Ok(ToResponse(identity, member)); |
| 94 | } |
| 95 | |
| 96 | private static IResult RemoveMember( |
| 97 | string slug, |
| 98 | string login, |
| 99 | HttpContext context, |
| 100 | ForgeRepository repository, |
| 101 | ForgeRepoAccessService access) |
| 102 | { |
| 103 | var org = repository.GetOrgBySlug(slug) |
| 104 | ?? throw new ForgeApiException(404, $"Organization '{slug}' not found."); |
| 105 | access.EnsureOrgCatalogAdmin(org.Id, access.Resolve(context)); |
| 106 | |
| 107 | var identity = repository.FindIdentityByOAuthLogin("github", login.Trim()) |
| 108 | ?? throw new ForgeApiException(404, "Identity not found."); |
| 109 | |
| 110 | if (!repository.RemoveOrgMember(org.Id, identity.Id)) |
| 111 | throw new ForgeApiException(404, "Organization member not found."); |
| 112 | |
| 113 | return Results.NoContent(); |
| 114 | } |
| 115 | |
| 116 | private static OrgMemberResponse ToResponse(ForgeIdentityEntity identity, ForgeOrgMemberEntity member) => |
| 117 | new( |
| 118 | identity.Login, |
| 119 | identity.DisplayName, |
| 120 | member.Role.ToSlug(), |
| 121 | member.CreatedAt); |
| 122 | } |
| 123 | |