using System.Text; using System.Text.Json; var builder = WebApplication.CreateBuilder(args); // Add services builder.Services.AddHttpClient(); builder.Services.AddCors(options => { options.AddDefaultPolicy(policy => { policy.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); }); }); var app = builder.Build(); app.UseCors(); app.UseDefaultFiles(); app.UseStaticFiles(); // Update this to point to your local API or production API const string API_BASE_URL = "https://citelynq.com/api"; // Proxy endpoint for full-text search app.MapGet("/api/search", async (HttpContext context, IHttpClientFactory httpClientFactory, ILogger logger) => { try { var apiKey = context.Request.Headers["X-API-Key"].FirstOrDefault(); if (string.IsNullOrEmpty(apiKey)) { return Results.BadRequest(new { error = "API key is required" }); } // Get query parameters var query = context.Request.Query["q"].FirstOrDefault(); var source = context.Request.Query["source"].FirstOrDefault(); var limit = context.Request.Query["limit"].FirstOrDefault() ?? "20"; if (string.IsNullOrEmpty(query)) { return Results.BadRequest(new { error = "Search query (q) is required" }); } // Build URL for CiteLynq API var httpClient = httpClientFactory.CreateClient(); var url = $"{API_BASE_URL}/search?q={Uri.EscapeDataString(query)}&limit={limit}"; if (!string.IsNullOrEmpty(source)) { url += $"&source={Uri.EscapeDataString(source)}"; } logger.LogInformation("Calling CiteLynq API: {Url}", url); var httpRequest = new HttpRequestMessage(HttpMethod.Get, url); httpRequest.Headers.Add("X-API-Key", apiKey); var response = await httpClient.SendAsync(httpRequest); var responseContent = await response.Content.ReadAsStringAsync(); logger.LogInformation("CiteLynq API Response Status: {StatusCode}", response.StatusCode); if (!response.IsSuccessStatusCode) { logger.LogError("CiteLynq API Error: {Response}", responseContent); try { var errorObj = JsonSerializer.Deserialize(responseContent); return Results.Json(errorObj, statusCode: (int)response.StatusCode); } catch { return Results.Json(new { error = responseContent }, statusCode: (int)response.StatusCode); } } return Results.Content(responseContent, "application/json"); } catch (Exception ex) { logger.LogError(ex, "Error in search endpoint"); return Results.Json(new { error = ex.Message, details = ex.ToString() }, statusCode: 500); } }); // Proxy endpoint for semantic search app.MapGet("/api/search/semantic", async (HttpContext context, IHttpClientFactory httpClientFactory, ILogger logger) => { try { var apiKey = context.Request.Headers["X-API-Key"].FirstOrDefault(); if (string.IsNullOrEmpty(apiKey)) { return Results.BadRequest(new { error = "API key is required" }); } // Get query parameters var query = context.Request.Query["q"].FirstOrDefault(); var source = context.Request.Query["source"].FirstOrDefault(); var limit = context.Request.Query["limit"].FirstOrDefault() ?? "20"; if (string.IsNullOrEmpty(query)) { return Results.BadRequest(new { error = "Search query (q) is required" }); } // Build URL for CiteLynq API var httpClient = httpClientFactory.CreateClient(); var url = $"{API_BASE_URL}/search/semantic?q={Uri.EscapeDataString(query)}&limit={limit}"; if (!string.IsNullOrEmpty(source)) { url += $"&source={Uri.EscapeDataString(source)}"; } logger.LogInformation("Calling CiteLynq API: {Url}", url); var httpRequest = new HttpRequestMessage(HttpMethod.Get, url); httpRequest.Headers.Add("X-API-Key", apiKey); var response = await httpClient.SendAsync(httpRequest); var responseContent = await response.Content.ReadAsStringAsync(); logger.LogInformation("CiteLynq API Response Status: {StatusCode}", response.StatusCode); if (!response.IsSuccessStatusCode) { logger.LogError("CiteLynq API Error: {Response}", responseContent); try { var errorObj = JsonSerializer.Deserialize(responseContent); return Results.Json(errorObj, statusCode: (int)response.StatusCode); } catch { return Results.Json(new { error = responseContent }, statusCode: (int)response.StatusCode); } } return Results.Content(responseContent, "application/json"); } catch (Exception ex) { logger.LogError(ex, "Error in semantic search endpoint"); return Results.Json(new { error = ex.Message, details = ex.ToString() }, statusCode: 500); } }); // Proxy endpoint for date-based search app.MapGet("/api/search/bydate", async (HttpContext context, IHttpClientFactory httpClientFactory, ILogger logger) => { try { var apiKey = context.Request.Headers["X-API-Key"].FirstOrDefault(); if (string.IsNullOrEmpty(apiKey)) { return Results.BadRequest(new { error = "API key is required" }); } // Get query parameters var query = context.Request.Query["q"].FirstOrDefault(); var month = context.Request.Query["month"].FirstOrDefault(); var day = context.Request.Query["day"].FirstOrDefault(); var source = context.Request.Query["source"].FirstOrDefault(); var limit = context.Request.Query["limit"].FirstOrDefault() ?? "20"; if (string.IsNullOrEmpty(query)) { return Results.BadRequest(new { error = "Search query (q) is required" }); } if (string.IsNullOrEmpty(month) || string.IsNullOrEmpty(day)) { return Results.BadRequest(new { error = "Month and day parameters are required for date search" }); } // Build URL for CiteLynq API var httpClient = httpClientFactory.CreateClient(); var url = $"{API_BASE_URL}/search/bydate?q={Uri.EscapeDataString(query)}&month={month}&day={day}&limit={limit}"; if (!string.IsNullOrEmpty(source)) { url += $"&source={Uri.EscapeDataString(source)}"; } logger.LogInformation("Calling CiteLynq API: {Url}", url); var httpRequest = new HttpRequestMessage(HttpMethod.Get, url); httpRequest.Headers.Add("X-API-Key", apiKey); var response = await httpClient.SendAsync(httpRequest); var responseContent = await response.Content.ReadAsStringAsync(); logger.LogInformation("CiteLynq API Response Status: {StatusCode}", response.StatusCode); if (!response.IsSuccessStatusCode) { logger.LogError("CiteLynq API Error: {Response}", responseContent); try { var errorObj = JsonSerializer.Deserialize(responseContent); return Results.Json(errorObj, statusCode: (int)response.StatusCode); } catch { return Results.Json(new { error = responseContent }, statusCode: (int)response.StatusCode); } } return Results.Content(responseContent, "application/json"); } catch (Exception ex) { logger.LogError(ex, "Error in date search endpoint"); return Results.Json(new { error = ex.Message, details = ex.ToString() }, statusCode: 500); } }); // Proxy endpoint to get article details app.MapGet("/api/articles/{id}", async (string id, HttpContext context, IHttpClientFactory httpClientFactory) => { try { var apiKey = context.Request.Headers["X-API-Key"].FirstOrDefault(); if (string.IsNullOrEmpty(apiKey)) { return Results.BadRequest(new { error = "API key is required" }); } var httpClient = httpClientFactory.CreateClient(); var httpRequest = new HttpRequestMessage(HttpMethod.Get, $"{API_BASE_URL}/articles/{id}"); httpRequest.Headers.Add("X-API-Key", apiKey); var response = await httpClient.SendAsync(httpRequest); var responseContent = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { return Results.Json( JsonSerializer.Deserialize(responseContent), statusCode: (int)response.StatusCode ); } return Results.Content(responseContent, "application/json"); } catch (Exception ex) { return Results.Problem(ex.Message); } }); app.Run();