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

53
crud.py
View file

@ -9,7 +9,10 @@ from fastapi import Request
from lnurl import encode as lnurl_encode from lnurl import encode as lnurl_encode
import shortuuid 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() myextension_id = urlsafe_short_hash()
await db.execute( await db.execute(
""" """
@ -21,7 +24,7 @@ async def create_myextension(wallet_id: str, data: CreateMyExtensionData, req: R
wallet_id, wallet_id,
data.name, data.name,
data.lnurlpayamount, data.lnurlpayamount,
data.lnurlwithdrawamount data.lnurlwithdrawamount,
), ),
) )
myextension = await get_myextension(myextension_id, req) myextension = await get_myextension(myextension_id, req)
@ -29,25 +32,33 @@ async def create_myextension(wallet_id: str, data: CreateMyExtensionData, req: R
return myextension 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) 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: if not row:
return None return None
rowAmended = MyExtension(**row) rowAmended = MyExtension(**row)
if req: if req:
rowAmended.lnurlpay = lnurl_encode( rowAmended.lnurlpay = lnurl_encode(
req.url_for("myextension.api_lnurl_pay", req.url_for("myextension.api_lnurl_pay", myextension_id=row.id)._url
myextension_id=row.id)._url
) )
rowAmended.lnurlwithdraw = lnurl_encode( rowAmended.lnurlwithdraw = lnurl_encode(
req.url_for("myextension.api_lnurl_withdraw", req.url_for(
"myextension.api_lnurl_withdraw",
myextension_id=row.id, myextension_id=row.id,
tickerhash=shortuuid.uuid(name=rowAmended.id + str(rowAmended.ticker)))._url tickerhash=shortuuid.uuid(name=rowAmended.id + str(rowAmended.ticker)),
)._url
) )
return rowAmended 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): if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids] wallet_ids = [wallet_ids]
@ -59,25 +70,33 @@ async def get_myextensions(wallet_ids: Union[str, List[str]], req: Optional[Requ
if req: if req:
for row in tempRows: for row in tempRows:
row.lnurlpay = lnurl_encode( row.lnurlpay = lnurl_encode(
req.url_for("myextension.api_lnurl_pay", req.url_for("myextension.api_lnurl_pay", myextension_id=row.id)._url
myextension_id=row.id)._url
) )
row.lnurlwithdraw = lnurl_encode( row.lnurlwithdraw = lnurl_encode(
req.url_for("myextension.api_lnurl_withdraw", req.url_for(
"myextension.api_lnurl_withdraw",
myextension_id=row.id, myextension_id=row.id,
tickerhash=shortuuid.uuid(name=row.id + str(row.ticker)))._url tickerhash=shortuuid.uuid(name=row.id + str(row.ticker)),
)._url
) )
return tempRows 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()]) q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
logger.debug( kwargs.items()) logger.debug(kwargs.items())
await db.execute( 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) myextension = await get_myextension(myextension_id, req)
assert myextension, "Newly updated myextension couldn't be retrieved" assert myextension, "Newly updated myextension couldn't be retrieved"
return myextension return myextension
async def delete_myextension(myextension_id: str) -> None: 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,6 +19,7 @@ import shortuuid
################################################# #################################################
################################################# #################################################
@myextension_ext.get( @myextension_ext.get(
"/api/v1/lnurl/pay/{myextension_id}", "/api/v1/lnurl/pay/{myextension_id}",
status_code=HTTPStatus.OK, status_code=HTTPStatus.OK,
@ -32,13 +33,18 @@ async def api_lnurl_pay(
if not myextension: if not myextension:
return {"status": "ERROR", "reason": "No myextension found"} return {"status": "ERROR", "reason": "No myextension found"}
return { return {
"callback": str(request.url_for("myextension.api_lnurl_pay_callback", myextension_id=myextension_id)), "callback": str(
request.url_for(
"myextension.api_lnurl_pay_callback", myextension_id=myextension_id
)
),
"maxSendable": myextension.lnurlpayamount * 1000, "maxSendable": myextension.lnurlpayamount * 1000,
"minSendable": myextension.lnurlpayamount * 1000, "minSendable": myextension.lnurlpayamount * 1000,
"metadata":"[[\"text/plain\", \"" + myextension.name + "\"]]", "metadata": '[["text/plain", "' + myextension.name + '"]]',
"tag": "payRequest" "tag": "payRequest",
} }
@myextension_ext.get( @myextension_ext.get(
"/api/v1/lnurl/pay/cb/{myextension_id}", "/api/v1/lnurl/pay/cb/{myextension_id}",
status_code=HTTPStatus.OK, status_code=HTTPStatus.OK,
@ -58,8 +64,8 @@ async def api_lnurl_pay_cb(
wallet_id=myextension.wallet, wallet_id=myextension.wallet,
amount=int(amount / 1000), amount=int(amount / 1000),
memo=myextension.name, memo=myextension.name,
unhashed_description=f"[[\"text/plain\", \"{myextension.name}\"]]".encode(), unhashed_description=f'[["text/plain", "{myextension.name}"]]'.encode(),
extra= { extra={
"tag": "MyExtension", "tag": "MyExtension",
"myextensionId": myextension_id, "myextensionId": myextension_id,
"extra": request.query_params.get("amount"), "extra": request.query_params.get("amount"),
@ -68,12 +74,10 @@ async def api_lnurl_pay_cb(
return { return {
"pr": payment_request, "pr": payment_request,
"routes": [], "routes": [],
"successAction": { "successAction": {"tag": "message", "message": f"Paid {myextension.name}"},
"tag": "message",
"message": f"Paid {myextension.name}"
}
} }
################################################# #################################################
######## A very simple LNURLwithdraw ############ ######## A very simple LNURLwithdraw ############
# https://github.com/lnurl/luds/blob/luds/03.md # # https://github.com/lnurl/luds/blob/luds/03.md #
@ -100,13 +104,18 @@ async def api_lnurl_withdraw(
return { return {
"tag": "withdrawRequest", "tag": "withdrawRequest",
"callback": str(request.url_for("myextension.api_lnurl_withdraw_callback", myextension_id=myextension_id)), "callback": str(
request.url_for(
"myextension.api_lnurl_withdraw_callback", myextension_id=myextension_id
)
),
"k1": k1, "k1": k1,
"defaultDescription": myextension.name, "defaultDescription": myextension.name,
"maxWithdrawable": myextension.lnurlwithdrawamount * 1000, "maxWithdrawable": myextension.lnurlwithdrawamount * 1000,
"minWithdrawable": myextension.lnurlwithdrawamount * 1000 "minWithdrawable": myextension.lnurlwithdrawamount * 1000,
} }
@myextension_ext.get( @myextension_ext.get(
"/api/v1/lnurl/withdraw/cb/{myextension_id}", "/api/v1/lnurl/withdraw/cb/{myextension_id}",
status_code=HTTPStatus.OK, status_code=HTTPStatus.OK,
@ -130,7 +139,9 @@ async def api_lnurl_withdraw_cb(
if k1Check != k1: if k1Check != k1:
return {"status": "ERROR", "reason": "Wrong k1 check provided"} 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(myextension.wallet)
logger.debug(pr) logger.debug(pr)
logger.debug(int(myextension.lnurlwithdrawamount * 1000)) logger.debug(int(myextension.lnurlwithdrawamount * 1000))
@ -138,6 +149,10 @@ async def api_lnurl_withdraw_cb(
wallet_id=myextension.wallet, wallet_id=myextension.wallet,
payment_request=pr, payment_request=pr,
max_sat=int(myextension.lnurlwithdrawamount * 1000), 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! # The migration file is like a blockchain, never edit only add!
async def m001_initial(db): async def m001_initial(db):
""" """
Initial templates table. Initial templates table.
@ -19,8 +20,10 @@ async def m001_initial(db):
""" """
) )
# Here we add another field to the database # Here we add another field to the database
async def m002_addtip_wallet(db): async def m002_addtip_wallet(db):
""" """
Add total to templates table Add total to templates table

View file

@ -9,6 +9,7 @@ from fastapi import Request
from lnbits.lnurl import encode as lnurl_encode from lnbits.lnurl import encode as lnurl_encode
from urllib.parse import urlparse from urllib.parse import urlparse
class CreateMyExtensionData(BaseModel): class CreateMyExtensionData(BaseModel):
wallet: Optional[str] wallet: Optional[str]
name: Optional[str] name: Optional[str]
@ -17,6 +18,7 @@ class CreateMyExtensionData(BaseModel):
lnurlwithdrawamount: Optional[int] lnurlwithdrawamount: Optional[int]
ticker: Optional[int] ticker: Optional[int]
class MyExtension(BaseModel): class MyExtension(BaseModel):
id: str id: str
wallet: Optional[str] wallet: Optional[str]

View file

@ -29,6 +29,7 @@ async def wait_for_paid_invoices():
# do somethhing when an invoice related top this extension is paid # do somethhing when an invoice related top this extension is paid
async def on_invoice_paid(payment: Payment) -> None: async def on_invoice_paid(payment: Payment) -> None:
logger.debug("payment received for myextension extension") logger.debug("payment received for myextension extension")
if payment.extra.get("tag") != "MyExtension": if payment.extra.get("tag") != "MyExtension":
@ -42,9 +43,7 @@ async def on_invoice_paid(payment: Payment) -> None:
total = myextension.total - payment.amount total = myextension.total - payment.amount
else: else:
total = myextension.total + payment.amount total = myextension.total + payment.amount
data_to_update = { data_to_update = {"total": total}
"total": total
}
await update_myextension(myextension_id=myextension_id, **data_to_update) await update_myextension(myextension_id=myextension_id, **data_to_update)
@ -55,7 +54,7 @@ async def on_invoice_paid(payment: Payment) -> None:
"name": myextension.name, "name": myextension.name,
"amount": payment.amount, "amount": payment.amount,
"fee": payment.fee, "fee": payment.fee,
"checking_id": payment.checking_id "checking_id": payment.checking_id,
} }
await websocketUpdater(myextension_id, str(some_payment_data)) await websocketUpdater(myextension_id, str(some_payment_data))

View file

@ -1,8 +1,3 @@
<q-expansion-item <q-expansion-item group="extras" icon="swap_vertical_circle" label="API info" :content-inset-level="0.5">
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-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> <p>
Some more info about my excellent extension. Some more info about my excellent extension.
</p> </p>
<small <small>Created by
>Created by <a class="text-secondary" href="https://github.com/benarc" target="_blank">Ben Arc</a>.</small>
<a <small>Repo
class="text-secondary" <a class="text-secondary" href="https://github.com/lnbits/myextension" target="_blank">MyExtension</a>.</small>
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-section>
</q-card> </q-card>
</q-expansion-item> </q-expansion-item>

View file

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

View file

@ -6,18 +6,12 @@
<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 <qrcode :value="qrValue" :options="{width: 800}" class="rounded-borders"></qrcode>
:value="qrValue"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive> </q-responsive>
</a> </a>
</div> </div>
<div class="row q-mt-lg q-gutter-sm"> <div class="row q-mt-lg q-gutter-sm">
<q-btn outline color="grey" @click="copyText(qrValue)" <q-btn outline color="grey" @click="copyText(qrValue)">Copy LNURL </q-btn>
>Copy LNURL </q-btn
>
</div> </div>
</q-card-section> </q-card-section>
@ -42,7 +36,7 @@
Moat extensions have a public page that can be shared 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). 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. In this example when a user pays the LNURLpay it triggers an event via a websocket waiting for the payment.
</p>.</p> </p>.</p>
</q-card-section> </q-card-section>
@ -102,7 +96,7 @@
} }
frame() frame()
}, },
connectWebocket(id){ connectWebocket(id) {
////////////////////////////////////////////////// //////////////////////////////////////////////////
///wait for pay action to happen and do a thing//// ///wait for pay action to happen and do a thing////
/////////////////////////////////////////////////// ///////////////////////////////////////////////////

View file

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

View file

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