Spaces:
Running
Running
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<QuoteDbContext>(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, [FromQuery] string tags = "") => | |
{ | |
if (!Enum.IsDefined(typeof(DataSetSources), dataset)) | |
return Results.BadRequest("Invalid dataset."); | |
int quoteCount = 0; | |
var quotes = GlobalData.Quotes; | |
var tagsList = new List<string>(); | |
if (!string.IsNullOrEmpty(tags)) | |
{ | |
try { | |
tagsList = tags.Split('|', StringSplitOptions.RemoveEmptyEntries).Select(x => x.ToLower()).ToList(); | |
} catch (Exception) { | |
return Results.BadRequest("Invalid tags."); | |
} | |
} | |
if (tagsList.Count > 0) | |
{ | |
quotes = quotes.Where(q => !string.IsNullOrEmpty(q.Categories) && q.Categories.Split(',').Any(x => tagsList.Select(y => y.ToLower()).Contains(x))).ToList(); | |
} | |
Quote? randomQuote = null; | |
if (dataset != DataSetSources.all) | |
{ | |
quoteCount = quotes.Count(q => q.DataSet == dataset.ToString()); | |
} | |
else | |
{ | |
quoteCount = quotes.Count(); //await db.Quotes.CountAsync(); | |
} | |
if (quoteCount == 0) | |
return Results.NotFound("No quotes available."); | |
var random = new Random(); | |
var randomIndex = random.Next(quoteCount); | |
if (dataset != DataSetSources.all) | |
{ | |
randomQuote = quotes | |
.Where(q => q.DataSet == dataset.ToString()) | |
.Skip(randomIndex) | |
.Take(1) | |
.FirstOrDefault(); | |
} | |
else | |
{ | |
randomQuote = 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<string>) | |
// Category (string) | |
var jsonString = await File.ReadAllTextAsync(path); | |
// TODO: import data and sanitize relevant fields: Trim and Trim('"') | |
var quotes = JsonSerializer.Deserialize<List<MittalInput>>(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<string>) | |
var jsonString = await File.ReadAllTextAsync(path); | |
// TODO: import data and sanitize relevant fields: Trim and Trim('"') | |
var quotes = JsonSerializer.Deserialize<List<QuotableInput>>(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<ManannInput>().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<QuoteDbContext>(); | |
//db.Database.EnsureCreated(); | |
GlobalData.Quotes = await db.Quotes.ToListAsync(); | |
//await SeedDatabase(db); | |
} | |
app.Run(); | |