From 75dd03b15aee87eb9cc0e6d6f7e1b7c8469e7951 Mon Sep 17 00:00:00 2001 From: padreug Date: Fri, 20 Jun 2025 21:51:38 +0200 Subject: [PATCH] Refactor MyExtension to DCA Admin: Update extension name and description in config.json, remove legacy MyExtension CRUD operations and related API endpoints, and adjust router tags. Clean up unused files and methods to streamline the codebase for DCA administration functionality. Refactor DCA Admin page endpoints: Update description, remove unused CRUD operations and API endpoints related to MyExtension, and streamline the code for improved clarity and functionality. Remove QR Code dialog from MyExtension index.html: streamline the UI by eliminating unused dialog components, enhancing code clarity and maintainability. --- __init__.py | 4 +- config.json | 44 +++-- crud.py | 36 ---- helpers.py | 17 -- models.py | 25 --- static/js/index.js | 218 +----------------------- tasks.py | 41 +---- templates/myextension/_myextension.html | 25 --- templates/myextension/index.html | 137 --------------- templates/myextension/myextension.html | 68 -------- views.py | 82 +-------- views_api.py | 160 ----------------- views_lnurl.py | 146 ---------------- 13 files changed, 32 insertions(+), 971 deletions(-) delete mode 100644 helpers.py delete mode 100644 templates/myextension/_myextension.html delete mode 100644 templates/myextension/myextension.html delete mode 100644 views_lnurl.py diff --git a/__init__.py b/__init__.py index 8df0f80..50f9d52 100644 --- a/__init__.py +++ b/__init__.py @@ -8,7 +8,6 @@ from .crud import db from .tasks import wait_for_paid_invoices, hourly_transaction_polling from .views import myextension_generic_router from .views_api import myextension_api_router -from .views_lnurl import myextension_lnurl_router logger.debug( "This logged message is from myextension/__init__.py, you can debug in your " @@ -16,10 +15,9 @@ logger.debug( ) -myextension_ext: APIRouter = APIRouter(prefix="/myextension", tags=["MyExtension"]) +myextension_ext: APIRouter = APIRouter(prefix="/myextension", tags=["DCA Admin"]) myextension_ext.include_router(myextension_generic_router) myextension_ext.include_router(myextension_api_router) -myextension_ext.include_router(myextension_lnurl_router) myextension_static_files = [ { diff --git a/config.json b/config.json index 6d51a5a..6be901a 100644 --- a/config.json +++ b/config.json @@ -1,38 +1,32 @@ { - "name": "MyExtension", - "short_description": "Minimal extension to build on", + "name": "DCA Admin", + "short_description": "Dollar Cost Averaging administration for Lamassu ATM integration", "tile": "/myextension/static/image/myextension.png", "min_lnbits_version": "1.0.0", "contributors": [ { - "name": "Alan Bits", - "uri": "https://github.com/alanbits", - "role": "Lead dev" + "name": "Atitlan Community", + "uri": "https://atitlan.io", + "role": "R&D Venue" }, { - "name": "Ben Arc", - "uri": "https://github.com/arcbtc", - "role": "Dev" + "name": "AtitlanIO", + "uri": "https://atitlan.io", + "role": "Developer" }, { - "name": "LNbits community", - "uri": "https://t.me/lnbits", - "role": "Emotional support" + "name": "LNbits", + "uri": "https://github.com/lnbits", + "role": "Developer" + }, + { + "name": "Claude", + "uri": "https://claude.ai", + "role": "Code Writing Agent" } ], - "images": [ - { - "uri": "https://raw.githubusercontent.com/lnbits/myextension/main/static/image/1.png", - "link": "https://www.youtube.com/embed/SkkIwO_X4i4?si=9JJh1Fc6GfHDZK6b" - }, - { - "uri": "https://raw.githubusercontent.com/lnbits/myextension/main/static/image/2.png" - }, - { - "uri": "https://raw.githubusercontent.com/lnbits/myextension/main/static/image/3.png" - } - ], - "description_md": "https://raw.githubusercontent.com/lnbits/myextension/main/description.md", - "terms_and_conditions_md": "https://raw.githubusercontent.com/lnbits/myextension/main/toc.md", + "images": [], + "description_md": "/myextension/description.md", + "terms_and_conditions_md": "/myextension/toc.md", "license": "MIT" } diff --git a/crud.py b/crud.py index d94b19b..2cf20e0 100644 --- a/crud.py +++ b/crud.py @@ -7,7 +7,6 @@ from lnbits.db import Database from lnbits.helpers import urlsafe_short_hash from .models import ( - CreateMyExtensionData, MyExtension, CreateDcaClientData, DcaClient, UpdateDcaClientData, CreateDepositData, DcaDeposit, UpdateDepositStatusData, CreateDcaPaymentData, DcaPayment, @@ -19,41 +18,6 @@ from .models import ( db = Database("ext_myextension") -async def create_myextension(data: CreateMyExtensionData) -> MyExtension: - data.id = urlsafe_short_hash() - await db.insert("myextension.maintable", data) - return MyExtension(**data.dict()) - - -async def get_myextension(myextension_id: str) -> Optional[MyExtension]: - return await db.fetchone( - "SELECT * FROM myextension.maintable WHERE id = :id", - {"id": myextension_id}, - MyExtension, - ) - - -async def get_myextensions(wallet_ids: Union[str, List[str]]) -> List[MyExtension]: - if isinstance(wallet_ids, str): - wallet_ids = [wallet_ids] - q = ",".join([f"'{w}'" for w in wallet_ids]) - return await db.fetchall( - f"SELECT * FROM myextension.maintable WHERE wallet IN ({q}) ORDER BY id", - model=MyExtension, - ) - - -async def update_myextension(data: CreateMyExtensionData) -> MyExtension: - await db.update("myextension.maintable", data) - return MyExtension(**data.dict()) - - -async def delete_myextension(myextension_id: str) -> None: - await db.execute( - "DELETE FROM myextension.maintable WHERE id = :id", {"id": myextension_id} - ) - - # DCA Client CRUD Operations async def create_dca_client(data: CreateDcaClientData) -> DcaClient: client_id = urlsafe_short_hash() diff --git a/helpers.py b/helpers.py deleted file mode 100644 index b7c8c4b..0000000 --- a/helpers.py +++ /dev/null @@ -1,17 +0,0 @@ -# Description: A place for helper functions. - -from fastapi import Request -from lnurl.core import encode as lnurl_encode - -# The lnurler function is used to generate the lnurlpay and lnurlwithdraw links -# from the lnurl api endpoints in views_lnurl.py. -# It needs the Request object to know the url of the LNbits. -# Lnurler is used in views_api.py - - -def lnurler(myex_id: str, route_name: str, req: Request) -> str: - url = req.url_for(route_name, myextension_id=myex_id) - url_str = str(url) - if url.netloc.endswith(".onion"): - url_str = url_str.replace("https://", "http://") - return str(lnurl_encode(url_str)) diff --git a/models.py b/models.py index 60656bf..964d67c 100644 --- a/models.py +++ b/models.py @@ -207,28 +207,3 @@ class UpdateLamassuConfigData(BaseModel): ssh_private_key: Optional[str] = None -# Legacy models (keep for backward compatibility during transition) -class CreateMyExtensionData(BaseModel): - id: Optional[str] = "" - name: str - lnurlpayamount: int - lnurlwithdrawamount: int - wallet: str - total: int = 0 - - -class MyExtension(BaseModel): - id: str - name: str - lnurlpayamount: int - lnurlwithdrawamount: int - wallet: str - total: int - lnurlpay: Optional[str] = "" - lnurlwithdraw: Optional[str] = "" - - -class CreatePayment(BaseModel): - myextension_id: str - amount: int - memo: str diff --git a/static/js/index.js b/static/js/index.js index 67ae246..fc9a2d5 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -119,32 +119,7 @@ window.app = Vue.createApp({ currencyOptions: [ { label: 'GTQ', value: 'GTQ' }, { label: 'USD', value: 'USD' } - ], - - // Legacy data (keep for backward compatibility) - invoiceAmount: 10, - qrValue: 'lnurlpay', - myex: [], - myexTable: { - columns: [ - { name: 'id', align: 'left', label: 'ID', field: 'id' }, - { name: 'name', align: 'left', label: 'Name', field: 'name' }, - { name: 'wallet', align: 'left', label: 'Wallet', field: 'wallet' }, - { name: 'total', align: 'left', label: 'Total sent/received', field: 'total' } - ], - pagination: { - rowsPerPage: 10 - } - }, - formDialog: { - show: false, - data: {}, - advanced: {} - }, - urlDialog: { - show: false, - data: {} - } + ] } }, @@ -666,194 +641,6 @@ window.app = Vue.createApp({ } }, - // Legacy Methods (keep for backward compatibility) - async closeFormDialog() { - this.formDialog.show = false - this.formDialog.data = {} - }, - async getMyExtensions() { - await LNbits.api - .request( - 'GET', - '/myextension/api/v1/myex', - this.g.user.wallets[0].inkey - ) - .then(response => { - this.myex = response.data - }) - .catch(err => { - LNbits.utils.notifyApiError(err) - }) - }, - async sendMyExtensionData() { - const data = { - name: this.formDialog.data.name, - lnurlwithdrawamount: this.formDialog.data.lnurlwithdrawamount, - lnurlpayamount: this.formDialog.data.lnurlpayamount - } - const wallet = _.findWhere(this.g.user.wallets, { - id: this.formDialog.data.wallet - }) - if (this.formDialog.data.id) { - data.id = this.formDialog.data.id - data.total = this.formDialog.data.total - await this.updateMyExtension(wallet, data) - } else { - await this.createMyExtension(wallet, data) - } - }, - - async updateMyExtensionForm(tempId) { - const myextension = _.findWhere(this.myex, { id: tempId }) - this.formDialog.data = { - ...myextension - } - if (this.formDialog.data.tip_wallet != '') { - this.formDialog.advanced.tips = true - } - if (this.formDialog.data.withdrawlimit >= 1) { - this.formDialog.advanced.otc = true - } - this.formDialog.show = true - }, - async createMyExtension(wallet, data) { - data.wallet = wallet.id - await LNbits.api - .request('POST', '/myextension/api/v1/myex', wallet.adminkey, data) - .then(response => { - this.myex.push(response.data) - this.closeFormDialog() - }) - .catch(error => { - LNbits.utils.notifyApiError(error) - }) - }, - - async updateMyExtension(wallet, data) { - data.wallet = wallet.id - await LNbits.api - .request( - 'PUT', - `/myextension/api/v1/myex/${data.id}`, - wallet.adminkey, - data - ) - .then(response => { - this.myex = _.reject(this.myex, obj => obj.id == data.id) - this.myex.push(response.data) - this.closeFormDialog() - }) - .catch(error => { - LNbits.utils.notifyApiError(error) - }) - }, - async deleteMyExtension(tempId) { - var myextension = _.findWhere(this.myex, { id: tempId }) - const wallet = _.findWhere(this.g.user.wallets, { - id: myextension.wallet - }) - await LNbits.utils - .confirmDialog('Are you sure you want to delete this MyExtension?') - .onOk(function () { - LNbits.api - .request( - 'DELETE', - '/myextension/api/v1/myex/' + tempId, - wallet.adminkey - ) - .then(() => { - this.myex = _.reject(this.myex, function (obj) { - return obj.id === myextension.id - }) - }) - .catch(error => { - LNbits.utils.notifyApiError(error) - }) - }) - }, - - async exportCSV() { - await LNbits.utils.exportCSV(this.myexTable.columns, this.myex) - }, - async itemsArray(tempId) { - const myextension = _.findWhere(this.myex, { id: tempId }) - return [...myextension.itemsMap.values()] - }, - async openformDialog(id) { - const [tempId, itemId] = id.split(':') - const myextension = _.findWhere(this.myex, { id: tempId }) - if (itemId) { - const item = myextension.itemsMap.get(id) - this.formDialog.data = { - ...item, - myextension: tempId - } - } else { - this.formDialog.data.myextension = tempId - } - this.formDialog.data.currency = myextension.currency - this.formDialog.show = true - }, - async openUrlDialog(tempid) { - this.urlDialog.data = _.findWhere(this.myex, { id: tempid }) - this.qrValue = this.urlDialog.data.lnurlpay - - // Connecting to our websocket fired in tasks.py - this.connectWebocket(this.urlDialog.data.id) - - this.urlDialog.show = true - }, - async closeformDialog() { - this.formDialog.show = false - this.formDialog.data = {} - }, - async createInvoice(tempid) { - /////////////////////////////////////////////////// - ///Simple call to the api to create an invoice///// - /////////////////////////////////////////////////// - myex = _.findWhere(this.myex, { id: tempid }) - const wallet = _.findWhere(this.g.user.wallets, { id: myex.wallet }) - const data = { - myextension_id: tempid, - amount: this.invoiceAmount, - memo: 'MyExtension - ' + myex.name - } - await LNbits.api - .request('POST', `/myextension/api/v1/myex/payment`, wallet.inkey, data) - .then(response => { - this.qrValue = response.data.payment_request - this.connectWebocket(wallet.inkey) - }) - .catch(error => { - LNbits.utils.notifyApiError(error) - }) - }, - connectWebocket(myextension_id) { - ////////////////////////////////////////////////// - ///wait for pay action to happen and do a thing//// - /////////////////////////////////////////////////// - if (location.protocol !== 'http:') { - localUrl = - 'wss://' + - document.domain + - ':' + - location.port + - '/api/v1/ws/' + - myextension_id - } else { - localUrl = - 'ws://' + - document.domain + - ':' + - location.port + - '/api/v1/ws/' + - myextension_id - } - this.connection = new WebSocket(localUrl) - this.connection.onmessage = () => { - this.urlDialog.show = false - } - } }, /////////////////////////////////////////////////// //////LIFECYCLE FUNCTIONS RUNNING ON PAGE LOAD///// @@ -866,9 +653,6 @@ window.app = Vue.createApp({ this.getDeposits(), this.getLamassuTransactions() ]) - - // Legacy data loading - await this.getMyExtensions() }, computed: { diff --git a/tasks.py b/tasks.py index 387aaa0..f653c3f 100644 --- a/tasks.py +++ b/tasks.py @@ -6,8 +6,6 @@ from lnbits.core.services import websocket_updater from lnbits.tasks import register_invoice_listener from loguru import logger -from .crud import get_myextension, update_myextension -from .models import CreateMyExtensionData from .transaction_processor import poll_lamassu_transactions ####################################### @@ -18,6 +16,7 @@ from .transaction_processor import poll_lamassu_transactions async def wait_for_paid_invoices(): + """Invoice listener for DCA-related payments""" invoice_queue = asyncio.Queue() register_invoice_listener(invoice_queue, "ext_myextension") while True: @@ -44,35 +43,11 @@ async def hourly_transaction_polling(): await asyncio.sleep(300) -# Do somethhing when an invoice related top this extension is paid - - async def on_invoice_paid(payment: Payment) -> None: - if payment.extra.get("tag") != "MyExtension": - return - - myextension_id = payment.extra.get("myextensionId") - assert myextension_id, "myextensionId not set in invoice" - myextension = await get_myextension(myextension_id) - assert myextension, "MyExtension does not exist" - - # update something in the db - if payment.extra.get("lnurlwithdraw"): - total = myextension.total - payment.amount - else: - total = myextension.total + payment.amount - - myextension.total = total - await update_myextension(CreateMyExtensionData(**myextension.dict())) - - # here we could send some data to a websocket on - # wss:///api/v1/ws/ and then listen to it on - - some_payment_data = { - "name": myextension.name, - "amount": payment.amount, - "fee": payment.fee, - "checking_id": payment.checking_id, - } - - await websocket_updater(myextension_id, str(some_payment_data)) + """Handle DCA-related invoice payments""" + # DCA payments are handled internally by the transaction processor + # This function can be extended if needed for additional payment processing + if payment.extra.get("tag") in ["dca_distribution", "dca_commission"]: + logger.info(f"DCA payment processed: {payment.checking_id} - {payment.amount} sats") + # Could add websocket notifications here if needed + pass diff --git a/templates/myextension/_myextension.html b/templates/myextension/_myextension.html deleted file mode 100644 index f9425a6..0000000 --- a/templates/myextension/_myextension.html +++ /dev/null @@ -1,25 +0,0 @@ - - - -

Some more info about my excellent extension.

- Created by - Ben Arc. - Repo - MyExtension. -
-
-
diff --git a/templates/myextension/index.html b/templates/myextension/index.html index fc455c2..389f60a 100644 --- a/templates/myextension/index.html +++ b/templates/myextension/index.html @@ -265,93 +265,6 @@ - - -
-
-
MyExtension
-
-
- Export to CSV -
-
- - - - - ${ col.label } - - - - - - -
-
@@ -854,55 +767,5 @@ - - - - - - - - - -
- - - -
-
- lnurlpay -
-
- lnurlwithdraw -
-
- - - - -
-
-
- Close -
-
-
{% endblock %} diff --git a/templates/myextension/myextension.html b/templates/myextension/myextension.html deleted file mode 100644 index bc52349..0000000 --- a/templates/myextension/myextension.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - -{% extends "public.html" %} {% block page %} -
-
- - - -
- Copy LNURL - -
-
-
-
-
- - -
Public page
-

- Most extensions have a public page that can be shared (this page will - still be accessible even if you have restricted access to your LNbits - install). -

- In this example when a user pays the LNURLpay it triggers an event via - a websocket waiting for the payment, which you can subscribe to - somewhere using wss://{your-lnbits}/api/v1/ws/{the-id-of-this-record} -

- - - - -
-
-
-{% endblock %} {% block scripts %} - -{% endblock %} diff --git a/views.py b/views.py index 34dd721..e6cf852 100644 --- a/views.py +++ b/views.py @@ -1,16 +1,10 @@ -# Description: Add your page endpoints here. +# Description: DCA Admin page endpoints. -from http import HTTPStatus - -from fastapi import APIRouter, Depends, HTTPException, Request +from fastapi import APIRouter, Depends, Request from fastapi.responses import HTMLResponse from lnbits.core.models import User from lnbits.decorators import check_user_exists from lnbits.helpers import template_renderer -from lnbits.settings import settings - -from .crud import get_myextension -from .helpers import lnurler myextension_generic_router = APIRouter() @@ -19,79 +13,9 @@ def myextension_renderer(): return template_renderer(["myextension/templates"]) -####################################### -##### ADD YOUR PAGE ENDPOINTS HERE #### -####################################### - - -# Backend admin page - - +# DCA Admin page @myextension_generic_router.get("/", response_class=HTMLResponse) async def index(req: Request, user: User = Depends(check_user_exists)): return myextension_renderer().TemplateResponse( "myextension/index.html", {"request": req, "user": user.json()} ) - - -# Frontend shareable page - - -@myextension_generic_router.get("/{myextension_id}") -async def myextension(req: Request, myextension_id): - myex = await get_myextension(myextension_id) - if not myex: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist." - ) - return myextension_renderer().TemplateResponse( - "myextension/myextension.html", - { - "request": req, - "myextension_id": myextension_id, - "lnurlpay": lnurler(myex.id, "myextension.api_lnurl_pay", req), - "web_manifest": f"/myextension/manifest/{myextension_id}.webmanifest", - }, - ) - - -# Manifest for public page, customise or remove manifest completely - - -@myextension_generic_router.get("/manifest/{myextension_id}.webmanifest") -async def manifest(myextension_id: str): - myextension = await get_myextension(myextension_id) - if not myextension: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist." - ) - - return { - "short_name": settings.lnbits_site_title, - "name": myextension.name + " - " + settings.lnbits_site_title, - "icons": [ - { - "src": ( - settings.lnbits_custom_logo - if settings.lnbits_custom_logo - else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png" - ), - "type": "image/png", - "sizes": "900x900", - } - ], - "start_url": "/myextension/" + myextension_id, - "background_color": "#1F2234", - "description": "Minimal extension to build on", - "display": "standalone", - "scope": "/myextension/" + myextension_id, - "theme_color": "#1F2234", - "shortcuts": [ - { - "name": myextension.name + " - " + settings.lnbits_site_title, - "short_name": myextension.name, - "description": myextension.name + " - " + settings.lnbits_site_title, - "url": "/myextension/" + myextension_id, - } - ], - } diff --git a/views_api.py b/views_api.py index a8b2fd0..7924e2d 100644 --- a/views_api.py +++ b/views_api.py @@ -11,11 +11,6 @@ from lnbits.decorators import require_admin_key, require_invoice_key from starlette.exceptions import HTTPException from .crud import ( - create_myextension, - delete_myextension, - get_myextension, - get_myextensions, - update_myextension, # DCA CRUD operations create_dca_client, get_dca_clients, @@ -39,9 +34,7 @@ from .crud import ( get_all_lamassu_transactions, get_lamassu_transaction ) -from .helpers import lnurler from .models import ( - CreateMyExtensionData, CreatePayment, MyExtension, # DCA models CreateDcaClientData, DcaClient, UpdateDcaClientData, CreateDepositData, DcaDeposit, UpdateDepositStatusData, @@ -52,159 +45,6 @@ from .models import ( myextension_api_router = APIRouter() -# Note: we add the lnurl params to returns so the links -# are generated in the MyExtension model in models.py - -## Get all the records belonging to the user - - -@myextension_api_router.get("/api/v1/myex") -async def api_myextensions( - req: Request, # Withoutthe lnurl stuff this wouldnt be needed - wallet: WalletTypeInfo = Depends(require_invoice_key), -) -> list[MyExtension]: - wallet_ids = [wallet.wallet.id] - user = await get_user(wallet.wallet.user) - wallet_ids = user.wallet_ids if user else [] - myextensions = await get_myextensions(wallet_ids) - - # Populate lnurlpay and lnurlwithdraw for each instance. - # Without the lnurl stuff this wouldnt be needed. - for myex in myextensions: - myex.lnurlpay = lnurler(myex.id, "myextension.api_lnurl_pay", req) - myex.lnurlwithdraw = lnurler(myex.id, "myextension.api_lnurl_withdraw", req) - - return myextensions - - -## Get a single record - - -@myextension_api_router.get( - "/api/v1/myex/{myextension_id}", - dependencies=[Depends(require_invoice_key)], -) -async def api_myextension(myextension_id: str, req: Request) -> MyExtension: - myex = await get_myextension(myextension_id) - if not myex: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist." - ) - # Populate lnurlpay and lnurlwithdraw. - # Without the lnurl stuff this wouldnt be needed. - myex.lnurlpay = lnurler(myex.id, "myextension.api_lnurl_pay", req) - myex.lnurlwithdraw = lnurler(myex.id, "myextension.api_lnurl_withdraw", req) - - return myex - - -## Create a new record - - -@myextension_api_router.post("/api/v1/myex", status_code=HTTPStatus.CREATED) -async def api_myextension_create( - req: Request, # Withoutthe lnurl stuff this wouldnt be needed - data: CreateMyExtensionData, - wallet: WalletTypeInfo = Depends(require_admin_key), -) -> MyExtension: - myex = await create_myextension(data) - - # Populate lnurlpay and lnurlwithdraw. - # Withoutthe lnurl stuff this wouldnt be needed. - myex.lnurlpay = lnurler(myex.id, "myextension.api_lnurl_pay", req) - myex.lnurlwithdraw = lnurler(myex.id, "myextension.api_lnurl_withdraw", req) - - return myex - - -## update a record - - -@myextension_api_router.put("/api/v1/myex/{myextension_id}") -async def api_myextension_update( - req: Request, # Withoutthe lnurl stuff this wouldnt be needed - data: CreateMyExtensionData, - myextension_id: str, - wallet: WalletTypeInfo = Depends(require_admin_key), -) -> MyExtension: - myex = await get_myextension(myextension_id) - if not myex: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist." - ) - - if wallet.wallet.id != myex.wallet: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not your MyExtension." - ) - - for key, value in data.dict().items(): - setattr(myex, key, value) - - myex = await update_myextension(data) - - # Populate lnurlpay and lnurlwithdraw. - # Without the lnurl stuff this wouldnt be needed. - myex.lnurlpay = lnurler(myex.id, "myextension.api_lnurl_pay", req) - myex.lnurlwithdraw = lnurler(myex.id, "myextension.api_lnurl_withdraw", req) - - return myex - - -## Delete a record - - -@myextension_api_router.delete("/api/v1/myex/{myextension_id}") -async def api_myextension_delete( - myextension_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) -): - myex = await get_myextension(myextension_id) - - if not myex: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist." - ) - - if myex.wallet != wallet.wallet.id: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not your MyExtension." - ) - - await delete_myextension(myextension_id) - return - - -# ANY OTHER ENDPOINTS YOU NEED - -## This endpoint creates a payment - - -@myextension_api_router.post("/api/v1/myex/payment", status_code=HTTPStatus.CREATED) -async def api_myextension_create_invoice(data: CreatePayment) -> dict: - myextension = await get_myextension(data.myextension_id) - - if not myextension: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist." - ) - - # we create a payment and add some tags, - # so tasks.py can grab the payment once its paid - - payment = await create_invoice( - wallet_id=myextension.wallet, - amount=data.amount, - memo=( - f"{data.memo} to {myextension.name}" if data.memo else f"{myextension.name}" - ), - extra={ - "tag": "myextension", - "amount": data.amount, - }, - ) - - return {"payment_hash": payment.payment_hash, "payment_request": payment.bolt11} - ################################################### ################ DCA API ENDPOINTS ################ diff --git a/views_lnurl.py b/views_lnurl.py deleted file mode 100644 index c953fe2..0000000 --- a/views_lnurl.py +++ /dev/null @@ -1,146 +0,0 @@ -# Description: Extensions that use LNURL usually have a few endpoints in views_lnurl.py. - -from http import HTTPStatus -from typing import Optional - -import shortuuid -from fastapi import APIRouter, Query, Request -from lnbits.core.services import create_invoice, pay_invoice -from loguru import logger - -from .crud import get_myextension - -################################################# -########### A very simple LNURLpay ############## -# https://github.com/lnurl/luds/blob/luds/06.md # -################################################# -################################################# - -myextension_lnurl_router = APIRouter() - - -@myextension_lnurl_router.get( - "/api/v1/lnurl/pay/{myextension_id}", - status_code=HTTPStatus.OK, - name="myextension.api_lnurl_pay", -) -async def api_lnurl_pay( - request: Request, - myextension_id: str, -): - myextension = await get_myextension(myextension_id) - if not myextension: - return {"status": "ERROR", "reason": "No myextension found"} - return { - "callback": str( - request.url_for( - "myextension.api_lnurl_pay_callback", myextension_id=myextension_id - ) - ), - "maxSendable": myextension.lnurlpayamount * 1000, - "minSendable": myextension.lnurlpayamount * 1000, - "metadata": '[["text/plain", "' + myextension.name + '"]]', - "tag": "payRequest", - } - - -@myextension_lnurl_router.get( - "/api/v1/lnurl/paycb/{myextension_id}", - status_code=HTTPStatus.OK, - name="myextension.api_lnurl_pay_callback", -) -async def api_lnurl_pay_cb( - request: Request, - myextension_id: str, - amount: int = Query(...), -): - myextension = await get_myextension(myextension_id) - logger.debug(myextension) - if not myextension: - return {"status": "ERROR", "reason": "No myextension found"} - - payment = await create_invoice( - wallet_id=myextension.wallet, - amount=int(amount / 1000), - memo=myextension.name, - unhashed_description=f'[["text/plain", "{myextension.name}"]]'.encode(), - extra={ - "tag": "MyExtension", - "myextensionId": myextension_id, - "extra": request.query_params.get("amount"), - }, - ) - return { - "pr": payment.bolt11, - "routes": [], - "successAction": {"tag": "message", "message": f"Paid {myextension.name}"}, - } - - -################################################# -######## A very simple LNURLwithdraw ############ -# https://github.com/lnurl/luds/blob/luds/03.md # -################################################# -## withdraw is unlimited, look at withdraw ext ## -## for more advanced withdraw options ## -################################################# - - -@myextension_lnurl_router.get( - "/api/v1/lnurl/withdraw/{myextension_id}", - status_code=HTTPStatus.OK, - name="myextension.api_lnurl_withdraw", -) -async def api_lnurl_withdraw( - request: Request, - myextension_id: str, -): - myextension = await get_myextension(myextension_id) - if not myextension: - return {"status": "ERROR", "reason": "No myextension found"} - k1 = shortuuid.uuid(name=myextension.id) - return { - "tag": "withdrawRequest", - "callback": str( - request.url_for( - "myextension.api_lnurl_withdraw_callback", myextension_id=myextension_id - ) - ), - "k1": k1, - "defaultDescription": myextension.name, - "maxWithdrawable": myextension.lnurlwithdrawamount * 1000, - "minWithdrawable": myextension.lnurlwithdrawamount * 1000, - } - - -@myextension_lnurl_router.get( - "/api/v1/lnurl/withdrawcb/{myextension_id}", - status_code=HTTPStatus.OK, - name="myextension.api_lnurl_withdraw_callback", -) -async def api_lnurl_withdraw_cb( - myextension_id: str, - pr: Optional[str] = None, - k1: Optional[str] = None, -): - assert k1, "k1 is required" - assert pr, "pr is required" - myextension = await get_myextension(myextension_id) - if not myextension: - return {"status": "ERROR", "reason": "No myextension found"} - - k1_check = shortuuid.uuid(name=myextension.id) - if k1_check != k1: - return {"status": "ERROR", "reason": "Wrong k1 check provided"} - - await pay_invoice( - wallet_id=myextension.wallet, - payment_request=pr, - max_sat=int(myextension.lnurlwithdrawamount * 1000), - extra={ - "tag": "MyExtension", - "myextensionId": myextension_id, - "lnurlwithdraw": True, - }, - ) - return {"status": "OK"}