Merge pull request #1321 from lnbits/MEGA

Make Example Great Again
This commit is contained in:
Arc 2023-01-06 15:45:05 +00:00 committed by GitHub
commit 4f4a578c82
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 396 additions and 73 deletions

View file

@ -1,16 +1,34 @@
import asyncio
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.staticfiles import StaticFiles
from lnbits.db import Database from lnbits.db import Database
from lnbits.helpers import template_renderer from lnbits.helpers import template_renderer
from lnbits.tasks import catch_everything_and_restart
db = Database("ext_example") db = Database("ext_example")
example_ext: APIRouter = APIRouter(prefix="/example", tags=["example"]) example_ext: APIRouter = APIRouter(prefix="/example", tags=["example"])
example_static_files = [
{
"path": "/example/static",
"app": StaticFiles(packages=[("lnbits", "extensions/example/static")]),
"name": "example_static",
}
]
def example_renderer(): def example_renderer():
return template_renderer(["lnbits/extensions/example/templates"]) return template_renderer(["lnbits/extensions/example/templates"])
from .views import * # noqa from .tasks import wait_for_paid_invoices
from .views_api import * # noqa from .views import *
from .views_api import *
def tpos_start():
loop = asyncio.get_event_loop()
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))

View file

@ -0,0 +1,6 @@
{
"name": "Build your own!",
"short_description": "Extension building guide",
"tile": "/example/static/bitcoin-extension.png",
"contributors": ["github_username"]
}

View file

@ -0,0 +1,5 @@
# crud.py is for communication with your extensions database
# add your dependencies here
# add your fnctions here

View file

@ -1,6 +0,0 @@
{
"name": "Build your own!!",
"short_description": "Join us, make an extension",
"tile": "/cashu/static/image/tile.png",
"contributors": ["github_username"]
}

View file

@ -1,3 +1,5 @@
# migrations.py is for building your database
# async def m001_initial(db): # async def m001_initial(db):
# await db.execute( # await db.execute(
# f""" # f"""

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View file

@ -0,0 +1,29 @@
# tasks.py is for asynchronous when invoices get paid
# add your dependencies here
import asyncio
from loguru import logger
from lnbits.core.models import Payment
from lnbits.helpers import get_current_extension_name
from lnbits.tasks import register_invoice_listener
async def wait_for_paid_invoices():
invoice_queue = asyncio.Queue()
register_invoice_listener(invoice_queue, get_current_extension_name())
while True:
payment = await invoice_queue.get()
await on_invoice_paid(payment)
async def on_invoice_paid(payment: Payment) -> None:
if (
payment.extra.get("tag") != "example"
): # Will grab any payment with the tag "example"
logger.debug(payment)
# Do something
return

View file

