-
-
Notifications
You must be signed in to change notification settings - Fork 10
feat: enhance catalog and recommendation services with Redis caching #88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 7 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
32e11c4
feat: enhance catalog and recommendation services with Redis caching
TimilsinaBimal a136e27
feat: enhance dynamic catalog building with Redis caching for library…
TimilsinaBimal 05cfce5
feat: refactor manifest handling and introduce ManifestService for im…
TimilsinaBimal 88f8ce9
feat: refactor profile caching logic to streamline profile and watche…
TimilsinaBimal 7cc960a
feat: refactor catalog updater to utilize ManifestService for library…
TimilsinaBimal 784310e
feat: implement UserCacheService for centralized caching of library i…
TimilsinaBimal c2b51d8
feat: enhance catalog caching by integrating UserCacheService for imp…
TimilsinaBimal 95b0ecb
feat: enhance catalog caching with configurable TTL and improved toke…
TimilsinaBimal d4b673a
feat: streamline catalog retrieval by removing redundant caching logi…
TimilsinaBimal 3535983
Update app/services/recommendation/catalog_service.py
TimilsinaBimal 60fbd0c
feat: improve token logging in UserCacheService by redacting sensitiv…
TimilsinaBimal 6e79931
refactor: update cache_profile_and_watched_sets to return profile and…
TimilsinaBimal 5e47066
Update app/services/user_cache.py
TimilsinaBimal 3edf23f
Update app/services/manifest.py
TimilsinaBimal c868368
Update app/services/redis_service.py
TimilsinaBimal File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,147 +1,20 @@ | ||
| from fastapi import HTTPException | ||
| from fastapi.routing import APIRouter | ||
| from loguru import logger | ||
|
|
||
| from app.core.config import settings | ||
| from app.core.settings import UserSettings | ||
| from app.core.version import __version__ | ||
| from app.services.catalog import DynamicCatalogService | ||
| from app.services.catalog_updater import get_config_id | ||
| from app.services.stremio.service import StremioBundle | ||
| from app.services.token_store import token_store | ||
| from app.services.translation import translation_service | ||
| from app.services.manifest import manifest_service | ||
|
|
||
| router = APIRouter() | ||
|
|
||
|
|
||
| def get_base_manifest(): | ||
| return { | ||
| "id": settings.ADDON_ID, | ||
| "version": __version__, | ||
| "name": settings.ADDON_NAME, | ||
| "description": "Movie and series recommendations based on your Stremio library.", | ||
| "logo": "https://raw.githubusercontent.com/TimilsinaBimal/Watchly/refs/heads/main/app/static/logo.png", | ||
| "background": ("https://raw.githubusercontent.com/TimilsinaBimal/Watchly/refs/heads/main/app/static/cover.png"), | ||
| "resources": ["catalog"], | ||
| "types": ["movie", "series"], | ||
| "idPrefixes": ["tt"], | ||
| "catalogs": [], | ||
| "behaviorHints": {"configurable": True, "configurationRequired": False}, | ||
| "stremioAddonsConfig": { | ||
| "issuer": "https://stremio-addons.net", | ||
| "signature": ( | ||
| "eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0..WSrhzzlj1TuDycD6QoVLuA.Dzmxzr4y83uqQF15r4tC1bB9-vtZRh1Rvy4BqgDYxu91c2esiJuov9KnnI_cboQCgZS7hjwnIqRSlQ-jEyGwXHHRerh9QklyfdxpXqNUyBgTWFzDOVdVvDYJeM_tGMmR.sezAChlWGV7lNS-t9HWB6A" # noqa | ||
| ), | ||
| }, | ||
| } | ||
|
|
||
|
|
||
| async def build_dynamic_catalogs( | ||
| bundle: StremioBundle, auth_key: str, user_settings: UserSettings | None | ||
| ) -> list[dict]: | ||
| # Fetch library using bundle directly | ||
| if not user_settings: | ||
| logger.error("User settings not found. Please reconfigure the addon.") | ||
| raise HTTPException(status_code=401, detail="User settings not found. Please reconfigure the addon.") | ||
|
|
||
| library_items = await bundle.library.get_library_items(auth_key) | ||
| dynamic_catalog_service = DynamicCatalogService( | ||
| language=user_settings.language, | ||
| ) | ||
| return await dynamic_catalog_service.get_dynamic_catalogs(library_items, user_settings) | ||
|
|
||
|
|
||
| async def _manifest_handler(token: str): | ||
| # response.headers["Cache-Control"] = "public, max-age=300" # 5 minutes | ||
| if not token: | ||
| raise HTTPException(status_code=401, detail="Missing token. Please reconfigure the addon.") | ||
|
|
||
| user_settings = None | ||
| try: | ||
| creds = await token_store.get_user_data(token) | ||
| if creds and creds.get("settings"): | ||
| user_settings = UserSettings(**creds["settings"]) | ||
| except Exception as e: | ||
| logger.error(f"[{token}] Error loading user data from token store: {e}") | ||
| raise HTTPException(status_code=401, detail="Invalid token session. Please reconfigure.") | ||
|
|
||
| if not creds: | ||
| raise HTTPException(status_code=401, detail="Token not found. Please reconfigure the addon.") | ||
|
|
||
| base_manifest = get_base_manifest() | ||
|
|
||
| bundle = StremioBundle() | ||
| fetched_catalogs = [] | ||
| try: | ||
| # Resolve Auth Key (with potential fallback to login) | ||
| auth_key = creds.get("authKey") | ||
| email = creds.get("email") | ||
| password = creds.get("password") | ||
|
|
||
| is_valid = False | ||
| if auth_key: | ||
| try: | ||
| await bundle.auth.get_user_info(auth_key) | ||
| is_valid = True | ||
| except Exception as e: | ||
| logger.debug(f"Auth key check failed for {email or 'unknown'}: {e}") | ||
| pass | ||
|
|
||
| if not is_valid and email and password: | ||
| try: | ||
| auth_key = await bundle.auth.login(email, password) | ||
| # Update store | ||
| creds["authKey"] = auth_key | ||
| await token_store.update_user_data(token, creds) | ||
| except Exception as e: | ||
| logger.error(f"Failed to refresh auth key during manifest fetch: {e}") | ||
|
|
||
| if auth_key: | ||
| fetched_catalogs = await build_dynamic_catalogs( | ||
| bundle, | ||
| auth_key, | ||
| user_settings, | ||
| ) | ||
| except Exception as e: | ||
| logger.exception(f"[{token}] Dynamic catalog build failed: {e}") | ||
| fetched_catalogs = [] | ||
| finally: | ||
| await bundle.close() | ||
|
|
||
| all_catalogs = [c.copy() for c in base_manifest["catalogs"]] + [c.copy() for c in fetched_catalogs] | ||
|
|
||
| translated_catalogs = [] | ||
|
|
||
| # translate to target language | ||
| if user_settings and user_settings.language: | ||
| for cat in all_catalogs: | ||
| if cat.get("name"): | ||
| try: | ||
| cat["name"] = await translation_service.translate(cat["name"], user_settings.language) | ||
| except Exception as e: | ||
| logger.warning(f"Failed to translate catalog name '{cat.get('name')}': {e}") | ||
| translated_catalogs.append(cat) | ||
| else: | ||
| translated_catalogs = all_catalogs | ||
|
|
||
| if user_settings: | ||
| order_map = {c.id: i for i, c in enumerate(user_settings.catalogs)} | ||
| translated_catalogs.sort(key=lambda x: order_map.get(get_config_id(x), 999)) | ||
|
|
||
| if translated_catalogs: | ||
| base_manifest["catalogs"] = translated_catalogs | ||
|
|
||
| return base_manifest | ||
|
|
||
|
|
||
| @router.get("/manifest.json") | ||
| async def manifest(): | ||
| manifest = get_base_manifest() | ||
| """Get base manifest for unauthenticated users.""" | ||
| manifest = manifest_service.get_base_manifest() | ||
| # since user is not logged in, return empty catalogs | ||
| manifest["catalogs"] = [] | ||
| return manifest | ||
|
|
||
|
|
||
| @router.get("/{token}/manifest.json") | ||
| async def manifest_token(token: str): | ||
| return await _manifest_handler(token) | ||
| """Get manifest for authenticated user.""" | ||
| return await manifest_service.get_manifest_for_token(token) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,15 @@ | ||
| RECOMMENDATIONS_CATALOG_NAME: str = "Top Picks For You" | ||
| DEFAULT_MIN_ITEMS: int = 8 | ||
| DEFAULT_CATALOG_LIMIT = 20 | ||
| DEFAULT_CATALOG_LIMIT: int = 20 | ||
|
|
||
| DEFAULT_CONCURRENCY_LIMIT: int = 30 | ||
|
|
||
|
|
||
| DEFAULT_MINIMUM_RATING_FOR_THEME_BASED_MOVIE: float = 7.2 | ||
| DEFAULT_MINIMUM_RATING_FOR_THEME_BASED_TV: float = 6.8 | ||
|
|
||
|
|
||
| # cache keys | ||
| LIBRARY_ITEMS_KEY: str = "watchly:library_items:{token}" | ||
| PROFILE_KEY: str = "watchly:profile:{token}:{content_type}" | ||
| WATCHED_SETS_KEY: str = "watchly:watched_sets:{token}:{content_type}" | ||
| CATALOG_KEY: str = "watchly:catalog:{token}:{type}:{id}" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.