feat: proxy para arquivos dos modulos com ratelimit
This commit is contained in:
parent
6b79a44e39
commit
f3de10cc4f
113
OnlyOneAccessTemplate/Controllers/DynamicProxyController.cs
Normal file
113
OnlyOneAccessTemplate/Controllers/DynamicProxyController.cs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using OnlyOneAccessTemplate.Services;
|
||||||
|
|
||||||
|
namespace OnlyOneAccessTemplate.Controllers
|
||||||
|
{
|
||||||
|
[Route("api/modules")]
|
||||||
|
public class DynamicProxyController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IModuleService _moduleService;
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly IRateLimitService _rateLimitService;
|
||||||
|
private readonly ILogger<DynamicProxyController> _logger;
|
||||||
|
|
||||||
|
public DynamicProxyController(
|
||||||
|
IModuleService moduleService,
|
||||||
|
HttpClient httpClient,
|
||||||
|
IRateLimitService rateLimitService,
|
||||||
|
ILogger<DynamicProxyController> logger)
|
||||||
|
{
|
||||||
|
_moduleService = moduleService;
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_rateLimitService = rateLimitService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{moduleId}/{action}")]
|
||||||
|
[HttpGet("{moduleId}/{action}")]
|
||||||
|
[HttpPut("{moduleId}/{action}")]
|
||||||
|
[HttpDelete("{moduleId}/{action}")]
|
||||||
|
public async Task<IActionResult> ProxyRequest(string moduleId, string action)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Buscar configuração do módulo
|
||||||
|
var module = await _moduleService.GetModuleConfigAsync(moduleId);
|
||||||
|
if (module == null || !module.IsActive)
|
||||||
|
{
|
||||||
|
return NotFound(new { success = false, message = "Módulo não encontrado ou inativo" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rate limiting
|
||||||
|
var clientIP = GetClientIP();
|
||||||
|
await _rateLimitService.RecordRequestAsync(clientIP);
|
||||||
|
|
||||||
|
if (await _rateLimitService.ShouldShowCaptchaAsync(clientIP))
|
||||||
|
{
|
||||||
|
return StatusCode(429, new
|
||||||
|
{
|
||||||
|
success = false,
|
||||||
|
message = "Rate limit exceeded",
|
||||||
|
requiresCaptcha = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar se o endpoint existe no mapeamento
|
||||||
|
if (!module.ProxyMappings.ContainsKey(action))
|
||||||
|
{
|
||||||
|
return NotFound(new { success = false, message = $"Ação '{action}' não disponível para este módulo" });
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetUrl = module.ProxyMappings[action];
|
||||||
|
|
||||||
|
// Preparar requisição
|
||||||
|
var requestMessage = new HttpRequestMessage(
|
||||||
|
new HttpMethod(Request.Method),
|
||||||
|
targetUrl + Request.QueryString);
|
||||||
|
|
||||||
|
// Copiar headers necessários
|
||||||
|
foreach (var header in module.Headers)
|
||||||
|
{
|
||||||
|
requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copiar corpo da requisição se necessário
|
||||||
|
if (Request.ContentLength > 0)
|
||||||
|
{
|
||||||
|
requestMessage.Content = new StreamContent(Request.Body);
|
||||||
|
if (Request.ContentType != null)
|
||||||
|
{
|
||||||
|
requestMessage.Content.Headers.TryAddWithoutValidation("Content-Type", Request.ContentType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Proxying {Method} request to {ModuleId}.{Action} -> {TargetUrl}",
|
||||||
|
Request.Method, moduleId, action, targetUrl);
|
||||||
|
|
||||||
|
// Fazer requisição
|
||||||
|
var response = await _httpClient.SendAsync(requestMessage);
|
||||||
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
// Retornar resposta
|
||||||
|
Response.StatusCode = (int)response.StatusCode;
|
||||||
|
|
||||||
|
if (response.Content.Headers.ContentType?.MediaType != null)
|
||||||
|
{
|
||||||
|
Response.ContentType = response.Content.Headers.ContentType.MediaType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Content(responseContent);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Erro no proxy para {ModuleId}.{Action}", moduleId, action);
|
||||||
|
return StatusCode(500, new { success = false, message = "Erro interno do servidor" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetClientIP()
|
||||||
|
{
|
||||||
|
return HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
OnlyOneAccessTemplate/Controllers/MenuController.cs
Normal file
51
OnlyOneAccessTemplate/Controllers/MenuController.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using OnlyOneAccessTemplate.Services;
|
||||||
|
|
||||||
|
namespace OnlyOneAccessTemplate.Controllers
|
||||||
|
{
|
||||||
|
[Route("api/menu")]
|
||||||
|
public class MenuController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IModuleService _moduleService;
|
||||||
|
|
||||||
|
public MenuController(IModuleService moduleService)
|
||||||
|
{
|
||||||
|
_moduleService = moduleService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("converters")]
|
||||||
|
public async Task<IActionResult> GetConvertersMenu(string language = "pt")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var modules = await _moduleService.GetAllActiveModulesAsync();
|
||||||
|
|
||||||
|
var menuItems = modules
|
||||||
|
.Where(m => m.ShowInMenu && m.IsActive && m.IsHealthy)
|
||||||
|
.OrderBy(m => m.MenuOrder)
|
||||||
|
.ThenBy(m => m.MenuTitle)
|
||||||
|
.GroupBy(m => m.MenuCategory)
|
||||||
|
.Select(g => new
|
||||||
|
{
|
||||||
|
category = g.Key,
|
||||||
|
items = g.Select(m => new
|
||||||
|
{
|
||||||
|
moduleId = m.ModuleId,
|
||||||
|
title = m.SeoTitles.ContainsKey(language) ? m.SeoTitles[language] : m.MenuTitle,
|
||||||
|
description = m.SeoDescriptions.ContainsKey(language) ? m.SeoDescriptions[language] : m.MenuDescription,
|
||||||
|
icon = m.MenuIcon,
|
||||||
|
url = $"/{language}/{m.RequestBy}",
|
||||||
|
order = m.MenuOrder,
|
||||||
|
isNew = m.CreatedAt > DateTime.UtcNow.AddDays(-7) // Novos nos últimos 7 dias
|
||||||
|
}).ToList()
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
return Ok(new { success = true, menu = menuItems });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return StatusCode(500, new { success = false, message = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
326
OnlyOneAccessTemplate/Controllers/ModuleManagementController.cs
Normal file
326
OnlyOneAccessTemplate/Controllers/ModuleManagementController.cs
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using OnlyOneAccessTemplate.Services;
|
||||||
|
using OnlyOneAccessTemplate.Models;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Http.Extensions;
|
||||||
|
|
||||||
|
namespace OnlyOneAccessTemplate.Controllers
|
||||||
|
{
|
||||||
|
[Route("api/module-management")]
|
||||||
|
[ApiController]
|
||||||
|
public class ModuleManagementController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IModuleService _moduleService;
|
||||||
|
private readonly ILogger<ModuleManagementController> _logger;
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
|
public ModuleManagementController(
|
||||||
|
IModuleService moduleService,
|
||||||
|
ILogger<ModuleManagementController> logger,
|
||||||
|
IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_moduleService = moduleService;
|
||||||
|
_logger = logger;
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("register")]
|
||||||
|
public async Task<IActionResult> RegisterModule([FromBody] ModuleRegistrationRequest request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Registrando novo módulo: {ModuleId}", request.ModuleId);
|
||||||
|
|
||||||
|
// Verificar se módulo já existe
|
||||||
|
var existing = await _moduleService.GetModuleConfigAsync(request.ModuleId);
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
return BadRequest(new { success = false, message = "Módulo já existe" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Testar conectividade com o módulo
|
||||||
|
var healthUrl = $"{request.BaseUrl.TrimEnd('/')}/{request.HealthEndpoint?.TrimStart('/') ?? "api/converter/health"}";
|
||||||
|
var isHealthy = await TestModuleHealth(healthUrl);
|
||||||
|
|
||||||
|
// Gerar API Key
|
||||||
|
var apiKey = GenerateApiKey();
|
||||||
|
|
||||||
|
// Criar configuração
|
||||||
|
var moduleConfig = new ModuleConfig
|
||||||
|
{
|
||||||
|
ModuleId = request.ModuleId,
|
||||||
|
Name = request.Name,
|
||||||
|
Url = $"{request.BaseUrl.TrimEnd('/')}/modules/{request.ModuleId}",
|
||||||
|
RequestBy = request.ModuleId,
|
||||||
|
IsActive = request.AutoActivate.HasValue && request.AutoActivate.Value && isHealthy,
|
||||||
|
CacheMinutes = request.CacheMinutes ?? 5,
|
||||||
|
|
||||||
|
// Proxy Configuration
|
||||||
|
UseProxy = true,
|
||||||
|
ProxyEndpoint = $"/api/modules/{request.ModuleId}",
|
||||||
|
ProxyMappings = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["convert"] = $"{request.BaseUrl}/api/converter/convert",
|
||||||
|
["config"] = $"{request.BaseUrl}/api/converter/config",
|
||||||
|
["health"] = $"{request.BaseUrl}/api/converter/health"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Security
|
||||||
|
ApiKey = apiKey,
|
||||||
|
AllowedOrigins = request.AllowedOrigins ?? new List<string> { Request.GetDisplayUrl() },
|
||||||
|
RateLimitPerMinute = request.RateLimitPerMinute ?? 60,
|
||||||
|
|
||||||
|
// Menu
|
||||||
|
MenuTitle = request.MenuTitle ?? request.Name,
|
||||||
|
MenuDescription = request.MenuDescription,
|
||||||
|
MenuIcon = request.MenuIcon ?? "fas fa-exchange-alt",
|
||||||
|
MenuCategory = request.MenuCategory ?? "Conversores",
|
||||||
|
MenuOrder = request.MenuOrder ?? 0,
|
||||||
|
ShowInMenu = request.ShowInMenu ?? true,
|
||||||
|
|
||||||
|
// SEO
|
||||||
|
SeoTitles = request.SeoTitles ?? new Dictionary<string, string>(),
|
||||||
|
SeoDescriptions = request.SeoDescriptions ?? new Dictionary<string, string>(),
|
||||||
|
SeoKeywords = request.SeoKeywords ?? new Dictionary<string, string>(),
|
||||||
|
|
||||||
|
// Technical
|
||||||
|
HealthEndpoint = request.HealthEndpoint ?? "/api/converter/health",
|
||||||
|
HealthCheckIntervalMinutes = request.HealthCheckIntervalMinutes ?? 5,
|
||||||
|
AutoStart = request.AutoActivate ?? true,
|
||||||
|
Version = request.Version ?? "1.0.0",
|
||||||
|
IsHealthy = isHealthy,
|
||||||
|
LastHealthCheck = DateTime.UtcNow,
|
||||||
|
|
||||||
|
// Developer
|
||||||
|
DeveloperName = request.DeveloperName,
|
||||||
|
DeveloperEmail = request.DeveloperEmail,
|
||||||
|
Repository = request.Repository,
|
||||||
|
Documentation = request.Documentation,
|
||||||
|
|
||||||
|
// Headers
|
||||||
|
Headers = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["X-API-Key"] = apiKey,
|
||||||
|
["User-Agent"] = "ConvertIt-MainApp/1.0"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await _moduleService.SaveModuleConfigAsync(moduleConfig);
|
||||||
|
|
||||||
|
_logger.LogInformation("Módulo {ModuleId} registrado com sucesso", request.ModuleId);
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
success = true,
|
||||||
|
message = "Módulo registrado com sucesso",
|
||||||
|
moduleId = request.ModuleId,
|
||||||
|
apiKey = apiKey,
|
||||||
|
proxyEndpoint = moduleConfig.ProxyEndpoint,
|
||||||
|
isHealthy = isHealthy,
|
||||||
|
isActive = moduleConfig.IsActive
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Erro ao registrar módulo {ModuleId}", request.ModuleId);
|
||||||
|
return StatusCode(500, new { success = false, message = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("modules")]
|
||||||
|
public async Task<IActionResult> ListModules()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var modules = await _moduleService.GetAllActiveModulesAsync();
|
||||||
|
|
||||||
|
var result = modules.Select(m => new
|
||||||
|
{
|
||||||
|
moduleId = m.ModuleId,
|
||||||
|
name = m.Name,
|
||||||
|
isActive = m.IsActive,
|
||||||
|
isHealthy = m.IsHealthy,
|
||||||
|
lastHealthCheck = m.LastHealthCheck,
|
||||||
|
version = m.Version,
|
||||||
|
menuTitle = m.MenuTitle,
|
||||||
|
menuCategory = m.MenuCategory,
|
||||||
|
showInMenu = m.ShowInMenu,
|
||||||
|
developerName = m.DeveloperName,
|
||||||
|
proxyEndpoint = m.ProxyEndpoint
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ok(new { success = true, modules = result });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Erro ao listar módulos");
|
||||||
|
return StatusCode(500, new { success = false, message = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("modules/{moduleId}/toggle")]
|
||||||
|
public async Task<IActionResult> ToggleModule(string moduleId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var module = await _moduleService.GetModuleConfigAsync(moduleId);
|
||||||
|
if (module == null)
|
||||||
|
{
|
||||||
|
return NotFound(new { success = false, message = "Módulo não encontrado" });
|
||||||
|
}
|
||||||
|
|
||||||
|
module.IsActive = !module.IsActive;
|
||||||
|
module.UpdatedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
|
await _moduleService.SaveModuleConfigAsync(module);
|
||||||
|
|
||||||
|
_logger.LogInformation("Módulo {ModuleId} {Status}", moduleId,
|
||||||
|
module.IsActive ? "ativado" : "desativado");
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
success = true,
|
||||||
|
moduleId = moduleId,
|
||||||
|
isActive = module.IsActive,
|
||||||
|
message = $"Módulo {(module.IsActive ? "ativado" : "desativado")} com sucesso"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Erro ao alternar módulo {ModuleId}", moduleId);
|
||||||
|
return StatusCode(500, new { success = false, message = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("modules/{moduleId}/health-check")]
|
||||||
|
public async Task<IActionResult> CheckModuleHealth(string moduleId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var module = await _moduleService.GetModuleConfigAsync(moduleId);
|
||||||
|
if (module == null)
|
||||||
|
{
|
||||||
|
return NotFound(new { success = false, message = "Módulo não encontrado" });
|
||||||
|
}
|
||||||
|
|
||||||
|
var healthUrl = $"{GetBaseUrl(module.Url)}{module.HealthEndpoint}";
|
||||||
|
var isHealthy = await TestModuleHealth(healthUrl);
|
||||||
|
|
||||||
|
module.IsHealthy = isHealthy;
|
||||||
|
module.LastHealthCheck = DateTime.UtcNow;
|
||||||
|
await _moduleService.SaveModuleConfigAsync(module);
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
success = true,
|
||||||
|
moduleId = moduleId,
|
||||||
|
isHealthy = isHealthy,
|
||||||
|
lastCheck = module.LastHealthCheck,
|
||||||
|
healthUrl = healthUrl
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Erro ao verificar saúde do módulo {ModuleId}", moduleId);
|
||||||
|
return StatusCode(500, new { success = false, message = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("modules/{moduleId}")]
|
||||||
|
public async Task<IActionResult> UnregisterModule(string moduleId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var module = await _moduleService.GetModuleConfigAsync(moduleId);
|
||||||
|
if (module == null)
|
||||||
|
{
|
||||||
|
return NotFound(new { success = false, message = "Módulo não encontrado" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aqui você implementaria a remoção (dependendo de como está armazenado)
|
||||||
|
// Por enquanto, vamos apenas desativar
|
||||||
|
module.IsActive = false;
|
||||||
|
module.UpdatedAt = DateTime.UtcNow;
|
||||||
|
await _moduleService.SaveModuleConfigAsync(module);
|
||||||
|
|
||||||
|
_logger.LogInformation("Módulo {ModuleId} desregistrado", moduleId);
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
success = true,
|
||||||
|
message = "Módulo desregistrado com sucesso"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Erro ao desregistrar módulo {ModuleId}", moduleId);
|
||||||
|
return StatusCode(500, new { success = false, message = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> TestModuleHealth(string healthUrl)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var client = new HttpClient();
|
||||||
|
client.Timeout = TimeSpan.FromSeconds(10);
|
||||||
|
var response = await client.GetAsync(healthUrl);
|
||||||
|
return response.IsSuccessStatusCode;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GenerateApiKey()
|
||||||
|
{
|
||||||
|
using var rng = RandomNumberGenerator.Create();
|
||||||
|
var bytes = new byte[32];
|
||||||
|
rng.GetBytes(bytes);
|
||||||
|
return Convert.ToBase64String(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetBaseUrl(string fullUrl)
|
||||||
|
{
|
||||||
|
var uri = new Uri(fullUrl);
|
||||||
|
return $"{uri.Scheme}://{uri.Host}:{uri.Port}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ModuleRegistrationRequest
|
||||||
|
{
|
||||||
|
public string ModuleId { get; set; } = "";
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
public string BaseUrl { get; set; } = "";
|
||||||
|
public bool? AutoActivate { get; set; } = true;
|
||||||
|
public int? CacheMinutes { get; set; } = 5;
|
||||||
|
public List<string>? AllowedOrigins { get; set; }
|
||||||
|
public int? RateLimitPerMinute { get; set; } = 60;
|
||||||
|
|
||||||
|
// Menu
|
||||||
|
public string? MenuTitle { get; set; }
|
||||||
|
public string? MenuDescription { get; set; }
|
||||||
|
public string? MenuIcon { get; set; }
|
||||||
|
public string? MenuCategory { get; set; }
|
||||||
|
public int? MenuOrder { get; set; }
|
||||||
|
public bool? ShowInMenu { get; set; } = true;
|
||||||
|
|
||||||
|
// SEO
|
||||||
|
public Dictionary<string, string>? SeoTitles { get; set; }
|
||||||
|
public Dictionary<string, string>? SeoDescriptions { get; set; }
|
||||||
|
public Dictionary<string, string>? SeoKeywords { get; set; }
|
||||||
|
|
||||||
|
// Technical
|
||||||
|
public string? HealthEndpoint { get; set; }
|
||||||
|
public int? HealthCheckIntervalMinutes { get; set; }
|
||||||
|
public string? Version { get; set; }
|
||||||
|
|
||||||
|
// Developer
|
||||||
|
public string? DeveloperName { get; set; }
|
||||||
|
public string? DeveloperEmail { get; set; }
|
||||||
|
public string? Repository { get; set; }
|
||||||
|
public string? Documentation { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -25,5 +25,44 @@ namespace OnlyOneAccessTemplate.Models
|
|||||||
public string? JavaScriptFunction { get; set; } // Função de inicialização
|
public string? JavaScriptFunction { get; set; } // Função de inicialização
|
||||||
public string? CssUrl { get; set; } // CSS opcional
|
public string? CssUrl { get; set; } // CSS opcional
|
||||||
public Dictionary<string, string> Assets { get; set; } = new(); // Assets adicionais
|
public Dictionary<string, string> Assets { get; set; } = new(); // Assets adicionais
|
||||||
|
|
||||||
|
// ADICIONAR na classe ModuleConfig existente:
|
||||||
|
|
||||||
|
// Configurações de Proxy Dinâmico
|
||||||
|
public string? ProxyEndpoint { get; set; } // "/api/modules/{moduleId}"
|
||||||
|
public Dictionary<string, string> ProxyMappings { get; set; } = new(); // endpoint -> moduleUrl
|
||||||
|
public bool UseProxy { get; set; } = true;
|
||||||
|
|
||||||
|
// Configurações de Segurança
|
||||||
|
public string? ApiKey { get; set; } // Gerado automaticamente
|
||||||
|
public List<string> AllowedOrigins { get; set; } = new();
|
||||||
|
public int RateLimitPerMinute { get; set; } = 60;
|
||||||
|
|
||||||
|
// Configurações de Menu
|
||||||
|
public string? MenuTitle { get; set; }
|
||||||
|
public string? MenuDescription { get; set; }
|
||||||
|
public string? MenuIcon { get; set; }
|
||||||
|
public string? MenuCategory { get; set; } = "Conversores";
|
||||||
|
public int MenuOrder { get; set; } = 0;
|
||||||
|
public bool ShowInMenu { get; set; } = true;
|
||||||
|
|
||||||
|
// Configurações de SEO
|
||||||
|
public Dictionary<string, string> SeoTitles { get; set; } = new();
|
||||||
|
public Dictionary<string, string> SeoDescriptions { get; set; } = new();
|
||||||
|
public Dictionary<string, string> SeoKeywords { get; set; } = new();
|
||||||
|
|
||||||
|
// Configurações Técnicas
|
||||||
|
public string? HealthEndpoint { get; set; } = "/api/converter/health";
|
||||||
|
public int HealthCheckIntervalMinutes { get; set; } = 5;
|
||||||
|
public bool AutoStart { get; set; } = true;
|
||||||
|
public string? Version { get; set; }
|
||||||
|
public DateTime? LastHealthCheck { get; set; }
|
||||||
|
public bool IsHealthy { get; set; } = false;
|
||||||
|
|
||||||
|
// Metadados do Desenvolvedor
|
||||||
|
public string? DeveloperName { get; set; }
|
||||||
|
public string? DeveloperEmail { get; set; }
|
||||||
|
public string? Repository { get; set; }
|
||||||
|
public string? Documentation { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
47
OnlyOneAccessTemplate/register-sentence-converter.http
Normal file
47
OnlyOneAccessTemplate/register-sentence-converter.http
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
### Registrar o módulo sentence-converter
|
||||||
|
POST https://localhost:7001/api/module-management/register
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"moduleId": "sentence-converter",
|
||||||
|
"name": "Conversor de Primeira Maiúscula",
|
||||||
|
"baseUrl": "https://localhost:7002",
|
||||||
|
"autoActivate": true,
|
||||||
|
"cacheMinutes": 5,
|
||||||
|
"rateLimitPerMinute": 60,
|
||||||
|
|
||||||
|
"menuTitle": "Primeira Maiúscula",
|
||||||
|
"menuDescription": "Converte texto para formato de primeira letra maiúscula",
|
||||||
|
"menuIcon": "fas fa-text-height",
|
||||||
|
"menuCategory": "Texto",
|
||||||
|
"menuOrder": 1,
|
||||||
|
"showInMenu": true,
|
||||||
|
|
||||||
|
"seoTitles": {
|
||||||
|
"pt": "Conversor para Primeira Maiúscula Online - Gratuito",
|
||||||
|
"en": "Sentence Case Converter Online - Free",
|
||||||
|
"es": "Convertidor a Mayúscula Inicial en Línea - Gratis"
|
||||||
|
},
|
||||||
|
"seoDescriptions": {
|
||||||
|
"pt": "Converta seu texto para o formato de primeira letra maiúscula rapidamente. Ferramenta gratuita e fácil de usar.",
|
||||||
|
"en": "Convert your text to sentence case format quickly. Free and easy to use tool.",
|
||||||
|
"es": "Convierte tu texto al formato de primera letra mayúscula rápidamente. Herramienta gratuita y fácil de usar."
|
||||||
|
},
|
||||||
|
|
||||||
|
"version": "1.0.0",
|
||||||
|
"developerName": "Seu Nome",
|
||||||
|
"developerEmail": "seu@email.com",
|
||||||
|
"repository": "https://github.com/user/sentence-converter"
|
||||||
|
}
|
||||||
|
|
||||||
|
### Listar todos os módulos
|
||||||
|
GET https://localhost:7001/api/module-management/modules
|
||||||
|
|
||||||
|
### Verificar saúde de um módulo
|
||||||
|
POST https://localhost:7001/api/module-management/modules/sentence-converter/health-check
|
||||||
|
|
||||||
|
### Ativar/desativar módulo
|
||||||
|
POST https://localhost:7001/api/module-management/modules/sentence-converter/toggle
|
||||||
|
|
||||||
|
### Buscar menu de conversores
|
||||||
|
GET https://localhost:7001/api/menu/converters?language=pt
|
||||||
Loading…
Reference in New Issue
Block a user