Release v1.3.0: Multi-source downloads, audio analyzer resilience, mobile improvements
Major Features: - Multi-source download system (Soulseek/Lidarr with fallback) - Configurable enrichment speed control (1-5x) - Mobile touch drag support for seek sliders - iOS PWA media controls (Control Center, Lock Screen) - Artist name alias resolution via Last.fm - Circuit breaker pattern for audio analysis Critical Fixes: - Audio analyzer stability (non-ASCII, BrokenProcessPool, OOM) - Discovery system race conditions and import failures - Radio decade categorization using originalYear - LastFM API response normalization - Mood bucket infinite loop prevention Security: - Bull Board admin authentication - Lidarr webhook signature verification - JWT token expiration and refresh - Encryption key validation on startup Closes #2, #6, #9, #13, #21, #26, #31, #34, #35, #37, #40, #43
This commit is contained in:
@@ -664,6 +664,21 @@ class ApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
async cleanupStaleJobs() {
|
||||
return this.request<{
|
||||
success: boolean;
|
||||
cleaned: {
|
||||
discoveryBatches: { cleaned: number; ids: string[] };
|
||||
downloadJobs: { cleaned: number; ids: string[] };
|
||||
spotifyImportJobs: { cleaned: number; ids: string[] };
|
||||
bullQueues: { cleaned: number; queues: string[] };
|
||||
};
|
||||
totalCleaned: number;
|
||||
}>("/settings/cleanup-stale-jobs", {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
|
||||
// System Settings Tests
|
||||
async testLidarr(url: string, apiKey: string) {
|
||||
return this.request<any>("/system-settings/test-lidarr", {
|
||||
@@ -686,10 +701,10 @@ class ApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
async testLastfm(apiKey: string, apiSecret: string) {
|
||||
async testLastfm(apiKey: string) {
|
||||
return this.request<any>("/system-settings/test-lastfm", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ apiKey, apiSecret }),
|
||||
body: JSON.stringify({ lastfmApiKey: apiKey }),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1347,6 +1362,20 @@ class ApiClient {
|
||||
);
|
||||
}
|
||||
|
||||
async syncLibraryEnrichment() {
|
||||
return this.request<{
|
||||
message: string;
|
||||
description: string;
|
||||
result: {
|
||||
artists: number;
|
||||
tracks: number;
|
||||
audioQueued: number;
|
||||
};
|
||||
}>("/enrichment/sync", {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
|
||||
async getEnrichmentProgress() {
|
||||
return this.request<{
|
||||
artists: {
|
||||
@@ -1423,6 +1452,27 @@ class ApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
async resetArtistMetadata(artistId: string) {
|
||||
return this.request<{ message: string; artist: any }>(
|
||||
`/enrichment/artists/${artistId}/reset`,
|
||||
{ method: "POST" }
|
||||
);
|
||||
}
|
||||
|
||||
async resetAlbumMetadata(albumId: string) {
|
||||
return this.request<{ message: string; album: any }>(
|
||||
`/enrichment/albums/${albumId}/reset`,
|
||||
{ method: "POST" }
|
||||
);
|
||||
}
|
||||
|
||||
async resetTrackMetadata(trackId: string) {
|
||||
return this.request<{ message: string; track: any }>(
|
||||
`/enrichment/tracks/${trackId}/reset`,
|
||||
{ method: "POST" }
|
||||
);
|
||||
}
|
||||
|
||||
// Homepage
|
||||
async getHomepageGenres(limit = 4) {
|
||||
return this.request<any[]>(`/homepage/genres?limit=${limit}`);
|
||||
|
||||
Reference in New Issue
Block a user