using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using YarpGateway.Data; using YarpGateway.Config; using YarpGateway.Models; using YarpGateway.Services; using Yarp.ReverseProxy.Configuration; namespace YarpGateway.Controllers; [ApiController] [Route("api/gateway")] public class GatewayConfigController : ControllerBase { private readonly IDbContextFactory _dbContextFactory; private readonly DatabaseRouteConfigProvider _routeProvider; private readonly DatabaseClusterConfigProvider _clusterProvider; private readonly IRouteCache _routeCache; public GatewayConfigController( IDbContextFactory dbContextFactory, DatabaseRouteConfigProvider routeProvider, DatabaseClusterConfigProvider clusterProvider, IRouteCache routeCache) { _dbContextFactory = dbContextFactory; _routeProvider = routeProvider; _clusterProvider = clusterProvider; _routeCache = routeCache; } [HttpGet("tenants")] public async Task GetTenants() { await using var db = _dbContextFactory.CreateDbContext(); var tenants = await db.Tenants .Where(t => !t.IsDeleted) .ToListAsync(); return Ok(tenants); } [HttpPost("tenants")] public async Task CreateTenant([FromBody] CreateTenantDto dto) { await using var db = _dbContextFactory.CreateDbContext(); var existing = await db.Tenants .FirstOrDefaultAsync(t => t.TenantCode == dto.TenantCode); if (existing != null) { return BadRequest($"Tenant code {dto.TenantCode} already exists"); } var tenant = new GwTenant { Id = GenerateId(), TenantCode = dto.TenantCode, TenantName = dto.TenantName, Status = 1 }; await db.Tenants.AddAsync(tenant); await db.SaveChangesAsync(); return Ok(tenant); } [HttpDelete("tenants/{id}")] public async Task DeleteTenant(long id) { await using var db = _dbContextFactory.CreateDbContext(); var tenant = await db.Tenants.FindAsync(id); if (tenant == null) return NotFound(); tenant.IsDeleted = true; await db.SaveChangesAsync(); return Ok(); } [HttpGet("tenants/{tenantCode}/routes")] public async Task GetTenantRoutes(string tenantCode) { await using var db = _dbContextFactory.CreateDbContext(); var routes = await db.TenantRoutes .Where(r => r.TenantCode == tenantCode && !r.IsDeleted) .ToListAsync(); return Ok(routes); } [HttpPost("tenants/{tenantCode}/routes")] public async Task CreateTenantRoute(string tenantCode, [FromBody] CreateTenantRouteDto dto) { await using var db = _dbContextFactory.CreateDbContext(); var tenant = await db.Tenants .FirstOrDefaultAsync(t => t.TenantCode == tenantCode); if (tenant == null) return BadRequest($"Tenant {tenantCode} not found"); var clusterId = $"{tenantCode}-{dto.ServiceName}"; var existing = await db.TenantRoutes .FirstOrDefaultAsync(r => r.ClusterId == clusterId); if (existing != null) return BadRequest($"Route for {tenantCode}/{dto.ServiceName} already exists"); var route = new GwTenantRoute { Id = GenerateId(), TenantCode = tenantCode, ServiceName = dto.ServiceName, ClusterId = clusterId, PathPattern = dto.PathPattern, Priority = 10, Status = 1, IsGlobal = false }; await db.TenantRoutes.AddAsync(route); await db.SaveChangesAsync(); await _routeCache.ReloadAsync(); return Ok(route); } [HttpGet("routes/global")] public async Task GetGlobalRoutes() { await using var db = _dbContextFactory.CreateDbContext(); var routes = await db.TenantRoutes .Where(r => r.IsGlobal && !r.IsDeleted) .ToListAsync(); return Ok(routes); } [HttpPost("routes/global")] public async Task CreateGlobalRoute([FromBody] CreateGlobalRouteDto dto) { await using var db = _dbContextFactory.CreateDbContext(); var existing = await db.TenantRoutes .FirstOrDefaultAsync(r => r.ServiceName == dto.ServiceName && r.IsGlobal); if (existing != null) { return BadRequest($"Global route for {dto.ServiceName} already exists"); } var route = new GwTenantRoute { Id = GenerateId(), TenantCode = string.Empty, ServiceName = dto.ServiceName, ClusterId = dto.ClusterId, PathPattern = dto.PathPattern, Priority = 0, Status = 1, IsGlobal = true }; await db.TenantRoutes.AddAsync(route); await db.SaveChangesAsync(); await _routeCache.ReloadAsync(); return Ok(route); } [HttpDelete("routes/{id}")] public async Task DeleteRoute(long id) { await using var db = _dbContextFactory.CreateDbContext(); var route = await db.TenantRoutes.FindAsync(id); if (route == null) return NotFound(); route.IsDeleted = true; await db.SaveChangesAsync(); await _routeCache.ReloadAsync(); return Ok(); } [HttpGet("clusters/{clusterId}/instances")] public async Task GetInstances(string clusterId) { await using var db = _dbContextFactory.CreateDbContext(); var instances = await db.ServiceInstances .Where(i => i.ClusterId == clusterId && !i.IsDeleted) .ToListAsync(); return Ok(instances); } [HttpPost("clusters/{clusterId}/instances")] public async Task AddInstance(string clusterId, [FromBody] CreateInstanceDto dto) { await using var db = _dbContextFactory.CreateDbContext(); var existing = await db.ServiceInstances .FirstOrDefaultAsync(i => i.ClusterId == clusterId && i.DestinationId == dto.DestinationId); if (existing != null) return BadRequest($"Instance {dto.DestinationId} already exists in cluster {clusterId}"); var instance = new GwServiceInstance { Id = GenerateId(), ClusterId = clusterId, DestinationId = dto.DestinationId, Address = dto.Address, Weight = dto.Weight, Health = 1, Status = 1 }; await db.ServiceInstances.AddAsync(instance); await db.SaveChangesAsync(); await _clusterProvider.ReloadAsync(); return Ok(instance); } [HttpDelete("instances/{id}")] public async Task DeleteInstance(long id) { await using var db = _dbContextFactory.CreateDbContext(); var instance = await db.ServiceInstances.FindAsync(id); if (instance == null) return NotFound(); instance.IsDeleted = true; await db.SaveChangesAsync(); await _clusterProvider.ReloadAsync(); return Ok(); } [HttpPost("reload")] public async Task ReloadConfig() { await _routeCache.ReloadAsync(); await _routeProvider.ReloadAsync(); await _clusterProvider.ReloadAsync(); return Ok(new { message = "Config reloaded successfully" }); } private long GenerateId() { return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); } public class CreateTenantDto { public string TenantCode { get; set; } = string.Empty; public string TenantName { get; set; } = string.Empty; } public class CreateTenantRouteDto { public string ServiceName { get; set; } = string.Empty; public string PathPattern { get; set; } = string.Empty; } public class CreateGlobalRouteDto { public string ServiceName { get; set; } = string.Empty; public string ClusterId { get; set; } = string.Empty; public string PathPattern { get; set; } = string.Empty; } public class CreateInstanceDto { public string DestinationId { get; set; } = string.Empty; public string Address { get; set; } = string.Empty; public int Weight { get; set; } = 1; } }