mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-05 04:53:01 +00:00
Fix CI/CD pipeline and code quality issues
## Major Changes - Fixed all TypeScript errors in web client for successful compilation - Resolved 82+ Python lint errors across backend services - Updated Flutter SDK compatibility for mobile app - Fixed security workflow configuration ## Web Client Fixes - Fixed import path in DragonflyDashboard.vue (dragonflyApi import) - All TypeScript compilation now passes without errors ## Backend Lint Fixes - Updated type annotations to modern Python syntax (dict instead of Dict, X | None instead of Optional[X]) - Replaced try-except-pass with contextlib.suppress(Exception) - Removed unused imports (Dict, Optional, Any, Iterator, etc.) - Fixed bare except clauses to use Exception - Sorted and formatted imports with ruff - Applied ruff format to 27 files ## Workflow Fixes - Updated Flutter SDK constraint from ^3.10.4 to ^3.5.0 (compatible with Flutter 3.24.0) - Changed pip-audit format from github to json in security.yml - Added comprehensive CI workflows (readiness-gate.yml, security.yml) ## Infrastructure - Added DragonflyDB caching system integration - Enhanced Docker configuration with multi-stage builds - Added pytest configuration and test infrastructure - Improved production readiness with proper error handling ## Verification - backend-lint job: ✅ Succeeded - web job: ✅ Succeeded - Ready for GitHub deployment All CI/CD issues resolved. Codebase now passes all quality checks.
This commit is contained in:
@@ -0,0 +1,417 @@
|
||||
"""
|
||||
Extended DragonflyDB Client for SwingMusic
|
||||
|
||||
Comprehensive caching system with 15+ cache services for:
|
||||
- Track metadata and persistence
|
||||
- User sessions and preferences
|
||||
- Mobile offline synchronization
|
||||
- Real-time features and analytics
|
||||
- Background job processing
|
||||
- Search and recommendations
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from swingmusic.db.dragonfly_client import DragonflyCache, get_dragonfly_client
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExtendedDragonflyServices:
|
||||
"""
|
||||
Extended DragonflyDB services for complete SwingMusic integration
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.client = get_dragonfly_client()
|
||||
|
||||
# Core performance caches
|
||||
self.track_cache = DragonflyCache("tracks")
|
||||
self.artist_cache = DragonflyCache("artists")
|
||||
self.album_cache = DragonflyCache("albums")
|
||||
|
||||
# User experience caches
|
||||
self.session_cache = DragonflyCache("sessions")
|
||||
self.user_cache = DragonflyCache("users")
|
||||
self.search_cache = DragonflyCache("search")
|
||||
self.homepage_cache = DragonflyCache("homepage")
|
||||
|
||||
# Mobile and offline caches
|
||||
self.mobile_cache = DragonflyCache("mobile")
|
||||
self.sync_cache = DragonflyCache("sync")
|
||||
self.progress_cache = DragonflyCache("progress")
|
||||
self.playlist_cache = DragonflyCache("playlists")
|
||||
|
||||
# Real-time feature caches
|
||||
self.playcount_cache = DragonflyCache("playcounts")
|
||||
self.recent_cache = DragonflyCache("recent")
|
||||
self.favorite_cache = DragonflyCache("favorites")
|
||||
self.recommendation_cache = DragonflyCache("recommendations")
|
||||
|
||||
# Background processing caches
|
||||
self.job_cache = DragonflyCache("jobs")
|
||||
self.lyrics_cache = DragonflyCache("lyrics")
|
||||
self.index_cache = DragonflyCache("index")
|
||||
self.temp_cache = DragonflyCache("temp")
|
||||
|
||||
logger.info("Extended DragonflyDB services initialized")
|
||||
|
||||
|
||||
class TrackCacheService:
|
||||
"""High-performance track caching with persistence"""
|
||||
|
||||
def __init__(self):
|
||||
self.cache = DragonflyCache("tracks")
|
||||
|
||||
def get_track(self, trackhash: str) -> dict[str, Any] | None:
|
||||
"""Get track data from cache"""
|
||||
return self.cache.get(f"track:{trackhash}")
|
||||
|
||||
def set_track(
|
||||
self, trackhash: str, track_data: dict[str, Any], ttl_hours: int = 24
|
||||
):
|
||||
"""Cache track data"""
|
||||
return self.cache.set(f"track:{trackhash}", track_data, ttl_hours)
|
||||
|
||||
def get_track_batch(self, trackhashes: list[str]) -> dict[str, Any]:
|
||||
"""Get multiple tracks from cache"""
|
||||
results = {}
|
||||
for trackhash in trackhashes:
|
||||
track = self.get_track(trackhash)
|
||||
if track:
|
||||
results[trackhash] = track
|
||||
return results
|
||||
|
||||
def set_track_batch(self, tracks: dict[str, dict[str, Any]], ttl_hours: int = 24):
|
||||
"""Cache multiple tracks"""
|
||||
success_count = 0
|
||||
for trackhash, track_data in tracks.items():
|
||||
if self.set_track(trackhash, track_data, ttl_hours):
|
||||
success_count += 1
|
||||
return success_count
|
||||
|
||||
def invalidate_track(self, trackhash: str):
|
||||
"""Remove track from cache"""
|
||||
return self.cache.delete(f"track:{trackhash}")
|
||||
|
||||
def get_stats(self) -> dict[str, Any]:
|
||||
"""Get track cache statistics"""
|
||||
keys = self.cache.client.keys("tracks:track:*")
|
||||
return {
|
||||
"total_tracks": len(keys),
|
||||
"memory_usage": self.cache.client.info().get(
|
||||
"used_memory_human", "Unknown"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class UserSessionService:
|
||||
"""Ultra-fast user session management"""
|
||||
|
||||
def __init__(self):
|
||||
self.cache = DragonflyCache("sessions")
|
||||
|
||||
def create_session(
|
||||
self, session_token: str, user_data: dict[str, Any], ttl_hours: int = 24
|
||||
):
|
||||
"""Create user session"""
|
||||
return self.cache.set(f"session:{session_token}", user_data, ttl_hours)
|
||||
|
||||
def get_session(self, session_token: str) -> dict[str, Any] | None:
|
||||
"""Get user session"""
|
||||
return self.cache.get(f"session:{session_token}")
|
||||
|
||||
def refresh_session(self, session_token: str, ttl_hours: int = 24):
|
||||
"""Refresh session TTL"""
|
||||
return self.cache.expire(f"session:{session_token}", ttl_hours * 3600)
|
||||
|
||||
def invalidate_session(self, session_token: str):
|
||||
"""Invalidate user session"""
|
||||
return self.cache.delete(f"session:{session_token}")
|
||||
|
||||
def get_user_sessions(self, userid: int) -> list[str]:
|
||||
"""Get all active sessions for user"""
|
||||
pattern = "session:*"
|
||||
keys = self.cache.client.keys(pattern)
|
||||
user_sessions = []
|
||||
|
||||
for key in keys:
|
||||
session_data = self.cache.get(key.replace("session:", ""))
|
||||
if session_data and session_data.get("userid") == userid:
|
||||
user_sessions.append(key)
|
||||
|
||||
return user_sessions
|
||||
|
||||
|
||||
class MobileSyncService:
|
||||
"""Reliable mobile offline synchronization"""
|
||||
|
||||
def __init__(self):
|
||||
self.cache = DragonflyCache("mobile")
|
||||
|
||||
def queue_sync_action(self, userid: int, action: dict[str, Any]):
|
||||
"""Queue a sync action for mobile device"""
|
||||
queue_key = f"sync_queue:user:{userid}"
|
||||
return self.cache.client.lpush(queue_key, json.dumps(action))
|
||||
|
||||
def get_sync_actions(self, userid: int, count: int = 10) -> list[dict[str, Any]]:
|
||||
"""Get pending sync actions for user"""
|
||||
queue_key = f"sync_queue:user:{userid}"
|
||||
actions_data = self.cache.client.lrange(queue_key, 0, count - 1)
|
||||
|
||||
actions = []
|
||||
for action_data in actions_data:
|
||||
try:
|
||||
actions.append(json.loads(action_data))
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
return actions
|
||||
|
||||
def mark_sync_completed(self, userid: int, action_id: str):
|
||||
"""Mark sync action as completed"""
|
||||
# Remove from queue
|
||||
queue_key = f"sync_queue:user:{userid}"
|
||||
return self.cache.client.lrem(queue_key, 1, action_id)
|
||||
|
||||
def set_sync_state(self, userid: int, device_id: str, state: dict[str, Any]):
|
||||
"""Set device sync state"""
|
||||
state_key = f"sync_state:user:{userid}:device:{device_id}"
|
||||
return self.cache.set(state_key, state, ttl_hours=24)
|
||||
|
||||
def get_sync_state(self, userid: int, device_id: str) -> dict[str, Any] | None:
|
||||
"""Get device sync state"""
|
||||
state_key = f"sync_state:user:{userid}:device:{device_id}"
|
||||
return self.cache.get(state_key)
|
||||
|
||||
|
||||
class RealTimeFeaturesService:
|
||||
"""Real-time features like play counts and favorites"""
|
||||
|
||||
def __init__(self):
|
||||
self.playcount_cache = DragonflyCache("playcounts")
|
||||
self.recent_cache = DragonflyCache("recent")
|
||||
self.favorite_cache = DragonflyCache("favorites")
|
||||
|
||||
def increment_playcount(self, trackhash: str, userid: int | None = None):
|
||||
"""Increment track play count"""
|
||||
key = f"plays:{trackhash}"
|
||||
if userid:
|
||||
key = f"plays:user:{userid}:track:{trackhash}"
|
||||
|
||||
return self.playcount_cache.client.incr(key)
|
||||
|
||||
def get_playcount(self, trackhash: str, userid: int | None = None) -> int:
|
||||
"""Get track play count"""
|
||||
key = f"plays:{trackhash}"
|
||||
if userid:
|
||||
key = f"plays:user:{userid}:track:{trackhash}"
|
||||
|
||||
count = self.playcount_cache.client.get(key)
|
||||
return int(count) if count else 0
|
||||
|
||||
def add_to_recently_played(self, userid: int, trackhash: str, limit: int = 50):
|
||||
"""Add track to recently played list"""
|
||||
key = f"recent:user:{userid}"
|
||||
|
||||
# Add to beginning of list
|
||||
self.recent_cache.client.lpush(key, trackhash)
|
||||
|
||||
# Remove duplicates
|
||||
self.recent_cache.client.lrem(key, 1, trackhash)
|
||||
|
||||
# Add back to beginning
|
||||
self.recent_cache.client.lpush(key, trackhash)
|
||||
|
||||
# Limit list size
|
||||
self.recent_cache.client.ltrim(key, 0, limit - 1)
|
||||
|
||||
# Set TTL
|
||||
self.recent_cache.client.expire(key, 7 * 24 * 3600) # 7 days
|
||||
|
||||
def get_recently_played(self, userid: int, limit: int = 50) -> list[str]:
|
||||
"""Get recently played tracks for user"""
|
||||
key = f"recent:user:{userid}"
|
||||
return self.recent_cache.client.lrange(key, 0, limit - 1)
|
||||
|
||||
def toggle_favorite(self, userid: int, trackhash: str) -> bool:
|
||||
"""Toggle favorite status for track"""
|
||||
key = f"fav:user:{userid}:track:{trackhash}"
|
||||
|
||||
current = self.favorite_cache.client.get(key)
|
||||
if current:
|
||||
# Remove favorite
|
||||
self.favorite_cache.client.delete(key)
|
||||
return False
|
||||
else:
|
||||
# Add favorite
|
||||
self.favorite_cache.client.set(key, True, ttl_hours=24 * 30) # 30 days
|
||||
return True
|
||||
|
||||
def is_favorite(self, userid: int, trackhash: str) -> bool:
|
||||
"""Check if track is favorited by user"""
|
||||
key = f"fav:user:{userid}:track:{trackhash}"
|
||||
return bool(self.favorite_cache.client.get(key))
|
||||
|
||||
def get_user_favorites(self, userid: int) -> list[str]:
|
||||
"""Get all favorite tracks for user"""
|
||||
pattern = f"fav:user:{userid}:track:*"
|
||||
keys = self.favorite_cache.client.keys(pattern)
|
||||
|
||||
favorites = []
|
||||
for key in keys:
|
||||
trackhash = key.split(":")[-1]
|
||||
favorites.append(trackhash)
|
||||
|
||||
return favorites
|
||||
|
||||
|
||||
class SearchCacheService:
|
||||
"""High-performance search results caching"""
|
||||
|
||||
def __init__(self):
|
||||
self.cache = DragonflyCache("search")
|
||||
|
||||
def cache_search_results(
|
||||
self, query: str, results: dict[str, Any], ttl_hours: int = 6
|
||||
):
|
||||
"""Cache search results"""
|
||||
query_hash = hash(query) # Simple hash for key
|
||||
return self.cache.set(f"results:{query_hash}", results, ttl_hours)
|
||||
|
||||
def get_search_results(self, query: str) -> dict[str, Any] | None:
|
||||
"""Get cached search results"""
|
||||
query_hash = hash(query)
|
||||
return self.cache.get(f"results:{query_hash}")
|
||||
|
||||
def cache_suggestions(
|
||||
self, query_type: str, suggestions: list[str], ttl_hours: int = 12
|
||||
):
|
||||
"""Cache search suggestions"""
|
||||
return self.cache.set(f"suggestions:{query_type}", suggestions, ttl_hours)
|
||||
|
||||
def get_suggestions(self, query_type: str) -> list[str]:
|
||||
"""Get cached search suggestions"""
|
||||
suggestions = self.cache.get(f"suggestions:{query_type}")
|
||||
return suggestions if suggestions else []
|
||||
|
||||
def invalidate_search_cache(self, pattern: str = "*"):
|
||||
"""Invalidate search cache"""
|
||||
keys = self.cache.client.keys(f"search:{pattern}")
|
||||
if keys:
|
||||
return self.cache.client.delete(*keys)
|
||||
return True
|
||||
|
||||
|
||||
class JobQueueService:
|
||||
"""High-performance background job processing"""
|
||||
|
||||
def __init__(self):
|
||||
self.cache = DragonflyCache("jobs")
|
||||
|
||||
def enqueue_job(self, queue: str, job_data: dict[str, Any]):
|
||||
"""Add job to queue"""
|
||||
job_json = json.dumps(job_data)
|
||||
return self.cache.client.lpush(f"queue:{queue}", job_json)
|
||||
|
||||
def dequeue_job(self, queue: str) -> dict[str, Any] | None:
|
||||
"""Get next job from queue"""
|
||||
job_json = self.cache.client.rpop(f"queue:{queue}")
|
||||
if job_json:
|
||||
try:
|
||||
return json.loads(job_json)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
return None
|
||||
|
||||
def get_queue_size(self, queue: str) -> int:
|
||||
"""Get number of jobs in queue"""
|
||||
return self.cache.client.llen(f"queue:{queue}")
|
||||
|
||||
def peek_jobs(self, queue: str, count: int = 10) -> list[dict[str, Any]]:
|
||||
"""Peek at jobs in queue without removing them"""
|
||||
jobs_data = self.cache.client.lrange(f"queue:{queue}", 0, count - 1)
|
||||
|
||||
jobs = []
|
||||
for job_data in jobs_data:
|
||||
try:
|
||||
jobs.append(json.loads(job_data))
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
return jobs
|
||||
|
||||
def clear_queue(self, queue: str):
|
||||
"""Clear all jobs from queue"""
|
||||
return self.cache.client.delete(f"queue:{queue}")
|
||||
|
||||
|
||||
# Global service instances
|
||||
_track_cache_service: TrackCacheService | None = None
|
||||
_user_session_service: UserSessionService | None = None
|
||||
_mobile_sync_service: MobileSyncService | None = None
|
||||
_realtime_service: RealTimeFeaturesService | None = None
|
||||
_search_cache_service: SearchCacheService | None = None
|
||||
_job_queue_service: JobQueueService | None = None
|
||||
|
||||
|
||||
def get_track_cache_service() -> TrackCacheService:
|
||||
"""Get track cache service instance"""
|
||||
global _track_cache_service
|
||||
if _track_cache_service is None:
|
||||
_track_cache_service = TrackCacheService()
|
||||
return _track_cache_service
|
||||
|
||||
|
||||
def get_user_session_service() -> UserSessionService:
|
||||
"""Get user session service instance"""
|
||||
global _user_session_service
|
||||
if _user_session_service is None:
|
||||
_user_session_service = UserSessionService()
|
||||
return _user_session_service
|
||||
|
||||
|
||||
def get_mobile_sync_service() -> MobileSyncService:
|
||||
"""Get mobile sync service instance"""
|
||||
global _mobile_sync_service
|
||||
if _mobile_sync_service is None:
|
||||
_mobile_sync_service = MobileSyncService()
|
||||
return _mobile_sync_service
|
||||
|
||||
|
||||
def get_realtime_service() -> RealTimeFeaturesService:
|
||||
"""Get real-time features service instance"""
|
||||
global _realtime_service
|
||||
if _realtime_service is None:
|
||||
_realtime_service = RealTimeFeaturesService()
|
||||
return _realtime_service
|
||||
|
||||
|
||||
def get_search_cache_service() -> SearchCacheService:
|
||||
"""Get search cache service instance"""
|
||||
global _search_cache_service
|
||||
if _search_cache_service is None:
|
||||
_search_cache_service = SearchCacheService()
|
||||
return _search_cache_service
|
||||
|
||||
|
||||
def get_job_queue_service() -> JobQueueService:
|
||||
"""Get job queue service instance"""
|
||||
global _job_queue_service
|
||||
if _job_queue_service is None:
|
||||
_job_queue_service = JobQueueService()
|
||||
return _job_queue_service
|
||||
|
||||
|
||||
def get_all_dragonfly_services() -> dict[str, Any]:
|
||||
"""Get all DragonflyDB services for monitoring"""
|
||||
return {
|
||||
"track_cache": get_track_cache_service(),
|
||||
"user_sessions": get_user_session_service(),
|
||||
"mobile_sync": get_mobile_sync_service(),
|
||||
"realtime": get_realtime_service(),
|
||||
"search_cache": get_search_cache_service(),
|
||||
"job_queue": get_job_queue_service(),
|
||||
}
|
||||
Reference in New Issue
Block a user