Move admin endpoints to internal port 5275 for security

This commit is contained in:
2026-02-04 22:55:21 -05:00
parent 07844cc9c5
commit 4b289e4ddd
2 changed files with 278 additions and 276 deletions

View File

@@ -2015,6 +2015,284 @@ public class AdminController : ControllerBase
GC.Collect(2, GCCollectionMode.Optimized, blocking: false);
}
}
#region Spotify Admin Endpoints
/// <summary>
/// Manual trigger endpoint to force fetch Spotify missing tracks.
/// </summary>
[HttpGet("spotify/sync")]
public async Task<IActionResult> TriggerSpotifySync([FromServices] IEnumerable<IHostedService> hostedServices)
{
try
{
if (!_spotifyImportSettings.Enabled)
{
return BadRequest(new { error = "Spotify Import is not enabled" });
}
_logger.LogInformation("Manual Spotify sync triggered via admin endpoint");
// Find the SpotifyMissingTracksFetcher service
var fetcherService = hostedServices
.OfType<allstarr.Services.Spotify.SpotifyMissingTracksFetcher>()
.FirstOrDefault();
if (fetcherService == null)
{
return BadRequest(new { error = "SpotifyMissingTracksFetcher service not found" });
}
// Trigger the sync in background
_ = Task.Run(async () =>
{
try
{
// Use reflection to call the private ExecuteOnceAsync method
var method = fetcherService.GetType().GetMethod("ExecuteOnceAsync",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (method != null)
{
await (Task)method.Invoke(fetcherService, new object[] { CancellationToken.None })!;
_logger.LogInformation("Manual Spotify sync completed successfully");
}
else
{
_logger.LogError("Could not find ExecuteOnceAsync method on SpotifyMissingTracksFetcher");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during manual Spotify sync");
}
});
return Ok(new {
message = "Spotify sync started in background",
timestamp = DateTime.UtcNow
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error triggering Spotify sync");
return StatusCode(500, new { error = "Internal server error" });
}
}
/// <summary>
/// Manual trigger endpoint to force Spotify track matching.
/// </summary>
[HttpGet("spotify/match")]
public async Task<IActionResult> TriggerSpotifyMatch([FromServices] IEnumerable<IHostedService> hostedServices)
{
try
{
if (!_spotifyApiSettings.Enabled)
{
return BadRequest(new { error = "Spotify API is not enabled" });
}
_logger.LogInformation("Manual Spotify track matching triggered via admin endpoint");
// Find the SpotifyTrackMatchingService
var matchingService = hostedServices
.OfType<allstarr.Services.Spotify.SpotifyTrackMatchingService>()
.FirstOrDefault();
if (matchingService == null)
{
return BadRequest(new { error = "SpotifyTrackMatchingService not found" });
}
// Trigger matching in background
_ = Task.Run(async () =>
{
try
{
// Use reflection to call the private ExecuteOnceAsync method
var method = matchingService.GetType().GetMethod("ExecuteOnceAsync",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (method != null)
{
await (Task)method.Invoke(matchingService, new object[] { CancellationToken.None })!;
_logger.LogInformation("Manual Spotify track matching completed successfully");
}
else
{
_logger.LogError("Could not find ExecuteOnceAsync method on SpotifyTrackMatchingService");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during manual Spotify track matching");
}
});
return Ok(new {
message = "Spotify track matching started in background",
timestamp = DateTime.UtcNow
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error triggering Spotify track matching");
return StatusCode(500, new { error = "Internal server error" });
}
}
/// <summary>
/// Clear Spotify playlist cache to force re-matching.
/// </summary>
[HttpPost("spotify/clear-cache")]
public async Task<IActionResult> ClearSpotifyCache()
{
try
{
var clearedKeys = new List<string>();
// Clear Redis cache for all configured playlists
foreach (var playlist in _spotifyImportSettings.Playlists)
{
var keys = new[]
{
$"spotify:playlist:{playlist.Name}",
$"spotify:playlist:items:{playlist.Name}",
$"spotify:matched:{playlist.Name}"
};
foreach (var key in keys)
{
await _cache.DeleteAsync(key);
clearedKeys.Add(key);
}
}
_logger.LogInformation("Cleared Spotify cache for {Count} keys via admin endpoint", clearedKeys.Count);
return Ok(new {
message = "Spotify cache cleared successfully",
clearedKeys = clearedKeys,
timestamp = DateTime.UtcNow
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error clearing Spotify cache");
return StatusCode(500, new { error = "Internal server error" });
}
}
#endregion
#region Debug Endpoints
/// <summary>
/// Gets endpoint usage statistics from the log file.
/// </summary>
[HttpGet("debug/endpoint-usage")]
public async Task<IActionResult> GetEndpointUsage(
[FromQuery] int top = 100,
[FromQuery] string? since = null)
{
try
{
var logFile = "/app/cache/endpoint-usage/endpoints.csv";
if (!System.IO.File.Exists(logFile))
{
return Ok(new {
message = "No endpoint usage data available",
endpoints = new object[0]
});
}
var lines = await System.IO.File.ReadAllLinesAsync(logFile);
var usage = new Dictionary<string, int>();
DateTime? sinceDate = null;
if (!string.IsNullOrEmpty(since) && DateTime.TryParse(since, out var parsedDate))
{
sinceDate = parsedDate;
}
foreach (var line in lines.Skip(1)) // Skip header
{
var parts = line.Split(',');
if (parts.Length >= 3)
{
var timestamp = parts[0];
var endpoint = parts[1];
// Filter by date if specified
if (sinceDate.HasValue && DateTime.TryParse(timestamp, out var logDate))
{
if (logDate < sinceDate.Value)
continue;
}
usage[endpoint] = usage.GetValueOrDefault(endpoint, 0) + 1;
}
}
var topEndpoints = usage
.OrderByDescending(kv => kv.Value)
.Take(top)
.Select(kv => new { endpoint = kv.Key, count = kv.Value })
.ToArray();
return Ok(new {
totalEndpoints = usage.Count,
totalRequests = usage.Values.Sum(),
since = since,
top = top,
endpoints = topEndpoints
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting endpoint usage");
return StatusCode(500, new { error = "Internal server error" });
}
}
/// <summary>
/// Clears the endpoint usage log file.
/// </summary>
[HttpDelete("debug/endpoint-usage")]
public IActionResult ClearEndpointUsage()
{
try
{
var logFile = "/app/cache/endpoint-usage/endpoints.csv";
if (System.IO.File.Exists(logFile))
{
System.IO.File.Delete(logFile);
_logger.LogInformation("Cleared endpoint usage log via admin endpoint");
return Ok(new {
message = "Endpoint usage log cleared successfully",
timestamp = DateTime.UtcNow
});
}
else
{
return Ok(new {
message = "No endpoint usage log file found",
timestamp = DateTime.UtcNow
});
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error clearing endpoint usage log");
return StatusCode(500, new { error = "Internal server error" });
}
}
#endregion
}
public class ConfigUpdateRequest