using System.Data.OleDb; using System.Text; using System.Text.Json; using System.Threading.RateLimiting; using Download_Faturas; using iText.Layout.Splitting; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Webhook_4docs { public class Program { private static readonly RateLimiter connRateLimiter = new ConcurrencyLimiter(new ConcurrencyLimiterOptions { PermitLimit = 1 }); public static void Main(string[] args) { static string IndexedFilename(string stub, string extension) { int ix = 0; string? filename = null; if (File.Exists(String.Format("{0}.{1}", stub, extension))) { do { ix++; filename = String.Format("{0} ({1}).{2}", stub, ix, extension); } while (File.Exists(filename)); } else { filename = String.Format("{0}.{1}", stub, extension); } return filename; } var builder = WebApplication.CreateBuilder(args); builder.Configuration.AddJsonFile("appsettings.json"); var connectionString = builder.Configuration.GetConnectionString("WebhookDbContext"); string? appFolder = builder.Configuration["PathBase"]; builder.Services.AddDbContext(options => options.UseNpgsql(connectionString) ); builder.Services.AddLogging(loggingBuilder => { loggingBuilder.ClearProviders(); loggingBuilder.AddConsole(); }); // Add services to the container. builder.Services.AddRazorPages(); var app = builder.Build(); var logger = app.Services.GetRequiredService>(); using (var scope = app.Services.CreateScope()) { var dbContext = scope.ServiceProvider.GetRequiredService(); dbContext.Database.EnsureCreated(); dbContext.Database.Migrate(); } string fatura_arquivo; // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UsePathBase(appFolder); app.UseRouting(); app.UseStaticFiles(); app.UseAuthorization(); app.MapRazorPages(); app.Use((context, next) => { context.Request.PathBase = new PathString(appFolder); return next(context); }); // Endpoint para \api app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapPatch("/api", async context => { string requestBody; using (var reader = new StreamReader(context.Request.Body, Encoding.UTF8, true, 1024, true)) { requestBody = await reader.ReadToEndAsync(); } var JsonBody = JsonDocument.Parse(requestBody).RootElement; string CaminhoDB = "X:/Middle/Informativo Setorial/Modelo Word/BD1_dados cadastrais e faturas.accdb"; if (JsonBody.TryGetProperty("requestID", out JsonElement fatura_ID_json)) { string fatura_ID = fatura_ID_json.ToString(); if (!JsonBody.TryGetProperty("json", out JsonElement root)) { return; } JsonElement DadosJson = JsonDocument.Parse(root.ToString()).RootElement; string tipoDocumento = root.Get("documentType").ToString() ?? string.Empty.ToLower(); if (tipoDocumento != string.Empty && tipoDocumento != "nota_fiscal") { return; } Fatura fatura = new(fatura_ID, JsonBody); bool completed = false; while (!completed) { var connLease = await connRateLimiter.AcquireAsync(); if (connLease.IsAcquired) { using (OleDbConnection conn = new(@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + CaminhoDB + ";Jet OLEDB:Database Password=gds21")) { if (conn != null) { if (conn.State == System.Data.ConnectionState.Closed) { await conn.OpenAsync(); } try { fatura.Processar(conn); } catch (Exception ex) { completed = true; connLease.Dispose(); throw new Exception(ex.Message); } } } completed = true; connLease.Dispose(); } } string? status = fatura.Status; int status_id = 0; switch (status) { case ("FATURA DUPLICADA NO BD"): status_id = 4; break; case ("UNIDADE CONSUMIDORA NÃO LOCALIZADA NO BD"): status_id = 5; break; case ("FATURA INCLUIDA NO BD"): status_id = 6; break; case (_): status = "ERRO"; status_id = 7; break; } try { string path = $@"X:\Middle\Carteira {fatura.Gestao![0]}\Carteira {fatura.Gestao}\Faturas fourdocs\{status_id} - {status}"; if (status_id == 6 && fatura.PastaTUSD!.Exists) { path = fatura.PastaTUSD!.FullName; } if (status_id != 4) { fatura_arquivo = IndexedFilename($@"{path}\ID {fatura_ID!} - Mês {fatura.Mes} - Empresa {fatura.Empresa} - Unidade {fatura.Unidade}", "pdf"); CriarArquivo(fatura_arquivo, JsonBody.GetProperty("pdfFile").ToString()); var DatabaseModel = new ProcessedInvoices { InvoiceId = Int32.Parse(fatura_ID), DateTimeProcessed = DateTime.UtcNow, Status = status, InvoicePath = fatura_arquivo }; logger.LogInformation("Fatura incluída no BD"); logger.LogInformation("Fatura salva na pasta " + fatura_arquivo); using (var scope = app.Services.CreateScope()) { var dbContext = scope.ServiceProvider.GetRequiredService(); dbContext.ProcessedInvoices.Add(DatabaseModel); await dbContext.SaveChangesAsync(); } } else { logger.LogInformation("Fatura duplicada no BD"); logger.LogInformation("Fatura descartada"); } } catch { logger.LogError("Erro no processamento da fatura"); } } else if (JsonBody.TryGetProperty("healthy", out _) && JsonBody.TryGetProperty("blame", out _) && JsonBody.TryGetProperty("locationID", out _)) { double errorID = 0; switch (JsonBody.GetProperty("healthy").GetBoolean(), JsonBody.GetProperty("blame").ToString().ToLower()) { case (false, "user"): logger.LogError("Loc ID: " + JsonBody.GetProperty("locationID").ToString() + " Sem acesso"); errorID = 1; break; case (false, "system"): logger.LogError("Loc ID: " + JsonBody.GetProperty("locationID").ToString() + " Com erro de sistema"); errorID = 2; break; case (false, _): logger.LogError("Loc ID: " + JsonBody.GetProperty("locationID").ToString() + " Com erro " + JsonBody.GetProperty("blame").ToString()); errorID = 3; break; case (true, _): //logger.LogInformation("Loc ID: " + JsonBody.GetProperty("locationID").ToString() + " está saudável"); break; } var errorText = GetErrorText(JsonBody); await UpdateErrorIdStatusAsync(CaminhoDB, JsonBody.GetProperty("locationID").GetInt64(), errorID, errorText, logger); } }); //implementação futura para receber as faturas enviadas via api de forma assíncrona - Upload4Docs (substituir parte do agendador de tarefas) endpoints.MapPut("/api", async context => { var a = 10; a++; await Task.CompletedTask; }); }); app.Run(); } //tenta pegar o deactivationReport, se não conseguir ou for null, tenta pegar o websiteText, se não conseguir ou for null, tenta pegar o systemText. As propriedades podem não exisir public static string GetErrorText(JsonElement root) { string errorText; if (root.TryGetProperty("deactivationReport", out JsonElement deactivationReportElement) && deactivationReportElement.ValueKind != JsonValueKind.Null) { errorText = deactivationReportElement.ToString(); } else if (root.TryGetProperty("websiteText", out JsonElement websiteTextElement) && websiteTextElement.ValueKind != JsonValueKind.Null) { errorText = websiteTextElement.ToString(); } else if (root.TryGetProperty("systemText", out JsonElement systemTextElement) && systemTextElement.ValueKind != JsonValueKind.Null) { errorText = systemTextElement.ToString(); } else { errorText = "Erro desconhecido"; } return errorText; } public static async Task UpdateErrorIdStatusAsync(string CaminhoDB, double location_id, double errorID, string systemText, ILogger logger) { int test = 0; int maxRetries = 3; int attempt = 0; while (attempt < maxRetries) { var connLease = await connRateLimiter.AcquireAsync(); if (connLease.IsAcquired) { try { using (OleDbConnection conn = new (@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + CaminhoDB + ";Jet OLEDB:Database Password=gds21")) { await conn.OpenAsync(); using (OleDbCommand cmd = new ( @"UPDATE AgVirtual4Docs SET errorID = @errorID, status = @status WHERE location_id = @location_id", conn)) { // Adiciona parâmetros de forma segura cmd.Parameters.AddWithValue("@errorID", errorID); cmd.Parameters.AddWithValue("@status", systemText); cmd.Parameters.AddWithValue("@location_id", location_id); // Executa o comando e captura o resultado test = await cmd.ExecuteNonQueryAsync(); } } break; // Sai do loop em caso de sucesso } catch (OleDbException ex) { // Registra o erro logger.LogInformation($"Erro no OleDb update: {ex.Message} (Tentativa {attempt + 1} de {maxRetries})"); if (attempt < maxRetries - 1) { // Aguarda antes de tentar novamente await Task.Delay(1000 * (int)Math.Pow(2, attempt)); // Retry com Backoff Exponencial } else { // Propaga a exceção na última tentativa throw; } } finally { connLease.Dispose(); } attempt++; // Incrementa a tentativa após adquirir o lease, mesmo que falhe } else { // Aguarda um curto período antes de tentar novamente se não conseguiu adquirir o lease await Task.Delay(200); } } return test; } public static void CriarArquivo(string fatura_arquivo, string pdfFile64) { //string fatura_arquivo = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\test.pdf"; if (!File.Exists(fatura_arquivo)) { byte[] bytes = Convert.FromBase64String(pdfFile64); System.IO.FileStream stream = new(fatura_arquivo, FileMode.CreateNew); System.IO.BinaryWriter writer = new(stream); writer.Write(bytes, 0, bytes.Length); writer.Close(); } } } public static partial class JsonExtensions { public static JsonElement? Get(this JsonElement element, string name) => element.ValueKind != JsonValueKind.Null && element.ValueKind != JsonValueKind.Undefined && element.TryGetProperty(name, out var value) ? value : (JsonElement?)null; public static JsonElement? Get(this JsonElement element, int index) { if (element.ValueKind == JsonValueKind.Null || element.ValueKind == JsonValueKind.Undefined) return null; // Throw if index < 0 return index < element.GetArrayLength() ? element[index] : null; } } }