fix account overview

This commit is contained in:
dni ⚡ 2024-09-29 22:23:45 +02:00 committed by Vlad Stan
parent cb56509850
commit 7828e134df
6 changed files with 131 additions and 139 deletions

View File

@ -23,6 +23,7 @@ from lnbits.settings import (
from .models import (
Account,
AccountFilters,
AccountOverview,
CreatePayment,
Payment,
PaymentFilters,
@ -62,7 +63,7 @@ async def delete_account(user_id: str, conn: Optional[Connection] = None) -> Non
async def get_accounts(
filters: Optional[Filters[AccountFilters]] = None,
conn: Optional[Connection] = None,
) -> Page[Account]:
) -> Page[AccountOverview]:
return await (conn or db).fetch_page(
"""
SELECT
@ -87,7 +88,7 @@ async def get_accounts(
[],
{},
filters=filters,
model=Account,
model=AccountOverview,
group_by=["accounts.id"],
)
@ -906,9 +907,11 @@ async def get_payments_history(
raise ValueError(f"Invalid group value: {group}")
values = {
"wallet": wallet_id,
"wallet_id": wallet_id,
}
where = [f"wallet = :wallet AND (status = '{PaymentState.SUCCESS}' OR amount < 0)"]
where = [
f"wallet_id = :wallet_id AND (status = '{PaymentState.SUCCESS}' OR amount < 0)"
]
transactions: list[dict] = await db.fetchall(
f"""
SELECT {date_trunc} date,

View File

@ -109,8 +109,8 @@ class ReleasePaymentInfo(BaseModel):
class PayToEnableInfo(BaseModel):
required: Optional[bool] = False
amount: Optional[int] = None
amount: int
required: bool = False
wallet: Optional[str] = None
@ -375,9 +375,12 @@ class ExtensionRelease(BaseModel):
class ExtensionMeta(BaseModel):
installed_release: Optional[ExtensionRelease] = None
latest_release: Optional[ExtensionRelease] = None
pay_to_enable: Optional[PayToEnableInfo] = None
payments: list[ReleasePaymentInfo] = []
dependencies: list[str] = []
archive: Optional[str] = None
featured: bool = False
class InstallableExtension(BaseModel):
@ -388,9 +391,6 @@ class InstallableExtension(BaseModel):
short_description: Optional[str] = None
icon: Optional[str] = None
stars: int = 0
featured = False
archive: Optional[str] = None
latest_release: Optional[ExtensionRelease] = None
meta: Optional[ExtensionMeta] = None
@property
@ -538,11 +538,15 @@ class InstallableExtension(BaseModel):
def check_latest_version(self, release: Optional[ExtensionRelease]):
if not release:
return
if not self.latest_release:
self.latest_release = release
if not self.meta or not self.meta.latest_release:
meta = self.meta or ExtensionMeta()
meta.latest_release = release
self.meta = meta
return
if version_parse(self.latest_release.version) < version_parse(release.version):
self.latest_release = release
if version_parse(self.meta.latest_release.version) < version_parse(
release.version
):
self.meta.latest_release = release
def find_existing_payment(
self, pay_link: Optional[str]
@ -602,8 +606,10 @@ class InstallableExtension(BaseModel):
source_repo,
config.tile,
),
latest_release=ExtensionRelease.from_github_release(
source_repo, latest_release
meta=ExtensionMeta(
latest_release=ExtensionRelease.from_github_release(
source_repo, latest_release
),
),
)
except Exception as e:
@ -612,12 +618,11 @@ class InstallableExtension(BaseModel):
@classmethod
def from_explicit_release(cls, e: ExplicitRelease) -> InstallableExtension:
meta = ExtensionMeta(dependencies=e.dependencies)
meta = ExtensionMeta(archive=e.archive, dependencies=e.dependencies)
return InstallableExtension(
id=e.id,
name=e.name,
version=e.version,
archive=e.archive,
short_description=e.short_description,
icon=e.icon,
meta=meta,
@ -641,11 +646,13 @@ class InstallableExtension(BaseModel):
existing_ext = next(
(ee for ee in extension_list if ee.id == r.id), None
)
if existing_ext:
existing_ext.check_latest_version(ext.latest_release)
if existing_ext and ext.meta:
existing_ext.check_latest_version(ext.meta.latest_release)
continue
ext.featured = ext.id in manifest.featured
meta = ext.meta or ExtensionMeta()
meta.featured = ext.id in manifest.featured
ext.meta = meta
extension_list += [ext]
extension_id_list += [ext.id]
@ -659,7 +666,9 @@ class InstallableExtension(BaseModel):
continue
ext = InstallableExtension.from_explicit_release(e)
ext.check_latest_version(release)
ext.featured = ext.id in manifest.featured
meta = ext.meta or ExtensionMeta()
meta.featured = ext.id in manifest.featured
ext.meta = meta
extension_list += [ext]
extension_id_list += [e.id]
except Exception as e:

View File

@ -113,7 +113,7 @@ class Account(BaseModel):
username: Optional[str] = None
password_hash: Optional[str] = None
email: Optional[str] = None
extra: Optional[UserExtra] = None
extra: UserExtra = UserExtra()
created_at: datetime = datetime.now()
updated_at: datetime = datetime.now()
@ -177,7 +177,7 @@ class User(BaseModel):
admin: bool = False
super_user: bool = False
has_password: bool = False
extra: Optional[UserExtra] = None
extra: UserExtra = UserExtra()
@property
def wallet_ids(self) -> list[str]:

View File

@ -9,7 +9,6 @@ from fastapi.exceptions import HTTPException
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
from fastapi.routing import APIRouter
from lnurl import decode as lnurl_decode
from loguru import logger
from pydantic.types import UUID4
from lnbits.core.extensions.models import Extension, ExtensionMeta, InstallableExtension
@ -76,98 +75,84 @@ async def robots():
@generic_router.get("/extensions", name="extensions", response_class=HTMLResponse)
async def extensions(request: Request, user: User = Depends(check_user_exists)):
try:
installed_exts: List[InstallableExtension] = await get_installed_extensions()
installed_exts_ids = [e.id for e in installed_exts]
installed_exts: List[InstallableExtension] = await get_installed_extensions()
installed_exts_ids = [e.id for e in installed_exts]
installable_exts = await InstallableExtension.get_installable_extensions()
installable_exts_ids = [e.id for e in installable_exts]
installable_exts += [
e for e in installed_exts if e.id not in installable_exts_ids
]
installable_exts = await InstallableExtension.get_installable_extensions()
installable_exts_ids = [e.id for e in installable_exts]
installable_exts += [e for e in installed_exts if e.id not in installable_exts_ids]
for e in installable_exts:
installed_ext = next((ie for ie in installed_exts if e.id == ie.id), None)
if installed_ext and installed_ext.meta:
installed_release = installed_ext.meta.installed_release
if installed_ext.meta.pay_to_enable and not user.admin:
# not a security leak, but better not to share the wallet id
installed_ext.meta.pay_to_enable.wallet = None
pay_to_enable = installed_ext.meta.pay_to_enable
for e in installable_exts:
installed_ext = next((ie for ie in installed_exts if e.id == ie.id), None)
if installed_ext and installed_ext.meta:
installed_release = installed_ext.meta.installed_release
if installed_ext.meta.pay_to_enable and not user.admin:
# not a security leak, but better not to share the wallet id
installed_ext.meta.pay_to_enable.wallet = None
pay_to_enable = installed_ext.meta.pay_to_enable
if e.meta:
e.meta.installed_release = installed_release
e.meta.pay_to_enable = pay_to_enable
else:
e.meta = ExtensionMeta(
installed_release=installed_release,
pay_to_enable=pay_to_enable,
)
# use the installed extension values
e.name = installed_ext.name
e.short_description = installed_ext.short_description
e.icon = installed_ext.icon
if e.meta:
e.meta.installed_release = installed_release
e.meta.pay_to_enable = pay_to_enable
else:
e.meta = ExtensionMeta(
installed_release=installed_release,
pay_to_enable=pay_to_enable,
)
# use the installed extension values
e.name = installed_ext.name
e.short_description = installed_ext.short_description
e.icon = installed_ext.icon
except Exception as ex:
logger.warning(ex)
installable_exts = []
installed_exts_ids = []
all_ext_ids = [ext.code for ext in Extension.get_valid_extensions()]
inactive_extensions = [e.id for e in await get_installed_extensions(active=False)]
db_version = await get_dbversions()
extensions = [
{
"id": ext.id,
"name": ext.name,
"icon": ext.icon,
"shortDescription": ext.short_description,
"stars": ext.stars,
"isFeatured": ext.meta.featured if ext.meta else False,
"dependencies": ext.meta.dependencies if ext.meta else "",
"isInstalled": ext.id in installed_exts_ids,
"hasDatabaseTables": ext.id in db_version,
"isAvailable": ext.id in all_ext_ids,
"isAdminOnly": ext.id in settings.lnbits_admin_extensions,
"isActive": ext.id not in inactive_extensions,
"latestRelease": (
dict(ext.meta.latest_release)
if ext.meta and ext.meta.latest_release
else None
),
"installedRelease": (
dict(ext.meta.installed_release)
if ext.meta and ext.meta.installed_release
else None
),
"payToEnable": (
dict(ext.meta.pay_to_enable)
if ext.meta and ext.meta.pay_to_enable
else {}
),
"isPaymentRequired": ext.requires_payment,
}
for ext in installable_exts
]
try:
all_ext_ids = [ext.code for ext in Extension.get_valid_extensions()]
inactive_extensions = [
e.id for e in await get_installed_extensions(active=False)
]
db_version = await get_dbversions()
extensions = [
{
"id": ext.id,
"name": ext.name,
"icon": ext.icon,
"shortDescription": ext.short_description,
"stars": ext.stars,
"isFeatured": ext.featured,
"dependencies": ext.meta.dependencies if ext.meta else "",
"isInstalled": ext.id in installed_exts_ids,
"hasDatabaseTables": ext.id in db_version,
"isAvailable": ext.id in all_ext_ids,
"isAdminOnly": ext.id in settings.lnbits_admin_extensions,
"isActive": ext.id not in inactive_extensions,
"latestRelease": (
dict(ext.latest_release) if ext.latest_release else None
),
"installedRelease": (
dict(ext.meta.installed_release)
if ext.meta and ext.meta.installed_release
else None
),
"payToEnable": (
dict(ext.meta.pay_to_enable)
if ext.meta and ext.meta.pay_to_enable
else {}
),
"isPaymentRequired": ext.requires_payment,
}
for ext in installable_exts
]
# refresh user state. Eg: enabled extensions.
# TODO: refactor
# user = await get_user(user.id) or user
# refresh user state. Eg: enabled extensions.
# TODO: refactor
# user = await get_user(user.id) or user
return template_renderer().TemplateResponse(
request,
"core/extensions.html",
{
"user": user.json(),
"extensions": extensions,
},
)
except Exception as exc:
logger.warning(exc)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(exc)
) from exc
return template_renderer().TemplateResponse(
request,
"core/extensions.html",
{
"user": user.json(),
"extensions": extensions,
},
)
@generic_router.get(

View File

@ -600,7 +600,6 @@ def model_to_dict(model: BaseModel) -> dict:
private fields starting with _ are ignored
:param model: Pydantic model
"""
# TODO: no recursion, maybe make them recursive?
_dict = model.dict()
for key, value in _dict.items():
if key.startswith("_"):
@ -617,8 +616,6 @@ def dict_to_model(_row: dict, model: type[TModel]) -> TModel:
:param _dict: Dictionary from database
:param model: Pydantic model
"""
# TODO: no recursion, maybe make them recursive?
# TODO: check why keys are sometimes not in the dict
_dict: dict = {}
for key, value in _row.items():
if key not in model.__fields__:
@ -628,10 +625,18 @@ def dict_to_model(_row: dict, model: type[TModel]) -> TModel:
continue
type_ = model.__fields__[key].type_
if issubclass(type_, BaseModel) and value is not None:
if isinstance(value, str) and value == "null":
_dict[key] = None
if isinstance(value, str):
if value == "null":
_dict[key] = None
continue
_subdict = json.loads(value)
elif isinstance(value, dict):
_subdict = value
else:
logger.warning(f"Expected str or dict, got {type(value)}")
continue
_dict[key] = type_.construct(**json.loads(value))
# recursively convert nested models
_dict[key] = dict_to_model(_subdict, type_)
continue
_dict[key] = value
return model.construct(**_dict)

View File

@ -165,32 +165,22 @@ window.app = Vue.createApp({
type: 'bubble',
options: {
scales: {
xAxes: [
{
type: 'linear',
ticks: {
beginAtZero: true
},
scaleLabel: {
display: true,
labelString: 'Tx count'
}
x: {
type: 'linear',
beginAtZero: true,
title: {
text: 'Transaction count'
}
],
yAxes: [
{
type: 'linear',
ticks: {
beginAtZero: true
},
scaleLabel: {
display: true,
labelString: 'User balance in million sats'
}
},
y: {
type: 'linear',
beginAtZero: true,
title: {
text: 'User balance in million sats'
}
]
}
},
tooltips: {
tooltip: {
callbacks: {
label: function (tooltipItem, data) {
const dataset = data.datasets[tooltipItem.datasetIndex]