using System.Linq; using System.Text.Json; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); builder.WebHost.ConfigureKestrel(serverOptions => { serverOptions.Listen(System.Net.IPAddress.Any, 7860); // Listen on all network interfaces on port 5000 }); builder.Services.AddDbContext<QuoteDbContext>(options => options.UseSqlite("Data Source=quotes.db")); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // Configure the HTTP request pipeline. //if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } const int MaxPageSize = 100; app.UseHttpsRedirection(); app.MapGet("/quotes", async (QuoteDbContext db, int pageNumber = 1, int pageSize = 10) => { if (pageNumber < 1) pageNumber = 1; if (pageSize < 1) pageSize = 10; pageSize = Math.Min(pageSize, MaxPageSize); // Limit pageSize to MaxPageSize // var quotes = await db.Quotes // .Skip((pageNumber - 1) * pageSize) // .Take(pageSize) // .ToListAsync(); var quotes = GlobalData.Quotes .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .ToList(); return Results.Ok(quotes); }); app.MapGet("/quotes/{id}", async (int id, QuoteDbContext db) => await db.Quotes.FindAsync(id) is Quote quote ? Results.Ok(quote) : Results.NotFound("Quote not found")); app.MapPost("/quotes", async (Quote newQuote, QuoteDbContext db) => { db.Quotes.Add(newQuote); await db.SaveChangesAsync(); return Results.Created($"/quotes/{newQuote.Id}", newQuote); }); app.MapPut("/quotes/{id}", async (int id, Quote updatedQuote, QuoteDbContext db) => { var quote = await db.Quotes.FindAsync(id); if (quote is null) return Results.NotFound("Quote not found"); quote.Author = updatedQuote.Author; quote.QuoteText = updatedQuote.QuoteText; quote.Source = updatedQuote.Source; quote.Book = updatedQuote.Book; quote.Categories = updatedQuote.Categories; quote.Url = updatedQuote.Url; quote.Isbn = updatedQuote.Isbn; quote.Language = updatedQuote.Language; quote.OriginalLanguage = updatedQuote.OriginalLanguage; await db.SaveChangesAsync(); return Results.NoContent(); }); app.MapDelete("/quotes/{id}", async (int id, QuoteDbContext db) => { var quote = await db.Quotes.FindAsync(id); if (quote is null) return Results.NotFound("Quote not found"); db.Quotes.Remove(quote); await db.SaveChangesAsync(); return Results.NoContent(); }); // Random quote endpoint app.MapGet("/quotes/random", async (QuoteDbContext db) => { var quoteCount = GlobalData.Quotes.Count(); //await db.Quotes.CountAsync(); if (quoteCount == 0) return Results.NotFound("No quotes available."); var random = new Random(); var randomIndex = random.Next(quoteCount); var randomQuote = GlobalData.Quotes[randomIndex]; //await db.Quotes.Skip(randomIndex).Take(1).FirstOrDefaultAsync(); return randomQuote != null ? Results.Ok(randomQuote) : Results.NotFound("No quote found."); }); // Search quotes by author, categories, book, or quoteText with pagination app.MapGet("/quotes/search", async (string search, QuoteDbContext db, int pageNumber = 1, int pageSize = 10) => { var query = GlobalData.Quotes.AsQueryable(); //db.Quotes.AsQueryable(); if (!string.IsNullOrWhiteSpace(search)) query = query.Where(q => q.Author.Contains(search)); if (!string.IsNullOrWhiteSpace(search)) query = query.Where(q => q.Categories.Contains(search)); if (!string.IsNullOrWhiteSpace(search)) query = query.Where(q => q.Book.Contains(search)); if (!string.IsNullOrWhiteSpace(search)) query = query.Where(q => q.QuoteText.Contains(search)); if (pageNumber < 1) pageNumber = 1; if (pageSize < 1) pageSize = 10; pageSize = Math.Min(pageSize, MaxPageSize); // Limit pageSize to MaxPageSize var paginatedQuotes = await query .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .ToListAsync(); return paginatedQuotes.Any() ? Results.Ok(paginatedQuotes) : Results.NotFound("No matching quotes found."); }); app.MapGet("/health", () => Results.Ok(new { Status = "Healthy", Timestamp = DateTime.UtcNow })); async Task SaveSource1Async(string jsonFilePath, QuoteDbContext db) { var path = Path.Combine(Directory.GetCurrentDirectory(), "data", jsonFilePath); if (!File.Exists(path)) throw new FileNotFoundException("The JSON file for seeding is missing."); // var jsonString = await File.ReadAllTextAsync(path); // // Deserialize JSON to a list of dictionaries // var dictionaryList = JsonSerializer.Deserialize<List<Dictionary<string, object>>>(jsonString); // if (dictionaryList == null) // throw new InvalidOperationException("Failed to deserialize JSON into dictionary list."); // Fields // ========== // Quote (string) // Author (string) // Tags (list<string>) // Category (string) // foreach (var dictionary in dictionaryList) // { // var tags = ""; // if(dictionary.TryGetValue("Tags", out var categories)) { // var list = JsonSerializer.Deserialize<List<string>>(categories.ToString()); // tags = String.Join(",", list.Select(x=> x.Trim().Trim('"'))); // } // var quote = new Quote // { // Author = dictionary.TryGetValue("Author", out var author) ? author.ToString() : null, // QuoteText = dictionary.TryGetValue("Quote", out var quoteText) ? quoteText.ToString() : null, // Categories = tags, // }; // db.Quotes.Add(quote); // } // TODO: import data and sanitize relevant fields: Trim and Trim('"') //save for each seed file await db.SaveChangesAsync(); } async Task SaveSource2Async(string jsonFile, QuoteDbContext db) { var path = Path.Combine(Directory.GetCurrentDirectory(), "data", jsonFile); if (!File.Exists(path)) throw new FileNotFoundException("The JSON file for seeding is missing."); // Fields // ========== // content (string) // author (string) // tags (list<string>) // TODO: import data and sanitize relevant fields: Trim and Trim('"') //save for each seed file await db.SaveChangesAsync(); } async Task SaveSource3Async(string csvFile, QuoteDbContext db) { var path = Path.Combine(Directory.GetCurrentDirectory(), "data", csvFile); if (!File.Exists(path)) throw new FileNotFoundException("The CSV file for seeding is missing."); // Fields // ========== // quote (string) // author (string) // category (string) // TODO: import data and sanitize relevant fields: Trim and Trim('"') //save for each seed file await db.SaveChangesAsync(); } // Seed database async Task SeedDatabase(QuoteDbContext db) { if (await db.Quotes.AnyAsync()) return; // Database is already seeded await SaveSource1Async("source1.json", db); await SaveSource2Async("source2.json", db); await SaveSource3Async("source3.csv", db); } using (var scope = app.Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService<QuoteDbContext>(); db.Database.EnsureCreated(); GlobalData.Quotes = await db.Quotes.ToListAsync(); await SeedDatabase(db); } app.Run();