This commit is contained in:
benarc 2024-02-01 17:18:55 +00:00
parent 66d44f95fb
commit 2cac36be17
12 changed files with 272 additions and 311 deletions

View file

@ -8,9 +8,7 @@ from lnbits.tasks import catch_everything_and_restart
db = Database("ext_myextension")
myextension_ext: APIRouter = APIRouter(
prefix="/myextension", tags=["MyExtension"]
)
myextension_ext: APIRouter = APIRouter(prefix="/myextension", tags=["MyExtension"])
temp_static_files = [
{
@ -19,14 +17,17 @@ temp_static_files = [
}
]
def myextension_renderer():
return template_renderer(["myextension/templates"])
from .lnurl import *
from .tasks import wait_for_paid_invoices
from .views import *
from .views_api import *
def myextension_start():
loop = asyncio.get_event_loop()
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))

63
crud.py
View file

@ -9,7 +9,10 @@ from fastapi import Request
from lnurl import encode as lnurl_encode
import shortuuid
async def create_myextension(wallet_id: str, data: CreateMyExtensionData, req: Request) -> MyExtension:
async def create_myextension(
wallet_id: str, data: CreateMyExtensionData, req: Request
) -> MyExtension:
myextension_id = urlsafe_short_hash()
await db.execute(
"""
@ -21,7 +24,7 @@ async def create_myextension(wallet_id: str, data: CreateMyExtensionData, req: R
wallet_id,
data.name,
data.lnurlpayamount,
data.lnurlwithdrawamount
data.lnurlwithdrawamount,
),
)
myextension = await get_myextension(myextension_id, req)
@ -29,25 +32,33 @@ async def create_myextension(wallet_id: str, data: CreateMyExtensionData, req: R
return myextension
async def get_myextension(myextension_id: str, req: Optional[Request] = None) -> Optional[MyExtension]:
async def get_myextension(
myextension_id: str, req: Optional[Request] = None
) -> Optional[MyExtension]:
logger.debug(myextension_id)
row = await db.fetchone("SELECT * FROM myextension.maintable WHERE id = ?", (myextension_id,))
row = await db.fetchone(
"SELECT * FROM myextension.maintable WHERE id = ?", (myextension_id,)
)
if not row:
return None
rowAmended = MyExtension(**row)
if req:
rowAmended.lnurlpay = lnurl_encode(
req.url_for("myextension.api_lnurl_pay",
myextension_id=row.id)._url
)
req.url_for("myextension.api_lnurl_pay", myextension_id=row.id)._url
)
rowAmended.lnurlwithdraw = lnurl_encode(
req.url_for("myextension.api_lnurl_withdraw",
myextension_id=row.id,
tickerhash=shortuuid.uuid(name=rowAmended.id + str(rowAmended.ticker)))._url
)
req.url_for(
"myextension.api_lnurl_withdraw",
myextension_id=row.id,
tickerhash=shortuuid.uuid(name=rowAmended.id + str(rowAmended.ticker)),
)._url
)
return rowAmended
async def get_myextensions(wallet_ids: Union[str, List[str]], req: Optional[Request] = None) -> List[MyExtension]:
async def get_myextensions(
wallet_ids: Union[str, List[str]], req: Optional[Request] = None
) -> List[MyExtension]:
if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids]
@ -59,25 +70,33 @@ async def get_myextensions(wallet_ids: Union[str, List[str]], req: Optional[Requ
if req:
for row in tempRows:
row.lnurlpay = lnurl_encode(
req.url_for("myextension.api_lnurl_pay",
myextension_id=row.id)._url
)
req.url_for("myextension.api_lnurl_pay", myextension_id=row.id)._url
)
row.lnurlwithdraw = lnurl_encode(
req.url_for("myextension.api_lnurl_withdraw",
myextension_id=row.id,
tickerhash=shortuuid.uuid(name=row.id + str(row.ticker)))._url
req.url_for(
"myextension.api_lnurl_withdraw",
myextension_id=row.id,
tickerhash=shortuuid.uuid(name=row.id + str(row.ticker)),
)._url
)
return tempRows
async def update_myextension(myextension_id: str, req: Optional[Request] = None, **kwargs) -> MyExtension:
async def update_myextension(
myextension_id: str, req: Optional[Request] = None, **kwargs
) -> MyExtension:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
logger.debug( kwargs.items())
logger.debug(kwargs.items())
await db.execute(
f"UPDATE myextension.maintable SET {q} WHERE id = ?", (*kwargs.values(), myextension_id)
f"UPDATE myextension.maintable SET {q} WHERE id = ?",
(*kwargs.values(), myextension_id),
)
myextension = await get_myextension(myextension_id, req)
assert myextension, "Newly updated myextension couldn't be retrieved"
return myextension
async def delete_myextension(myextension_id: str) -> None:
await db.execute("DELETE FROM myextension.maintable WHERE id = ?", (myextension_id,))
await db.execute(
"DELETE FROM myextension.maintable WHERE id = ?", (myextension_id,)
)

View file

@ -19,8 +19,9 @@ import shortuuid
#################################################
#################################################
@myextension_ext.get(
"/api/v1/lnurl/pay/{myextension_id}",
"/api/v1/lnurl/pay/{myextension_id}",
status_code=HTTPStatus.OK,
name="myextension.api_lnurl_pay",
)
@ -32,15 +33,20 @@ async def api_lnurl_pay(
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"
}
"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_ext.get(
"/api/v1/lnurl/pay/cb/{myextension_id}",
"/api/v1/lnurl/pay/cb/{myextension_id}",
status_code=HTTPStatus.OK,
name="myextension.api_lnurl_pay_callback",
)
@ -53,27 +59,25 @@ async def api_lnurl_pay_cb(
logger.debug(myextension)
if not myextension:
return {"status": "ERROR", "reason": "No myextension found"}
payment_hash, payment_request = await create_invoice(
wallet_id=myextension.wallet,
amount=int(amount / 1000),
memo=myextension.name,
unhashed_description=f"[[\"text/plain\", \"{myextension.name}\"]]".encode(),
extra= {
unhashed_description=f'[["text/plain", "{myextension.name}"]]'.encode(),
extra={
"tag": "MyExtension",
"myextensionId": myextension_id,
"extra": request.query_params.get("amount"),
},
)
return {
"pr": payment_request,
"pr": payment_request,
"routes": [],
"successAction": {
"tag": "message",
"message": f"Paid {myextension.name}"
}
"successAction": {"tag": "message", "message": f"Paid {myextension.name}"},
}
#################################################
######## A very simple LNURLwithdraw ############
# https://github.com/lnurl/luds/blob/luds/03.md #
@ -99,16 +103,21 @@ async def api_lnurl_withdraw(
return {"status": "ERROR", "reason": "LNURLw already used"}
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
}
"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_ext.get(
"/api/v1/lnurl/withdraw/cb/{myextension_id}",
"/api/v1/lnurl/withdraw/cb/{myextension_id}",
status_code=HTTPStatus.OK,
name="myextension.api_lnurl_withdraw_callback",
)
@ -125,12 +134,14 @@ async def api_lnurl_withdraw_cb(
myextension = await get_myextension(myextension_id)
if not myextension:
return {"status": "ERROR", "reason": "No myextension found"}
k1Check = shortuuid.uuid(name=myextension.id + str(myextension.ticker))
if k1Check != k1:
return {"status": "ERROR", "reason": "Wrong k1 check provided"}
await update_myextension(myextension_id=myextension_id, ticker=myextension.ticker + 1)
await update_myextension(
myextension_id=myextension_id, ticker=myextension.ticker + 1
)
logger.debug(myextension.wallet)
logger.debug(pr)
logger.debug(int(myextension.lnurlwithdrawamount * 1000))
@ -138,6 +149,10 @@ async def api_lnurl_withdraw_cb(
wallet_id=myextension.wallet,
payment_request=pr,
max_sat=int(myextension.lnurlwithdrawamount * 1000),
extra={"tag": "MyExtension", "myextensionId": myextension_id, "lnurlwithdraw": True}
extra={
"tag": "MyExtension",
"myextensionId": myextension_id,
"lnurlwithdraw": True,
},
)
return {"status": "OK"}
return {"status": "OK"}

View file

@ -1,5 +1,6 @@
# The migration file is like a blockchain, never edit only add!
async def m001_initial(db):
"""
Initial templates table.
@ -19,8 +20,10 @@ async def m001_initial(db):
"""
)
# Here we add another field to the database
async def m002_addtip_wallet(db):
"""
Add total to templates table
@ -29,4 +32,4 @@ async def m002_addtip_wallet(db):
"""
ALTER TABLE myextension.maintable ADD ticker INTEGER DEFAULT 1;
"""
)
)

