From 0518e6f7d0fcaf5468c786be74f1a49d5f476636 Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Mon, 17 Feb 2025 20:00:26 +0000 Subject: [PATCH] fix models --- crud.py | 31 +++++++-------- models.py | 94 ++++++++++++++++++-------------------------- tasks.py | 24 +++++++----- views_api.py | 107 ++++++++++++++++++++++++++++++++++----------------- 4 files changed, 138 insertions(+), 118 deletions(-) diff --git a/crud.py b/crud.py index 1767b02..c268435 100644 --- a/crud.py +++ b/crud.py @@ -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}, diff --git a/models.py b/models.py index e8c762d..5a5556c 100644 --- a/models.py +++ b/models.py @@ -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 diff --git a/tasks.py b/tasks.py index 3f1f0c4..1adabd4 100644 --- a/tasks.py +++ b/tasks.py @@ -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, [])] diff --git a/views_api.py b/views_api.py index 619e7ba..2bd68fb 100644 --- a/views_api.py +++ b/views_api.py @@ -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()