using Microsoft.AspNetCore.Mvc; using SqrtSpace.SpaceTime.AspNetCore; using SqrtSpace.SpaceTime.Core; using SampleWebApi.Models; using SampleWebApi.Services; namespace SampleWebApi.Controllers; [ApiController] [Route("api/[controller]")] public class ProductsController : ControllerBase { private readonly IProductService _productService; private readonly ILogger _logger; public ProductsController(IProductService productService, ILogger logger) { _productService = productService; _logger = logger; } /// /// Get all products with memory-efficient paging /// /// /// This endpoint demonstrates basic pagination to limit memory usage. /// For very large datasets, consider using the streaming endpoint instead. /// [HttpGet] public async Task>> GetProducts( [FromQuery] int page = 1, [FromQuery] int pageSize = 100) { if (pageSize > 1000) { return BadRequest("Page size cannot exceed 1000 items"); } var result = await _productService.GetProductsPagedAsync(page, pageSize); return Ok(result); } /// /// Stream products using √n batching for memory efficiency /// /// /// This endpoint streams large datasets using √n-sized batches. /// It's ideal for processing millions of records without loading them all into memory. /// The response is streamed as newline-delimited JSON (NDJSON). /// [HttpGet("stream")] [SpaceTimeStreaming(ChunkStrategy = ChunkStrategy.SqrtN)] public async IAsyncEnumerable StreamProducts( [FromQuery] string? category = null, [FromQuery] decimal? minPrice = null) { await foreach (var product in _productService.StreamProductsAsync(category, minPrice)) { yield return product; } } /// /// Search products with memory-aware filtering /// /// /// This endpoint uses external sorting when the result set is large, /// automatically spilling to disk if memory pressure is detected. /// [HttpGet("search")] public async Task>> SearchProducts( [FromQuery] string query, [FromQuery] string? sortBy = "name", [FromQuery] bool descending = false) { if (string.IsNullOrWhiteSpace(query)) { return BadRequest("Search query is required"); } var results = await _productService.SearchProductsAsync(query, sortBy, descending); return Ok(results); } /// /// Bulk update product prices with checkpointing /// /// /// This endpoint demonstrates checkpoint-enabled bulk operations. /// If the operation fails, it can be resumed from the last checkpoint. /// Pass the same operationId to resume a failed operation. /// [HttpPost("bulk-update-prices")] [EnableCheckpoint(Strategy = CheckpointStrategy.Linear)] public async Task> BulkUpdatePrices( [FromBody] BulkPriceUpdateRequest request, [FromHeader(Name = "X-Operation-Id")] string? operationId = null) { operationId ??= Guid.NewGuid().ToString(); var checkpoint = HttpContext.Features.Get(); if (checkpoint != null) { // Try to restore from previous checkpoint var state = await checkpoint.CheckpointManager.RestoreLatestCheckpointAsync(); if (state != null) { _logger.LogInformation("Resuming bulk update from checkpoint. Processed: {count}", state.ProcessedCount); } } var result = await _productService.BulkUpdatePricesAsync( request.CategoryFilter, request.PriceMultiplier, operationId, checkpoint?.CheckpointManager); return Ok(result); } /// /// Export products to CSV with memory streaming /// /// /// This endpoint exports products to CSV format using streaming to minimize memory usage. /// Even millions of products can be exported without loading them all into memory. /// [HttpGet("export/csv")] public async Task ExportToCsv([FromQuery] string? category = null) { Response.ContentType = "text/csv"; Response.Headers.Append("Content-Disposition", $"attachment; filename=products_{DateTime.UtcNow:yyyyMMdd}.csv"); await _productService.ExportToCsvAsync(Response.Body, category); } /// /// Get product price statistics using memory-efficient aggregation /// /// /// This endpoint calculates statistics over large datasets using external aggregation /// when memory pressure is detected. /// [HttpGet("statistics")] public async Task> GetStatistics([FromQuery] string? category = null) { var stats = await _productService.GetStatisticsAsync(category); return Ok(stats); } } public class BulkPriceUpdateRequest { public string? CategoryFilter { get; set; } public decimal PriceMultiplier { get; set; } } public class BulkUpdateResult { public string OperationId { get; set; } = ""; public int TotalProducts { get; set; } public int UpdatedProducts { get; set; } public int FailedProducts { get; set; } public bool Completed { get; set; } public string? CheckpointId { get; set; } }