diff --git a/ChatRAG.csproj b/ChatRAG.csproj
index 750d5fe..e0d4285 100644
--- a/ChatRAG.csproj
+++ b/ChatRAG.csproj
@@ -26,6 +26,7 @@
+
diff --git a/ChatRAG.csproj.user b/ChatRAG.csproj.user
index e5a2ec0..b2208ee 100644
--- a/ChatRAG.csproj.user
+++ b/ChatRAG.csproj.user
@@ -2,8 +2,8 @@
http
- ApiControllerEmptyScaffolder
- root/Common/Api
+ MvcControllerEmptyScaffolder
+ root/Common/MVC/Controller
ProjectDebugger
diff --git a/Controllers/ChatController.cs b/Controllers/ChatController.cs
index 9f6b7f7..8e18666 100644
--- a/Controllers/ChatController.cs
+++ b/Controllers/ChatController.cs
@@ -7,6 +7,7 @@ using ChatRAG.Data;
using ChatRAG.Models;
using ChatRAG.Requests;
using BlazMapper;
+using ChatRAG.Services.Contracts;
#pragma warning disable SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
@@ -21,23 +22,23 @@ namespace ChatApi.Controllers
private readonly TextFilter _textFilter;
private readonly UserDataRepository _userDataRepository;
private readonly ProjectDataRepository _projectDataRepository;
- private readonly TextData _textData;
+ private readonly ITextDataService _textDataService;
private readonly IHttpClientFactory _httpClientFactory;
public ChatController(
ILogger logger,
IResponseService responseService,
UserDataRepository userDataRepository,
- TextData textData,
+ ITextDataService textDataService,
ProjectDataRepository projectDataRepository,
IHttpClientFactory httpClientFactory)
{
_logger = logger;
_responseService = responseService;
_userDataRepository = userDataRepository;
- _textData = textData;
+ _textDataService = textDataService;
_projectDataRepository = projectDataRepository;
- this._httpClientFactory = httpClientFactory;
+ _httpClientFactory = httpClientFactory;
}
[HttpPost]
@@ -80,7 +81,13 @@ namespace ChatApi.Controllers
{
try
{
- await _textData.SalvarNoMongoDB(request.Id, request.Title, request.Content, request.ProjectId);
+ await _textDataService.SaveDocumentAsync(new DocumentInput
+ {
+ Id = request.Id,
+ Title = request.Title,
+ Content = request.Content,
+ ProjectId = request.ProjectId
+ });
return Created();
}
catch (Exception ex)
@@ -97,7 +104,13 @@ namespace ChatApi.Controllers
{
foreach(var request in requests)
{
- await _textData.SalvarNoMongoDB(request.Id, request.Title, request.Content, request.ProjectId);
+ await _textDataService.SaveDocumentAsync(new DocumentInput
+ {
+ Id = request.Id,
+ Title = request.Title,
+ Content = request.Content,
+ ProjectId = request.ProjectId
+ });
}
return Created();
}
@@ -111,7 +124,7 @@ namespace ChatApi.Controllers
[Route("texts")]
public async Task> GetTexts()
{
- var texts = await _textData.GetAll();
+ var texts = await _textDataService.GetAll();
return texts.Select(t => {
return new TextResponse
{
@@ -126,7 +139,7 @@ namespace ChatApi.Controllers
[Route("texts/id/{id}")]
public async Task GetText([FromRoute] string id)
{
- var textItem = await _textData.GetById(id);
+ var textItem = await _textDataService.GetById(id);
return new TextResponse {
Id = textItem.Id,
diff --git a/Controllers/MigrationController.cs b/Controllers/MigrationController.cs
new file mode 100644
index 0000000..ee104a9
--- /dev/null
+++ b/Controllers/MigrationController.cs
@@ -0,0 +1,419 @@
+using ChatApi.Data;
+using ChatRAG.Contracts.VectorSearch;
+using ChatRAG.Data;
+using ChatRAG.Models;
+using ChatRAG.Services.Contracts;
+using ChatRAG.Services.SearchVectors;
+using ChatRAG.Settings.ChatRAG.Configuration;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+using Microsoft.SemanticKernel.Embeddings;
+
+#pragma warning disable SKEXP0001
+
+namespace ChatApi.Controllers
+{
+ [ApiController]
+ [Route("api/[controller]")]
+ public class MigrationController : ControllerBase
+ {
+ private readonly IVectorDatabaseFactory _factory;
+ private readonly ILogger _logger;
+ private readonly VectorDatabaseSettings _settings;
+ private readonly ITextEmbeddingGenerationService _embeddingService;
+ private readonly TextDataRepository _mongoRepository;
+
+ public MigrationController(
+ IVectorDatabaseFactory factory,
+ ILogger logger,
+ IOptions settings,
+ ITextEmbeddingGenerationService embeddingService,
+ TextDataRepository mongoRepository)
+ {
+ _factory = factory;
+ _logger = logger;
+ _settings = settings.Value;
+ _embeddingService = embeddingService;
+ _mongoRepository = mongoRepository;
+ }
+
+ ///
+ /// Status da migração e informações dos providers
+ ///
+ [HttpGet("status")]
+ public async Task GetMigrationStatus()
+ {
+ try
+ {
+ var currentProvider = _factory.GetActiveProvider();
+
+ var status = new
+ {
+ CurrentProvider = currentProvider,
+ Settings = new
+ {
+ Provider = _settings.Provider,
+ MongoDB = _settings.MongoDB != null ? new
+ {
+ DatabaseName = _settings.MongoDB.DatabaseName,
+ TextCollection = _settings.MongoDB.TextCollectionName
+ } : null,
+ Qdrant = _settings.Qdrant != null ? new
+ {
+ Host = _settings.Qdrant.Host,
+ Port = _settings.Qdrant.Port,
+ Collection = _settings.Qdrant.CollectionName,
+ VectorSize = _settings.Qdrant.VectorSize
+ } : null
+ },
+ Stats = await GetProvidersStats()
+ };
+
+ return Ok(status);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Erro ao obter status da migração");
+ return StatusCode(500, new { error = ex.Message });
+ }
+ }
+
+ ///
+ /// Migra dados do MongoDB para Qdrant
+ ///
+ [HttpPost("mongo-to-qdrant")]
+ public async Task MigrateMongoToQdrant(
+ [FromQuery] string? projectId = null,
+ [FromQuery] int batchSize = 50,
+ [FromQuery] bool dryRun = false)
+ {
+ try
+ {
+ if (_settings.Provider != "MongoDB")
+ {
+ return BadRequest("Migração só funciona quando o provider atual é MongoDB");
+ }
+
+ _logger.LogInformation("Iniciando migração MongoDB → Qdrant. ProjectId: {ProjectId}, DryRun: {DryRun}",
+ projectId, dryRun);
+
+ var result = await PerformMigration(projectId, batchSize, dryRun);
+
+ return Ok(result);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Erro durante migração MongoDB → Qdrant");
+ return StatusCode(500, new { error = ex.Message });
+ }
+ }
+
+ ///
+ /// Migra dados do Qdrant para MongoDB
+ ///
+ [HttpPost("qdrant-to-mongo")]
+ public async Task MigrateQdrantToMongo(
+ [FromQuery] string? projectId = null,
+ [FromQuery] int batchSize = 50,
+ [FromQuery] bool dryRun = false)
+ {
+ try
+ {
+ if (_settings.Provider != "Qdrant")
+ {
+ return BadRequest("Migração só funciona quando o provider atual é Qdrant");
+ }
+
+ _logger.LogInformation("Iniciando migração Qdrant → MongoDB. ProjectId: {ProjectId}, DryRun: {DryRun}",
+ projectId, dryRun);
+
+ // TODO: Implementar migração reversa se necessário
+ return BadRequest("Migração Qdrant → MongoDB ainda não implementada");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Erro durante migração Qdrant → MongoDB");
+ return StatusCode(500, new { error = ex.Message });
+ }
+ }
+
+ ///
+ /// Compara dados entre MongoDB e Qdrant
+ ///
+ [HttpPost("compare")]
+ public async Task CompareProviders([FromQuery] string? projectId = null)
+ {
+ try
+ {
+ // Cria serviços para ambos os providers manualmente
+ var mongoService = CreateMongoService();
+ var qdrantService = await CreateQdrantService();
+
+ var comparison = await CompareData(mongoService, qdrantService, projectId);
+
+ return Ok(comparison);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Erro ao comparar providers");
+ return StatusCode(500, new { error = ex.Message });
+ }
+ }
+
+ ///
+ /// Limpa todos os dados do provider atual
+ ///
+ [HttpDelete("clear-current")]
+ public async Task ClearCurrentProvider([FromQuery] string? projectId = null)
+ {
+ try
+ {
+ var vectorSearchService = _factory.CreateVectorSearchService();
+
+ if (string.IsNullOrEmpty(projectId))
+ {
+ // Limpar tudo - PERIGOSO!
+ return BadRequest("Limpeza completa requer confirmação. Use /clear-current/confirm");
+ }
+
+ // Limpar apenas um projeto
+ var documents = await vectorSearchService.GetDocumentsByProjectAsync(projectId);
+ var ids = documents.Select(d => d.Id).ToList();
+
+ foreach (var id in ids)
+ {
+ await vectorSearchService.DeleteDocumentAsync(id);
+ }
+
+ return Ok(new
+ {
+ provider = _settings.Provider,
+ projectId,
+ deletedCount = ids.Count
+ });
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Erro ao limpar dados do provider");
+ return StatusCode(500, new { error = ex.Message });
+ }
+ }
+
+ ///
+ /// Testa conectividade dos providers
+ ///
+ [HttpGet("test-connections")]
+ public async Task TestConnections()
+ {
+ var results = new Dictionary();
+
+ // Testar MongoDB
+ try
+ {
+ var mongoService = CreateMongoService();
+ var mongoHealth = await mongoService.IsHealthyAsync();
+ var mongoStats = await mongoService.GetStatsAsync();
+
+ results["MongoDB"] = new
+ {
+ healthy = mongoHealth,
+ stats = mongoStats
+ };
+ }
+ catch (Exception ex)
+ {
+ results["MongoDB"] = new { healthy = false, error = ex.Message };
+ }
+
+ // Testar Qdrant
+ try
+ {
+ var qdrantService = await CreateQdrantService();
+ var qdrantHealth = await qdrantService.IsHealthyAsync();
+ var qdrantStats = await qdrantService.GetStatsAsync();
+
+ results["Qdrant"] = new
+ {
+ healthy = qdrantHealth,
+ stats = qdrantStats
+ };
+ }
+ catch (Exception ex)
+ {
+ results["Qdrant"] = new { healthy = false, error = ex.Message };
+ }
+
+ return Ok(results);
+ }
+
+ // ========================================
+ // MÉTODOS PRIVADOS
+ // ========================================
+
+ private async Task