Merge branch 'v1' into feat_lnurl_v1
This commit is contained in:
commit
5bdc334249
8 changed files with 1422 additions and 1038 deletions
|
|
@ -19,3 +19,15 @@ async def m001_initial(db):
|
|||
);
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
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};
|
||||
"""
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# Data models for your extension
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import Request
|
||||
|
|
|
|||
2115
poetry.lock
generated
2115
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -6,7 +6,7 @@ authors = ["benarc", "dni <dni@lnbits.com>"]
|
|||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10 | ^3.9"
|
||||
lnbits = "*"
|
||||
lnbits = {version = "*", allow-prereleases = true}
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = "^24.3.0"
|
||||
|
|
|
|||
280
static/js/index.js
Normal file
280
static/js/index.js
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
///////////////////////////////////////////////////
|
||||
//////////an object we can update with data////////
|
||||
///////////////////////////////////////////////////
|
||||
const mapMyExtension = obj => {
|
||||
obj.date = Quasar.date.formatDate(
|
||||
new Date(obj.created_at),
|
||||
'YYYY-MM-DD HH:mm'
|
||||
)
|
||||
obj.myextension = ['/myextension/', obj.id].join('')
|
||||
return obj
|
||||
}
|
||||
|
||||
window.app = Vue.createApp({
|
||||
el: '#vue',
|
||||
mixins: [windowMixin],
|
||||
delimiters: ['${', '}'],
|
||||
data() {
|
||||
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() {
|
||||
LNbits.api
|
||||
.request(
|
||||
'GET',
|
||||
'/myextension/api/v1/myex?all_wallets=true',
|
||||
this.g.user.wallets[0].inkey
|
||||
)
|
||||
.then(response => {
|
||||
this.myex = response.data.map(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(tempId) {
|
||||
const myextension = _.findWhere(this.myex, {id: tempId})
|
||||
|
||||
LNbits.utils
|
||||
.confirmDialog('Are you sure you want to delete this MyExtension?')
|
||||
.onOk(() => {
|
||||
LNbits.api
|
||||
.request(
|
||||
'DELETE',
|
||||
'/myextension/api/v1/myex/' + tempId,
|
||||
_.findWhere(this.g.user.wallets, {id: myextension.wallet})
|
||||
.adminkey
|
||||
)
|
||||
.then(response => {
|
||||
this.myex = _.reject(this.myex, obj => {
|
||||
return obj.id == tempId
|
||||
})
|
||||
})
|
||||
.catch(LNbits.utils.notifyApiError)
|
||||
})
|
||||
},
|
||||
exportCSV() {
|
||||
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
|
||||
const end = Date.now() + 2 * 1000
|
||||
const colors = ['#FFD700', '#ffffff']
|
||||
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
|
||||
}
|
||||
},
|
||||
connectWebocket(wallet_id) {
|
||||
//////////////////////////////////////////////////
|
||||
///wait for pay action to happen and do a thing////
|
||||
///////////////////////////////////////////////////
|
||||
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 = e => {
|
||||
this.makeItRain()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
//////LIFECYCLE FUNCTIONS RUNNING ON PAGE LOAD/////
|
||||
///////////////////////////////////////////////////
|
||||
created() {
|
||||
if (this.g.user.wallets.length) {
|
||||
this.getMyExtensions()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -3,7 +3,9 @@
|
|||
<!--/////////////////////////////////////////////////-->
|
||||
|
||||
{% 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="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
||||
<q-card>
|
||||
|
|
|
|||
5
views.py
5
views.py
|
|
@ -1,12 +1,11 @@
|
|||
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.decorators import check_user_exists
|
||||
from lnbits.helpers import template_renderer
|
||||
from lnbits.settings import settings
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.responses import HTMLResponse
|
||||
|
||||
from .crud import get_myextension
|
||||
|
||||
|
|
|
|||
23
views_api.py
23
views_api.py
|
|
@ -29,7 +29,7 @@ myextension_api_router = APIRouter()
|
|||
## 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(
|
||||
req: Request,
|
||||
all_wallets: bool = Query(False),
|
||||
|
|
@ -37,7 +37,7 @@ async def api_myextensions(
|
|||
):
|
||||
wallet_ids = [wallet.wallet.id]
|
||||
if all_wallets:
|
||||
user = await get_user(wallet.wallet.user)
|
||||
user = await get_user(key_info.wallet.user)
|
||||
wallet_ids = user.wallet_ids if user else []
|
||||
return [
|
||||
{
|
||||
|
|
@ -54,7 +54,6 @@ async def api_myextensions(
|
|||
|
||||
@myextension_api_router.get(
|
||||
"/api/v1/myex/{myextension_id}",
|
||||
status_code=HTTPStatus.OK,
|
||||
dependencies=[Depends(require_invoice_key)],
|
||||
)
|
||||
async def api_myextension(myextension_id: str, req: Request):
|
||||
|
|
@ -80,11 +79,13 @@ async def api_myextension_update(
|
|||
myextension_id: str,
|
||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||
) -> MyExtension:
|
||||
|
||||
myextension = await get_myextension(myextension_id)
|
||||
assert myextension, "MyExtension couldn't be retrieved"
|
||||
if not myextension:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist."
|
||||
)
|
||||
|
||||
if wallet.wallet.id != myextension.wallet:
|
||||
if key_info.wallet.id != myextension.wallet:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.FORBIDDEN, detail="Not your MyExtension."
|
||||
)
|
||||
|
|
@ -123,7 +124,7 @@ async def api_myextension_create(
|
|||
|
||||
@myextension_api_router.delete("/api/v1/myex/{myextension_id}")
|
||||
async def api_myextension_delete(
|
||||
myextension_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||
myextension_id: str, key_info: WalletTypeInfo = Depends(require_admin_key)
|
||||
):
|
||||
myextension = await get_myextension(myextension_id)
|
||||
|
||||
|
|
@ -132,13 +133,12 @@ async def api_myextension_delete(
|
|||
status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist."
|
||||
)
|
||||
|
||||
if myextension.wallet != wallet.wallet.id:
|
||||
if myextension.wallet != key_info.wallet.id:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.FORBIDDEN, detail="Not your MyExtension."
|
||||
)
|
||||
|
||||
await delete_myextension(myextension_id)
|
||||
return "", HTTPStatus.NO_CONTENT
|
||||
|
||||
|
||||
# ANY OTHER ENDPOINTS YOU NEED
|
||||
|
|
@ -162,7 +162,6 @@ async def api_myextension_create_invoice(
|
|||
# we create a payment and add some tags,
|
||||
# so tasks.py can grab the payment once its paid
|
||||
|
||||
try:
|
||||
payment = await create_invoice(
|
||||
wallet_id=myextension.wallet,
|
||||
amount=amount,
|
||||
|
|
@ -172,9 +171,5 @@ async def api_myextension_create_invoice(
|
|||
"amount": amount,
|
||||
},
|
||||
)
|
||||
except Exception as exc:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(exc)
|
||||
) from exc
|
||||
|
||||
return {"payment_hash": payment.payment_hash, "payment_request": payment.bolt11}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue