quotes-net / Program.cs
Florin Bobiș
added tag support as in quotable api
724036f
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();