using Microsoft.Extensions.FileProviders; using System.IO; using System.Collections.Concurrent; using Microsoft.Extensions.Logging; using TelegramSaaS.Application; using TelegramSaaS.Application.Common.Interfaces.Services; using TelegramSaaS.Infrastructure; using MongoDB.Driver; using TelegramSaaS.Infrastructure.Persistence; using Telegram.Bot; var builder = WebApplication.CreateBuilder(args); builder.Logging.AddProvider(new InMemoryLoggerProvider()); // Add services to the container builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; }); // Learn more about configuring OpenAPI builder.Services.AddEndpointsApiExplorer(); builder.Services.AddOpenApi(); // Register Clean Architecture layers builder.Services.AddApplication(); builder.Services.AddInfrastructure(builder.Configuration); var app = builder.Build(); // Ensure local storage folders are generated at startup using (var scope = app.Services.CreateScope()) { var storageService = scope.ServiceProvider.GetRequiredService(); storageService.EnsureDirectoriesCreated(); } // Configure the HTTP request pipeline if (app.Environment.IsDevelopment()) { app.MapOpenApi(); } // Enable local static image file serving var storagePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "storage"); Directory.CreateDirectory(storagePath); app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(storagePath), RequestPath = "/storage" }); // HF Space handles HTTPS at the proxy level — no redirect needed // app.UseHttpsRedirection(); app.MapControllers(); // Serve files dynamically from MongoDB app.MapGet("/storage/{*path}", async (string path, MongoDbContext dbContext) => { var cleanPath = path.Replace('\\', '/'); var fileCollection = dbContext.GetCollection("Files"); var file = await fileCollection.Find(x => x.Id == cleanPath).FirstOrDefaultAsync(); if (file == null) { return Results.NotFound(); } return Results.File(file.Content, file.ContentType); }); // Simple health endpoint app.MapGet("/health", () => Results.Ok(new { Status = "Healthy", Timestamp = DateTime.UtcNow })); // Diagnostic endpoint — test Telegram API connectivity app.MapGet("/diag", async (ITelegramBotClient botClient, IConfiguration configuration) => { var results = new Dictionary(); var rawToken = configuration["Telegram:BotToken"] ?? ""; var maskedToken = string.IsNullOrEmpty(rawToken) ? "empty" : (rawToken.Length > 10 ? $"{rawToken[..6]}...{rawToken[^6..]}" : "too short"); results["token_configured"] = maskedToken; try { var me = await botClient.GetMe(); results["bot"] = $"@{me.Username} ({me.FirstName})"; results["telegram_api"] = "OK"; } catch (Exception ex) { results["bot"] = "FAILED"; results["telegram_api"] = ex.Message; } try { var webhookInfo = await botClient.GetWebhookInfo(); results["webhook_url"] = webhookInfo.Url ?? "NOT SET"; results["webhook_pending"] = webhookInfo.PendingUpdateCount.ToString(); results["webhook_error"] = webhookInfo.LastErrorMessage ?? "none"; results["webhook_last_error_date"] = webhookInfo.LastErrorDate?.ToString() ?? "none"; } catch (Exception ex) { results["webhook"] = $"FAILED: {ex.Message}"; } return Results.Ok(results); }); app.MapGet("/diag/db", async (MongoDbContext dbContext) => { try { var usersCol = dbContext.GetCollection("Users"); var jobsCol = dbContext.GetCollection("GenerationJobs"); var serversCol = dbContext.GetCollection("GpuServers"); var jobs = await jobsCol.Find(_ => true).SortByDescending(j => j.CreatedAt).Limit(10).ToListAsync(); var users = await usersCol.Find(_ => true).SortByDescending(u => u.CreatedAt).Limit(10).ToListAsync(); var servers = await serversCol.Find(_ => true).ToListAsync(); return Results.Ok(new { Status = "OK", JobsCount = await jobsCol.CountDocumentsAsync(_ => true), UsersCount = await usersCol.CountDocumentsAsync(_ => true), Servers = servers, RecentJobs = jobs, RecentUsers = users }); } catch (Exception ex) { return Results.Problem(title: "Database Diagnostics Failed", detail: ex.ToString()); } }); app.MapGet("/diag/testdbwrite", async (MongoDbContext dbContext) => { try { var jobsCol = dbContext.GetCollection("GenerationJobs"); var testJob = new TelegramSaaS.Domain.Entities.GenerationJob { UserId = "afe67ef9-82a3-4596-8b3c-4c7a319ce2e5", Prompt = "Test database write", Type = TelegramSaaS.Domain.Entities.JobType.TextToImage, Status = TelegramSaaS.Domain.Entities.JobStatus.Pending, CreditCost = 0, CreatedAt = DateTime.UtcNow }; await jobsCol.InsertOneAsync(testJob); await jobsCol.DeleteOneAsync(j => j.Id == testJob.Id); return Results.Ok(new { Status = "Success", Message = "Database write and delete completed successfully.", JobId = testJob.Id }); } catch (Exception ex) { return Results.Problem(title: "Database Write Test Failed", detail: ex.ToString()); } }); // Auto-register Telegram webhook on startup _ = Task.Run(async () => { try { await Task.Delay(3000); // Wait for server to fully start using var scope = app.Services.CreateScope(); var botClient = scope.ServiceProvider.GetRequiredService(); var logger = scope.ServiceProvider.GetRequiredService>(); var baseUrl = app.Configuration["Storage:BaseUrl"] ?? "https://raghava0450-saluai.hf.space"; var webhookUrl = $"{baseUrl.TrimEnd('/')}/api/v1/telegram/webhook"; await botClient.SetWebhook(webhookUrl, dropPendingUpdates: true); logger.LogInformation("✅ Telegram webhook set: {WebhookUrl}", webhookUrl); } catch (Exception ex) { try { using var scope = app.Services.CreateScope(); var logger = scope.ServiceProvider.GetRequiredService>(); logger.LogError(ex, "❌ Failed to set webhook"); } catch { Console.WriteLine($"❌ Failed to set webhook: {ex.Message}"); } } }); app.MapGet("/diag/logs", () => Results.Ok(InMemoryLogStore.Logs.ToList())); app.Run(); public static class InMemoryLogStore { public static ConcurrentQueue Logs { get; } = new(); public static void Add(string log) { Logs.Enqueue($"[{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss}] {log}"); while (Logs.Count > 200) { Logs.TryDequeue(out _); } } } public class InMemoryLogger : ILogger { private readonly string _name; public InMemoryLogger(string name) { _name = name; } public IDisposable? BeginScope(TState state) where TState : notnull => null; public bool IsEnabled(LogLevel logLevel) => true; public void Log( LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { var message = formatter(state, exception); if (exception != null) { message += Environment.NewLine + exception.ToString(); } InMemoryLogStore.Add($"[{logLevel}] [{_name}] {message}"); } } public class InMemoryLoggerProvider : ILoggerProvider { public ILogger CreateLogger(string categoryName) { return new InMemoryLogger(categoryName); } public void Dispose() {} }