Version Info + Notifier (#51)
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
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
|
||||
Reference in New Issue
Block a user