mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2024-11-19 09:54:21 +01:00
fix account overview
This commit is contained in:
parent
cb56509850
commit
7828e134df
@ -23,6 +23,7 @@ from lnbits.settings import (
|
|||||||
from .models import (
|
from .models import (
|
||||||
Account,
|
Account,
|
||||||
AccountFilters,
|
AccountFilters,
|
||||||
|
AccountOverview,
|
||||||
CreatePayment,
|
CreatePayment,
|
||||||
Payment,
|
Payment,
|
||||||
PaymentFilters,
|
PaymentFilters,
|
||||||
@ -62,7 +63,7 @@ async def delete_account(user_id: str, conn: Optional[Connection] = None) -> Non
|
|||||||
async def get_accounts(
|
async def get_accounts(
|
||||||
filters: Optional[Filters[AccountFilters]] = None,
|
filters: Optional[Filters[AccountFilters]] = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> Page[Account]:
|
) -> Page[AccountOverview]:
|
||||||
return await (conn or db).fetch_page(
|
return await (conn or db).fetch_page(
|
||||||
"""
|
"""
|
||||||
SELECT
|
SELECT
|
||||||
@ -87,7 +88,7 @@ async def get_accounts(
|
|||||||
[],
|
[],
|
||||||
{},
|
{},
|
||||||
filters=filters,
|
filters=filters,
|
||||||
model=Account,
|
model=AccountOverview,
|
||||||
group_by=["accounts.id"],
|
group_by=["accounts.id"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -906,9 +907,11 @@ async def get_payments_history(
|
|||||||
raise ValueError(f"Invalid group value: {group}")
|
raise ValueError(f"Invalid group value: {group}")
|
||||||
|
|
||||||
values = {
|
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(
|
transactions: list[dict] = await db.fetchall(
|
||||||
f"""
|
f"""
|
||||||
SELECT {date_trunc} date,
|
SELECT {date_trunc} date,
|
||||||
|
@ -109,8 +109,8 @@ class ReleasePaymentInfo(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class PayToEnableInfo(BaseModel):
|
class PayToEnableInfo(BaseModel):
|
||||||
required: Optional[bool] = False
|
amount: int
|
||||||
amount: Optional[int] = None
|
required: bool = False
|
||||||
wallet: Optional[str] = None
|
wallet: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
@ -375,9 +375,12 @@ class ExtensionRelease(BaseModel):
|
|||||||
|
|
||||||
class ExtensionMeta(BaseModel):
|
class ExtensionMeta(BaseModel):
|
||||||
installed_release: Optional[ExtensionRelease] = None
|
installed_release: Optional[ExtensionRelease] = None
|
||||||
|
latest_release: Optional[ExtensionRelease] = None
|
||||||
pay_to_enable: Optional[PayToEnableInfo] = None
|
pay_to_enable: Optional[PayToEnableInfo] = None
|
||||||
payments: list[ReleasePaymentInfo] = []
|
payments: list[ReleasePaymentInfo] = []
|
||||||
dependencies: list[str] = []
|
dependencies: list[str] = []
|
||||||
|
archive: Optional[str] = None
|
||||||
|
featured: bool = False
|
||||||
|
|
||||||
|
|
||||||
class InstallableExtension(BaseModel):
|
class InstallableExtension(BaseModel):
|
||||||
@ -388,9 +391,6 @@ class InstallableExtension(BaseModel):
|
|||||||
short_description: Optional[str] = None
|
short_description: Optional[str] = None
|
||||||
icon: Optional[str] = None
|
icon: Optional[str] = None
|
||||||
stars: int = 0
|
stars: int = 0
|
||||||
featured = False
|
|
||||||
archive: Optional[str] = None
|
|
||||||
latest_release: Optional[ExtensionRelease] = None
|
|
||||||
meta: Optional[ExtensionMeta] = None
|
meta: Optional[ExtensionMeta] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -538,11 +538,15 @@ class InstallableExtension(BaseModel):
|
|||||||
def check_latest_version(self, release: Optional[ExtensionRelease]):
|
def check_latest_version(self, release: Optional[ExtensionRelease]):
|
||||||
if not release:
|
if not release:
|
||||||
return
|
return
|
||||||
if not self.latest_release:
|
if not self.meta or not self.meta.latest_release:
|
||||||
self.latest_release = release
|
meta = self.meta or ExtensionMeta()
|
||||||
|
meta.latest_release = release
|
||||||
|
self.meta = meta
|
||||||
return
|
return
|
||||||
if version_parse(self.latest_release.version) < version_parse(release.version):
|
if version_parse(self.meta.latest_release.version) < version_parse(
|
||||||
self.latest_release = release
|
release.version
|
||||||
|
):
|
||||||
|
self.meta.latest_release = release
|
||||||
|
|
||||||
def find_existing_payment(
|
def find_existing_payment(
|
||||||
self, pay_link: Optional[str]
|
self, pay_link: Optional[str]
|
||||||
@ -602,8 +606,10 @@ class InstallableExtension(BaseModel):
|
|||||||
source_repo,
|
source_repo,
|
||||||
config.tile,
|
config.tile,
|
||||||
),
|
),
|
||||||
latest_release=ExtensionRelease.from_github_release(
|
meta=ExtensionMeta(
|
||||||
source_repo, latest_release
|
latest_release=ExtensionRelease.from_github_release(
|
||||||
|
source_repo, latest_release
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -612,12 +618,11 @@ class InstallableExtension(BaseModel):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_explicit_release(cls, e: ExplicitRelease) -> InstallableExtension:
|
def from_explicit_release(cls, e: ExplicitRelease) -> InstallableExtension:
|
||||||
meta = ExtensionMeta(dependencies=e.dependencies)
|
meta = ExtensionMeta(archive=e.archive, dependencies=e.dependencies)
|
||||||
return InstallableExtension(
|
return InstallableExtension(
|
||||||
id=e.id,
|
id=e.id,
|
||||||
name=e.name,
|
name=e.name,
|
||||||
version=e.version,
|
version=e.version,
|
||||||
archive=e.archive,
|
|
||||||
short_description=e.short_description,
|
short_description=e.short_description,
|
||||||
icon=e.icon,
|
icon=e.icon,
|
||||||
meta=meta,
|
meta=meta,
|
||||||
@ -641,11 +646,13 @@ class InstallableExtension(BaseModel):
|
|||||||
existing_ext = next(
|
existing_ext = next(
|
||||||
(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:
|
if existing_ext and ext.meta:
|
||||||
existing_ext.check_latest_version(ext.latest_release)
|
existing_ext.check_latest_version(ext.meta.latest_release)
|
||||||
continue
|
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_list += [ext]
|
||||||
extension_id_list += [ext.id]
|
extension_id_list += [ext.id]
|
||||||
|
|
||||||
@ -659,7 +666,9 @@ class InstallableExtension(BaseModel):
|
|||||||
continue
|
continue
|
||||||
ext = InstallableExtension.from_explicit_release(e)
|
ext = InstallableExtension.from_explicit_release(e)
|
||||||
ext.check_latest_version(release)
|
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_list += [ext]
|
||||||
extension_id_list += [e.id]
|
extension_id_list += [e.id]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -113,7 +113,7 @@ class Account(BaseModel):
|
|||||||
username: Optional[str] = None
|
username: Optional[str] = None
|
||||||
password_hash: Optional[str] = None
|
password_hash: Optional[str] = None
|
||||||
email: Optional[str] = None
|
email: Optional[str] = None
|
||||||
extra: Optional[UserExtra] = None
|
extra: UserExtra = UserExtra()
|
||||||
created_at: datetime = datetime.now()
|
created_at: datetime = datetime.now()
|
||||||
updated_at: datetime = datetime.now()
|
updated_at: datetime = datetime.now()
|
||||||
|
|
||||||
@ -177,7 +177,7 @@ class User(BaseModel):
|
|||||||
admin: bool = False
|
admin: bool = False
|
||||||
super_user: bool = False
|
super_user: bool = False
|
||||||
has_password: bool = False
|
has_password: bool = False
|
||||||
extra: Optional[UserExtra] = None
|
extra: UserExtra = UserExtra()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def wallet_ids(self) -> list[str]:
|
def wallet_ids(self) -> list[str]:
|
||||||
|
@ -9,7 +9,6 @@ from fastapi.exceptions import HTTPException
|
|||||||
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
|
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
|
||||||
from fastapi.routing import APIRouter
|
from fastapi.routing import APIRouter
|
||||||
from lnurl import decode as lnurl_decode
|
from lnurl import decode as lnurl_decode
|
||||||
from loguru import logger
|
|
||||||
from pydantic.types import UUID4
|
from pydantic.types import UUID4
|
||||||
|
|
||||||
from lnbits.core.extensions.models import Extension, ExtensionMeta, InstallableExtension
|
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)
|
@generic_router.get("/extensions", name="extensions", response_class=HTMLResponse)
|
||||||
async def extensions(request: Request, user: User = Depends(check_user_exists)):
|
async def extensions(request: Request, user: User = Depends(check_user_exists)):
|
||||||
try:
|
installed_exts: List[InstallableExtension] = await get_installed_extensions()
|
||||||
installed_exts: List[InstallableExtension] = await get_installed_extensions()
|
installed_exts_ids = [e.id for e in installed_exts]
|
||||||
installed_exts_ids = [e.id for e in installed_exts]
|
|
||||||
|
|
||||||
installable_exts = await InstallableExtension.get_installable_extensions()
|
installable_exts = await InstallableExtension.get_installable_extensions()
|
||||||
installable_exts_ids = [e.id for e in installable_exts]
|
installable_exts_ids = [e.id for e in installable_exts]
|
||||||
installable_exts += [
|
installable_exts += [e for e in installed_exts if e.id not in installable_exts_ids]
|
||||||
e for e in installed_exts if e.id not in installable_exts_ids
|
|
||||||
]
|
|
||||||
|
|
||||||
for e in installable_exts:
|
for e in installable_exts:
|
||||||
installed_ext = next((ie for ie in installed_exts if e.id == ie.id), None)
|
installed_ext = next((ie for ie in installed_exts if e.id == ie.id), None)
|
||||||
if installed_ext and installed_ext.meta:
|
if installed_ext and installed_ext.meta:
|
||||||
installed_release = installed_ext.meta.installed_release
|
installed_release = installed_ext.meta.installed_release
|
||||||
if installed_ext.meta.pay_to_enable and not user.admin:
|
if installed_ext.meta.pay_to_enable and not user.admin:
|
||||||
# not a security leak, but better not to share the wallet id
|
# not a security leak, but better not to share the wallet id
|
||||||
installed_ext.meta.pay_to_enable.wallet = None
|
installed_ext.meta.pay_to_enable.wallet = None
|
||||||
pay_to_enable = installed_ext.meta.pay_to_enable
|
pay_to_enable = installed_ext.meta.pay_to_enable
|
||||||
|
|
||||||
if e.meta:
|
if e.meta:
|
||||||
e.meta.installed_release = installed_release
|
e.meta.installed_release = installed_release
|
||||||
e.meta.pay_to_enable = pay_to_enable
|
e.meta.pay_to_enable = pay_to_enable
|
||||||
else:
|
else:
|
||||||
e.meta = ExtensionMeta(
|
e.meta = ExtensionMeta(
|
||||||
installed_release=installed_release,
|
installed_release=installed_release,
|
||||||
pay_to_enable=pay_to_enable,
|
pay_to_enable=pay_to_enable,
|
||||||
)
|
)
|
||||||
# use the installed extension values
|
# use the installed extension values
|
||||||
e.name = installed_ext.name
|
e.name = installed_ext.name
|
||||||
e.short_description = installed_ext.short_description
|
e.short_description = installed_ext.short_description
|
||||||
e.icon = installed_ext.icon
|
e.icon = installed_ext.icon
|
||||||
|
|
||||||
except Exception as ex:
|
all_ext_ids = [ext.code for ext in Extension.get_valid_extensions()]
|
||||||
logger.warning(ex)
|
inactive_extensions = [e.id for e in await get_installed_extensions(active=False)]
|
||||||
installable_exts = []
|
db_version = await get_dbversions()
|
||||||
installed_exts_ids = []
|
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:
|
# refresh user state. Eg: enabled extensions.
|
||||||
all_ext_ids = [ext.code for ext in Extension.get_valid_extensions()]
|
# TODO: refactor
|
||||||
inactive_extensions = [
|
# user = await get_user(user.id) or user
|
||||||
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.
|
return template_renderer().TemplateResponse(
|
||||||
# TODO: refactor
|
request,
|
||||||
# user = await get_user(user.id) or user
|
"core/extensions.html",
|
||||||
|
{
|
||||||
return template_renderer().TemplateResponse(
|
"user": user.json(),
|
||||||
request,
|
"extensions": extensions,
|
||||||
"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
|
|
||||||
|
|
||||||
|
|
||||||
@generic_router.get(
|
@generic_router.get(
|
||||||
|
17
lnbits/db.py
17
lnbits/db.py
@ -600,7 +600,6 @@ def model_to_dict(model: BaseModel) -> dict:
|
|||||||
private fields starting with _ are ignored
|
private fields starting with _ are ignored
|
||||||
:param model: Pydantic model
|
:param model: Pydantic model
|
||||||
"""
|
"""
|
||||||
# TODO: no recursion, maybe make them recursive?
|
|
||||||
_dict = model.dict()
|
_dict = model.dict()
|
||||||
for key, value in _dict.items():
|
for key, value in _dict.items():
|
||||||
if key.startswith("_"):
|
if key.startswith("_"):
|
||||||
@ -617,8 +616,6 @@ def dict_to_model(_row: dict, model: type[TModel]) -> TModel:
|
|||||||
:param _dict: Dictionary from database
|
:param _dict: Dictionary from database
|
||||||
:param model: Pydantic model
|
:param model: Pydantic model
|
||||||
"""
|
"""
|
||||||
# TODO: no recursion, maybe make them recursive?
|
|
||||||
# TODO: check why keys are sometimes not in the dict
|
|
||||||
_dict: dict = {}
|
_dict: dict = {}
|
||||||
for key, value in _row.items():
|
for key, value in _row.items():
|
||||||
if key not in model.__fields__:
|
if key not in model.__fields__:
|
||||||
@ -628,10 +625,18 @@ def dict_to_model(_row: dict, model: type[TModel]) -> TModel:
|
|||||||
continue
|
continue
|
||||||
type_ = model.__fields__[key].type_
|
type_ = model.__fields__[key].type_
|
||||||
if issubclass(type_, BaseModel) and value is not None:
|
if issubclass(type_, BaseModel) and value is not None:
|
||||||
if isinstance(value, str) and value == "null":
|
if isinstance(value, str):
|
||||||
_dict[key] = None
|
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
|
continue
|
||||||
_dict[key] = type_.construct(**json.loads(value))
|
# recursively convert nested models
|
||||||
|
_dict[key] = dict_to_model(_subdict, type_)
|
||||||
continue
|
continue
|
||||||
_dict[key] = value
|
_dict[key] = value
|
||||||
return model.construct(**_dict)
|
return model.construct(**_dict)
|
||||||
|
@ -165,32 +165,22 @@ window.app = Vue.createApp({
|
|||||||
type: 'bubble',
|
type: 'bubble',
|
||||||
options: {
|
options: {
|
||||||
scales: {
|
scales: {
|
||||||
xAxes: [
|
x: {
|
||||||
{
|
type: 'linear',
|
||||||
type: 'linear',
|
beginAtZero: true,
|
||||||
ticks: {
|
title: {
|
||||||
beginAtZero: true
|
text: 'Transaction count'
|
||||||
},
|
|
||||||
scaleLabel: {
|
|
||||||
display: true,
|
|
||||||
labelString: 'Tx count'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
},
|
||||||
yAxes: [
|
y: {
|
||||||
{
|
type: 'linear',
|
||||||
type: 'linear',
|
beginAtZero: true,
|
||||||
ticks: {
|
title: {
|
||||||
beginAtZero: true
|
text: 'User balance in million sats'
|
||||||
},
|
|
||||||
scaleLabel: {
|
|
||||||
display: true,
|
|
||||||
labelString: 'User balance in million sats'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
tooltips: {
|
tooltip: {
|
||||||
callbacks: {
|
callbacks: {
|
||||||
label: function (tooltipItem, data) {
|
label: function (tooltipItem, data) {
|
||||||
const dataset = data.datasets[tooltipItem.datasetIndex]
|
const dataset = data.datasets[tooltipItem.datasetIndex]
|
||||||
|
Loading…
Reference in New Issue
Block a user