View file

@ -9,6 +9,7 @@ from fastapi import Request
from lnbits.lnurl import encode as lnurl_encode
from urllib.parse import urlparse
class CreateMyExtensionData(BaseModel):
wallet: Optional[str]
name: Optional[str]
@ -17,6 +18,7 @@ class CreateMyExtensionData(BaseModel):
lnurlwithdrawamount: Optional[int]
ticker: Optional[int]
class MyExtension(BaseModel):
id: str
wallet: Optional[str]
@ -30,4 +32,4 @@ class MyExtension(BaseModel):
@classmethod
def from_row(cls, row: Row) -> "MyExtension":
return cls(**dict(row))
return cls(**dict(row))

View file

@ -29,6 +29,7 @@ async def wait_for_paid_invoices():
# do somethhing when an invoice related top this extension is paid
async def on_invoice_paid(payment: Payment) -> None:
logger.debug("payment received for myextension extension")
if payment.extra.get("tag") != "MyExtension":
@ -42,20 +43,18 @@ async def on_invoice_paid(payment: Payment) -> None:
total = myextension.total - payment.amount
else:
total = myextension.total + payment.amount
data_to_update = {
"total": total
}
data_to_update = {"total": total}
await update_myextension(myextension_id=myextension_id, **data_to_update)
# here we could send some data to a websocket 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 = {
"name": myextension.name,
"amount": payment.amount,
"fee": payment.fee,
"checking_id": payment.checking_id
"checking_id": payment.checking_id,
}
await websocketUpdater(myextension_id, str(some_payment_data))

