Spaces:
Sleeping
Sleeping
| 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<IStorageService>(); | |
| 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<MongoFile>("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<string, string>(); | |
| 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<TelegramSaaS.Domain.Entities.User>("Users"); | |
| var jobsCol = dbContext.GetCollection<TelegramSaaS.Domain.Entities.GenerationJob>("GenerationJobs"); | |
| var serversCol = dbContext.GetCollection<TelegramSaaS.Domain.Entities.GpuServer>("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<TelegramSaaS.Domain.Entities.GenerationJob>("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<ITelegramBotClient>(); | |
| var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>(); | |
| 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<ILogger<Program>>(); | |
| 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<string> 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>(TState state) where TState : notnull => null; | |
| public bool IsEnabled(LogLevel logLevel) => true; | |
| public void Log<TState>( | |
| LogLevel logLevel, | |
| EventId eventId, | |
| TState state, | |
| Exception? exception, | |
| Func<TState, Exception?, string> 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() {} | |
| } | |