@ -1,58 +1,349 @@
{% 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 page %}
<q-card>
<q-card-section> <q-dialog v-model="thingDialog.show" position="top">
<h5 class="text-subtitle1 q-mt-none q-mb-md"> <q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
Frameworks used by {{SITE_TITLE}} <q-form @submit="sendThingDialog" class="q-gutter-md">
</h5> <q-input
<q-list> filled
<q-item dense
v-for="tool in tools" v-model.trim="thingDialog.data.name"
:key="tool.name" type="text"
tag="a" label="Name *"
:href="tool.url" ></q-input>
target="_blank" <q-input
filled
dense
v-model.trim="thingDialog.data.email"
type="email"
label="Name *"
></q-input>
<q-select
filled
dense
emit-value
v-model="thingDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet *"
></q-select>
<div class="row q-mt-lg">
<q-btn
v-if="thingDialog.data.id"
unelevated
color="primary"
type="submit"
>Update thing</q-btn
> >
{% raw %} <q-btn
<!-- with raw Flask won't try to interpret the Vue moustaches --> v-else
<q-item-section> unelevated
<q-item-label>{{ tool.name }}</q-item-label> color="primary"
<q-item-label caption>{{ tool.language }}</q-item-label> :disable="thingDialog.data.name == null"
</q-item-section> type="submit"
{% endraw %} >Create thing</q-btn
</q-item> >
</q-list> <q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
<q-separator class="q-my-lg"></q-separator> </div>
</q-form>
</q-card>
</q-dialog>
<q-card flat>
<q-card-section>
<div class="text-h5 q-mb-md">
{{SITE_TITLE}} Extension Development Guide
<small>(Collection of resources for extension developers)</small>
</div>
<q-card unelevated flat>
<q-tabs
v-model="tab"
dense
class="text-grey"
active-color="primary"
indicator-color="primary"
align="justify"
narrow-indicator
>
<q-tab name="frameworks" label="Frameworks"></q-tab>
<q-tab name="tools" label="Useful Tools"></q-tab>
<q-tab name="structure" label="File Structure"></q-tab>
<q-tab name="enviroment" label="Dev Enviroment"></q-tab>
<q-tab name="submission" label="Submisson to LNbits repo"></q-tab>
</q-tabs>
<q-separator></q-separator>
<q-tab-panels v-model="tab">
<q-tab-panel name="frameworks">
<div class="text-h6">Frameworks</div>
<div>
<q-splitter v-model="splitterModel">
<template v-slot:before>
<q-tabs v-model="framworktab" vertical>
<q-tab name="fastapi"
><img src="./static/fastapi-framework.png" />FASTAPI</q-tab
>
<q-tab name="quasar"
><img src="./static/quasar-framework.png" />QUASAR</q-tab
>
<q-tab name="vuejs"
><img src="./static/vuejs-framework.png" />VUE-JS</q-tab
>
</q-tabs>
</template>
<template v-slot:after>
<q-tab-panels v-model="framworktab">
<q-tab-panel name="fastapi" class="text-body1">
<a href="https://fastapi.tiangolo.com/"
><img src="./static/fastapilogo.png"
/></a>
<p> <p>
A magical "g" is always available, with info about the user, wallets and LNbits API is built using
extensions: <a
href="https://fastapi.tiangolo.com/"
class="text-primary"
>FastAPI</a
>, a high-performance, easy to code API framework.<br /><br />
FastAPI auto-generates swagger UI docs for testing
endpoints <a class="text-primary" href="../docs">/docs</a>
</p> </p>
<code class="text-caption">{% raw %}{{ g }}{% endraw %}</code>
<i>
<strong>TIP:</strong> Although it is possible for
extensions to use other extensions API endpoints (such as
with the Satspay and Onchain extension), ideally an
extension should only use LNbits
<a href="../docs#/default" class="text-primary">core</a>
endpoints. </i
><br /><br />
<code class="bg-grey-3 text-black">views.py</code> is used
for setting application routes:
<img src="./static/fastapi-example.png" />
<code class="bg-grey-3 text-black">views_api.py</code> is
used for setting application API endpoints:
<img src="./static/fastapi-example2.png" />
</q-tab-panel>
<q-tab-panel name="quasar" class="text-body1">
<a href="https://quasar.dev/"
><img src="./static/quasarlogo.png"
/></a>
<p>
LNbits uses
<a class="text-primary" href="https://quasar.dev/"
>Quasar Framework</a
>
for frontend deisgn elements. Quasar Framework is an
open-source Vue.js based framework for building apps.
</p>
<i>
<strong>TIP:</strong> Look through
<code class="bg-grey-3 text-black">/template</code> files
in other extensions for examples of Quasar elements being
used. </i
><br /><br />
<p>
In the below example we make a dialogue popup box (box can
be triggered
<q-btn
size="sm"
color="primary"
@click="thingDialog.show = true"
>here</q-btn
>): <q-tooltip>Exmple of a tooltip!</q-tooltip>
</p>
<img src="./static/quasar-example.png" /><br /><br />
<div class="text-h6">Useful links:</div>
<q-btn
color="primary"
type="a"
href="https://quasar.dev/style/"
>Style (typography, spacing, etc)</q-btn
>
<q-btn
color="primary"
type="a"
href="https://quasar.dev/vue-components/"
>Genral components (cards, buttons, popup dialogs,
etc)</q-btn
>
<q-btn
color="primary"
type="a"
href="https://quasar.dev/layout/grid"
>Layouts (rows/columns, flexbox)</q-btn
>
</q-tab-panel>
<q-tab-panel
v-if="someBool == true"
name="vuejs"
class="text-body1"
>
<a href="https://vuejs.org/">
<img src="./static/vuejslogo.png"
/></a>
<p>
LNbits uses
<a href="https://vuejs.org/" class="text-primary">Vue</a>
components for best-in-class high-performance and
responsive performance.
</p>
<p>
Typical example of Vue components in a frontend script:
</p>
<img
src="./static/script-example.png"
style="max-width: 800px"
/><br /><br />
<p>
In a page body, models can be called. <br />Content can be
conditionally rendered using Vue's
<code class="bg-grey-3 text-black">v-if</code>:
</p>
<img
src="./static/vif-example.png"
style="max-width: 800px"
/>
</q-tab-panel>
</q-tab-panels>
</template>
</q-splitter>
</div>
</q-tab-panel>
<q-tab-panel name="tools">
<div class="text-h6">Useful Tools</div>
<div>
<q-splitter v-model="splitterModel">
<template v-slot:before>
<q-tabs v-model="usefultab" vertical>
<q-tab name="magicalg">MAGICAL G</q-tab>
<q-tab name="exchange">EXCHANGE RATES</q-tab>
</q-tabs>
</template>
<template v-slot:after>
<q-tab-panels v-model="usefultab">
<q-tab-panel name="magicalg" class="text-body1">
<div class="text-h5 q-mb-md">Magical G</div>
<p>
A magical "g" (ie
<code class="bg-grey-3 text-black"
>this.g.user.wallets[0].inkey</code
>) is always available, with info about the user, wallets
and extensions:
</p>
<code class="text-caption"
>{% raw %}{{ g }}{% endraw %}</code
>
</q-tab-panel>
<q-tab-panel name="exchange">
<div class="text-h6">Exchange rates</div>
<p>
LNbits includes a handy
<a
href="../docs#/default/api_fiat_as_sats_api_v1_conversion_post"
class="text-primary"
>exchange rate function</a
>, that streams rates from 6 different sources.
</p>
Exchange rate API:<br />
<img src="./static/conversion-example.png" /><br /><br />
Exchange rate functions, included using
<code class="bg-grey-3 text-black"
>from lnbits.utils.exchange_rates import
fiat_amount_as_satoshis</code
>:<br />
<img src="./static/conversion-example2.png" />
</q-tab-panel>
</q-tab-panels>
</template>
</q-splitter>
</div>
</q-tab-panel>
<q-tab-panel name="structure">
<div class="text-h6">File Structure</div>
Coming soon...
</q-tab-panel>
<q-tab-panel name="enviroment">
<div class="text-h6">Dev Enviroment</div>
Coming soon...
</q-tab-panel>
<q-tab-panel name="submission">
<div class="text-h6">Submission</div>
Coming soon...
</q-tab-panel>
</q-tab-panels>
</q-card>
</q-card-section> </q-card-section>
</q-card> </q-card>
{% endblock %} {% block scripts %} {{ window_vars(user) }} {% endblock %} {% block scripts %} {{ window_vars(user) }}
<script> <script>
var someMapObject = obj => {
obj._data = _.clone(obj)
obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm'
)
// here you can do something with the mapped data
return obj
}
new Vue({ new Vue({
el: '#vue', el: '#vue',
mixins: [windowMixin], mixins: [windowMixin],
data: function () { data: function () {
return { return {
tools: [] ///// Declare models/variables /////
thingDialog: {
show: false,
data: {}
},
someBool: true,
splitterModel: 20,
exampleData: [],
tab: 'frameworks',
framworktab: 'fastapi',
usefultab: 'magicalg'
} }
}, },
created: function () { ///// Where functions live /////
var self = this methods: {
exampleFunction(data) {
// axios is available for making requests var theData = data
axios({ LNbits.api
method: 'GET', .request(
url: '/example/api/v1/tools', 'GET', // Type of request
headers: { '/example/api/v1/test/' + theData, // URL of the endpoint
'X-example-header': 'not-used' this.g.user.wallets[0].inkey // Often endpoints require a key
} )
}).then(function (response) { .then(response => {
self.tools = response.data this.exampleData = response.data.map(someMapObject) // Often whats returned is mapped onto some model
}) })
.catch(error => {
LNbits.utils.notifyApiError(error) // Error will be passed to the frontend
})
},
sendThingDialog() {
console.log(this.thingDialog)
}
},
///// To run on startup /////
created: function () {
self = this // Often used to run a real object, rather than the event (all a bit confusing really)
self.exampleFunction('lorum')
} }
}) })
</script> </script>

View file

@ -2,34 +2,12 @@
# add your dependencies here # add your dependencies here
# import httpx
# (use httpx just like requests, except instead of response.ok there's only the
# response.is_error that is its inverse)
from . import example_ext from . import example_ext
# add your endpoints here # add your endpoints here
@example_ext.get("/api/v1/tools") @example_ext.get("/api/v1/test/{test_data}")
async def api_example(): async def api_example(test_data):
"""Try to add descriptions for others.""" # Do some python things and return the data
tools = [ return test_data
{
"name": "fastAPI",
"url": "https://fastapi.tiangolo.com/",
"language": "Python",
},
{
"name": "Vue.js",
"url": "https://vuejs.org/",
"language": "JavaScript",
},
{
"name": "Quasar Framework",
"url": "https://quasar.dev/",
"language": "JavaScript",
},
]
return tools