View file

@ -1,8 +1,3 @@
<q-expansion-item
group="extras"
icon="swap_vertical_circle"
label="API info"
:content-inset-level="0.5"
>
<q-expansion-item group="extras" icon="swap_vertical_circle" label="API info" :content-inset-level="0.5">
<q-btn flat label="Swagger API" type="a" href="../docs#/MyExtension"></q-btn>
</q-expansion-item>
</q-expansion-item>

View file

@ -4,24 +4,10 @@
<p>
Some more info about my excellent extension.
</p>
<small
>Created by
<a
class="text-secondary"
href="https://github.com/benarc"
target="_blank"
>Ben Arc</a
>.</small
>
<small
>Repo
<a
class="text-secondary"
href="https://github.com/lnbits/myextension"
target="_blank"
>MyExtension</a
>.</small
>
<small>Created by
<a class="text-secondary" href="https://github.com/benarc" target="_blank">Ben Arc</a>.</small>
<small>Repo
<a class="text-secondary" href="https://github.com/lnbits/myextension" target="_blank">MyExtension</a>.</small>
</q-card-section>
</q-card>
</q-expansion-item>
</q-expansion-item>

View file

@ -4,10 +4,8 @@
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
<q-card>
<q-card-section>
<q-btn unelevated color="primary" @click="formDialog.show = true"
>New MyExtension</q-btn
>
</q-card-section>
<q-btn unelevated color="primary" @click="formDialog.show = true">New MyExtension</q-btn>
</q-card-section>
</q-card>
<q-card>
@ -20,14 +18,8 @@
<q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn>
</div>
</div>
<q-table
dense
flat
:data="temps"
row-key="id"
:columns="tempsTable.columns"
:pagination.sync="tempsTable.pagination"
>
<q-table dense flat :data="temps" row-key="id" :columns="tempsTable.columns"
:pagination.sync="tempsTable.pagination">
<myextension v-slot:header="props">
<q-tr :props="props">
<q-th v-for="col in props.cols" :key="col.name" :props="props">
@ -38,63 +30,30 @@
<template v-slot:body="props">
<q-tr :props="props">
<q-td
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<div v-if="col.field == 'total'">${ col.value / 1000} sats</div>
<div v-else>${ col.value }</div>
</q-td>
<q-td auto-width>
<q-btn
unelevated
dense
size="sm"
icon="qr_code"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
class="q-mr-sm"
@click="openUrlDialog(props.row.id)"
></q-btn></q-td>
<q-td auto-width>
<q-btn
unelevated
dense
size="sm"
icon="launch"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
:href="props.row.myextension"
target="_blank"
><q-tooltip>Open public page</q-tooltip></q-btn
></q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
<div v-if="col.field == 'total'">${ col.value / 1000} sats</div>
<div v-else>${ col.value }</div>
</q-td>
<q-td auto-width>
<q-btn unelevated dense size="sm" icon="qr_code" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
class="q-mr-sm" @click="openUrlDialog(props.row.id)"></q-btn></q-td>
<q-td auto-width>
<q-btn unelevated dense size="sm" icon="launch" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a" :href="props.row.myextension" target="_blank"><q-tooltip>Open public
page</q-tooltip></q-btn></q-td>
<q-td>
<q-btn
flat
dense
size="xs"
@click="updateMyExtensionForm(props.row.id)"
icon="edit"
color="light-blue"
>
<q-tooltip> Edit copilot </q-tooltip>
</q-btn>
</q-td>
<q-td>
<q-btn flat dense size="xs" @click="updateMyExtensionForm(props.row.id)" icon="edit" color="light-blue">
<q-tooltip> Edit copilot </q-tooltip>
</q-btn>
</q-td>
<q-td>
<q-btn flat dense size="xs" @click="deleteMyExtension(props.row.id)" icon="cancel" color="pink">
<q-tooltip> Delete copilot </q-tooltip>
</q-btn>
</q-td>
<q-td>
<q-btn
flat
dense
size="xs"
@click="deleteMyExtension(props.row.id)"
icon="cancel"
color="pink"
>
<q-tooltip> Delete copilot </q-tooltip>
</q-btn>
</q-td>
</q-tr>
</template>
@ -107,7 +66,8 @@
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-my-none">{{SITE_TITLE}} MyExtension extension</h6>
<p>Simple extension you can use as a base for your own extension. <br/> Includes very simple LNURL-pay and LNURL-withdraw example.</p>
<p>Simple extension you can use as a base for your own extension. <br /> Includes very simple LNURL-pay and
LNURL-withdraw example.</p>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
@ -123,54 +83,20 @@
<q-dialog v-model="formDialog.show" position="top" @hide="closeFormDialog">
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
<q-form @submit="sendMyExtensionData" class="q-gutter-md">
<q-input
filled
dense
v-model.trim="formDialog.data.name"
label="Name"
placeholder="Name for your record"
></q-input>
<q-select
filled
dense
emit-value
v-model="formDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet *"
></q-select>
<q-input
filled
dense
type="number"
v-model.trim="formDialog.data.lnurlwithdrawamount"
label="LNURL-withdraw amount"
></q-input>
<q-input
filled
dense
type="number"
v-model.trim="formDialog.data.lnurlpayamount"
label="LNURL-pay amount"
></q-input>
<q-input filled dense v-model.trim="formDialog.data.name" label="Name"
placeholder="Name for your record"></q-input>
<q-select filled dense emit-value v-model="formDialog.data.wallet" :options="g.user.walletOptions"
label="Wallet *"></q-select>
<q-input filled dense type="number" v-model.trim="formDialog.data.lnurlwithdrawamount"
label="LNURL-withdraw amount"></q-input>
<q-input filled dense type="number" v-model.trim="formDialog.data.lnurlpayamount"
label="LNURL-pay amount"></q-input>
<div class="row q-mt-lg">
<q-btn
v-if="formDialog.data.id"
unelevated
color="primary"
type="submit"
>Update MyExtension</q-btn
>
<q-btn
v-else
unelevated
color="primary"
<q-btn v-if="formDialog.data.id" unelevated color="primary" type="submit">Update MyExtension</q-btn>
<q-btn v-else unelevated color="primary"
:disable="formDialog.data.name == null || formDialog.data.wallet == null || formDialog.data.lnurlwithdrawamount == null || formDialog.data.lnurlpayamount == null"
type="submit"
>Create MyExtension</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
type="submit">Create MyExtension</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
</div>
</q-form>
</q-card>
@ -178,31 +104,30 @@
<q-dialog v-model="urlDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<lnbits-qrcode
:value="qrValue"
></lnbits-qrcode>
</q-responsive>
<center><q-btn label="copy" @click="copyText(qrValue)"></q-btn>
</center>
<lnbits-qrcode :value="qrValue"></lnbits-qrcode>
</q-responsive>
<center><q-btn label="copy" @click="copyText(qrValue)"></q-btn>
</center>
<q-separator></q-separator>
<q-separator></q-separator>
<div class="row justify-start q-mt-lg">
<div class="col col-md-auto">
<q-btn outline style="color: primmary;" @click="qrValue = urlDialog.data.lnurlpay">lnurlpay</q-btn>
</div>
<div class="col col-md-auto">
<q-btn outline style="color: primmary;" @click="qrValue = urlDialog.data.lnurlwithdraw">lnurlwithdraw</q-btn>
</div>
<div class="col q-pl-md">
<q-input filled bottom-slots dense v-model="invoiceAmount">
<template v-slot:append>
<q-btn round @click="createInvoice(urlDialog.data.wallet, urlDialog.data.id)" color="primary" flat icon="add_circle" />
<q-btn round @click="createInvoice(urlDialog.data.wallet, urlDialog.data.id)" color="primary" flat
icon="add_circle" />
</template>
<template v-slot:hint>
Create an invoice
@ -213,7 +138,7 @@
<div class="row q-mt-lg">
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div>
</q-card>
@ -242,15 +167,15 @@
temps: [],
tempsTable: {
columns: [
{name: 'id', align: 'left', label: 'ID', field: 'id'},
{name: 'name', align: 'left', label: 'Name', field: 'name'},
{ 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'},
{ name: 'total', align: 'left', label: 'Total sent/received', field: 'total' },
],
pagination: {
rowsPerPage: 10
@ -317,7 +242,7 @@
}
},
updateMyExtensionForm(tempId) {
const myextension = _.findWhere(this.temps, {id: tempId})
const myextension = _.findWhere(this.temps, { id: tempId })
this.formDialog.data = {
...myextension
}
@ -361,7 +286,7 @@
},
deleteMyExtension: function (tempId) {
var self = this
var myextension = _.findWhere(this.temps, {id: tempId})
var myextension = _.findWhere(this.temps, { id: tempId })
LNbits.utils
.confirmDialog('Are you sure you want to delete this MyExtension?')
@ -370,7 +295,7 @@
.request(
'DELETE',
'/myextension/api/v1/temps/' + tempId,
_.findWhere(self.g.user.wallets, {id: myextension.wallet}).adminkey
_.findWhere(self.g.user.wallets, { id: myextension.wallet }).adminkey
)
.then(function (response) {
self.temps = _.reject(self.temps, function (obj) {
@ -386,17 +311,17 @@
LNbits.utils.exportCSV(this.tempsTable.columns, this.temps)
},
itemsArray(tempId) {
const myextension = _.findWhere(this.temps, {id: tempId})
const myextension = _.findWhere(this.temps, { id: tempId })
return [...myextension.itemsMap.values()]
},
itemFormatPrice(price, id) {
const myextension = id.split(':')[0]
const currency = _.findWhere(this.temps, {id: myextension}).currency
const currency = _.findWhere(this.temps, { id: myextension }).currency
return LNbits.utils.formatCurrency(Number(price).toFixed(2), currency)
},
openformDialog(id) {
const [tempId, itemId] = id.split(':')
const myextension = _.findWhere(this.temps, {id: tempId})
const myextension = _.findWhere(this.temps, { id: tempId })
if (itemId) {
const item = myextension.itemsMap.get(id)
this.formDialog.data = {
@ -419,16 +344,16 @@
}
},
openUrlDialog(id) {
this.urlDialog.data = _.findWhere(this.temps, {id})
this.urlDialog.data = _.findWhere(this.temps, { 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/////
///////////////////////////////////////////////////
///////////////////////////////////////////////////
///Simple call to the api to create an invoice/////
///////////////////////////////////////////////////
console.log(walletId)
const wallet = _.findWhere(this.g.user.wallets, {
id: walletId
@ -486,32 +411,32 @@
}
frame()
},
connectWebocket(id){
//////////////////////////////////////////////////
///wait for pay action to happen and do a thing////
///////////////////////////////////////////////////
self = this
connectWebocket(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/' +
id
} else {
localUrl =
'ws://' +
document.domain +
':' +
location.port +
'/api/v1/ws/' +
id
}
this.connection = new WebSocket(localUrl)
this.connection.onmessage = function (e) {
self.makeItRain()
}
localUrl =
'wss://' +
document.domain +
':' +
location.port +
'/api/v1/ws/' +
id
} else {
localUrl =
'ws://' +
document.domain +
':' +
location.port +
'/api/v1/ws/' +
id
}
this.connection = new WebSocket(localUrl)
this.connection.onmessage = function (e) {
self.makeItRain()
}
}
},
created: function () {
@ -521,4 +446,4 @@
}
})
</script>
{% endblock %}
{% endblock %}

View file

@ -6,29 +6,23 @@
<div class="text-center">
<a class="text-secondary" href="lightning:{{ lnurl }}">
<q-responsive :ratio="1" class="q-mx-md">
<qrcode
:value="qrValue"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
<qrcode :value="qrValue" :options="{width: 800}" class="rounded-borders"></qrcode>
</q-responsive>
</a>
</div>
<div class="row q-mt-lg q-gutter-sm">
<q-btn outline color="grey" @click="copyText(qrValue)"
>Copy LNURL </q-btn
>
<q-btn outline color="grey" @click="copyText(qrValue)">Copy LNURL </q-btn>
</div>
</q-card-section>
<div class="row justify-start q-mt-lg">
<div class="col col-md-auto">
<q-btn outline style="color: primmary;" @click="qrValue = '{{ lnurlpay }}'">lnurlpay</q-btn>
</div>
<div class="col col-md-auto">
<q-btn outline style="color: primmary;" @click="qrValue = '{{ lnurlwithdraw }}'">lnurlwithdraw</q-btn>
</div>
</div>
@ -40,15 +34,15 @@
<h6 class="text-subtitle1 q-mb-sm q-mt-none">Public page</h6>
<p class="q-my-none">
Moat extensions have a public page that can be shared
(this page will still be accessible even if you have restricted
(this page will still be accessible even if you have restricted
access to your LNbits install).
<br/>
<br />
In this example when a user pays the LNURLpay it triggers an event via a websocket waiting for the payment.
</p>.</p>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
<q-list> </q-list>
<q-list> </q-list>
</q-card-section>
</q-card>
</div>
@ -102,34 +96,34 @@
}
frame()
},
connectWebocket(id){
//////////////////////////////////////////////////
///wait for pay action to happen and do a thing////
///////////////////////////////////////////////////
self = this
connectWebocket(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/' +
id
} else {
localUrl =
'ws://' +
document.domain +
':' +
location.port +
'/api/v1/ws/' +
id
}
this.connection = new WebSocket(localUrl)
this.connection.onmessage = function (e) {
self.makeItRain()
}
localUrl =
'wss://' +
document.domain +
':' +
location.port +
'/api/v1/ws/' +
id
} else {
localUrl =
'ws://' +
document.domain +
':' +
location.port +
'/api/v1/ws/' +
id
}
this.connection = new WebSocket(localUrl)
this.connection.onmessage = function (e) {
self.makeItRain()
}
}
},
})
</script>
{% endblock %}
{% endblock %}

