fix models

This commit is contained in:
Riccardo Balbo 2025-02-17 20:00:26 +00:00
commit 0518e6f7d0
4 changed files with 137 additions and 117 deletions

31
crud.py
View file

@ -8,11 +8,11 @@ from .models import (
CreateNWCKey, CreateNWCKey,
DeleteNWC, DeleteNWC,
GetBudgetsNWC, GetBudgetsNWC,
GetNWCKey,
GetWalletNWC, GetWalletNWC,
NWCBudget, NWCBudget,
NWCKey, NWCKey,
TrackedSpendNWC, TrackedSpendNWC,
GetNWC
) )
db = Database("ext_nwcprovider") db = Database("ext_nwcprovider")
@ -21,7 +21,7 @@ db = Database("ext_nwcprovider")
async def create_nwc(data: CreateNWCKey) -> NWCKey: async def create_nwc(data: CreateNWCKey) -> NWCKey:
nwckey_entry = NWCKey( nwckey_entry = NWCKey(
pubkey=data.pubkey, pubkey=data.pubkey,
wallet=data.wallet_id, wallet=data.wallet,
description=data.description, description=data.description,
expires_at=int(data.expires_at) if data.expires_at else 0, expires_at=int(data.expires_at) if data.expires_at else 0,
permissions=" ".join(data.permissions), permissions=" ".join(data.permissions),
@ -31,20 +31,20 @@ async def create_nwc(data: CreateNWCKey) -> NWCKey:
await db.insert("nwcprovider.keys", nwckey_entry) await db.insert("nwcprovider.keys", nwckey_entry)
if data.budgets: if data.budgets:
for budget in data.budgets: for budget in data.budgets:
budget_entry = NWCKey( budget_entry = NWCBudget( # fixme
pubkey=data.pubkey, pubkey=data.pubkey,
budget_msats=budget.budget_msats, budget_msats=budget.budget_msats,
refresh_window=budget.refresh_window, refresh_window=budget.refresh_window,
created_at=budget.created_at, created_at=budget.created_at,
) )
await db.insert("nwcprovider.budgets", budget_entry) await db.insert("nwcprovider.budgets", budget_entry)
return NWCKey(**data.dict()) return NWCKey(**nwckey_entry.dict())
async def delete_nwc(data: DeleteNWC) -> None: async def delete_nwc(data: DeleteNWC) -> None:
await db.execute( await db.execute(
"DELETE FROM nwcprovider.keys WHERE pubkey = :pubkey AND wallet = :wallet", "DELETE FROM nwcprovider.keys WHERE pubkey = :pubkey AND wallet = :wallet",
{"pubkey": data.pubkey, "wallet": data.wallet_id}, {"pubkey": data.pubkey, "wallet": data.wallet},
) )
@ -55,16 +55,16 @@ async def get_wallet_nwcs(data: GetWalletNWC) -> List[NWCKey]:
WHERE wallet = :wallet AND (expires_at = 0 OR expires_at > :expires) WHERE wallet = :wallet AND (expires_at = 0 OR expires_at > :expires)
""", """,
{ {
"wallet": data.wallet_id, "wallet": data.wallet,
"expires": int(time.time()) if not data.include_expired else -1, "expires": int(time.time()) if not data.include_expired else -1,
}, },
model=NWCKey, model=NWCKey,
) )
async def get_nwc(data: GetNWCKey) -> Optional[NWCKey]: async def get_nwc(data: GetNWC) -> Optional[NWCKey]:
# expires_at = 0 means it never expires # expires_at = 0 means it never expires
if data.wallet_id: if data.wallet:
row = await db.fetchone( row = await db.fetchone(
""" """
SELECT * FROM nwcprovider.keys SELECT * FROM nwcprovider.keys
@ -73,7 +73,7 @@ async def get_nwc(data: GetNWCKey) -> Optional[NWCKey]:
""", """,
{ {
"pubkey": data.pubkey, "pubkey": data.pubkey,
"wallet": data.wallet_id, "wallet": data.wallet,
"expires": int(time.time()) if not data.include_expired else -1, "expires": int(time.time()) if not data.include_expired else -1,
}, },
NWCKey, NWCKey,
@ -139,7 +139,9 @@ async def get_budgets_nwc(data: GetBudgetsNWC) -> Optional[NWCBudget]:
async def tracked_spend_nwc(data: TrackedSpendNWC, action): async def tracked_spend_nwc(data: TrackedSpendNWC, action):
async def r(): async def r():
created_at = int(time.time()) created_at = int(time.time())
budgets = await get_budgets_nwc(data.pubkey) budgets = await get_budgets_nwc(GetBudgetsNWC(
pubkey=data.pubkey
))
in_budget = True in_budget = True
for budget in budgets: for budget in budgets:
last_cycle, next_cycle = budget.get_timestamp_range() last_cycle, next_cycle = budget.get_timestamp_range()
@ -194,14 +196,7 @@ async def get_config_nwc(key: str):
async def set_config_nwc(key: str, value: str): async def set_config_nwc(key: str, value: str):
await db.execute( await db.execute(
""" """
DELETE FROM nwcprovider.config INSERT OR REPLACE INTO nwcprovider.config (key, value)
WHERE key = :key
""",
{"key": key},
)
await db.execute(
"""
INSERT INTO nwcprovider.config (key, value)
VALUES (:key, :value) VALUES (:key, :value)
""", """,
{"key": key, "value": value}, {"key": key, "value": value},

View file

@ -29,38 +29,6 @@ class NWCKey(BaseModel):
return cls(**row) return cls(**row)
class GetNWCKey(BaseModel):
pubkey: str
wallet_id: Optional[str] = None
include_expired: Optional[bool] = False
refresh_last_used: Optional[bool] = False
class GetNWCs(BaseModel):
include_expired: bool = False
calculate_spent_budget: bool = False
class GetWalletNWC(BaseModel):
wallet_id: Optional[str] = None
include_expired: Optional[bool] = False
class GetBudgetsNWC(BaseModel):
pubkey: str
calculate_spent: Optional[bool] = False
class TrackedSpendNWC(BaseModel):
pubkey: str
amount_msats: int
class DeleteNWC(BaseModel):
pubkey: str
wallet_id: Optional[str] = None
class OnInvoicePaid(BaseModel): class OnInvoicePaid(BaseModel):
class Config: class Config:
arbitrary_types_allowed = True arbitrary_types_allowed = True
@ -95,23 +63,50 @@ class NWCBudget(BaseModel):
return cls(**dict(row)) return cls(**dict(row))
class NWCLog(BaseModel):
id: int
pubkey: str
payload: str
created_at: int
@classmethod
def from_row(cls, row: Row) -> "NWCLog":
return cls(**dict(row))
class NWCNewBudget(BaseModel): class NWCNewBudget(BaseModel):
budget_msats: int budget_msats: int
refresh_window: int refresh_window: int
created_at: int created_at: int
# CRUD models
class CreateNWCKey(BaseModel):
pubkey: str
wallet: str
description: str
expires_at: int
permissions: List[str]
budgets: Optional[List[NWCNewBudget]] = None
class DeleteNWC(BaseModel):
pubkey: str
wallet: Optional[str] = None
class GetWalletNWC(BaseModel):
wallet: Optional[str] = None
include_expired: Optional[bool] = False
class GetNWC(BaseModel):
pubkey: str
wallet: Optional[str] = None
include_expired: Optional[bool] = False
refresh_last_used: Optional[bool] = False
class GetBudgetsNWC(BaseModel):
pubkey: str
calculate_spent: Optional[bool] = False
class TrackedSpendNWC(BaseModel):
pubkey: str
amount_msats: int
# API models
class NWCRegistrationRequest(BaseModel): class NWCRegistrationRequest(BaseModel):
permissions: List[str] permissions: List[str]
description: str description: str
@ -119,20 +114,7 @@ class NWCRegistrationRequest(BaseModel):
budgets: List[NWCNewBudget] budgets: List[NWCNewBudget]
class RegisterNWC(BaseModel):
pubkey: str
registration_data: NWCRegistrationRequest
class NWCGetResponse(BaseModel): class NWCGetResponse(BaseModel):
data: NWCKey data: NWCKey
budgets: List[NWCBudget] budgets: List[NWCBudget]
class CreateNWCKey(BaseModel):
pubkey: str
wallet_id: str
description: str
expires_at: int
permissions: List[str]
budgets: Optional[List[NWCNewBudget]] = None

View file

@ -19,7 +19,7 @@ from loguru import logger
from .crud import get_config_nwc, get_nwc, tracked_spend_nwc from .crud import get_config_nwc, get_nwc, tracked_spend_nwc
from .execution_queue import execution_queue from .execution_queue import execution_queue
from .models import NWCKey, OnInvoicePaid from .models import NWCKey, OnInvoicePaid, TrackedSpendNWC, GetNWC
from .nwcp import NWCServiceProvider from .nwcp import NWCServiceProvider
from .permission import nwc_permissions from .permission import nwc_permissions
@ -66,7 +66,10 @@ async def _process_invoice(
payment_hash = None payment_hash = None
try: try:
in_budget, payment_hash = await tracked_spend_nwc( in_budget, payment_hash = await tracked_spend_nwc(
pubkey, amount_msats, execute_payment TrackedSpendNWC(
pubkey=pubkey,
amount_msats=amount_msats
), execute_payment
) )
if not in_budget: if not in_budget:
error = { error = {
@ -108,7 +111,10 @@ async def _process_invoice(
async def _on_pay_invoice( async def _on_pay_invoice(
data: OnInvoicePaid, data: OnInvoicePaid,
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: ) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
nwc = await get_nwc(data.pubkey, None, False, True) nwc = await get_nwc(GetNWC(
pubkey=data.pubkey,
refresh_last_used=True
))
error = await _check(nwc, "pay_invoice", data.payload) error = await _check(nwc, "pay_invoice", data.payload)
if error: if error:
return [(None, error, [])] return [(None, error, [])]
@ -138,7 +144,7 @@ async def _on_pay_invoice(
async def _on_multi_pay_invoice( async def _on_multi_pay_invoice(
data: OnInvoicePaid, data: OnInvoicePaid,
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: ) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
nwc = await get_nwc(data.pubkey, None, False, True) nwc = await get_nwc(GetNWC(pubkey=data.pubkey, refresh_last_used=True))
error = await _check(nwc, "multi_pay_invoice", data.payload) error = await _check(nwc, "multi_pay_invoice", data.payload)
if error: if error:
return [(None, error, [])] return [(None, error, [])]
@ -184,7 +190,7 @@ async def _on_multi_pay_invoice(
async def _on_make_invoice( async def _on_make_invoice(
data: OnInvoicePaid, data: OnInvoicePaid,
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: ) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
nwc = await get_nwc(data.pubkey, None, False, True) nwc = await get_nwc(GetNWC(pubkey=data.pubkey, refresh_last_used=True))
error = await _check(nwc, "make_invoice", data.payload) error = await _check(nwc, "make_invoice", data.payload)
if error: if error:
return [(None, error, [])] return [(None, error, [])]
@ -236,7 +242,7 @@ async def _on_make_invoice(
async def _on_lookup_invoice( async def _on_lookup_invoice(
data: OnInvoicePaid, data: OnInvoicePaid,
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: ) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
nwc = await get_nwc(data.pubkey, None, False, True) nwc = await get_nwc(GetNWC(pubkey=data.pubkey, refresh_last_used=True))
error = await _check(nwc, "lookup_invoice", data.payload) error = await _check(nwc, "lookup_invoice", data.payload)
if error: if error:
return [(None, error, [])] return [(None, error, [])]
@ -283,7 +289,7 @@ async def _on_lookup_invoice(
async def _on_list_transactions( async def _on_list_transactions(
data: OnInvoicePaid, data: OnInvoicePaid,
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: ) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
nwc = await get_nwc(data.pubkey, None, False, True) nwc = await get_nwc(GetNWC(pubkey=data.pubkey, refresh_last_used=True))
error = await _check(nwc, "list_transactions", data.payload) error = await _check(nwc, "list_transactions", data.payload)
if error: if error:
return [(None, error, [])] return [(None, error, [])]
@ -339,7 +345,7 @@ async def _on_list_transactions(
async def _on_get_balance( async def _on_get_balance(
data: OnInvoicePaid, data: OnInvoicePaid,
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: ) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
nwc = await get_nwc(data.pubkey, None, False, True) nwc = await get_nwc(GetNWC(pubkey=data.pubkey, refresh_last_used= True))
error = await _check(nwc, "get_balance", data.payload) error = await _check(nwc, "get_balance", data.payload)
if error: if error:
return [(None, error, [])] return [(None, error, [])]
@ -357,7 +363,7 @@ async def _on_get_balance(
async def _on_get_info( async def _on_get_info(
data: OnInvoicePaid, data: OnInvoicePaid,
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: ) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
nwc = await get_nwc(data.pubkey, None, False, True) nwc = await get_nwc(GetNWC(pubkey=data.pubkey, refresh_last_used=True))
error = await _check(nwc, "get_info", data.payload) error = await _check(nwc, "get_info", data.payload)
if error: if error:
return [(None, error, [])] return [(None, error, [])]

View file

@ -2,7 +2,7 @@ from http import HTTPStatus
from typing import Dict, List, Optional from typing import Dict, List, Optional
import secp256k1 import secp256k1
from fastapi import APIRouter, Depends, Request from fastapi import APIRouter, Depends, Request, Query
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from lnbits.core.models import WalletTypeInfo from lnbits.core.models import WalletTypeInfo
from lnbits.decorators import check_admin, require_admin_key from lnbits.decorators import check_admin, require_admin_key
@ -18,13 +18,14 @@ from .crud import (
set_config_nwc, set_config_nwc,
) )
from .models import ( from .models import (
DeleteNWC, NWCRegistrationRequest,
GetBudgetsNWC, GetBudgetsNWC,
GetNWCKey,
GetNWCs,
GetWalletNWC, GetWalletNWC,
NWCGetResponse, NWCGetResponse,
RegisterNWC, CreateNWCKey,
DeleteNWC,
GetNWC,
GetBudgetsNWC
) )
from .permission import nwc_permissions from .permission import nwc_permissions
@ -32,31 +33,36 @@ nwcprovider_api_router = APIRouter()
# Get supported permissions # Get supported permissions
@nwcprovider_api_router.get("/api/v1/permissions", status_code=HTTPStatus.OK) @nwcprovider_api_router.get(
async def api_get_permissions( "/api/v1/permissions",
wallet: WalletTypeInfo = Depends(require_admin_key), status_code=HTTPStatus.OK
) -> Dict: )
async def api_get_permissions() -> Dict:
return nwc_permissions return nwc_permissions
## Get nwc keys associated with the wallet ## Get nwc keys associated with the wallet
@nwcprovider_api_router.get( @nwcprovider_api_router.get(
"/api/v1/nwc", status_code=HTTPStatus.OK, response_model=List[NWCGetResponse] "/api/v1/nwc",
status_code=HTTPStatus.OK,
response_model=List[NWCGetResponse]
) )
async def api_get_nwcs( async def api_get_nwcs(
data: GetNWCs, include_expired: bool = False,
calculate_spent_budget: bool = False,
wallet: WalletTypeInfo = Depends(require_admin_key), wallet: WalletTypeInfo = Depends(require_admin_key),
): ):
wallet_id = wallet.wallet.id wallet_id = wallet.wallet.id
wallet_nwcs = GetWalletNWC( wallet_nwcs = GetWalletNWC(
wallet_id=wallet_id, include_expired=data.include_expired wallet=wallet_id,
include_expired=include_expired
) )
nwcs = await get_wallet_nwcs(wallet_nwcs) nwcs = await get_wallet_nwcs(wallet_nwcs)
out = [] out = []
for nwc in nwcs: for nwc in nwcs:
budgets_nwc = GetBudgetsNWC( budgets_nwc = GetBudgetsNWC(
pubkey=nwc.pubkey, calculate_spent=data.calculate_spent_budget pubkey=nwc.pubkey,
calculate_spent=calculate_spent_budget
) )
budgets = await get_budgets_nwc(budgets_nwc) budgets = await get_budgets_nwc(budgets_nwc)
res = NWCGetResponse(data=nwc, budgets=budgets) res = NWCGetResponse(data=nwc, budgets=budgets)
@ -66,24 +72,37 @@ async def api_get_nwcs(
# Get a nwc key # Get a nwc key
@nwcprovider_api_router.get( @nwcprovider_api_router.get(
"/api/v1/nwc/{pubkey}", status_code=HTTPStatus.OK, response_model=NWCGetResponse "/api/v1/nwc/{pubkey}",
status_code=HTTPStatus.OK,
response_model=NWCGetResponse
) )
async def api_get_nwc( async def api_get_nwc(
data: GetNWCKey, wallet: WalletTypeInfo = Depends(require_admin_key) pubkey: str,
include_expired: Optional[bool] = False,
wallet: WalletTypeInfo = Depends(require_admin_key)
) -> NWCGetResponse: ) -> NWCGetResponse:
wallet_id = wallet.wallet.id wallet_id = wallet.wallet.id
nwc = await get_nwc(data.pubkey, wallet_id, data.include_expired) nwc = await get_nwc(GetNWC(pubkey=pubkey, wallet=wallet_id, include_expired=include_expired))
if not nwc: if not nwc:
raise Exception("Pubkey has no associated wallet") raise Exception("Pubkey has no associated wallet")
res = NWCGetResponse(data=nwc, budgets=await get_budgets_nwc(data.pubkey)) res = NWCGetResponse(data=nwc, budgets=await get_budgets_nwc(
GetBudgetsNWC(
pubkey=pubkey
)
))
return res return res
# Get pairing url for given secret # Get pairing url for given secret
@nwcprovider_api_router.get( @nwcprovider_api_router.get(
"/api/v1/pairing/{secret}", status_code=HTTPStatus.OK, response_model=str "/api/v1/pairing/{secret}",
status_code=HTTPStatus.OK,
response_model=str
) )
async def api_get_pairing_url(req: Request, secret: str) -> str: async def api_get_pairing_url(
req: Request,
secret: str
) -> str:
pprivkey: Optional[str] = await get_config_nwc("provider_key") pprivkey: Optional[str] = await get_config_nwc("provider_key")
if not pprivkey: if not pprivkey:
raise Exception("Extension is not configured") raise Exception("Extension is not configured")
@ -123,38 +142,54 @@ async def api_get_pairing_url(req: Request, secret: str) -> str:
response_model=NWCGetResponse, response_model=NWCGetResponse,
) )
async def api_register_nwc( async def api_register_nwc(
data: RegisterNWC, # Use the Pydantic model here pubkey: str,
data: NWCRegistrationRequest,
wallet: WalletTypeInfo = Depends(require_admin_key), wallet: WalletTypeInfo = Depends(require_admin_key),
): ):
wallet_id = wallet.wallet.id wallet_id = wallet.wallet.id
nwc = await create_nwc( nwc = await create_nwc(
data.pubkey, CreateNWCKey(
wallet_id, pubkey=pubkey,
data.registration_data.description, wallet=wallet_id,
data.registration_data.expires_at, description=data.description,
data.registration_data.permissions, expires_at=data.expires_at,
data.registration_data.budgets, permissions=data.permissions,
budgets=data.budgets,
)
)
budgets = await get_budgets_nwc(
GetBudgetsNWC(
pubkey=pubkey
)
) )
budgets = await get_budgets_nwc(data.pubkey)
res = NWCGetResponse(data=nwc, budgets=budgets) res = NWCGetResponse(data=nwc, budgets=budgets)
return res return res
# Delete a nwc key # Delete a nwc key
@nwcprovider_api_router.delete("/api/v1/nwc/{pubkey}", status_code=HTTPStatus.OK) @nwcprovider_api_router.delete(
"/api/v1/nwc/{pubkey}",
status_code=HTTPStatus.OK
)
async def api_delete_nwc( async def api_delete_nwc(
data: DeleteNWC, wallet: WalletTypeInfo = Depends(require_admin_key) pubkey: str,
wallet: WalletTypeInfo = Depends(require_admin_key)
): ):
wallet_id = wallet.wallet.id wallet_id = wallet.wallet.id
await delete_nwc(data.pubkey, wallet_id) await delete_nwc(DeleteNWC(
pubkey=pubkey,
wallet=wallet_id
))
return JSONResponse( return JSONResponse(
content={"message": f"NWC key {data.pubkey} deleted successfully."} content={"message": f"NWC key {pubkey} deleted successfully."}
) )
# Get config # Get config
@nwcprovider_api_router.get( @nwcprovider_api_router.get(
"/api/v1/config", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] "/api/v1/config",
status_code=HTTPStatus.OK,
dependencies=[Depends(check_admin)]
) )
async def api_get_all_config_nwc(): async def api_get_all_config_nwc():
config = await get_all_config_nwc() config = await get_all_config_nwc()
@ -176,10 +211,12 @@ async def api_get_config_nwc(key: str):
# Set config # Set config
@nwcprovider_api_router.post( @nwcprovider_api_router.post(
"/api/v1/config", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] "/api/v1/config",
status_code=HTTPStatus.OK,
dependencies=[Depends(check_admin)]
) )
async def api_set_config_nwc(req: Request): async def api_set_config_nwc(req: Request):
data = await req.json() data = await req.json()
for key, value in data.items(): for key, value in data.items():
await set_config_nwc(key, value) await set_config_nwc(key, value)
return await api_get_all_config_nwc(req) return await api_get_all_config_nwc()