84 lines
2.6 KiB
Python
84 lines
2.6 KiB
Python
import logging
|
|
|
|
import httpx
|
|
import msgspec
|
|
|
|
from api.v1.schemas.version import GitHubRelease
|
|
from infrastructure.cache.cache_keys import GITHUB_RELEASES_PREFIX
|
|
from infrastructure.cache.memory_cache import CacheInterface
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
GITHUB_API_URL = "https://api.github.com/repos/HabiRabbu/Musicseerr/releases"
|
|
GITHUB_RELEASES_CACHE_KEY = f"{GITHUB_RELEASES_PREFIX}all"
|
|
GITHUB_RELEASES_CACHE_TTL = 3600
|
|
|
|
|
|
class _GitHubReleaseRaw(msgspec.Struct):
|
|
"""Raw GitHub API response struct for decoding."""
|
|
|
|
tag_name: str
|
|
published_at: str
|
|
html_url: str
|
|
name: str | None = None
|
|
body: str | None = None
|
|
prerelease: bool = False
|
|
draft: bool = False
|
|
|
|
|
|
class GitHubRepository:
|
|
def __init__(self, http_client: httpx.AsyncClient, cache: CacheInterface):
|
|
self._client = http_client
|
|
self._cache = cache
|
|
|
|
async def fetch_releases(self) -> list[GitHubRelease]:
|
|
"""Fetch all non-draft releases from GitHub, with 1hr server-side cache."""
|
|
cached = await self._cache.get(GITHUB_RELEASES_CACHE_KEY)
|
|
if cached is not None:
|
|
return cached
|
|
|
|
try:
|
|
response = await self._client.get(
|
|
GITHUB_API_URL,
|
|
headers={"Accept": "application/vnd.github+json"},
|
|
timeout=10.0,
|
|
)
|
|
if response.status_code != 200:
|
|
logger.warning("GitHub releases API returned %s", response.status_code)
|
|
return []
|
|
|
|
raw_releases = msgspec.json.decode(
|
|
response.content, type=list[_GitHubReleaseRaw]
|
|
)
|
|
releases = [
|
|
GitHubRelease(
|
|
tag_name=r.tag_name,
|
|
name=r.name or r.tag_name,
|
|
body=r.body or "",
|
|
published_at=r.published_at,
|
|
html_url=r.html_url,
|
|
prerelease=r.prerelease,
|
|
)
|
|
for r in raw_releases
|
|
if not r.draft
|
|
]
|
|
|
|
await self._cache.set(
|
|
GITHUB_RELEASES_CACHE_KEY,
|
|
releases,
|
|
ttl_seconds=GITHUB_RELEASES_CACHE_TTL,
|
|
)
|
|
return releases
|
|
|
|
except (httpx.HTTPError, msgspec.DecodeError) as e:
|
|
logger.error("Failed to fetch GitHub releases: %s", e)
|
|
return []
|
|
|
|
async def fetch_latest_release(self) -> GitHubRelease | None:
|
|
"""Get the latest non-prerelease release."""
|
|
releases = await self.fetch_releases()
|
|
for release in releases:
|
|
if not release.prerelease:
|
|
return release
|
|
return None
|