feat: show badge if an extension has paid releases (#3385)

Co-authored-by: dni  <office@dnilabs.com>
This commit is contained in:
Vlad Stan 2025-10-06 19:06:18 +03:00 committed by GitHub
parent 015262c9b3
commit f48d24aca8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 88 additions and 16 deletions

View file

@ -39,6 +39,7 @@ class ExplicitRelease(BaseModel):
info_notification: str | None info_notification: str | None
critical_notification: str | None critical_notification: str | None
details_link: str | None details_link: str | None
paid_features: str | None
pay_link: str | None pay_link: str | None
def is_version_compatible(self): def is_version_compatible(self):
@ -187,6 +188,7 @@ class ExtensionRelease(BaseModel):
icon: str | None = None icon: str | None = None
details_link: str | None = None details_link: str | None = None
paid_features: str | None = None
pay_link: str | None = None pay_link: str | None = None
cost_sats: int | None = None cost_sats: int | None = None
paid_sats: int | None = 0 paid_sats: int | None = 0
@ -256,6 +258,7 @@ class ExtensionRelease(BaseModel):
html_url=e.html_url, html_url=e.html_url,
details_link=e.details_link, details_link=e.details_link,
pay_link=e.pay_link, pay_link=e.pay_link,
paid_features=e.paid_features,
repo=e.repo, repo=e.repo,
icon=e.icon, icon=e.icon,
) )
@ -308,6 +311,9 @@ class ExtensionMeta(BaseModel):
dependencies: list[str] = [] dependencies: list[str] = []
archive: str | None = None archive: str | None = None
featured: bool = False featured: bool = False
paid_features: str | None = None
has_paid_release: bool = False
has_free_release: bool = False
class InstallableExtension(BaseModel): class InstallableExtension(BaseModel):
@ -451,9 +457,23 @@ class InstallableExtension(BaseModel):
shutil.rmtree(self.ext_upgrade_dir, True) shutil.rmtree(self.ext_upgrade_dir, True)
def check_latest_version(self, release: ExtensionRelease | None): def check_release_updates(self, release: ExtensionRelease | None):
self._check_latest_version(release)
self._check_payment_link(release)
def find_existing_payment(self, pay_link: str | None) -> ReleasePaymentInfo | None:
if not pay_link or not self.meta or not self.meta.payments:
return None
return next(
(p for p in self.meta.payments if p.pay_link == pay_link),
None,
)
def _check_latest_version(self, release: ExtensionRelease | None):
if not release: if not release:
return return
if not release.is_version_compatible:
return
if not self.meta or not self.meta.latest_release: if not self.meta or not self.meta.latest_release:
meta = self.meta or ExtensionMeta() meta = self.meta or ExtensionMeta()
meta.latest_release = release meta.latest_release = release
@ -464,13 +484,25 @@ class InstallableExtension(BaseModel):
): ):
self.meta.latest_release = release self.meta.latest_release = release
def find_existing_payment(self, pay_link: str | None) -> ReleasePaymentInfo | None: def _check_payment_link(self, release: ExtensionRelease | None):
if not pay_link or not self.meta or not self.meta.payments: if not release:
return None return
return next( if not release.is_version_compatible:
(p for p in self.meta.payments if p.pay_link == pay_link), return
None, if not self.meta:
self.meta = ExtensionMeta()
if release.pay_link:
self.meta.has_paid_release = True
else:
self.meta.has_free_release = True
print(
"### release.paid_features",
release.name,
release.version,
release.paid_features,
) )
if release.paid_features:
self.meta.paid_features = release.paid_features
def _restore_payment_info(self): def _restore_payment_info(self):
if ( if (
@ -596,7 +628,7 @@ class InstallableExtension(BaseModel):
(ee for ee in extension_list if ee.id == r.id), None (ee for ee in extension_list if ee.id == r.id), None
) )
if existing_ext and ext.meta: if existing_ext and ext.meta:
existing_ext.check_latest_version(ext.meta.latest_release) existing_ext.check_release_updates(ext.meta.latest_release)
continue continue
meta = ext.meta or ExtensionMeta() meta = ext.meta or ExtensionMeta()
@ -610,10 +642,10 @@ class InstallableExtension(BaseModel):
(ee for ee in extension_list if ee.id == e.id), None (ee for ee in extension_list if ee.id == e.id), None
) )
if existing_ext: if existing_ext:
existing_ext.check_latest_version(release) existing_ext.check_release_updates(release)
continue continue
ext = InstallableExtension.from_explicit_release(e) ext = InstallableExtension.from_explicit_release(e)
ext.check_latest_version(release) ext.check_release_updates(release)
meta = ext.meta or ExtensionMeta() meta = ext.meta or ExtensionMeta()
meta.featured = ext.id in manifest.featured meta.featured = ext.id in manifest.featured
ext.meta = meta ext.meta = meta

