From 336be491ba1a8599b9f451b3729210d0588a5d87 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Tue, 16 Dec 2025 09:35:28 -0500 Subject: [PATCH] feat(nests): add year-based routing for nest endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support /nests/{year}/{nestName} routes to disambiguate older programs that don't have year digits in the name. Refactor GetNestPathAsync to handle 2025+ .pep files in flat directory vs older .zip files in year subdirectories. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- PepApi.Core/Controllers/NestsController.cs | 147 ++++++++++----------- 1 file changed, 72 insertions(+), 75 deletions(-) diff --git a/PepApi.Core/Controllers/NestsController.cs b/PepApi.Core/Controllers/NestsController.cs index 9af86f5..76a347a 100644 --- a/PepApi.Core/Controllers/NestsController.cs +++ b/PepApi.Core/Controllers/NestsController.cs @@ -28,13 +28,6 @@ public class NestsController : ControllerBase if (year == 0) year = DateTime.Now.Year; - var dir = Path.Combine(_nestDirectory, year.ToString()); - - if (!Directory.Exists(dir)) - { - return Ok(new List()); - } - var nestHeaders = await _db.NestHeaders .Where(n => n.DateProgrammed != null && n.DateProgrammed.Value.Year == year) .ToListAsync(); @@ -69,93 +62,66 @@ public class NestsController : ControllerBase }; } - // Get nest by name - finds most recent if year not specified - [HttpGet("{nestName}")] - public async Task> GetNestInfo(string nestName, [FromQuery] int? year = null) - { - var nestFile = year.HasValue - ? GetNestPath(nestName, year.Value) - : await FindMostRecentNestAsync(nestName); - - if (nestFile == null || !System.IO.File.Exists(nestFile)) - return NotFound(new { message = "Nest not found" }); - - var details = await GetNestDetailsAsync(nestFile); - return Ok(details); - } - - // Get nest by year and name (backward compatibility) [HttpGet("{year:int:regex(^\\d{{4}}$)}/{nestName}")] public async Task> GetNestInfoByYear(int year, string nestName) { - var nestFile = GetNestPath(nestName, year); + var nestFile = await GetNestPathAsync(nestName, year); - if (!System.IO.File.Exists(nestFile)) + if (nestFile == null) return NotFound(new { message = "Nest not found" }); var details = await GetNestDetailsAsync(nestFile); return Ok(details); } - // Download nest file - finds most recent if year not specified - [HttpGet("{nestName}/download")] - public async Task DownloadFile(string nestName, [FromQuery] int? year = null) + [HttpGet("{nestName}")] + public async Task> GetNestInfo(string nestName) { - var filePath = year.HasValue - ? GetNestPath(nestName, year.Value) - : await FindMostRecentNestAsync(nestName); + var nestFile = await GetNestPathAsync(nestName); - if (filePath == null || !System.IO.File.Exists(filePath)) + if (nestFile == null) return NotFound(new { message = "Nest not found" }); - var bytes = System.IO.File.ReadAllBytes(filePath); - var fileName = Path.GetFileName(filePath); - var mimeType = "application/octet-stream"; - - return File(bytes, mimeType, fileName); + var details = await GetNestDetailsAsync(nestFile); + return Ok(details); } - // Download nest file by year (backward compatibility) [HttpGet("{year:int:regex(^\\d{{4}}$)}/{nestName}/download")] - public IActionResult DownloadFileByYear(int year, string nestName) + public async Task DownloadFileByYear(int year, string nestName) { - var filePath = GetNestPath(nestName, year); + var filePath = await GetNestPathAsync(nestName, year); - if (!System.IO.File.Exists(filePath)) + if (filePath == null) return NotFound(new { message = "Nest not found" }); - var bytes = System.IO.File.ReadAllBytes(filePath); + var bytes = await System.IO.File.ReadAllBytesAsync(filePath); var fileName = Path.GetFileName(filePath); var mimeType = "application/octet-stream"; return File(bytes, mimeType, fileName); } - // Get plates - finds most recent if year not specified - [HttpGet("{nestName}/plates")] - public async Task>> GetPlates(string nestName, [FromQuery] int? year = null) + [HttpGet("{nestName}/download")] + public async Task DownloadFile(string nestName) { - var nestFile = year.HasValue - ? GetNestPath(nestName, year.Value) - : await FindMostRecentNestAsync(nestName); + var filePath = await GetNestPathAsync(nestName); - if (nestFile == null || !System.IO.File.Exists(nestFile)) + if (filePath == null) return NotFound(new { message = "Nest not found" }); - var nest = Nest.Load(nestFile); - var plates = PepHelper.GetPlates(nest); - var combined = PepHelper.CombineLikePlates(plates); + var bytes = await System.IO.File.ReadAllBytesAsync(filePath); + var fileName = Path.GetFileName(filePath); + var mimeType = "application/octet-stream"; - return Ok(combined); + return File(bytes, mimeType, fileName); } - // Get plates by year (backward compatibility) [HttpGet("{year:int:regex(^\\d{{4}}$)}/{nestName}/plates")] - public ActionResult> GetPlatesByYear(int year, string nestName) + public async Task>> GetPlatesByYear(int year, string nestName) { - var nestFile = GetNestPath(nestName, year); + var nestFile = await GetNestPathAsync(nestName, year); - if (!System.IO.File.Exists(nestFile)) + if (nestFile == null) return NotFound(new { message = "Nest not found" }); var nest = Nest.Load(nestFile); @@ -165,19 +131,19 @@ public class NestsController : ControllerBase return Ok(combined); } - private async Task FindMostRecentNestAsync(string nestName) + [HttpGet("{nestName}/plates")] + public async Task>> GetPlates(string nestName) { - // Query database for most recent nest by name - var mostRecent = await _db.NestHeaders - .Where(n => n.NestName.ToUpper() == nestName.ToUpper() && n.DateProgrammed != null) - .OrderByDescending(n => n.DateProgrammed) - .FirstOrDefaultAsync(); + var nestFile = await GetNestPathAsync(nestName); - if (mostRecent == null) - return null; + if (nestFile == null) + return NotFound(new { message = "Nest not found" }); - var year = mostRecent.DateProgrammed!.Value.Year; - return GetNestPath(nestName, year); + var nest = Nest.Load(nestFile); + var plates = PepHelper.GetPlates(nest); + var combined = PepHelper.CombineLikePlates(plates); + + return Ok(combined); } /// @@ -297,18 +263,49 @@ public class NestsController : ControllerBase }); } - private string GetNestPath(string nestName, int year) + private async Task GetNestPathAsync(string nestName, int? year = null) { - var fileName = nestName + ".zip"; - var filePath = Path.Combine(_nestDirectory, year.ToString(), fileName); + // If year is specified, look directly in that year's directory + if (year.HasValue) + { + // 2025+ nests use .pep in flat directory + if (year.Value >= 2025) + { + var pepPath = Path.Combine(_nestDirectory, nestName + ".pep"); + if (System.IO.File.Exists(pepPath)) + return pepPath; + } - if (System.IO.File.Exists(filePath)) - return filePath; + // Older nests use .zip in year subdirectory + var zipPath = Path.Combine(_nestDirectory, year.Value.ToString(), nestName + ".zip"); + if (System.IO.File.Exists(zipPath)) + return zipPath; - fileName = nestName + ".pep"; - filePath = Path.Combine(_nestDirectory, year.ToString(), fileName); + return null; + } - return filePath; + // No year specified - try flat directory first (new 2025+ nests use .pep extension) + var flatPepPath = Path.Combine(_nestDirectory, nestName + ".pep"); + if (System.IO.File.Exists(flatPepPath)) + return flatPepPath; + + // Fall back to year-based directory lookup for older nests + var nestHeader = await _db.NestHeaders + .Where(n => n.NestName.ToUpper() == nestName.ToUpper() && n.DateProgrammed != null) + .OrderByDescending(n => n.DateProgrammed) + .FirstOrDefaultAsync(); + + if (nestHeader == null) + return null; + + var dbYear = nestHeader.DateProgrammed!.Value.Year; + + // Older nests used .zip extension + var yearZipPath = Path.Combine(_nestDirectory, dbYear.ToString(), nestName + ".zip"); + if (System.IO.File.Exists(yearZipPath)) + return yearZipPath; + + return null; } private async Task GetNestDetailsAsync(string nestFilePath)