mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
Add delete button for manual track mappings
- Added DELETE /api/admin/mappings/tracks endpoint - Removes mapping from JSON file and Redis cache - Deletes file if it becomes empty after removal - Added 'Remove' button to each mapping in web UI - Enhanced confirm dialog explaining consequences for both local and external mappings - Supports removing both Jellyfin (local) and external provider mappings - Allows phasing out local mappings in favor of Spotify Import plugin
This commit is contained in:
@@ -2957,6 +2957,66 @@ public class AdminController : ControllerBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete a manual track mapping
|
||||||
|
/// </summary>
|
||||||
|
[HttpDelete("mappings/tracks")]
|
||||||
|
public async Task<IActionResult> DeleteTrackMapping([FromQuery] string playlist, [FromQuery] string spotifyId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(playlist) || string.IsNullOrEmpty(spotifyId))
|
||||||
|
{
|
||||||
|
return BadRequest(new { error = "playlist and spotifyId parameters are required" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mappingsDir = "/app/cache/mappings";
|
||||||
|
var safeName = string.Join("_", playlist.Split(Path.GetInvalidFileNameChars()));
|
||||||
|
var filePath = Path.Combine(mappingsDir, $"{safeName}_mappings.json");
|
||||||
|
|
||||||
|
if (!System.IO.File.Exists(filePath))
|
||||||
|
{
|
||||||
|
return NotFound(new { error = "Mapping file not found for playlist" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load existing mappings
|
||||||
|
var json = await System.IO.File.ReadAllTextAsync(filePath);
|
||||||
|
var mappings = JsonSerializer.Deserialize<Dictionary<string, ManualMappingEntry>>(json);
|
||||||
|
|
||||||
|
if (mappings == null || !mappings.ContainsKey(spotifyId))
|
||||||
|
{
|
||||||
|
return NotFound(new { error = "Mapping not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the mapping
|
||||||
|
mappings.Remove(spotifyId);
|
||||||
|
|
||||||
|
// Save back to file (or delete file if empty)
|
||||||
|
if (mappings.Count == 0)
|
||||||
|
{
|
||||||
|
System.IO.File.Delete(filePath);
|
||||||
|
_logger.LogInformation("🗑️ Deleted empty mapping file for playlist {Playlist}", playlist);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var updatedJson = JsonSerializer.Serialize(mappings, new JsonSerializerOptions { WriteIndented = true });
|
||||||
|
await System.IO.File.WriteAllTextAsync(filePath, updatedJson);
|
||||||
|
_logger.LogInformation("🗑️ Deleted mapping: {Playlist} - {SpotifyId}", playlist, spotifyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also remove from Redis cache
|
||||||
|
var cacheKey = $"manual:mapping:{playlist}:{spotifyId}";
|
||||||
|
await _cache.DeleteAsync(cacheKey);
|
||||||
|
|
||||||
|
return Ok(new { success = true, message = "Mapping deleted successfully" });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to delete track mapping for {Playlist} - {SpotifyId}", playlist, spotifyId);
|
||||||
|
return StatusCode(500, new { error = "Failed to delete track mapping" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test Spotify lyrics API by fetching lyrics for a specific Spotify track ID
|
/// Test Spotify lyrics API by fetching lyrics for a specific Spotify track ID
|
||||||
/// Example: GET /api/admin/lyrics/spotify/test?trackId=3yII7UwgLF6K5zW3xad3MP
|
/// Example: GET /api/admin/lyrics/spotify/test?trackId=3yII7UwgLF6K5zW3xad3MP
|
||||||
|
|||||||
@@ -710,11 +710,12 @@
|
|||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th>Target</th>
|
<th>Target</th>
|
||||||
<th>Created</th>
|
<th>Created</th>
|
||||||
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="mappings-table-body">
|
<tbody id="mappings-table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" class="loading">
|
<td colspan="6" class="loading">
|
||||||
<span class="spinner"></span> Loading mappings...
|
<span class="spinner"></span> Loading mappings...
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -1395,7 +1396,7 @@
|
|||||||
const tbody = document.getElementById('mappings-table-body');
|
const tbody = document.getElementById('mappings-table-body');
|
||||||
|
|
||||||
if (data.mappings.length === 0) {
|
if (data.mappings.length === 0) {
|
||||||
tbody.innerHTML = '<tr><td colspan="5" style="text-align:center;color:var(--text-secondary);padding:40px;">No manual mappings found.</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center;color:var(--text-secondary);padding:40px;">No manual mappings found.</td></tr>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1419,6 +1420,9 @@
|
|||||||
<td>${typeBadge}</td>
|
<td>${typeBadge}</td>
|
||||||
<td>${targetDisplay}</td>
|
<td>${targetDisplay}</td>
|
||||||
<td style="color:var(--text-secondary);font-size:0.85rem;">${createdDate}</td>
|
<td style="color:var(--text-secondary);font-size:0.85rem;">${createdDate}</td>
|
||||||
|
<td>
|
||||||
|
<button class="danger" style="padding:4px 12px;font-size:0.8rem;" onclick="deleteTrackMapping('${escapeHtml(m.playlist)}', '${m.spotifyId}')">Remove</button>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
@@ -1428,6 +1432,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteTrackMapping(playlist, spotifyId) {
|
||||||
|
if (!confirm(`Remove manual mapping for ${spotifyId} in playlist "${playlist}"?\n\nThis will:\n• Delete the manual mapping from the cache\n• Allow the track to be matched automatically again\n• For local (Jellyfin) tracks: Stop injecting locally if now available via Spotify Import plugin\n• For external tracks: Allow re-matching with potentially better results\n\nThis action cannot be undone.`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/admin/mappings/tracks?playlist=${encodeURIComponent(playlist)}&spotifyId=${encodeURIComponent(spotifyId)}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
showToast('Mapping removed successfully', 'success');
|
||||||
|
await fetchTrackMappings();
|
||||||
|
} else {
|
||||||
|
const error = await res.json();
|
||||||
|
showToast(error.error || 'Failed to remove mapping', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to delete mapping:', error);
|
||||||
|
showToast('Failed to remove mapping', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchConfig() {
|
async function fetchConfig() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/admin/config');
|
const res = await fetch('/api/admin/config');
|
||||||
|
|||||||
Reference in New Issue
Block a user