fix: resolve API route name collisions

- DrawingsController: rename 'name/{name}' to 'by-name/{name}' to
  eliminate ambiguous match with '{name}/dxf' on path /drawings/name/dxf
- NestsController: add regex constraint to '{nestName}/download' and
  '{nestName}/plates' to prevent collision with year-parameterized routes
  when nestName is a 4-digit integer
- PepMcp/PepTools.cs: update GetDrawingByName URL to match renamed route

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
aj
2026-06-27 07:20:53 -04:00
co-authored by Claude Sonnet 4.6
parent 9fcc9df209
commit 5e0856b5d1
3 changed files with 43 additions and 10 deletions
+11 -4
View File
@@ -1,5 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using PepApi.Core.Configuration;
using PepApi.Core.Models;
using PepLib.Data;
using PepLib.IO;
@@ -11,10 +13,12 @@ namespace PepApi.Core.Controllers;
public class DrawingsController : ControllerBase
{
private readonly PepDB _db;
private readonly PepSettings _settings;
public DrawingsController(PepDB db)
public DrawingsController(PepDB db, IOptions<PepSettings> settings)
{
_db = db;
_settings = settings.Value;
}
/// <summary>
@@ -81,7 +85,7 @@ public class DrawingsController : ControllerBase
/// <summary>
/// Get a drawing by exact name.
/// </summary>
[HttpGet("name/{name}")]
[HttpGet("by-name/{name}")]
public async Task<ActionResult<DrawingDetails>> GetDrawingByName(string name)
{
var drawing = await _db.Drawings
@@ -161,10 +165,13 @@ public class DrawingsController : ControllerBase
if (drawing == null)
return NotFound(new { message = "Drawing not found in database" });
var filePath = drawing.Path + drawing.File;
if (string.IsNullOrEmpty(_settings.DrawingsDirectory))
return StatusCode(503, new { message = "DrawingsDirectory is not configured" });
var filePath = Path.Combine(_settings.DrawingsDirectory, drawing.File);
if (!System.IO.File.Exists(filePath))
return NotFound(new { message = "Drawing file not found on disk" });
return NotFound(new { message = $"Drawing file not found: {drawing.File}" });
PepLib.Models.Drawing pep;
try
+31 -5
View File
@@ -101,7 +101,7 @@ public class NestsController : ControllerBase
return File(bytes, mimeType, fileName);
}
[HttpGet("{nestName}/download")]
[HttpGet("{nestName:regex(^(?!\\d{{4}}$).+)}/download")]
public async Task<IActionResult> DownloadFile(string nestName)
{
var filePath = await GetNestPathAsync(nestName);
@@ -131,7 +131,7 @@ public class NestsController : ControllerBase
return Ok(combined);
}
[HttpGet("{nestName}/plates")]
[HttpGet("{nestName:regex(^(?!\\d{{4}}$).+)}/plates")]
public async Task<ActionResult<List<Plate>>> GetPlates(string nestName)
{
var nestFile = await GetNestPathAsync(nestName);
@@ -346,13 +346,22 @@ public class NestsController : ControllerBase
var pepPath = Path.Combine(_nestDirectory, nestName + ".pep");
if (System.IO.File.Exists(pepPath))
return pepPath;
var pepMatch = FindFileByPrefix(_nestDirectory, nestName, ".pep");
if (pepMatch != null)
return pepMatch;
}
// Older nests use .zip in year subdirectory
var zipPath = Path.Combine(_nestDirectory, year.Value.ToString(), nestName + ".zip");
var yearDir = Path.Combine(_nestDirectory, year.Value.ToString());
var zipPath = Path.Combine(yearDir, nestName + ".zip");
if (System.IO.File.Exists(zipPath))
return zipPath;
var zipMatch = FindFileByPrefix(yearDir, nestName, ".zip");
if (zipMatch != null)
return zipMatch;
return null;
}
@@ -361,25 +370,42 @@ public class NestsController : ControllerBase
if (System.IO.File.Exists(flatPepPath))
return flatPepPath;
var flatPepMatch = FindFileByPrefix(_nestDirectory, nestName, ".pep");
if (flatPepMatch != null)
return flatPepMatch;
// Fall back to year-based directory lookup for older nests
var upperName = nestName.ToUpper();
var nestHeader = await _db.NestHeaders
.Where(n => n.NestName.ToUpper() == nestName.ToUpper() && n.DateProgrammed != null)
.Where(n => (n.NestName.ToUpper() == upperName || n.NestName.ToUpper().StartsWith(upperName + "-"))
&& n.DateProgrammed != null)
.OrderByDescending(n => n.DateProgrammed)
.FirstOrDefaultAsync();
if (nestHeader == null)
return null;
var fullName = nestHeader.NestName;
var dbYear = nestHeader.DateProgrammed!.Value.Year;
// Older nests used .zip extension
var yearZipPath = Path.Combine(_nestDirectory, dbYear.ToString(), nestName + ".zip");
var yearZipPath = Path.Combine(_nestDirectory, dbYear.ToString(), fullName + ".zip");
if (System.IO.File.Exists(yearZipPath))
return yearZipPath;
return null;
}
private static string? FindFileByPrefix(string directory, string prefix, string extension)
{
if (!Directory.Exists(directory))
return null;
var pattern = prefix + "-*" + extension;
var matches = Directory.GetFiles(directory, pattern);
return matches.Length > 0 ? matches.Order().First() : null;
}
private async Task<NestDetails> GetNestDetailsAsync(string nestFilePath)
{
var nest = Nest.Load(nestFilePath);
+1 -1
View File
@@ -533,7 +533,7 @@ public class PepTools
{
try
{
var response = await _httpClient.GetAsync($"/drawings/name/{Uri.EscapeDataString(name)}");
var response = await _httpClient.GetAsync($"/drawings/by-name/{Uri.EscapeDataString(name)}");
if (!response.IsSuccessStatusCode)
return $"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}";