feat(nests): add year-based routing for nest endpoints
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 <noreply@anthropic.com>
This commit is contained in:
@@ -28,13 +28,6 @@ public class NestsController : ControllerBase
|
|||||||
if (year == 0)
|
if (year == 0)
|
||||||
year = DateTime.Now.Year;
|
year = DateTime.Now.Year;
|
||||||
|
|
||||||
var dir = Path.Combine(_nestDirectory, year.ToString());
|
|
||||||
|
|
||||||
if (!Directory.Exists(dir))
|
|
||||||
{
|
|
||||||
return Ok(new List<NestSummary>());
|
|
||||||
}
|
|
||||||
|
|
||||||
var nestHeaders = await _db.NestHeaders
|
var nestHeaders = await _db.NestHeaders
|
||||||
.Where(n => n.DateProgrammed != null && n.DateProgrammed.Value.Year == year)
|
.Where(n => n.DateProgrammed != null && n.DateProgrammed.Value.Year == year)
|
||||||
.ToListAsync();
|
.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<ActionResult<NestDetails>> 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}")]
|
[HttpGet("{year:int:regex(^\\d{{4}}$)}/{nestName}")]
|
||||||
public async Task<ActionResult<NestDetails>> GetNestInfoByYear(int year, string nestName)
|
public async Task<ActionResult<NestDetails>> 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" });
|
return NotFound(new { message = "Nest not found" });
|
||||||
|
|
||||||
var details = await GetNestDetailsAsync(nestFile);
|
var details = await GetNestDetailsAsync(nestFile);
|
||||||
return Ok(details);
|
return Ok(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download nest file - finds most recent if year not specified
|
[HttpGet("{nestName}")]
|
||||||
[HttpGet("{nestName}/download")]
|
public async Task<ActionResult<NestDetails>> GetNestInfo(string nestName)
|
||||||
public async Task<IActionResult> DownloadFile(string nestName, [FromQuery] int? year = null)
|
|
||||||
{
|
{
|
||||||
var filePath = year.HasValue
|
var nestFile = await GetNestPathAsync(nestName);
|
||||||
? GetNestPath(nestName, year.Value)
|
|
||||||
: await FindMostRecentNestAsync(nestName);
|
|
||||||
|
|
||||||
if (filePath == null || !System.IO.File.Exists(filePath))
|
if (nestFile == null)
|
||||||
return NotFound(new { message = "Nest not found" });
|
return NotFound(new { message = "Nest not found" });
|
||||||
|
|
||||||
var bytes = System.IO.File.ReadAllBytes(filePath);
|
var details = await GetNestDetailsAsync(nestFile);
|
||||||
var fileName = Path.GetFileName(filePath);
|
return Ok(details);
|
||||||
var mimeType = "application/octet-stream";
|
|
||||||
|
|
||||||
return File(bytes, mimeType, fileName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download nest file by year (backward compatibility)
|
|
||||||
[HttpGet("{year:int:regex(^\\d{{4}}$)}/{nestName}/download")]
|
[HttpGet("{year:int:regex(^\\d{{4}}$)}/{nestName}/download")]
|
||||||
public IActionResult DownloadFileByYear(int year, string nestName)
|
public async Task<IActionResult> 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" });
|
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 fileName = Path.GetFileName(filePath);
|
||||||
var mimeType = "application/octet-stream";
|
var mimeType = "application/octet-stream";
|
||||||
|
|
||||||
return File(bytes, mimeType, fileName);
|
return File(bytes, mimeType, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get plates - finds most recent if year not specified
|
[HttpGet("{nestName}/download")]
|
||||||
[HttpGet("{nestName}/plates")]
|
public async Task<IActionResult> DownloadFile(string nestName)
|
||||||
public async Task<ActionResult<List<Plate>>> GetPlates(string nestName, [FromQuery] int? year = null)
|
|
||||||
{
|
{
|
||||||
var nestFile = year.HasValue
|
var filePath = await GetNestPathAsync(nestName);
|
||||||
? GetNestPath(nestName, year.Value)
|
|
||||||
: await FindMostRecentNestAsync(nestName);
|
|
||||||
|
|
||||||
if (nestFile == null || !System.IO.File.Exists(nestFile))
|
if (filePath == null)
|
||||||
return NotFound(new { message = "Nest not found" });
|
return NotFound(new { message = "Nest not found" });
|
||||||
|
|
||||||
var nest = Nest.Load(nestFile);
|
var bytes = await System.IO.File.ReadAllBytesAsync(filePath);
|
||||||
var plates = PepHelper.GetPlates(nest);
|
var fileName = Path.GetFileName(filePath);
|
||||||
var combined = PepHelper.CombineLikePlates(plates);
|
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")]
|
[HttpGet("{year:int:regex(^\\d{{4}}$)}/{nestName}/plates")]
|
||||||
public ActionResult<List<Plate>> GetPlatesByYear(int year, string nestName)
|
public async Task<ActionResult<List<Plate>>> 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" });
|
return NotFound(new { message = "Nest not found" });
|
||||||
|
|
||||||
var nest = Nest.Load(nestFile);
|
var nest = Nest.Load(nestFile);
|
||||||
@@ -165,19 +131,19 @@ public class NestsController : ControllerBase
|
|||||||
return Ok(combined);
|
return Ok(combined);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string?> FindMostRecentNestAsync(string nestName)
|
[HttpGet("{nestName}/plates")]
|
||||||
|
public async Task<ActionResult<List<Plate>>> GetPlates(string nestName)
|
||||||
{
|
{
|
||||||
// Query database for most recent nest by name
|
var nestFile = await GetNestPathAsync(nestName);
|
||||||
var mostRecent = await _db.NestHeaders
|
|
||||||
.Where(n => n.NestName.ToUpper() == nestName.ToUpper() && n.DateProgrammed != null)
|
|
||||||
.OrderByDescending(n => n.DateProgrammed)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
|
|
||||||
if (mostRecent == null)
|
if (nestFile == null)
|
||||||
return null;
|
return NotFound(new { message = "Nest not found" });
|
||||||
|
|
||||||
var year = mostRecent.DateProgrammed!.Value.Year;
|
var nest = Nest.Load(nestFile);
|
||||||
return GetNestPath(nestName, year);
|
var plates = PepHelper.GetPlates(nest);
|
||||||
|
var combined = PepHelper.CombineLikePlates(plates);
|
||||||
|
|
||||||
|
return Ok(combined);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -297,18 +263,49 @@ public class NestsController : ControllerBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetNestPath(string nestName, int year)
|
private async Task<string?> GetNestPathAsync(string nestName, int? year = null)
|
||||||
{
|
{
|
||||||
var fileName = nestName + ".zip";
|
// If year is specified, look directly in that year's directory
|
||||||
var filePath = Path.Combine(_nestDirectory, year.ToString(), fileName);
|
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))
|
// Older nests use .zip in year subdirectory
|
||||||
return filePath;
|
var zipPath = Path.Combine(_nestDirectory, year.Value.ToString(), nestName + ".zip");
|
||||||
|
if (System.IO.File.Exists(zipPath))
|
||||||
|
return zipPath;
|
||||||
|
|
||||||
fileName = nestName + ".pep";
|
return null;
|
||||||
filePath = Path.Combine(_nestDirectory, year.ToString(), fileName);
|
}
|
||||||
|
|
||||||
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<NestDetails> GetNestDetailsAsync(string nestFilePath)
|
private async Task<NestDetails> GetNestDetailsAsync(string nestFilePath)
|
||||||
|
|||||||
Reference in New Issue
Block a user