View file

@ -22,6 +22,7 @@ temps = Jinja2Templates(directory="temps")
# Backend admin page
@myextension_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)):
return myextension_renderer().TemplateResponse(
@ -31,6 +32,7 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
# Frontend shareable page
@myextension_ext.get("/{myextension_id}")
async def myextension(request: Request, myextension_id):
myextension = await get_myextension(myextension_id, request)
@ -52,9 +54,10 @@ async def myextension(request: Request, myextension_id):
# Manifest for public page, customise or remove manifest completely
@myextension_ext.get("/manifest/{myextension_id}.webmanifest")
async def manifest(myextension_id: str):
myextension= await get_myextension(myextension_id)
myextension = await get_myextension(myextension_id)
if not myextension:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist."
@ -86,4 +89,4 @@ async def manifest(myextension_id: str):
"url": "/myextension/" + myextension_id,
}
],
}
}

View file

@ -25,7 +25,7 @@ from .crud import (
update_myextension,
delete_myextension,
get_myextension,
get_myextensions
get_myextensions,
)
from .models import CreateMyExtensionData
@ -36,25 +36,29 @@ from .models import CreateMyExtensionData
## Get all the records belonging to the user
@myextension_ext.get("/api/v1/temps", status_code=HTTPStatus.OK)
async def api_myextensions(
req: Request, all_wallets:
bool = Query(False),
wallet: WalletTypeInfo = Depends(get_key_type)
req: Request,
all_wallets: bool = Query(False),
wallet: WalletTypeInfo = Depends(get_key_type),
):
wallet_ids = [wallet.wallet.id]
if all_wallets:
user = await get_user(wallet.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [myextension.dict() for myextension in await get_myextensions(wallet_ids, req)]
return [
myextension.dict() for myextension in await get_myextensions(wallet_ids, req)
]
## Get a single record
@myextension_ext.get("/api/v1/temps/{myextension_id}", status_code=HTTPStatus.OK)
async def api_myextension(
req: Request,
myextension_id: str,
WalletTypeInfo = Depends(get_key_type)):
req: Request, myextension_id: str, WalletTypeInfo=Depends(get_key_type)
):
myextension = await get_myextension(myextension_id, req)
if not myextension:
raise HTTPException(
@ -62,8 +66,10 @@ async def api_myextension(
)
return myextension.dict()
## update a record
@myextension_ext.put("/api/v1/temps/{myextension_id}")
async def api_myextension_update(
req: Request,
@ -79,25 +85,33 @@ async def api_myextension_update(
assert myextension, "MyExtension couldn't be retrieved"
if wallet.wallet.id != myextension.wallet:
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your MyExtension.")
myextension = await update_myextension(myextension_id=myextension_id, **data.dict(), req=req)
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not your MyExtension."
)
myextension = await update_myextension(
myextension_id=myextension_id, **data.dict(), req=req
)
return myextension.dict()
## Create a new record
@myextension_ext.post("/api/v1/temps", status_code=HTTPStatus.CREATED)
async def api_myextension_create(
req: Request,
data: CreateMyExtensionData,
wallet: WalletTypeInfo = Depends(get_key_type)
data: CreateMyExtensionData,
wallet: WalletTypeInfo = Depends(get_key_type),
):
myextension = await create_myextension(wallet_id=wallet.wallet.id, data=data, req=req)
myextension = await create_myextension(
wallet_id=wallet.wallet.id, data=data, req=req
)
return myextension.dict()
## Delete a record
@myextension_ext.delete("/api/v1/temps/{myextension_id}")
async def api_myextension_delete(
myextension_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
@ -110,7 +124,9 @@ async def api_myextension_delete(
)
if myextension.wallet != wallet.wallet.id:
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your MyExtension.")
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not your MyExtension."
)
await delete_myextension(myextension_id)
return "", HTTPStatus.NO_CONTENT
@ -120,7 +136,10 @@ async def api_myextension_delete(
## This endpoint creates a payment
@myextension_ext.post("/api/v1/temps/payment/{myextension_id}", status_code=HTTPStatus.CREATED)
@myextension_ext.post(
"/api/v1/temps/payment/{myextension_id}", status_code=HTTPStatus.CREATED
)
async def api_tpos_create_invoice(
myextension_id: str, amount: int = Query(..., ge=1), memo: str = ""
) -> dict:
@ -130,7 +149,7 @@ async def api_tpos_create_invoice(
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
try:
@ -148,4 +167,4 @@ async def api_tpos_create_invoice(
except Exception as e:
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
return {"payment_hash": payment_hash, "payment_request": payment_request}
return {"payment_hash": payment_hash, "payment_request": payment_request}