using System.Globalization; using System.Linq; using System.Text.Json; using CsvHelper; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.ObjectPool; 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(options => options.UseSqlite("Data Source=quotes.db")); builder.Services.AddCors(options => { options.AddPolicy(name: "CorsPolicy", builder => { builder.AllowAnyOrigin() .AllowAnyHeader() .AllowAnyMethod(); }); }); 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.UseCors(); app.MapGet("/quotes", async (QuoteDbContext db, int pageNumber = 1, int pageSize = 10, [FromQuery] DataSetSources dataset = DataSetSources.all) => { if (!Enum.IsDefined(typeof(DataSetSources), dataset)) return Results.BadRequest("Invalid dataset."); if (pageNumber < 1) pageNumber = 1; if (pageSize < 1) pageSize = 10; pageSize = Math.Min(pageSize, MaxPageSize); // Limit pageSize to MaxPageSize var quotes = GlobalData.Quotes; if (dataset != DataSetSources.all) { quotes = quotes.Where(x => x.DataSet == dataset.ToString()).ToList(); } quotes = 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, [FromQuery] DataSetSources dataset = DataSetSources.all) => { if (!Enum.IsDefined(typeof(DataSetSources), dataset)) return Results.BadRequest("Invalid dataset."); int quoteCount = 0; if (dataset != DataSetSources.all) { quoteCount = GlobalData.Quotes.Count(q => q.DataSet == dataset.ToString()); } else { 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); Quote? randomQuote = null; if (dataset != DataSetSources.all) { randomQuote = GlobalData.Quotes .Where(q => q.DataSet == dataset.ToString()) .Skip(randomIndex) .Take(1) .FirstOrDefault(); } else { 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 query, QuoteDbContext db, int pageNumber = 1, int pageSize = 10, [FromQuery] DataSetSources dataset = DataSetSources.all) => { if (!Enum.IsDefined(typeof(DataSetSources), dataset)) return Results.BadRequest("Invalid dataset."); var queryQuotes = GlobalData.Quotes.AsQueryable(); //db.Quotes.AsQueryable(); if (dataset != DataSetSources.all) { queryQuotes = queryQuotes.Where(q => q.DataSet == dataset.ToString()); } if (!string.IsNullOrWhiteSpace(query)) { query = query.ToLower(); queryQuotes = queryQuotes.Where(q => q.Author.ToLower().Contains(query) || (q.Categories != null && q.Categories.ToLower().Contains(query)) || (q.Book != null && q.Book.ToLower().Contains(query)) || q.QuoteText.ToLower().Contains(query) ); } if (pageNumber < 1) pageNumber = 1; if (pageSize < 1) pageSize = 10; pageSize = Math.Min(pageSize, MaxPageSize); // Limit pageSize to MaxPageSize var paginatedQuotes = queryQuotes .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .ToList(); return paginatedQuotes.Any() ? Results.Ok(paginatedQuotes) : Results.NotFound("No matching quotes found."); }); app.MapGet("/health", () => Results.Ok(new { Status = "Healthy", Timestamp = DateTime.UtcNow })); app.MapGet("/", () => Results.Ok(new { message = "Hello!", 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."); // Fields // ========== // Quote (string) // Author (string) // Tags (list) // Category (string) var jsonString = await File.ReadAllTextAsync(path); // TODO: import data and sanitize relevant fields: Trim and Trim('"') var quotes = JsonSerializer.Deserialize>(jsonString); var uniqueQuotes = quotes.DistinctBy(x => x.Content).ToList(); foreach (var quote in uniqueQuotes) { var q = quote.GetQuote1(); if (!db.Quotes.Any(x => x.QuoteText == q.QuoteText.CleanString())) { db.Quotes.Add(q); 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) var jsonString = await File.ReadAllTextAsync(path); // TODO: import data and sanitize relevant fields: Trim and Trim('"') var quotes = JsonSerializer.Deserialize>(jsonString); var uniqueQuotes = quotes.DistinctBy(x => x.Content).ToList(); foreach (var quote in uniqueQuotes) { var q = quote.GetQuote2(); if (!db.Quotes.Any(x => x.QuoteText == q.QuoteText.CleanString())) { db.Quotes.Add(q); 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) //read csv file into list of records var reader = new StreamReader(path); var csv = new CsvReader(reader, CultureInfo.InvariantCulture); var records = csv.GetRecords().ToList(); foreach (var record in records) { var q = record.GetQuote3(); if (!db.Quotes.Any(x => x.QuoteText == q.QuoteText.CleanString())) { db.Quotes.Add(q); 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(); //db.Database.EnsureCreated(); GlobalData.Quotes = await db.Quotes.ToListAsync(); //await SeedDatabase(db); } app.Run();