View file

@ -137,8 +137,40 @@
@click="showExtensionDetails(extension.id, extension.details_link)" @click="showExtensionDetails(extension.id, extension.details_link)"
v-text="extension.name" v-text="extension.name"
></div> ></div>
<div> <div style="justify-content: space-between; display: flex">
<lnbits-extension-rating :rating="0" /> <lnbits-extension-rating :rating="0" />
<q-btn-group size="xs" style="margin: 5px 0">
<q-btn
v-if="extension.hasFreeRelease"
color="green"
size="xs"
:label="$t('free')"
>
<q-tooltip>
<span v-text="$t('extension_has_free_release')"></span>
</q-tooltip>
</q-btn>
<q-btn
v-if="extension.hasPaidRelease || extension.paidFeatures"
color="primary"
size="xs"
:label="$t('paid')"
>
<q-tooltip>
<span
v-if="extension.hasPaidRelease"
v-text="$t('extension_has_paid_release')"
></span>
<br
v-if="extension.hasPaidRelease && extension.paidFeatures"
/>
<span
v-if="extension.paidFeatures"
v-text="extension.paidFeatures"
></span>
</q-tooltip>
</q-btn>
</q-btn-group>
</div> </div>
<div style="justify-content: space-between; display: flex"> <div style="justify-content: space-between; display: flex">
<q-toggle <q-toggle

View file

@ -61,9 +61,10 @@ async def api_install_extension(data: CreateExtension):
data.ext_id, data.source_repo, data.archive, data.version data.ext_id, data.source_repo, data.archive, data.version
) )
if not release: if not release:
raise HTTPException( raise HTTPException(HTTPStatus.NOT_FOUND, "Release not found")
status_code=HTTPStatus.NOT_FOUND, detail="Release not found"
) if not release.is_version_compatible:
raise HTTPException(HTTPStatus.BAD_REQUEST, "Incompatible extension version.")
release.payment_hash = data.payment_hash release.payment_hash = data.payment_hash
ext_meta = ExtensionMeta(installed_release=release) ext_meta = ExtensionMeta(installed_release=release)

View file

@ -126,6 +126,9 @@ async def extensions(request: Request, user: User = Depends(check_user_exists)):
if ext.meta and ext.meta.latest_release if ext.meta and ext.meta.latest_release
else None else None
), ),
"hasPaidRelease": ext.meta.has_paid_release if ext.meta else False,
"hasFreeRelease": ext.meta.has_free_release if ext.meta else False,
"paidFeatures": ext.meta.paid_features if ext.meta else False,
"installedRelease": ( "installedRelease": (
dict(ext.meta.installed_release) dict(ext.meta.installed_release)
if ext.meta and ext.meta.installed_release if ext.meta and ext.meta.installed_release

File diff suppressed because one or more lines are too long

View file

@ -168,6 +168,8 @@ window.localisation.en = {
'Only admin accounts can create extensions', 'Only admin accounts can create extensions',
admin_only: 'Admin Only', admin_only: 'Admin Only',
new_version: 'New Version', new_version: 'New Version',
extension_has_free_release: 'Has free releases',
extension_has_paid_release: 'Has paid releases',
extension_depends_on: 'Depends on:', extension_depends_on: 'Depends on:',
extension_rating_soon: 'Ratings coming soon', extension_rating_soon: 'Ratings coming soon',
extension_installed_version: 'Installed version', extension_installed_version: 'Installed version',
@ -663,5 +665,7 @@ window.localisation.en = {
callback_success_url_hint: callback_success_url_hint:
'The user will be redirected to this URL after the payment is successful', 'The user will be redirected to this URL after the payment is successful',
connected: 'Connected', connected: 'Connected',
not_connected: 'Not Connected' not_connected: 'Not Connected',
free: 'Free',
paid: 'Paid'
} }