using System.Text; using Nuuru.Tools.AuthFlowChart.Models; namespace Nuuru.Tools.AuthFlowChart.Output; /// /// Generates Mermaid flowchart diagrams from controller analysis results /// public class MermaidGenerator { public string Generate(List controllers) { var sb = new StringBuilder(); sb.AppendLine("flowchart TB"); sb.AppendLine(); // Style definitions sb.AppendLine(" %% Style definitions for auth levels"); sb.AppendLine(" classDef anonymous fill:#90EE90,stroke:#228B22,color:#000"); sb.AppendLine(" classDef authenticated fill:#87CEEB,stroke:#4169E1,color:#000"); sb.AppendLine(" classDef userPerm fill:#FFB347,stroke:#FF8C00,color:#000"); sb.AppendLine(" classDef modPerm fill:#DDA0DD,stroke:#8B008B,color:#000"); sb.AppendLine(" classDef adminPerm fill:#FF6B6B,stroke:#DC143C,color:#000"); sb.AppendLine(); foreach (var controller in controllers) { GenerateControllerSubgraph(sb, controller); } return sb.ToString(); } public void GenerateMarkdownFile(List controllers, string outputPath) { var sb = new StringBuilder(); sb.AppendLine("# Nuuru API Authentication Flowchart"); sb.AppendLine(); sb.AppendLine($"Generated on: {DateTime.Now:yyyy-MM-dd HH:mm}"); sb.AppendLine(); sb.AppendLine("## Legend"); sb.AppendLine(); sb.AppendLine("| Color | Auth Level | Description |"); sb.AppendLine("|-------|------------|-------------|"); sb.AppendLine("| Green | Anonymous | No authentication required |"); sb.AppendLine("| Blue | Authenticated | Any logged-in user |"); sb.AppendLine("| Orange | User | User-level permission (`user.*`) |"); sb.AppendLine("| Purple | Moderation | Moderation permission (`moderation.*`) |"); sb.AppendLine("| Red | Admin | Admin permission (`admin.*`) |"); sb.AppendLine(); sb.AppendLine("**Note:** Permissions marked with `(runtime)` are checked inline in the code, not via attributes."); sb.AppendLine(); sb.AppendLine("## Diagram"); sb.AppendLine(); sb.AppendLine("```mermaid"); sb.Append(Generate(controllers)); sb.AppendLine("```"); sb.AppendLine(); // Add summary table GenerateSummaryTable(sb, controllers); // Add detailed endpoint list GenerateDetailedList(sb, controllers); File.WriteAllText(outputPath, sb.ToString()); } private void GenerateControllerSubgraph(StringBuilder sb, ControllerInfo controller) { var nodePrefix = SanitizeId(controller.DisplayName); sb.AppendLine($" subgraph {nodePrefix}[\"{controller.DisplayName}
{controller.RoutePrefix}\"]"); int nodeIndex = 0; foreach (var endpoint in controller.Endpoints) { var nodeId = $"{nodePrefix}_{nodeIndex++}"; var nodeLabel = BuildNodeLabel(endpoint, controller); var styleClass = GetStyleClass(endpoint); sb.AppendLine($" {nodeId}[\"{nodeLabel}\"]:::{styleClass}"); } sb.AppendLine(" end"); sb.AppendLine(); } private string BuildNodeLabel(EndpointInfo endpoint, ControllerInfo controller) { var parts = new List(); // HTTP Method and Route var route = endpoint.RouteTemplate; if (string.IsNullOrEmpty(route)) route = "/"; else if (!route.StartsWith("/")) route = "/" + route; parts.Add($"{endpoint.HttpMethod} {route}"); // Attribute permission if (!string.IsNullOrEmpty(endpoint.AttributePermission)) { parts.Add(endpoint.AttributePermission); } // Inline permissions foreach (var check in endpoint.InlineChecks) { parts.Add($"{check.Permission} (runtime)"); } return string.Join("
", parts); } private string GetStyleClass(EndpointInfo endpoint) { // Determine the highest level of auth required var allPermissions = endpoint.AllPermissions.ToList(); if (allPermissions.Any(p => p.Permission.StartsWith("admin."))) return "adminPerm"; if (allPermissions.Any(p => p.Permission.StartsWith("moderation."))) return "modPerm"; if (allPermissions.Any(p => p.Permission.StartsWith("user."))) return "userPerm"; if (endpoint.AuthLevel == AuthLevel.Authenticated) return "authenticated"; return "anonymous"; } private static string SanitizeId(string name) { // Remove non-alphanumeric characters for valid Mermaid IDs return new string(name.Where(c => char.IsLetterOrDigit(c)).ToArray()); } private void GenerateSummaryTable(StringBuilder sb, List controllers) { sb.AppendLine("## Summary"); sb.AppendLine(); sb.AppendLine("| Controller | Total | Anonymous | Authenticated | User Perm | Mod Perm | Admin Perm |"); sb.AppendLine("|------------|-------|-----------|---------------|-----------|----------|------------|"); foreach (var controller in controllers) { var total = controller.Endpoints.Count; var anonymous = CountByAuth(controller, AuthLevel.Anonymous); var authenticated = CountByAuth(controller, AuthLevel.Authenticated); var userPerm = CountByPermissionPrefix(controller, "user."); var modPerm = CountByPermissionPrefix(controller, "moderation."); var adminPerm = CountByPermissionPrefix(controller, "admin."); sb.AppendLine($"| {controller.DisplayName} | {total} | {anonymous} | {authenticated} | {userPerm} | {modPerm} | {adminPerm} |"); } var totals = new { Total = controllers.Sum(c => c.Endpoints.Count), Anonymous = controllers.Sum(c => CountByAuth(c, AuthLevel.Anonymous)), Authenticated = controllers.Sum(c => CountByAuth(c, AuthLevel.Authenticated)), UserPerm = controllers.Sum(c => CountByPermissionPrefix(c, "user.")), ModPerm = controllers.Sum(c => CountByPermissionPrefix(c, "moderation.")), AdminPerm = controllers.Sum(c => CountByPermissionPrefix(c, "admin.")) }; sb.AppendLine($"| **Total** | **{totals.Total}** | **{totals.Anonymous}** | **{totals.Authenticated}** | **{totals.UserPerm}** | **{totals.ModPerm}** | **{totals.AdminPerm}** |"); sb.AppendLine(); } private static int CountByAuth(ControllerInfo controller, AuthLevel level) { return controller.Endpoints.Count(e => e.AuthLevel == level && string.IsNullOrEmpty(e.AttributePermission) && !e.InlineChecks.Any()); } private static int CountByPermissionPrefix(ControllerInfo controller, string prefix) { return controller.Endpoints.Count(e => e.AllPermissions.Any(p => p.Permission.StartsWith(prefix))); } private void GenerateDetailedList(StringBuilder sb, List controllers) { sb.AppendLine("## Detailed Endpoint List"); sb.AppendLine(); foreach (var controller in controllers) { sb.AppendLine($"### {controller.DisplayName}"); sb.AppendLine(); sb.AppendLine($"Base route: `{controller.RoutePrefix}`"); sb.AppendLine(); if (controller.DefaultAuthLevel != AuthLevel.Anonymous || !string.IsNullOrEmpty(controller.DefaultPermission)) { sb.AppendLine($"Controller-level auth: **{FormatAuthLevel(controller.DefaultAuthLevel, controller.DefaultPermission)}**"); sb.AppendLine(); } sb.AppendLine("| Method | Route | Auth | Permissions |"); sb.AppendLine("|--------|-------|------|-------------|"); foreach (var endpoint in controller.Endpoints) { var route = string.IsNullOrEmpty(endpoint.RouteTemplate) ? "/" : endpoint.RouteTemplate; var auth = FormatAuthLevel(endpoint.AuthLevel, endpoint.AttributePermission); var permissions = FormatPermissions(endpoint); sb.AppendLine($"| {endpoint.HttpMethod} | `{route}` | {auth} | {permissions} |"); } sb.AppendLine(); } } private static string FormatAuthLevel(AuthLevel level, string? permission) { return level switch { AuthLevel.Anonymous => "Anonymous", AuthLevel.Authenticated => "Authenticated", AuthLevel.Permission when !string.IsNullOrEmpty(permission) => $"`{permission}`", _ => "Unknown" }; } private static string FormatPermissions(EndpointInfo endpoint) { var parts = new List(); if (!string.IsNullOrEmpty(endpoint.AttributePermission)) { parts.Add($"`{endpoint.AttributePermission}`"); } foreach (var check in endpoint.InlineChecks) { parts.Add($"`{check.Permission}` (runtime)"); } return parts.Count > 0 ? string.Join(", ", parts) : "-"; } }