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:
@@ -1,5 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using PepApi.Core.Configuration;
|
||||||
using PepApi.Core.Models;
|
using PepApi.Core.Models;
|
||||||
using PepLib.Data;
|
using PepLib.Data;
|
||||||
using PepLib.IO;
|
using PepLib.IO;
|
||||||
@@ -11,10 +13,12 @@ namespace PepApi.Core.Controllers;
|
|||||||
public class DrawingsController : ControllerBase
|
public class DrawingsController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly PepDB _db;
|
private readonly PepDB _db;
|
||||||
|
private readonly PepSettings _settings;
|
||||||
|
|
||||||
public DrawingsController(PepDB db)
|
public DrawingsController(PepDB db, IOptions<PepSettings> settings)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
|
_settings = settings.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -81,7 +85,7 @@ public class DrawingsController : ControllerBase
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a drawing by exact name.
|
/// Get a drawing by exact name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HttpGet("name/{name}")]
|
[HttpGet("by-name/{name}")]
|
||||||
public async Task<ActionResult<DrawingDetails>> GetDrawingByName(string name)
|
public async Task<ActionResult<DrawingDetails>> GetDrawingByName(string name)
|
||||||
{
|
{
|
||||||
var drawing = await _db.Drawings
|
var drawing = await _db.Drawings
|
||||||
@@ -161,10 +165,13 @@ public class DrawingsController : ControllerBase
|
|||||||
if (drawing == null)
|
if (drawing == null)
|
||||||
return NotFound(new { message = "Drawing not found in database" });
|
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))
|
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;
|
PepLib.Models.Drawing pep;
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public class NestsController : ControllerBase
|
|||||||
return File(bytes, mimeType, fileName);
|
return File(bytes, mimeType, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{nestName}/download")]
|
[HttpGet("{nestName:regex(^(?!\\d{{4}}$).+)}/download")]
|
||||||
public async Task<IActionResult> DownloadFile(string nestName)
|
public async Task<IActionResult> DownloadFile(string nestName)
|
||||||
{
|
{
|
||||||
var filePath = await GetNestPathAsync(nestName);
|
var filePath = await GetNestPathAsync(nestName);
|
||||||
@@ -131,7 +131,7 @@ public class NestsController : ControllerBase
|
|||||||
return Ok(combined);
|
return Ok(combined);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{nestName}/plates")]
|
[HttpGet("{nestName:regex(^(?!\\d{{4}}$).+)}/plates")]
|
||||||
public async Task<ActionResult<List<Plate>>> GetPlates(string nestName)
|
public async Task<ActionResult<List<Plate>>> GetPlates(string nestName)
|
||||||
{
|
{
|
||||||
var nestFile = await GetNestPathAsync(nestName);
|
var nestFile = await GetNestPathAsync(nestName);
|
||||||
@@ -346,13 +346,22 @@ public class NestsController : ControllerBase
|
|||||||
var pepPath = Path.Combine(_nestDirectory, nestName + ".pep");
|
var pepPath = Path.Combine(_nestDirectory, nestName + ".pep");
|
||||||
if (System.IO.File.Exists(pepPath))
|
if (System.IO.File.Exists(pepPath))
|
||||||
return pepPath;
|
return pepPath;
|
||||||
|
|
||||||
|
var pepMatch = FindFileByPrefix(_nestDirectory, nestName, ".pep");
|
||||||
|
if (pepMatch != null)
|
||||||
|
return pepMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Older nests use .zip in year subdirectory
|
// 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))
|
if (System.IO.File.Exists(zipPath))
|
||||||
return zipPath;
|
return zipPath;
|
||||||
|
|
||||||
|
var zipMatch = FindFileByPrefix(yearDir, nestName, ".zip");
|
||||||
|
if (zipMatch != null)
|
||||||
|
return zipMatch;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,25 +370,42 @@ public class NestsController : ControllerBase
|
|||||||
if (System.IO.File.Exists(flatPepPath))
|
if (System.IO.File.Exists(flatPepPath))
|
||||||
return flatPepPath;
|
return flatPepPath;
|
||||||
|
|
||||||
|
var flatPepMatch = FindFileByPrefix(_nestDirectory, nestName, ".pep");
|
||||||
|
if (flatPepMatch != null)
|
||||||
|
return flatPepMatch;
|
||||||
|
|
||||||
// Fall back to year-based directory lookup for older nests
|
// Fall back to year-based directory lookup for older nests
|
||||||
|
var upperName = nestName.ToUpper();
|
||||||
var nestHeader = await _db.NestHeaders
|
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)
|
.OrderByDescending(n => n.DateProgrammed)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
if (nestHeader == null)
|
if (nestHeader == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
var fullName = nestHeader.NestName;
|
||||||
var dbYear = nestHeader.DateProgrammed!.Value.Year;
|
var dbYear = nestHeader.DateProgrammed!.Value.Year;
|
||||||
|
|
||||||
// Older nests used .zip extension
|
// 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))
|
if (System.IO.File.Exists(yearZipPath))
|
||||||
return yearZipPath;
|
return yearZipPath;
|
||||||
|
|
||||||
return null;
|
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)
|
private async Task<NestDetails> GetNestDetailsAsync(string nestFilePath)
|
||||||
{
|
{
|
||||||
var nest = Nest.Load(nestFilePath);
|
var nest = Nest.Load(nestFilePath);
|
||||||
|
|||||||
+1
-1
@@ -533,7 +533,7 @@ public class PepTools
|
|||||||
{
|
{
|
||||||
try
|
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)
|
if (!response.IsSuccessStatusCode)
|
||||||
return $"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}";
|
return $"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}";
|
||||||
|
|||||||
Reference in New Issue
Block a user