using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using D3ROVoice.Api.Data; using D3ROVoice.Api.Dtos; using D3ROVoice.Api.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace D3ROVoice.Api.Controllers; [ApiController] [Route("api/[controller]")] public class AdminController : ControllerBase { private readonly AppDbContext _db; private readonly ISttProxyService _sttService; private static readonly DateTime _serverStartTime = DateTime.UtcNow; public AdminController(AppDbContext db, ISttProxyService sttService) { _db = db; _sttService = sttService; } [HttpGet("stats")] public async Task GetStats() { var totalUsers = await _db.Users.CountAsync(); var today = DateTime.UtcNow.Date; var activeUsersToday = await _db.Users.CountAsync(u => u.LastLoginAt >= today); var totalRequests = await _db.UsageLogs.CountAsync() + await _db.SttUsageLogs.CountAsync(); var totalLlmCost = await _db.UsageLogs.SumAsync(u => (decimal?)u.CalculatedCost) ?? 0m; var totalSttCost = await _db.SttUsageLogs.SumAsync(u => (decimal?)u.CalculatedCost) ?? 0m; var totalCost = totalLlmCost + totalSttCost; var errorCount = await _db.ErrorLogs.CountAsync(); var recentErrors = await _db.ErrorLogs .OrderByDescending(e => e.CreatedAt) .Take(10) .Select(e => new ServerErrorLogDto(e.Id, e.ErrorType, e.Message, e.Endpoint, e.CreatedAt)) .ToListAsync(); var uptime = (DateTime.UtcNow - _serverStartTime).TotalSeconds; var stats = new ServerStatsDto( totalUsers, activeUsersToday, totalRequests, totalCost, uptime, errorCount, recentErrors ); return Ok(stats); } [HttpGet("users")] public async Task GetUsers() { var users = await _db.Users .OrderByDescending(u => u.CreatedAt) .Select(u => new UserInfoDto(u.Id, u.Email, u.Role, u.CreatedAt, u.LastLoginAt, u.IsActive)) .ToListAsync(); return Ok(users); } // ── LLM Model Endpoints ─────────────────────────────────────────────── [HttpGet("endpoints")] public async Task GetEndpoints() { var endpoints = await _db.ModelEndpoints .OrderBy(m => m.Id) .ToListAsync(); return Ok(endpoints); } [HttpPost("endpoints")] public async Task CreateEndpoint([FromBody] CreateModelEndpointDto dto) { if (string.IsNullOrWhiteSpace(dto.ModelId) || string.IsNullOrWhiteSpace(dto.ModelName)) { return BadRequest(new { message = "ModelId와 ModelName은 필수 항목입니다." }); } var existing = await _db.ModelEndpoints.FirstOrDefaultAsync(m => m.ModelId == dto.ModelId); if (existing != null) { return Conflict(new { message = "이미 존재하는 ModelId입니다." }); } var endpoint = new ServiceModelEndpoint { ModelId = dto.ModelId.Trim(), ModelName = dto.ModelName.Trim(), Provider = dto.Provider.Trim(), EndpointUrl = dto.EndpointUrl.Trim(), ApiKey = dto.ApiKey ?? "", CostPer1kPromptTokens = dto.CostPer1kPromptTokens, CostPer1kCompletionTokens = dto.CostPer1kCompletionTokens, IsActive = true, CreatedAt = DateTime.UtcNow }; _db.ModelEndpoints.Add(endpoint); await _db.SaveChangesAsync(); return Ok(endpoint); } [HttpPut("endpoints/{id}")] public async Task UpdateEndpoint(int id, [FromBody] UpdateModelEndpointDto dto) { var endpoint = await _db.ModelEndpoints.FindAsync(id); if (endpoint == null) return NotFound(); endpoint.ModelName = dto.ModelName.Trim(); endpoint.Provider = dto.Provider.Trim(); endpoint.EndpointUrl = dto.EndpointUrl.Trim(); endpoint.ApiKey = dto.ApiKey ?? ""; endpoint.CostPer1kPromptTokens = dto.CostPer1kPromptTokens; endpoint.CostPer1kCompletionTokens = dto.CostPer1kCompletionTokens; endpoint.IsActive = dto.IsActive; await _db.SaveChangesAsync(); return Ok(endpoint); } [HttpDelete("endpoints/{id}")] public async Task DeleteEndpoint(int id) { var endpoint = await _db.ModelEndpoints.FindAsync(id); if (endpoint == null) return NotFound(); _db.ModelEndpoints.Remove(endpoint); await _db.SaveChangesAsync(); return Ok(new { message = "삭제되었습니다." }); } // ── STT / Transcription Provider Endpoints ──────────────────────────── [HttpGet("stt-endpoints")] public async Task GetSttEndpoints() { var endpoints = await _sttService.GetAllEndpointsAsync(); return Ok(endpoints); } [HttpGet("stt-endpoints/{id}")] public async Task GetSttEndpoint(int id) { var endpoints = await _sttService.GetAllEndpointsAsync(); var endpoint = endpoints.FirstOrDefault(e => e.Id == id); if (endpoint == null) return NotFound(new { message = $"STT Endpoint {id} not found." }); return Ok(endpoint); } [HttpPost("stt-endpoints")] public async Task CreateSttEndpoint([FromBody] CreateSttEndpointDto dto) { if (string.IsNullOrWhiteSpace(dto.Name) || string.IsNullOrWhiteSpace(dto.EndpointUrl)) { return BadRequest(new { message = "이름과 Endpoint URL은 필수입니다." }); } var endpoint = await _sttService.CreateEndpointAsync(dto); return Ok(endpoint); } [HttpPut("stt-endpoints/{id}")] public async Task UpdateSttEndpoint(int id, [FromBody] UpdateSttEndpointDto dto) { try { var endpoint = await _sttService.UpdateEndpointAsync(id, dto); return Ok(endpoint); } catch (KeyNotFoundException) { return NotFound(new { message = $"STT Endpoint {id} not found." }); } } [HttpDelete("stt-endpoints/{id}")] public async Task DeleteSttEndpoint(int id) { var deleted = await _sttService.DeleteEndpointAsync(id); if (!deleted) return NotFound(); return Ok(new { message = "STT 엔드포인트가 성공적으로 삭제되었습니다." }); } [HttpPost("stt-endpoints/{id}/set-default")] public async Task SetDefaultSttEndpoint(int id) { var success = await _sttService.SetDefaultEndpointAsync(id); if (!success) return NotFound(); return Ok(new { id, isDefault = true, success = true, message = "기본 클라우드 전사 프로바이더로 설정되었습니다." }); } [HttpPost("stt-endpoints/{id}/test")] public async Task TestSttEndpoint(int id) { var result = await _sttService.TestEndpointAsync(id); return Ok(result); } [HttpPost("stt-endpoints/test-direct")] public async Task TestSttDirect([FromQuery] string endpointUrl, [FromQuery] string? apiKey) { var result = await _sttService.TestEndpointAsync(0, apiKey, endpointUrl); return Ok(result); } [HttpGet("stt-usage")] public async Task GetSttUsageReport() { var report = await _sttService.GetUsageReportAsync(); return Ok(report); } // ── LLM Usage Report ────────────────────────────────────────────────── [HttpGet("usage")] public async Task GetUsageReport() { var logs = await _db.UsageLogs.AsNoTracking().ToListAsync(); var totalRequests = logs.Count; var totalPromptTokens = logs.Sum(l => l.PromptTokens); var totalCompletionTokens = logs.Sum(l => l.CompletionTokens); var totalCost = logs.Sum(l => l.CalculatedCost); var userSummaries = logs .GroupBy(l => new { l.UserId, l.UserEmail }) .Select(g => new UserUsageSummaryDto( g.Key.UserId, g.Key.UserEmail, g.Count(), g.Sum(x => x.TotalTokens), g.Sum(x => x.CalculatedCost) )) .OrderByDescending(u => u.TotalCost) .ToList(); var modelSummaries = logs .GroupBy(l => l.ModelId) .Select(g => new ModelUsageSummaryDto( g.Key, g.Key, g.Count(), g.Sum(x => x.TotalTokens), g.Sum(x => x.CalculatedCost) )) .OrderByDescending(m => m.TotalCost) .ToList(); var report = new UsageReportDto( totalRequests, totalPromptTokens, totalCompletionTokens, totalCost, userSummaries, modelSummaries ); return Ok(report); } }