diff --git a/config.json b/config.json index 66d391d..b68048a 100644 --- a/config.json +++ b/config.json @@ -2,7 +2,7 @@ "name": "NWC Service Provider", "short_description": "A NWC service provider for LNbits", "tile": "/nwcprovider/static/image/nwcprovider.png", - "min_lnbits_version": "0.12.5", + "min_lnbits_version": "1.0.0", "contributors": [ { "name": "Riccardo Balbo", diff --git a/crud.py b/crud.py index d66d50e..c68aa0b 100644 --- a/crud.py +++ b/crud.py @@ -9,7 +9,7 @@ from .models import ( NWCKey, CreateNWCKey, GetNWCKey, - GetWalletNWCKey, + GetWalletNWC, GetBudgetsNWC, TrackedSpendNWC, DeleteNWC @@ -44,7 +44,7 @@ async def delete_nwc(data:DeleteNWC) -> None: "DELETE FROM nwcprovider.keys WHERE pubkey = :pubkey AND wallet = :wallet", {"pubkey": data.pubkey, "wallet": data.wallet_id} ) -async def get_wallet_nwcs(data: GetWalletNWCKey) -> List[NWCKey]: +async def get_wallet_nwcs(data: GetWalletNWC) -> List[NWCKey]: return await db.fetchall( """ SELECT * FROM nwcprovider.keys diff --git a/models.py b/models.py index b99a52f..0460427 100644 --- a/models.py +++ b/models.py @@ -5,7 +5,7 @@ from sqlite3 import Row from typing import Any, Dict, List, Optional from pydantic import BaseModel - +from .nwcp import NWCServiceProvider class NWCKey(BaseModel): pubkey: str @@ -32,7 +32,11 @@ class GetNWCKey(BaseModel): include_expired: Optional[bool] = False refresh_last_used: Optional[bool] = False -class GetWalletNWCKey(BaseModel): +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 @@ -46,7 +50,12 @@ class TrackedSpendNWC(BaseModel): class DeleteNWC(BaseModel): pubkey: str - wallet_id: str + wallet_id: Optional[str] = None + +class OnInvoicePaid(BaseModel): + sp: NWCServiceProvider + pubkey: str + payload: Dict class NWCBudget(BaseModel): id: int @@ -96,6 +105,9 @@ class NWCRegistrationRequest(BaseModel): expires_at: int budgets: List[NWCNewBudget] +class RegisterNWC(BaseModel): + pubkey: str + registration_data: NWCRegistrationRequest class NWCGetResponse(BaseModel): data: NWCKey diff --git a/tasks.py b/tasks.py index 356bb43..1f03e5f 100644 --- a/tasks.py +++ b/tasks.py @@ -19,12 +19,12 @@ 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 +from .models import NWCKey, OnInvoicePaid from .nwcp import NWCServiceProvider from .permission import nwc_permissions -async def _check(nwc: Optional[NWCKey], method: str, payload: Dict) -> Optional[Dict]: +async def _check(nwc: Optional[NWCKey], method: str) -> Optional[Dict]: # check if not nwc: return { @@ -106,15 +106,15 @@ async def _process_invoice( async def _on_pay_invoice( - sp: NWCServiceProvider, pubkey: str, payload: Dict -) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: - nwc = await get_nwc(pubkey, None, False, True) - error = await _check(nwc, "pay_invoice", payload) + data:OnInvoicePaid + ) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: + nwc = await get_nwc(data.pubkey, None, False, True) + error = await _check(nwc, "pay_invoice", data.payload) if error: return [(None, error, [])] if not nwc: raise Exception("Pubkey has no associated wallet") - params = payload.get("params", {}) + params = data.payload.get("params", {}) invoice = params.get("invoice", None) # Ensures invoice is provided if not invoice: @@ -122,7 +122,7 @@ async def _on_pay_invoice( invoice_data = bolt11_decode(invoice) amount_msats = int(invoice_data.amount_msat or 0) res = await _process_invoice( - nwc.wallet, pubkey, invoice, amount_msats, invoice_data.description + nwc.wallet, data.pubkey, invoice, amount_msats, invoice_data.description ) error = res.get("error") if error: @@ -136,15 +136,15 @@ async def _on_pay_invoice( async def _on_multi_pay_invoice( - sp: NWCServiceProvider, pubkey: str, payload: Dict + data:OnInvoicePaid ) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: - nwc = await get_nwc(pubkey, None, False, True) - error = await _check(nwc, "multi_pay_invoice", payload) + nwc = await get_nwc(data.pubkey, None, False, True) + error = await _check(nwc, "multi_pay_invoice", data.payload) if error: return [(None, error, [])] if not nwc: raise Exception("Pubkey has no associated wallet") - params = payload.get("params", {}) + params = data.payload.get("params", {}) invoices = params.get("invoices", []) results: List[Tuple[Optional[Dict], Optional[Dict], List]] = [] @@ -182,15 +182,15 @@ async def _on_multi_pay_invoice( async def _on_make_invoice( - sp: NWCServiceProvider, pubkey: str, payload: Dict + data:OnInvoicePaid ) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: - nwc = await get_nwc(pubkey, None, False, True) - error = await _check(nwc, "make_invoice", payload) + nwc = await get_nwc(data.pubkey, None, False, True) + error = await _check(nwc, "make_invoice", data.payload) if error: return [(None, error, [])] if not nwc: raise Exception("Pubkey has no associated wallet") - params = payload.get("params", {}) + params = data.payload.get("params", {}) amount_msats = params.get("amount", None) # Ensures amount is provided if not amount_msats: @@ -234,15 +234,15 @@ async def _on_make_invoice( async def _on_lookup_invoice( - sp: NWCServiceProvider, pubkey: str, payload: Dict + data:OnInvoicePaid ) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: - nwc = await get_nwc(pubkey, None, False, True) - error = await _check(nwc, "lookup_invoice", payload) + nwc = await get_nwc(data.pubkey, None, False, True) + error = await _check(nwc, "lookup_invoice", data.payload) if error: return [(None, error, [])] if not nwc: raise Exception("Pubkey has no associated wallet") - params = payload.get("params", {}) + params = data.payload.get("params", {}) payment_hash = params.get("payment_hash", None) invoice = params.get("invoice", None) # Ensure payment_hash or invoice are provided @@ -281,20 +281,20 @@ async def _on_lookup_invoice( async def _on_list_transactions( - sp: NWCServiceProvider, pubkey: str, payload: Dict + data:OnInvoicePaid ) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: - nwc = await get_nwc(pubkey, None, False, True) - error = await _check(nwc, "list_transactions", payload) + nwc = await get_nwc(data.pubkey, None, False, True) + error = await _check(nwc, "list_transactions", data.payload) if error: return [(None, error, [])] if not nwc: raise Exception("Pubkey has no associated wallet") - tfrom = payload.get("from", 0) - tto = payload.get("to", int(time.time())) - limit = payload.get("limit", 10) - offset = payload.get("offset", 0) - unpaid = payload.get("unpaid", False) - tx_type = payload.get("type", None) + tfrom = data.payload.get("from", 0) + tto = data.payload.get("to", int(time.time())) + limit = data.payload.get("limit", 10) + offset = data.payload.get("offset", 0) + unpaid = data.payload.get("unpaid", False) + tx_type = data.payload.get("type", None) values = [] filters: Filters = Filters() filters.where(["time <= ?"]) @@ -337,10 +337,10 @@ async def _on_list_transactions( async def _on_get_balance( - sp: NWCServiceProvider, pubkey: str, payload: Dict + data:OnInvoicePaid ) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: - nwc = await get_nwc(pubkey, None, False, True) - error = await _check(nwc, "get_balance", payload) + nwc = await get_nwc(data.pubkey, None, False, True) + error = await _check(nwc, "get_balance", data.payload) if error: return [(None, error, [])] if not nwc: @@ -355,10 +355,10 @@ async def _on_get_balance( async def _on_get_info( - sp: NWCServiceProvider, pubkey: str, payload: Dict + data:OnInvoicePaid ) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: - nwc = await get_nwc(pubkey, None, False, True) - error = await _check(nwc, "get_info", payload) + nwc = await get_nwc(data.pubkey, None, False, True) + error = await _check(nwc, "get_info", data.payload) if error: return [(None, error, [])] if not nwc: diff --git a/views_api.py b/views_api.py index 3db81a6..a961460 100644 --- a/views_api.py +++ b/views_api.py @@ -17,7 +17,15 @@ from .crud import ( get_wallet_nwcs, set_config_nwc, ) -from .models import NWCGetResponse, NWCRegistrationRequest +from .models import ( + NWCGetResponse, + RegisterNWC, + GetWalletNWC, + GetBudgetsNWC, + GetNWCKey, + GetNWCs, + DeleteNWC +) from .permission import nwc_permissions nwcprovider_api_router = APIRouter() @@ -26,7 +34,6 @@ nwcprovider_api_router = APIRouter() # Get supported permissions @nwcprovider_api_router.get("/api/v1/permissions", status_code=HTTPStatus.OK) async def api_get_permissions( - req: Request, wallet: WalletTypeInfo = Depends(require_admin_key), ) -> Dict: return nwc_permissions @@ -37,17 +44,23 @@ async def api_get_permissions( "/api/v1/nwc", status_code=HTTPStatus.OK, response_model=List[NWCGetResponse] ) async def api_get_nwcs( - req: Request, - include_expired: bool = False, - calculate_spent_budget: bool = False, + data: GetNWCs, wallet: WalletTypeInfo = Depends(require_admin_key), ): wallet_id = wallet.wallet.id - nwcs = await get_wallet_nwcs(wallet_id, include_expired) + wallet_nwcs = GetWalletNWC( + wallet_id=wallet_id, + include_expired=data.include_expired + ) + nwcs = await get_wallet_nwcs(wallet_nwcs) out = [] for nwc in nwcs: - budgets = await get_budgets_nwc(nwc.pubkey, calculate_spent_budget) + budgets_nwc = GetBudgetsNWC( + pubkey=nwc.pubkey, + calculate_spent=data.calculate_spent_budget + ) + budgets = await get_budgets_nwc(budgets_nwc) res = NWCGetResponse(data=nwc, budgets=budgets) out.append(res) return out @@ -58,16 +71,14 @@ async def api_get_nwcs( "/api/v1/nwc/{pubkey}", status_code=HTTPStatus.OK, response_model=NWCGetResponse ) async def api_get_nwc( - req: Request, - pubkey: str, - include_expired: Optional[bool] = False, - wallet: WalletTypeInfo = Depends(require_admin_key), -) -> NWCGetResponse: + data:GetNWCKey, + wallet: WalletTypeInfo = Depends(require_admin_key) + ) -> NWCGetResponse: wallet_id = wallet.wallet.id - nwc = await get_nwc(pubkey, wallet_id, include_expired) + nwc = await get_nwc(data.pubkey, wallet_id, data.include_expired) if not nwc: raise Exception("Pubkey has no associated wallet") - res = NWCGetResponse(data=nwc, budgets=await get_budgets_nwc(pubkey)) + res = NWCGetResponse(data=nwc, budgets=await get_budgets_nwc(data.pubkey)) return res @@ -115,21 +126,19 @@ async def api_get_pairing_url(req: Request, secret: str) -> str: response_model=NWCGetResponse, ) async def api_register_nwc( - req: Request, - pubkey: str, - registration_data: NWCRegistrationRequest, # Use the Pydantic model here + data:RegisterNWC, # Use the Pydantic model here wallet: WalletTypeInfo = Depends(require_admin_key), ): wallet_id = wallet.wallet.id nwc = await create_nwc( - pubkey, + data.pubkey, wallet_id, - registration_data.description, - registration_data.expires_at, - registration_data.permissions, - registration_data.budgets, + data.registration_data.description, + data.registration_data.expires_at, + data.registration_data.permissions, + data.registration_data.budgets, ) - budgets = await get_budgets_nwc(pubkey) + budgets = await get_budgets_nwc(data.pubkey) res = NWCGetResponse(data=nwc, budgets=budgets) return res @@ -137,20 +146,18 @@ async def api_register_nwc( # Delete a nwc key @nwcprovider_api_router.delete("/api/v1/nwc/{pubkey}", status_code=HTTPStatus.OK) async def api_delete_nwc( - req: Request, pubkey: str, wallet: WalletTypeInfo = Depends(require_admin_key) + data:DeleteNWC, wallet: WalletTypeInfo = Depends(require_admin_key) ): wallet_id = wallet.wallet.id - await delete_nwc(pubkey, wallet_id) - return JSONResponse(content={"message": f"NWC key {pubkey} deleted successfully."}) + await delete_nwc(data.pubkey, wallet_id) + return JSONResponse(content={"message": f"NWC key {data.pubkey} deleted successfully."}) # Get config @nwcprovider_api_router.get( "/api/v1/config", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] ) -async def api_get_all_config_nwc( - req: Request, -): +async def api_get_all_config_nwc(): config = await get_all_config_nwc() return config @@ -161,7 +168,7 @@ async def api_get_all_config_nwc( status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)], ) -async def api_get_config_nwc(req: Request, key: str): +async def api_get_config_nwc(key: str): config = await get_config_nwc(key) out = {} out[key] = config