131 lines
4.6 KiB
C#
131 lines
4.6 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using D3ROVoice.Api.Dtos;
|
|
using D3ROVoice.Api.Services;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Configuration;
|
|
|
|
namespace D3ROVoice.Api.Controllers;
|
|
|
|
[ApiController]
|
|
[Authorize]
|
|
[Route("api/[controller]")]
|
|
public class SttController : ControllerBase
|
|
{
|
|
private const string InternalGatewayHeader = "X-D3RO-STT-Gateway-Token";
|
|
private readonly ISttProxyService _sttService;
|
|
private readonly IConfiguration _configuration;
|
|
|
|
public SttController(ISttProxyService sttService, IConfiguration configuration)
|
|
{
|
|
_sttService = sttService;
|
|
_configuration = configuration;
|
|
}
|
|
|
|
[HttpPost("transcribe")]
|
|
[Consumes("application/json", "multipart/form-data")]
|
|
public Task<IActionResult> Transcribe()
|
|
{
|
|
// User-facing transcription is exclusively handled by the Supabase
|
|
// stt-proxy, which owns authenticated identity, quota reservation and
|
|
// usage persistence. This legacy provider path must not bypass it.
|
|
return Task.FromResult<IActionResult>(StatusCode(
|
|
StatusCodes.Status410Gone,
|
|
new { error = "stt_edge_gateway_required" }));
|
|
}
|
|
|
|
[HttpPost("internal/transcribe")]
|
|
[AllowAnonymous]
|
|
[Consumes("multipart/form-data")]
|
|
[RequestSizeLimit(26 * 1024 * 1024)]
|
|
public async Task<IActionResult> TranscribeFromQuotaGateway()
|
|
{
|
|
var configuredToken = _configuration["D3RO_API_TOKEN"]?.Trim() ?? string.Empty;
|
|
if (Encoding.UTF8.GetByteCount(configuredToken) < 32)
|
|
{
|
|
return StatusCode(StatusCodes.Status503ServiceUnavailable, new { error = "stt_gateway_not_configured" });
|
|
}
|
|
|
|
var suppliedToken = Request.Headers[InternalGatewayHeader].ToString();
|
|
if (!FixedTimeTokenEquals(configuredToken, suppliedToken))
|
|
{
|
|
return Unauthorized(new { error = "stt_gateway_unauthorized" });
|
|
}
|
|
|
|
if (!Request.HasFormContentType)
|
|
{
|
|
return StatusCode(StatusCodes.Status415UnsupportedMediaType, new { error = "unsupported_media_type" });
|
|
}
|
|
|
|
var form = await Request.ReadFormAsync();
|
|
var file = form.Files.GetFile("file") ?? form.Files.GetFile("audio");
|
|
if (file == null || file.Length == 0)
|
|
{
|
|
return BadRequest(new { error = "missing_audio" });
|
|
}
|
|
|
|
await using var memoryStream = new MemoryStream();
|
|
await file.CopyToAsync(memoryStream);
|
|
var language = form["language"].ToString();
|
|
var prompt = form["prompt"].ToString();
|
|
var request = new SttTranscribeRequest(
|
|
AudioBase64: null,
|
|
Language: string.IsNullOrWhiteSpace(language) ? "ko" : language,
|
|
InitialPrompt: string.IsNullOrWhiteSpace(prompt) ? null : prompt,
|
|
ModelId: null,
|
|
Provider: null);
|
|
|
|
try
|
|
{
|
|
var result = await _sttService.TranscribeAsync(
|
|
userId: 0,
|
|
userEmail: "edge-internal",
|
|
request,
|
|
memoryStream.ToArray(),
|
|
file.ContentType ?? "application/octet-stream",
|
|
file.FileName ?? "recording.bin",
|
|
recordUsage: false);
|
|
return Ok(result);
|
|
}
|
|
catch (SttProviderUnavailableException)
|
|
{
|
|
return StatusCode(StatusCodes.Status503ServiceUnavailable, new { error = "stt_provider_unavailable" });
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
return BadRequest(new { error = "invalid_audio" });
|
|
}
|
|
catch
|
|
{
|
|
return StatusCode(StatusCodes.Status502BadGateway, new { error = "stt_upstream_failed" });
|
|
}
|
|
}
|
|
|
|
private static bool FixedTimeTokenEquals(string configured, string supplied)
|
|
{
|
|
var configuredDigest = SHA256.HashData(Encoding.UTF8.GetBytes(configured));
|
|
var suppliedDigest = SHA256.HashData(Encoding.UTF8.GetBytes(supplied));
|
|
return CryptographicOperations.FixedTimeEquals(configuredDigest, suppliedDigest);
|
|
}
|
|
|
|
[HttpGet("providers")]
|
|
[Authorize(Policy = "ManagerOrAbove")]
|
|
public async Task<IActionResult> GetActiveProviders()
|
|
{
|
|
var endpoints = await _sttService.GetAllEndpointsAsync();
|
|
return Ok(endpoints);
|
|
}
|
|
|
|
[HttpPost("test")]
|
|
[Authorize(Policy = "ManagerOrAbove")]
|
|
public async Task<IActionResult> TestConnection([FromQuery] int endpointId = 0)
|
|
{
|
|
var result = await _sttService.TestEndpointAsync(endpointId);
|
|
return Ok(result);
|
|
}
|
|
}
|