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,
DeleteNWC,
GetBudgetsNWC,
GetNWCKey,
GetWalletNWC,
NWCBudget,
NWCKey,
TrackedSpendNWC,
GetNWC
)
db = Database("ext_nwcprovider")
@ -21,7 +21,7 @@ db = Database("ext_nwcprovider")
async def create_nwc(data: CreateNWCKey) -> NWCKey:
nwckey_entry = NWCKey(
pubkey=data.pubkey,
wallet=data.wallet_id,
wallet=data.wallet,
description=data.description,
expires_at=int(data.expires_at) if data.expires_at else 0,
permissions=" ".join(data.permissions),
@ -31,20 +31,20 @@ async def create_nwc(data: CreateNWCKey) -> NWCKey:
await db.insert("nwcprovider.keys", nwckey_entry)
if data.budgets:
for budget in data.budgets:
budget_entry = NWCKey(
budget_entry = NWCBudget( # fixme
pubkey=data.pubkey,
budget_msats=budget.budget_msats,
refresh_window=budget.refresh_window,
created_at=budget.created_at,
)
await db.insert("nwcprovider.budgets", budget_entry)
return NWCKey(**data.dict())
return NWCKey(**nwckey_entry.dict())
async def delete_nwc(data: DeleteNWC) -> None:
await db.execute(
"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)
""",
{
"wallet": data.wallet_id,
"wallet": data.wallet,
"expires": int(time.time()) if not data.include_expired else -1,
},
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
if data.wallet_id:
if data.wallet:
row = await db.fetchone(
"""
SELECT * FROM nwcprovider.keys
@ -73,7 +73,7 @@ async def get_nwc(data: GetNWCKey) -> Optional[NWCKey]:
""",
{
"pubkey": data.pubkey,
"wallet": data.wallet_id,
"wallet": data.wallet,
"expires": int(time.time()) if not data.include_expired else -1,
},
NWCKey,
@ -139,7 +139,9 @@ async def get_budgets_nwc(data: GetBudgetsNWC) -> Optional[NWCBudget]:
async def tracked_spend_nwc(data: TrackedSpendNWC, action):
async def r():
created_at = int(time.time())
budgets = await get_budgets_nwc(data.pubkey)
budgets = await get_budgets_nwc(GetBudgetsNWC(
pubkey=data.pubkey
))
in_budget = True
for budget in budgets:
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):
await db.execute(
"""
DELETE FROM nwcprovider.config
WHERE key = :key
""",
{"key": key},
)
await db.execute(
"""
INSERT INTO nwcprovider.config (key, value)
INSERT OR REPLACE INTO nwcprovider.config (key, value)
VALUES (:key, :value)
""",
{"key": key, "value": value},

View file

@ -29,38 +29,6 @@ class NWCKey(BaseModel):
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 Config:
arbitrary_types_allowed = True
@ -95,23 +63,50 @@ class NWCBudget(BaseModel):
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):
budget_msats: int
refresh_window: 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):
permissions: List[str]
description: str
@ -119,20 +114,7 @@ class NWCRegistrationRequest(BaseModel):
budgets: List[NWCNewBudget]
class RegisterNWC(BaseModel):
pubkey: str
registration_data: NWCRegistrationRequest
class NWCGetResponse(BaseModel):
data: NWCKey
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 .execution_queue import execution_queue
from .models import NWCKey, OnInvoicePaid
from .models import NWCKey, OnInvoicePaid, TrackedSpendNWC, GetNWC
from .nwcp import NWCServiceProvider
from .permission import nwc_permissions
@ -66,7 +66,10 @@ async def _process_invoice(
payment_hash = None
try:
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:
error = {
@ -108,7 +111,10 @@ async def _process_invoice(
async def _on_pay_invoice(
data: OnInvoicePaid,
) -> 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)
if error:
return [(None, error, [])]
@ -138,7 +144,7 @@ async def _on_pay_invoice(
async def _on_multi_pay_invoice(
data: OnInvoicePaid,
) -> 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)
if error:
return [(None, error, [])]
@ -184,7 +190,7 @@ async def _on_multi_pay_invoice(
async def _on_make_invoice(
data: OnInvoicePaid,
) -> 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)
if error:
return [(None, error, [])]
@ -236,7 +242,7 @@ async def _on_make_invoice(
async def _on_lookup_invoice(
data: OnInvoicePaid,
) -> 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)
if error:
return [(None, error, [])]
@ -283,7 +289,7 @@ async def _on_lookup_invoice(
async def _on_list_transactions(
data: OnInvoicePaid,
) -> 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)
if error:
return [(None, error, [])]
@ -339,7 +345,7 @@ async def _on_list_transactions(
async def _on_get_balance(
data: OnInvoicePaid,
) -> 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)
if error:
return [(None, error, [])]
@ -357,7 +363,7 @@ async def _on_get_balance(
async def _on_get_info(
data: OnInvoicePaid,
) -> 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)
if error:
return [(None, error, [])]

View file

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