From 27b308ae1a59fa88ba689e2ea58c9b8f9a832655 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 16 Jan 2023 14:56:34 +0200 Subject: [PATCH] feat: separate extension release into new API --- lnbits/core/templates/core/install.html | 28 +++++++++++--- lnbits/core/views/api.py | 10 ++--- lnbits/core/views/generic.py | 4 +- lnbits/extension_manger.py | 49 ++++++++++++++++++++++--- 4 files changed, 74 insertions(+), 17 deletions(-) diff --git a/lnbits/core/templates/core/install.html b/lnbits/core/templates/core/install.html index 8fb8dbe8c..1eeb6639f 100644 --- a/lnbits/core/templates/core/install.html +++ b/lnbits/core/templates/core/install.html @@ -153,18 +153,25 @@ -
- +
+ -
Repo name
+
+ +
{ + repos[release.source_repo] = repos[release.source_repo] || [] + repos[release.source_repo].push(release) + return repos + }, {}) + console.log('### releases', this.selectedExtensionReleases) + console.log('### repos', this.selectedExtensionRepos) }, showExtensionDetails: function (extension) { diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 1a4da8f97..299f31442 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -42,6 +42,7 @@ from lnbits.decorators import ( ) from lnbits.extension_manger import ( Extension, + ExtensionRelease, InstallableExtension, get_valid_extensions, ) @@ -800,12 +801,11 @@ async def api_uninstall_extension(ext_id: str, user: User = Depends(check_admin) @core_app.get("/api/v1/extension/{ext_id}/releases") async def get_extension_releases(ext_id: str, user: User = Depends(check_admin)): try: - installable_extensions: List[ - InstallableExtension - ] = await InstallableExtension.get_installable_extensions() - extensions = [e for e in installable_extensions if e.id == ext_id] + extension_releases: List[ + ExtensionRelease + ] = await InstallableExtension.get_extension_releases(ext_id) - return extensions + return extension_releases except Exception as ex: raise HTTPException( diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 13cb5c55a..6d2d01af4 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -113,7 +113,9 @@ async def extensions_install( "dependencies": ext.dependencies, "isInstalled": ext.id in installed_extensions, "isActive": not ext.id in inactive_extensions, - "release": dict(ext.release) if ext.release else None, + "latestRelease": dict(ext.latest_release) + if ext.latest_release + else None, }, extension_list, ) diff --git a/lnbits/extension_manger.py b/lnbits/extension_manger.py index ee06569d1..1bae0b413 100644 --- a/lnbits/extension_manger.py +++ b/lnbits/extension_manger.py @@ -6,7 +6,6 @@ import sys import urllib.request import zipfile from http import HTTPStatus -from platform import release from typing import List, NamedTuple, Optional import httpx @@ -107,6 +106,7 @@ class ExtensionRelease(BaseModel): name: str version: str archive: str + source_repo: str hash: Optional[str] published_at: Optional[str] url: Optional[str] @@ -118,6 +118,7 @@ class ExtensionRelease(BaseModel): name=r["name"], version=r["tag_name"], archive=r["zipball_url"], + # source_repo=r[] # description=r["body"], # bad for JSON published_at=r["published_at"], url=r["html_url"], @@ -127,7 +128,7 @@ class ExtensionRelease(BaseModel): class InstallableExtension(BaseModel): id: str name: str - archive: str #todo: move to installed_release + archive: str # todo: move to installed_release hash: str short_description: Optional[str] = None details: Optional[str] = None @@ -137,7 +138,9 @@ class InstallableExtension(BaseModel): is_admin_only: bool = False version: str = "none" # todo: move to Release stars: int = 0 - release: Optional[ExtensionRelease] + latest_release: Optional[ExtensionRelease] + installed_release: Optional[ExtensionRelease] + all_releases: List[ExtensionRelease] = [] @property def zip_path(self) -> str: @@ -233,7 +236,7 @@ class InstallableExtension(BaseModel): version="0", stars=repo["stargazers_count"], icon_url=icon_to_github_url(org, config.get("tile")), - release=ExtensionRelease.from_github_release(latest_release), + latest_release=ExtensionRelease.from_github_release(latest_release), ) except Exception as e: logger.warning(e) @@ -268,6 +271,7 @@ class InstallableExtension(BaseModel): @classmethod async def get_installable_extensions(cls) -> List["InstallableExtension"]: extension_list: List[InstallableExtension] = [] + extension_id_list: List[str] = [] async with httpx.AsyncClient() as client: for url in settings.lnbits_extensions_manifests: @@ -278,7 +282,9 @@ class InstallableExtension(BaseModel): continue manifest = resp.json() if "extensions" in manifest: - for e in manifest["extensions"] or []: + for e in manifest["extensions"]: + if e["id"] in extension_id_list: + continue extension_list += [ InstallableExtension( id=e["id"], @@ -293,18 +299,51 @@ class InstallableExtension(BaseModel): else [], ) ] + extension_id_list += [e["id"]] if "repos" in manifest: for r in manifest["repos"]: ext = await InstallableExtension.from_repo( r["organisation"], r["repository"] ) if ext: + if ext.id in extension_id_list: + continue extension_list += [ext] + extension_id_list += [ext.id] except Exception as e: logger.warning(f"Manifest {url} failed with '{str(e)}'") return extension_list + @classmethod + async def get_extension_releases(cls, ext_id: str) -> List["ExtensionRelease"]: + extension_releases: List[ExtensionRelease] = [] + async with httpx.AsyncClient() as client: + for url in settings.lnbits_extensions_manifests: + try: + resp = await client.get(url) + if resp.status_code != 200: + logger.warning(f"Cannot fetch extensions manifest at: {url}") + continue + manifest = resp.json() + if "extensions" in manifest: + for e in manifest["extensions"]: + if e["id"] == ext_id: + extension_releases += [ + ExtensionRelease( + name=e["name"], + version=e["version"], + archive=e["archive"], + hash=e["hash"], + source_repo=url, + description=e["shortDescription"], + ) + ] + except Exception as e: + logger.warning(f"Manifest {url} failed with '{str(e)}'") + + return extension_releases + class InstalledExtensionMiddleware: def __init__(self, app: ASGIApp) -> None: