Merge pull request #3 from lnbits/feat_lnurl_v1
another v1 fixup + moved lnurl stuff to models
This commit is contained in:
commit
2b0281857a
16 changed files with 1532 additions and 1494 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
from lnbits.tasks import create_permanent_unique_task
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from .crud import db
|
from .crud import db
|
||||||
|
|
@ -39,8 +40,6 @@ def myextension_stop():
|
||||||
|
|
||||||
|
|
||||||
def myextension_start():
|
def myextension_start():
|
||||||
from lnbits.tasks import create_permanent_unique_task
|
|
||||||
|
|
||||||
task = create_permanent_unique_task("ext_myextension", wait_for_paid_invoices)
|
task = create_permanent_unique_task("ext_myextension", wait_for_paid_invoices)
|
||||||
scheduled_tasks.append(task)
|
scheduled_tasks.append(task)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "MyExtension",
|
"name": "MyExtension",
|
||||||
"short_description": "Minimal extension to build on",
|
"short_description": "Minimal extension to build on",
|
||||||
"tile": "/myextension/static/image/myextension.png",
|
"tile": "/myextension/static/image/myextension.png",
|
||||||
"min_lnbits_version": "0.12.5",
|
"min_lnbits_version": "1.0.0",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
{
|
{
|
||||||
"name": "Alan Bits",
|
"name": "Alan Bits",
|
||||||
|
|
|
||||||
70
crud.py
70
crud.py
|
|
@ -1,77 +1,45 @@
|
||||||
from typing import Optional, Union
|
# Description: This file contains the CRUD operations for talking to the database.
|
||||||
|
|
||||||
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
from lnbits.db import Database
|
from lnbits.db import Database
|
||||||
from lnbits.helpers import insert_query, update_query
|
from loguru import logger
|
||||||
|
|
||||||
from .models import MyExtension
|
from .models import MyExtension
|
||||||
|
|
||||||
db = Database("ext_myextension")
|
db = Database("ext_myextension")
|
||||||
table_name = "myextension.maintable"
|
|
||||||
|
|
||||||
|
|
||||||
async def create_myextension(data: MyExtension) -> MyExtension:
|
async def create_myextension(data: MyExtension) -> MyExtension:
|
||||||
await db.execute(
|
await db.insert("myextension.maintable", data)
|
||||||
insert_query(table_name, data),
|
|
||||||
(*data.dict().values(),),
|
|
||||||
)
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
# this is how we used to do it
|
|
||||||
|
|
||||||
# myextension_id = urlsafe_short_hash()
|
|
||||||
# await db.execute(
|
|
||||||
# """
|
|
||||||
# INSERT INTO myextension.maintable
|
|
||||||
# (id, wallet, name, lnurlpayamount, lnurlwithdrawamount)
|
|
||||||
# VALUES (?, ?, ?, ?, ?)
|
|
||||||
# """,
|
|
||||||
# (
|
|
||||||
# myextension_id,
|
|
||||||
# wallet_id,
|
|
||||||
# data.name,
|
|
||||||
# data.lnurlpayamount,
|
|
||||||
# data.lnurlwithdrawamount,
|
|
||||||
# ),
|
|
||||||
# )
|
|
||||||
# myextension = await get_myextension(myextension_id)
|
|
||||||
# assert myextension, "Newly created table couldn't be retrieved"
|
|
||||||
|
|
||||||
|
|
||||||
async def get_myextension(myextension_id: str) -> Optional[MyExtension]:
|
async def get_myextension(myextension_id: str) -> Optional[MyExtension]:
|
||||||
row = await db.fetchone(
|
return await db.fetchone(
|
||||||
f"SELECT * FROM {table_name} WHERE id = ?", (myextension_id,)
|
"SELECT * FROM myextension.maintable WHERE id = :id",
|
||||||
|
{"id": myextension_id},
|
||||||
|
MyExtension,
|
||||||
)
|
)
|
||||||
return MyExtension(**row) if row else None
|
|
||||||
|
|
||||||
|
|
||||||
async def get_myextensions(wallet_ids: Union[str, list[str]]) -> list[MyExtension]:
|
async def get_myextensions(wallet_ids: Union[str, List[str]]) -> List[MyExtension]:
|
||||||
if isinstance(wallet_ids, str):
|
if isinstance(wallet_ids, str):
|
||||||
wallet_ids = [wallet_ids]
|
wallet_ids = [wallet_ids]
|
||||||
|
q = ",".join([f"'{w}'" for w in wallet_ids])
|
||||||
q = ",".join(["?"] * len(wallet_ids))
|
logger.debug(q)
|
||||||
rows = await db.fetchall(
|
return await db.fetchall(
|
||||||
f"SELECT * FROM {table_name} WHERE wallet IN ({q})", (*wallet_ids,)
|
f"SELECT * FROM myextension.maintable WHERE wallet IN ({q}) ORDER BY id",
|
||||||
|
model=MyExtension,
|
||||||
)
|
)
|
||||||
return [MyExtension(**row) for row in rows]
|
|
||||||
|
|
||||||
|
|
||||||
async def update_myextension(data: MyExtension) -> MyExtension:
|
async def update_myextension(data: MyExtension) -> MyExtension:
|
||||||
await db.execute(
|
await db.update("myextension.maintable", data)
|
||||||
update_query(table_name, data),
|
|
||||||
(
|
|
||||||
*data.dict().values(),
|
|
||||||
data.id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return data
|
return data
|
||||||
# this is how we used to do it
|
|
||||||
|
|
||||||
# q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
|
|
||||||
# await db.execute(
|
|
||||||
# f"UPDATE myextension.maintable SET {q} WHERE id = ?",
|
|
||||||
# (*kwargs.values(), myextension_id),
|
|
||||||
# )
|
|
||||||
|
|
||||||
|
|
||||||
async def delete_myextension(myextension_id: str) -> None:
|
async def delete_myextension(myextension_id: str) -> None:
|
||||||
await db.execute(f"DELETE FROM {table_name} WHERE id = ?", (myextension_id,))
|
await db.execute(
|
||||||
|
"DELETE FROM myextension.maintable WHERE id = :id", {"id": myextension_id}
|
||||||
|
)
|
||||||
|
|
|
||||||
17
helpers.py
Normal file
17
helpers.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# 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))
|
||||||
|
|
@ -15,9 +15,19 @@ async def m001_initial(db):
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
total INTEGER DEFAULT 0,
|
total INTEGER DEFAULT 0,
|
||||||
lnurlpayamount INTEGER DEFAULT 0,
|
lnurlpayamount INTEGER DEFAULT 0,
|
||||||
lnurlwithdrawamount INTEGER DEFAULT 0,
|
lnurlwithdrawamount INTEGER DEFAULT 0
|
||||||
lnurlwithdraw TEXT,
|
|
||||||
lnurlpay TEXT
|
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def m002_add_timestamp(db):
|
||||||
|
"""
|
||||||
|
Add timestamp to templates table.
|
||||||
|
"""
|
||||||
|
await db.execute(
|
||||||
|
f"""
|
||||||
|
ALTER TABLE myextension.maintable
|
||||||
|
ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now};
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
|
||||||
19
models.py
19
models.py
|
|
@ -1,6 +1,4 @@
|
||||||
# Data models for your extension
|
# Description: Pydantic data models dictate what is passed between frontend and backend.
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
@ -9,16 +7,21 @@ class CreateMyExtensionData(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
lnurlpayamount: int
|
lnurlpayamount: int
|
||||||
lnurlwithdrawamount: int
|
lnurlwithdrawamount: int
|
||||||
wallet: Optional[str] = None
|
|
||||||
total: int = 0
|
total: int = 0
|
||||||
|
|
||||||
|
|
||||||
class MyExtension(BaseModel):
|
class MyExtension(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
wallet: str
|
|
||||||
lnurlpayamount: int
|
|
||||||
name: str
|
name: str
|
||||||
|
lnurlpayamount: int
|
||||||
lnurlwithdrawamount: int
|
lnurlwithdrawamount: int
|
||||||
|
wallet: str
|
||||||
total: int
|
total: int
|
||||||
lnurlpay: Optional[str]
|
lnurlpay: str = ""
|
||||||
lnurlwithdraw: Optional[str]
|
lnurlwithdraw: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePayment(BaseModel):
|
||||||
|
myextension_id: str
|
||||||
|
amount: int
|
||||||
|
memo: str
|
||||||
|
|
|
||||||
2118
poetry.lock
generated
2118
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -6,7 +6,8 @@ authors = ["benarc", "dni <dni@lnbits.com>"]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.10 | ^3.9"
|
python = "^3.10 | ^3.9"
|
||||||
lnbits = "*"
|
lnbits = {version = "*", allow-prereleases = true}
|
||||||
|
mypy = "^1.13.0"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = "^24.3.0"
|
black = "^24.3.0"
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 47 KiB |
242
static/js/index.js
Normal file
242
static/js/index.js
Normal file
|
|
@ -0,0 +1,242 @@
|
||||||
|
window.app = Vue.createApp({
|
||||||
|
el: '#vue',
|
||||||
|
mixins: [windowMixin],
|
||||||
|
delimiters: ['${', '}'],
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
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: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////
|
||||||
|
////////////////METHODS FUNCTIONS//////////////////
|
||||||
|
///////////////////////////////////////////////////
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
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})
|
||||||
|
await LNbits.utils
|
||||||
|
.confirmDialog('Are you sure you want to delete this MyExtension?')
|
||||||
|
.onOk(function () {
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'DELETE',
|
||||||
|
'/myextension/api/v1/myex/' + tempId,
|
||||||
|
_.findWhere(this.g.user.wallets, {id: myextension.wallet})
|
||||||
|
.adminkey
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
this.myex = _.reject(this.myex, obj => obj.id == tempId)
|
||||||
|
})
|
||||||
|
.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)
|
||||||
|
|
||||||
|
// We can also use this Lnbits core websocket function for getting a payment reaction
|
||||||
|
const wallet = _.findWhere(this.g.user.wallets, {
|
||||||
|
id: this.urlDialog.data.wallet
|
||||||
|
})
|
||||||
|
eventReactionWebocket(wallet.inkey)
|
||||||
|
|
||||||
|
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/////
|
||||||
|
///////////////////////////////////////////////////
|
||||||
|
async created() {
|
||||||
|
await this.getMyExtensions()
|
||||||
|
}
|
||||||
|
})
|
||||||
4
tasks.py
4
tasks.py
|
|
@ -2,7 +2,6 @@ import asyncio
|
||||||
|
|
||||||
from lnbits.core.models import Payment
|
from lnbits.core.models import Payment
|
||||||
from lnbits.core.services import websocket_updater
|
from lnbits.core.services import websocket_updater
|
||||||
from lnbits.helpers import get_current_extension_name
|
|
||||||
from lnbits.tasks import register_invoice_listener
|
from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
from .crud import get_myextension, update_myextension
|
from .crud import get_myextension, update_myextension
|
||||||
|
|
@ -16,7 +15,7 @@ from .crud import get_myextension, update_myextension
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
async def wait_for_paid_invoices():
|
||||||
invoice_queue = asyncio.Queue()
|
invoice_queue = asyncio.Queue()
|
||||||
register_invoice_listener(invoice_queue, get_current_extension_name())
|
register_invoice_listener(invoice_queue, "ext_myextension")
|
||||||
while True:
|
while True:
|
||||||
payment = await invoice_queue.get()
|
payment = await invoice_queue.get()
|
||||||
await on_invoice_paid(payment)
|
await on_invoice_paid(payment)
|
||||||
|
|
@ -45,7 +44,6 @@ async def on_invoice_paid(payment: Payment) -> None:
|
||||||
|
|
||||||
# here we could send some data to a websocket on
|
# here we could send some data to a websocket on
|
||||||
# wss://<your-lnbits>/api/v1/ws/<myextension_id> and then listen to it on
|
# wss://<your-lnbits>/api/v1/ws/<myextension_id> and then listen to it on
|
||||||
# the frontend, which we do with index.html connectWebocket()
|
|
||||||
|
|
||||||
some_payment_data = {
|
some_payment_data = {
|
||||||
"name": myextension.name,
|
"name": myextension.name,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
<!--/////////////////////////////////////////////////-->
|
<!--/////////////////////////////////////////////////-->
|
||||||
|
|
||||||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
||||||
%} {% block page %}
|
%} {% block scripts %} {{ window_vars(user) }}
|
||||||
|
<script src="{{ static_url_for('myextension/static', path='js/index.js') }}"></script>
|
||||||
|
{% endblock %} {% block page %}
|
||||||
<div class="row q-col-gutter-md" id="makeItRain">
|
<div class="row q-col-gutter-md" id="makeItRain">
|
||||||
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
||||||
<q-card>
|
<q-card>
|
||||||
|
|
@ -27,10 +29,10 @@
|
||||||
<q-table
|
<q-table
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
:data="myex"
|
:rows="myex"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
:columns="myexTable.columns"
|
:columns="myexTable.columns"
|
||||||
:pagination.sync="myexTable.pagination"
|
v-model:pagination="myexTable.pagination"
|
||||||
>
|
>
|
||||||
<myextension v-slot:header="props">
|
<myextension v-slot:header="props">
|
||||||
<q-tr :props="props">
|
<q-tr :props="props">
|
||||||
|
|
@ -65,7 +67,7 @@
|
||||||
icon="launch"
|
icon="launch"
|
||||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
||||||
type="a"
|
type="a"
|
||||||
:href="props.row.myextension"
|
:href="props.row.id"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
><q-tooltip>Open public page</q-tooltip></q-btn
|
><q-tooltip>Open public page</q-tooltip></q-btn
|
||||||
></q-td
|
></q-td
|
||||||
|
|
@ -220,7 +222,7 @@
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<q-btn
|
<q-btn
|
||||||
round
|
round
|
||||||
@click="createInvoice(urlDialog.data.wallet, urlDialog.data.id)"
|
@click="createInvoice(urlDialog.data.id)"
|
||||||
color="primary"
|
color="primary"
|
||||||
flat
|
flat
|
||||||
icon="add_circle"
|
icon="add_circle"
|
||||||
|
|
@ -236,296 +238,4 @@
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.4.0/dist/confetti.browser.min.js"></script>
|
|
||||||
<script>
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
//////////an object we can update with data////////
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
const mapMyExtension = obj => {
|
|
||||||
obj.date = Quasar.utils.date.formatDate(
|
|
||||||
new Date(obj.time * 1000),
|
|
||||||
'YYYY-MM-DD HH:mm'
|
|
||||||
)
|
|
||||||
obj.myextension = ['/myextension/', obj.id].join('')
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
new Vue({
|
|
||||||
el: '#vue',
|
|
||||||
mixins: [windowMixin],
|
|
||||||
delimiters: ['${', '}'],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
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: {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
////////////////METHODS FUNCTIONS//////////////////
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
closeFormDialog() {
|
|
||||||
this.formDialog.show = false
|
|
||||||
this.formDialog.data = {}
|
|
||||||
},
|
|
||||||
getMyExtensions: function () {
|
|
||||||
var self = this
|
|
||||||
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'GET',
|
|
||||||
'/myextension/api/v1/myex?all_wallets=true',
|
|
||||||
this.g.user.wallets[0].inkey
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
self.myex = response.data.map(function (obj) {
|
|
||||||
return mapMyExtension(obj)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
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.wallet = wallet.id
|
|
||||||
data.total = this.formDialog.data.total
|
|
||||||
this.updateMyExtension(wallet, data)
|
|
||||||
} else {
|
|
||||||
this.createMyExtension(wallet, data)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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
|
|
||||||
},
|
|
||||||
createMyExtension(wallet, data) {
|
|
||||||
LNbits.api
|
|
||||||
.request('POST', '/myextension/api/v1/myex', wallet.adminkey, data)
|
|
||||||
.then(response => {
|
|
||||||
this.myex.push(mapMyExtension(response.data))
|
|
||||||
this.closeFormDialog()
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
updateMyExtension(wallet, data) {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'PUT',
|
|
||||||
`/myextension/api/v1/myex/${data.id}`,
|
|
||||||
wallet.adminkey,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
.then(response => {
|
|
||||||
this.myex = _.reject(this.myex, obj => {
|
|
||||||
return obj.id == data.id
|
|
||||||
})
|
|
||||||
this.myex.push(mapMyExtension(response.data))
|
|
||||||
this.closeFormDialog()
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
deleteMyExtension: function (tempId) {
|
|
||||||
var self = this
|
|
||||||
var myextension = _.findWhere(this.myex, {id: tempId})
|
|
||||||
|
|
||||||
LNbits.utils
|
|
||||||
.confirmDialog('Are you sure you want to delete this MyExtension?')
|
|
||||||
.onOk(function () {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'DELETE',
|
|
||||||
'/myextension/api/v1/myex/' + tempId,
|
|
||||||
_.findWhere(self.g.user.wallets, {id: myextension.wallet})
|
|
||||||
.adminkey
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
self.myex = _.reject(self.myex, function (obj) {
|
|
||||||
return obj.id == tempId
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
exportCSV: function () {
|
|
||||||
LNbits.utils.exportCSV(this.myexTable.columns, this.myex)
|
|
||||||
},
|
|
||||||
itemsArray(tempId) {
|
|
||||||
const myextension = _.findWhere(this.myex, {id: tempId})
|
|
||||||
return [...myextension.itemsMap.values()]
|
|
||||||
},
|
|
||||||
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
|
|
||||||
},
|
|
||||||
closeformDialog() {
|
|
||||||
this.formDialog.show = false
|
|
||||||
this.formDialog.data = {}
|
|
||||||
},
|
|
||||||
openUrlDialog(id) {
|
|
||||||
this.urlDialog.data = _.findWhere(this.myex, {id})
|
|
||||||
this.qrValue = this.urlDialog.data.lnurlpay
|
|
||||||
console.log(this.urlDialog.data.id)
|
|
||||||
this.connectWebocket(this.urlDialog.data.id)
|
|
||||||
this.urlDialog.show = true
|
|
||||||
},
|
|
||||||
createInvoice(walletId, myextensionId) {
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
///Simple call to the api to create an invoice/////
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
console.log(walletId)
|
|
||||||
const wallet = _.findWhere(this.g.user.wallets, {
|
|
||||||
id: walletId
|
|
||||||
})
|
|
||||||
const dataToSend = {
|
|
||||||
out: false,
|
|
||||||
amount: this.invoiceAmount,
|
|
||||||
memo: 'Invoice created by MyExtension',
|
|
||||||
extra: {
|
|
||||||
tag: 'MyExtension',
|
|
||||||
myextensionId: myextensionId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LNbits.api
|
|
||||||
.request('POST', `/api/v1/payments`, wallet.inkey, dataToSend)
|
|
||||||
.then(response => {
|
|
||||||
this.qrValue = response.data.payment_request
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
makeItRain() {
|
|
||||||
document.getElementById('vue').disabled = true
|
|
||||||
var end = Date.now() + 2 * 1000
|
|
||||||
var colors = ['#FFD700', '#ffffff']
|
|
||||||
function frame() {
|
|
||||||
confetti({
|
|
||||||
particleCount: 2,
|
|
||||||
angle: 60,
|
|
||||||
spread: 55,
|
|
||||||
origin: {x: 0},
|
|
||||||
colors: colors,
|
|
||||||
zIndex: 999999
|
|
||||||
})
|
|
||||||
confetti({
|
|
||||||
particleCount: 2,
|
|
||||||
angle: 120,
|
|
||||||
spread: 55,
|
|
||||||
origin: {x: 1},
|
|
||||||
colors: colors,
|
|
||||||
zIndex: 999999
|
|
||||||
})
|
|
||||||
if (Date.now() < end) {
|
|
||||||
requestAnimationFrame(frame)
|
|
||||||
} else {
|
|
||||||
document.getElementById('vue').disabled = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
frame()
|
|
||||||
},
|
|
||||||
connectWebocket(wallet_id) {
|
|
||||||
//////////////////////////////////////////////////
|
|
||||||
///wait for pay action to happen and do a thing////
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
self = this
|
|
||||||
if (location.protocol !== 'http:') {
|
|
||||||
localUrl =
|
|
||||||
'wss://' +
|
|
||||||
document.domain +
|
|
||||||
':' +
|
|
||||||
location.port +
|
|
||||||
'/api/v1/ws/' +
|
|
||||||
wallet_id
|
|
||||||
} else {
|
|
||||||
localUrl =
|
|
||||||
'ws://' +
|
|
||||||
document.domain +
|
|
||||||
':' +
|
|
||||||
location.port +
|
|
||||||
'/api/v1/ws/' +
|
|
||||||
wallet_id
|
|
||||||
}
|
|
||||||
this.connection = new WebSocket(localUrl)
|
|
||||||
this.connection.onmessage = function (e) {
|
|
||||||
self.makeItRain()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
//////LIFECYCLE FUNCTIONS RUNNING ON PAGE LOAD/////
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
created: function () {
|
|
||||||
if (this.g.user.wallets.length) {
|
|
||||||
this.getMyExtensions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,11 @@
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<a class="text-secondary" href="lightning:{{ lnurl }}">
|
<a class="text-secondary" href="lightning:{{ lnurl }}">
|
||||||
<q-responsive :ratio="1" class="q-mx-md">
|
<q-responsive :ratio="1" class="q-mx-md">
|
||||||
<qrcode
|
<lnbits-qrcode
|
||||||
:value="qrValue"
|
:value="qrValue"
|
||||||
:options="{width: 800}"
|
:options="{width: 800}"
|
||||||
class="rounded-borders"
|
class="rounded-borders"
|
||||||
></qrcode>
|
></lnbits-qrcode>
|
||||||
</q-responsive>
|
</q-responsive>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -49,9 +49,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
window.app = Vue.createApp({
|
||||||
|
|
||||||
new Vue({
|
|
||||||
el: '#vue',
|
el: '#vue',
|
||||||
mixins: [windowMixin],
|
mixins: [windowMixin],
|
||||||
data: function () {
|
data: function () {
|
||||||
|
|
|
||||||
22
views.py
22
views.py
|
|
@ -1,14 +1,16 @@
|
||||||
|
# Description: Add your page endpoints here.
|
||||||
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Request
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
from lnbits.core.models import User
|
from lnbits.core.models import User
|
||||||
from lnbits.decorators import check_user_exists
|
from lnbits.decorators import check_user_exists
|
||||||
from lnbits.helpers import template_renderer
|
from lnbits.helpers import template_renderer
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from starlette.exceptions import HTTPException
|
|
||||||
from starlette.responses import HTMLResponse
|
|
||||||
|
|
||||||
from .crud import get_myextension
|
from .crud import get_myextension
|
||||||
|
from .helpers import lnurler
|
||||||
|
|
||||||
myextension_generic_router = APIRouter()
|
myextension_generic_router = APIRouter()
|
||||||
|
|
||||||
|
|
@ -26,9 +28,9 @@ def myextension_renderer():
|
||||||
|
|
||||||
|
|
||||||
@myextension_generic_router.get("/", response_class=HTMLResponse)
|
@myextension_generic_router.get("/", response_class=HTMLResponse)
|
||||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
async def index(req: Request, user: User = Depends(check_user_exists)):
|
||||||
return myextension_renderer().TemplateResponse(
|
return myextension_renderer().TemplateResponse(
|
||||||
"myextension/index.html", {"request": request, "user": user.dict()}
|
"myextension/index.html", {"request": req, "user": user.json()}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -36,18 +38,18 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
|
||||||
|
|
||||||
|
|
||||||
@myextension_generic_router.get("/{myextension_id}")
|
@myextension_generic_router.get("/{myextension_id}")
|
||||||
async def myextension(request: Request, myextension_id):
|
async def myextension(req: Request, myextension_id):
|
||||||
myextension = await get_myextension(myextension_id)
|
myex = await get_myextension(myextension_id)
|
||||||
if not myextension:
|
if not myex:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist."
|
status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist."
|
||||||
)
|
)
|
||||||
return myextension_renderer().TemplateResponse(
|
return myextension_renderer().TemplateResponse(
|
||||||
"myextension/myextension.html",
|
"myextension/myextension.html",
|
||||||
{
|
{
|
||||||
"request": request,
|
"request": req,
|
||||||
"myextension_id": myextension_id,
|
"myextension_id": myextension_id,
|
||||||
"lnurlpay": myextension.lnurlpay,
|
"lnurlpay": lnurler(myex.id, "myextension.api_lnurl_pay", req),
|
||||||
"web_manifest": f"/myextension/manifest/{myextension_id}.webmanifest",
|
"web_manifest": f"/myextension/manifest/{myextension_id}.webmanifest",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
190
views_api.py
190
views_api.py
|
|
@ -1,16 +1,13 @@
|
||||||
|
# Description: This file contains the extensions API endpoints.
|
||||||
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query, Request
|
from fastapi import APIRouter, Depends, Request
|
||||||
from lnbits.core.crud import get_user
|
from lnbits.core.crud import get_user
|
||||||
from lnbits.core.models import WalletTypeInfo
|
from lnbits.core.models import WalletTypeInfo
|
||||||
from lnbits.core.services import create_invoice
|
from lnbits.core.services import create_invoice
|
||||||
from lnbits.decorators import (
|
from lnbits.decorators import require_admin_key, require_invoice_key
|
||||||
get_key_type,
|
|
||||||
require_admin_key,
|
|
||||||
require_invoice_key,
|
|
||||||
)
|
|
||||||
from lnbits.helpers import urlsafe_short_hash
|
from lnbits.helpers import urlsafe_short_hash
|
||||||
from lnurl import encode as lnurl_encode
|
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
from .crud import (
|
from .crud import (
|
||||||
|
|
@ -20,28 +17,34 @@ from .crud import (
|
||||||
get_myextensions,
|
get_myextensions,
|
||||||
update_myextension,
|
update_myextension,
|
||||||
)
|
)
|
||||||
from .models import CreateMyExtensionData, MyExtension
|
from .helpers import lnurler
|
||||||
|
from .models import CreateMyExtensionData, CreatePayment, MyExtension
|
||||||
|
|
||||||
myextension_api_router = APIRouter()
|
myextension_api_router = APIRouter()
|
||||||
|
|
||||||
|
# Note: we add the lnurl params to returns so the links
|
||||||
#######################################
|
# are generated in the MyExtension model in models.py
|
||||||
##### ADD YOUR API ENDPOINTS HERE #####
|
|
||||||
#######################################
|
|
||||||
|
|
||||||
## Get all the records belonging to the user
|
## Get all the records belonging to the user
|
||||||
|
|
||||||
|
|
||||||
@myextension_api_router.get("/api/v1/myex", status_code=HTTPStatus.OK)
|
@myextension_api_router.get("/api/v1/myex")
|
||||||
async def api_myextensions(
|
async def api_myextensions(
|
||||||
all_wallets: bool = Query(False),
|
req: Request, # Withoutthe lnurl stuff this wouldnt be needed
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
||||||
):
|
) -> list[MyExtension]:
|
||||||
wallet_ids = [wallet.wallet.id]
|
wallet_ids = [wallet.wallet.id]
|
||||||
if all_wallets:
|
user = await get_user(wallet.wallet.user)
|
||||||
user = await get_user(wallet.wallet.user)
|
wallet_ids = user.wallet_ids if user else []
|
||||||
wallet_ids = user.wallet_ids if user else []
|
myextensions = await get_myextensions(wallet_ids)
|
||||||
return [myextension.dict() for myextension in 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
|
## Get a single record
|
||||||
|
|
@ -49,43 +52,20 @@ async def api_myextensions(
|
||||||
|
|
||||||
@myextension_api_router.get(
|
@myextension_api_router.get(
|
||||||
"/api/v1/myex/{myextension_id}",
|
"/api/v1/myex/{myextension_id}",
|
||||||
status_code=HTTPStatus.OK,
|
|
||||||
dependencies=[Depends(require_invoice_key)],
|
dependencies=[Depends(require_invoice_key)],
|
||||||
)
|
)
|
||||||
async def api_myextension(myextension_id: str):
|
async def api_myextension(myextension_id: str, req: Request) -> MyExtension:
|
||||||
myextension = await get_myextension(myextension_id)
|
myex = await get_myextension(myextension_id)
|
||||||
if not myextension:
|
if not myex:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist."
|
status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist."
|
||||||
)
|
)
|
||||||
return myextension.dict()
|
# 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
|
||||||
## update a record
|
|
||||||
|
|
||||||
|
|
||||||
@myextension_api_router.put("/api/v1/myex/{myextension_id}")
|
|
||||||
async def api_myextension_update(
|
|
||||||
data: CreateMyExtensionData,
|
|
||||||
myextension_id: str,
|
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
|
||||||
) -> MyExtension:
|
|
||||||
if not myextension_id:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist."
|
|
||||||
)
|
|
||||||
myextension = await get_myextension(myextension_id)
|
|
||||||
assert myextension, "MyExtension couldn't be retrieved"
|
|
||||||
|
|
||||||
if wallet.wallet.id != myextension.wallet:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.FORBIDDEN, detail="Not your MyExtension."
|
|
||||||
)
|
|
||||||
|
|
||||||
for key, value in data.dict().items():
|
|
||||||
setattr(myextension, key, value)
|
|
||||||
|
|
||||||
return await update_myextension(myextension)
|
|
||||||
|
|
||||||
|
|
||||||
## Create a new record
|
## Create a new record
|
||||||
|
|
@ -93,29 +73,53 @@ async def api_myextension_update(
|
||||||
|
|
||||||
@myextension_api_router.post("/api/v1/myex", status_code=HTTPStatus.CREATED)
|
@myextension_api_router.post("/api/v1/myex", status_code=HTTPStatus.CREATED)
|
||||||
async def api_myextension_create(
|
async def api_myextension_create(
|
||||||
request: Request,
|
req: Request, # Withoutthe lnurl stuff this wouldnt be needed
|
||||||
data: CreateMyExtensionData,
|
data: CreateMyExtensionData,
|
||||||
key_type: WalletTypeInfo = Depends(require_admin_key),
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
) -> MyExtension:
|
) -> MyExtension:
|
||||||
myextension_id = urlsafe_short_hash()
|
myex = MyExtension(**data.dict(), wallet=wallet.wallet.id, id=urlsafe_short_hash())
|
||||||
lnurlpay = lnurl_encode(
|
myex = await create_myextension(myex)
|
||||||
str(request.url_for("myextension.api_lnurl_pay", myextension_id=myextension_id))
|
|
||||||
)
|
# Populate lnurlpay and lnurlwithdraw.
|
||||||
lnurlwithdraw = lnurl_encode(
|
# Withoutthe lnurl stuff this wouldnt be needed.
|
||||||
str(
|
myex.lnurlpay = lnurler(myex.id, "myextension.api_lnurl_pay", req)
|
||||||
request.url_for(
|
myex.lnurlwithdraw = lnurler(myex.id, "myextension.api_lnurl_withdraw", req)
|
||||||
"myextension.api_lnurl_withdraw", myextension_id=myextension_id
|
|
||||||
)
|
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: MyExtension,
|
||||||
|
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."
|
||||||
)
|
)
|
||||||
)
|
|
||||||
data.wallet = data.wallet or key_type.wallet.id
|
if wallet.wallet.id != myex.wallet:
|
||||||
myext = MyExtension(
|
raise HTTPException(
|
||||||
id=myextension_id,
|
status_code=HTTPStatus.FORBIDDEN, detail="Not your MyExtension."
|
||||||
lnurlpay=lnurlpay,
|
)
|
||||||
lnurlwithdraw=lnurlwithdraw,
|
|
||||||
**data.dict(),
|
for key, value in data.dict().items():
|
||||||
)
|
setattr(myex, key, value)
|
||||||
return await create_myextension(myext)
|
|
||||||
|
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
|
## Delete a record
|
||||||
|
|
@ -125,20 +129,19 @@ async def api_myextension_create(
|
||||||
async def api_myextension_delete(
|
async def api_myextension_delete(
|
||||||
myextension_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
|
myextension_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||||
):
|
):
|
||||||
myextension = await get_myextension(myextension_id)
|
myex = await get_myextension(myextension_id)
|
||||||
|
|
||||||
if not myextension:
|
if not myex:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist."
|
status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist."
|
||||||
)
|
)
|
||||||
|
|
||||||
if myextension.wallet != wallet.wallet.id:
|
if myex.wallet != wallet.wallet.id:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.FORBIDDEN, detail="Not your MyExtension."
|
status_code=HTTPStatus.FORBIDDEN, detail="Not your MyExtension."
|
||||||
)
|
)
|
||||||
|
|
||||||
await delete_myextension(myextension_id)
|
await delete_myextension(myextension_id)
|
||||||
return "", HTTPStatus.NO_CONTENT
|
|
||||||
|
|
||||||
|
|
||||||
# ANY OTHER ENDPOINTS YOU NEED
|
# ANY OTHER ENDPOINTS YOU NEED
|
||||||
|
|
@ -146,13 +149,9 @@ async def api_myextension_delete(
|
||||||
## This endpoint creates a payment
|
## This endpoint creates a payment
|
||||||
|
|
||||||
|
|
||||||
@myextension_api_router.post(
|
@myextension_api_router.post("/api/v1/myex/payment", status_code=HTTPStatus.CREATED)
|
||||||
"/api/v1/myex/payment/{myextension_id}", status_code=HTTPStatus.CREATED
|
async def api_myextension_create_invoice(data: CreatePayment) -> dict:
|
||||||
)
|
myextension = await get_myextension(data.myextension_id)
|
||||||
async def api_myextension_create_invoice(
|
|
||||||
myextension_id: str, amount: int = Query(..., ge=1), memo: str = ""
|
|
||||||
) -> dict:
|
|
||||||
myextension = await get_myextension(myextension_id)
|
|
||||||
|
|
||||||
if not myextension:
|
if not myextension:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
@ -162,19 +161,16 @@ async def api_myextension_create_invoice(
|
||||||
# we create a payment and add some tags,
|
# we create a payment and add some tags,
|
||||||
# so tasks.py can grab the payment once its paid
|
# so tasks.py can grab the payment once its paid
|
||||||
|
|
||||||
try:
|
payment = await create_invoice(
|
||||||
payment_hash, payment_request = await create_invoice(
|
wallet_id=myextension.wallet,
|
||||||
wallet_id=myextension.wallet,
|
amount=data.amount,
|
||||||
amount=amount,
|
memo=(
|
||||||
memo=f"{memo} to {myextension.name}" if memo else f"{myextension.name}",
|
f"{data.memo} to {myextension.name}" if data.memo else f"{myextension.name}"
|
||||||
extra={
|
),
|
||||||
"tag": "myextension",
|
extra={
|
||||||
"amount": amount,
|
"tag": "myextension",
|
||||||
},
|
"amount": data.amount,
|
||||||
)
|
},
|
||||||
except Exception as exc:
|
)
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(exc)
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
return {"payment_hash": payment_hash, "payment_request": payment_request}
|
return {"payment_hash": payment.payment_hash, "payment_request": payment.bolt11}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
# Maybe your extension needs some LNURL stuff.
|
# Description: Extensions that use LNURL usually have a few endpoints in views_lnurl.py.
|
||||||
# Here is a very simple example of how to do it.
|
|
||||||
# Feel free to delete this file if you don't need it.
|
|
||||||
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
@ -61,7 +59,7 @@ async def api_lnurl_pay_cb(
|
||||||
if not myextension:
|
if not myextension:
|
||||||
return {"status": "ERROR", "reason": "No myextension found"}
|
return {"status": "ERROR", "reason": "No myextension found"}
|
||||||
|
|
||||||
_, payment_request = await create_invoice(
|
payment = await create_invoice(
|
||||||
wallet_id=myextension.wallet,
|
wallet_id=myextension.wallet,
|
||||||
amount=int(amount / 1000),
|
amount=int(amount / 1000),
|
||||||
memo=myextension.name,
|
memo=myextension.name,
|
||||||
|
|
@ -73,7 +71,7 @@ async def api_lnurl_pay_cb(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
"pr": payment_request,
|
"pr": payment.bolt11,
|
||||||
"routes": [],
|
"routes": [],
|
||||||
"successAction": {"tag": "message", "message": f"Paid {myextension.name}"},
|
"successAction": {"tag": "message", "message": f"Paid {myextension.name}"},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue