Merge remote-tracking branch 'origin/main' into fix-satoshi-formatting-nip5-index

This commit is contained in:
ben
2023-01-10 18:45:22 +00:00
9 changed files with 172 additions and 23 deletions

View File

@@ -28,7 +28,9 @@ Going over the example extension's structure:
Adding new dependencies Adding new dependencies
----------------------- -----------------------
If for some reason your extensions needs a new python package to work, you can add a new package using `venv`, or `poerty`: DO NOT ADD NEW DEPENDENCIES. Try to use the dependencies that are availabe in `pyproject.toml`. Getting the LNbits project to accept a new dependency is time consuming and uncertain, and may result in your extension NOT being made available to others.
If for some reason your extensions must have a new python package to work, and its nees are not met in `pyproject.toml`, you can add a new package using `venv`, or `poerty`:
```sh ```sh
$ poetry add <package> $ poetry add <package>
@@ -37,8 +39,7 @@ $ ./venv/bin/pip install <package>
``` ```
**But we need an extra step to make sure LNbits doesn't break in production.** **But we need an extra step to make sure LNbits doesn't break in production.**
Dependencies need to be added to `pyproject.toml` and `requirements.txt`, then tested by running on `venv` and `poetry`. Dependencies need to be added to `pyproject.toml` and `requirements.txt`, then tested by running on `venv` and `poetry` compatability can be tested with `nix build .#checks.x86_64-linux.vmTest`.
`nix` compatability can be tested with `nix build .#checks.x86_64-linux.vmTest`.
SQLite to PostgreSQL migration SQLite to PostgreSQL migration

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -51,8 +51,15 @@
<q-card flat> <q-card flat>
<q-card-section> <q-card-section>
<div class="text-h5 q-mb-md"> <div class="text-h5 q-mb-md">
{{SITE_TITLE}} Extension Development Guide Extension Development Guide
<small>(Collection of resources for extension developers)</small> <small
>(also check the
<a
class="text-primary"
href="http://docs.lnbits.org/devs/development.html"
>docs</a
>)</small
>
</div> </div>
<q-card unelevated flat> <q-card unelevated flat>
@@ -188,8 +195,8 @@
<p> <p>
LNbits uses LNbits uses
<a href="https://vuejs.org/" class="text-primary">Vue</a> <a href="https://vuejs.org/" class="text-primary">Vue</a>
components for best-in-class high-performance and responsive for best-in-class, responsive and high-performance
performance. components.
</p> </p>
<p>Typical example of Vue components in a frontend script:</p> <p>Typical example of Vue components in a frontend script:</p>
@@ -199,8 +206,7 @@
/><br /><br /> /><br /><br />
<p> <p>
In a page body, models can be called. <br />Content can be Content can be conditionally rendered using Vue's
conditionally rendered using Vue's
<code class="bg-grey-3 text-black">v-if</code>: <code class="bg-grey-3 text-black">v-if</code>:
</p> </p>
<img <img
@@ -220,6 +226,8 @@
<q-tabs v-model="usefultab" align="left"> <q-tabs v-model="usefultab" align="left">
<q-tab name="magicalg">MAGICAL G</q-tab> <q-tab name="magicalg">MAGICAL G</q-tab>
<q-tab name="exchange">EXCHANGE RATES</q-tab> <q-tab name="exchange">EXCHANGE RATES</q-tab>
<q-tab name="qrcodes">QR CODES</q-tab>
<q-tab name="websockets">WEBSOCKETS</q-tab>
</q-tabs> </q-tabs>
</template> </template>
@@ -255,6 +263,85 @@
>:<br /> >:<br />
<img src="./static/conversion-example2.png" /> <img src="./static/conversion-example2.png" />
</q-tab-panel> </q-tab-panel>
<q-tab-panel name="qrcodes" class="text-body1">
<div class="text-h5 q-mb-md">QR Codes</div>
<p>
For most purposes use Quasar's inbuilt VueQrcode library:
</p>
<img src="./static/qrcode-example1.png" />
<p>
LNbits does also include a handy
<a
href="../docs#/default/img_api_v1_qrcode__data__get"
class="text-primary"
>
QR code enpoint</a
>
</p>
{% raw %} You can use via
<a
href="/api/v1/qrcode/some-data-you-want-in-a-qrcode"
class="text-primary"
>{{protocol + location}}{% endraw
%}/api/v1/qrcode/some-data-you-want-in-a-qrcode:</a
><br />
<br />
<img src="./static/qrcode-example.png" />
<br />
<img
class="bg-white"
width="300px"
src="/api/v1/qrcode/some-data-you-want-in-a-qrcode"
/>
<br />
</q-tab-panel>
<q-tab-panel name="websockets" class="text-body1">
<div class="text-h5 q-mb-md">Websockets</div>
<p>
Fastapi includes a great
<a
class="text-primary"
href="https://fastapi.tiangolo.com/advanced/websockets/#websockets-client"
>websocket tool</a
>
</p>
{% raw %}
<p>
A few LNbits extensions also make use of a weird and useful
websocket/GET tool built into LNbits, such as extensions
Copilot and LNURLDevices<br />
You can subscribe to websocket with
<code class="bg-grey-3 text-black"
>wss:{{location}}/api/v1/ws/{SOME-ID}</code
><br />
You can post to any clients subscribed to the endpoint with
<code class="bg-grey-3 text-black"
>{{protocol +
location}}/api/v1/ws/{SOME-ID}/{THE-DATA-YOU-WANT-TO-POST}</code
><br />
<br />
<strong
><div id="text-to-change">
DEMO: Hit
<a
target="_blank"
href="/api/v1/ws/32872r23g29/blah%20blah%20blah"
class="text-primary"
>{{protocol +
location}}/api/v1/ws/32872r23g29/blah%20blah%20blah</a
>
in a different browser window to change this text to
`blah blah blah`.
</div></strong
>
<br />
Function used in this demo:<br />
<img src="./static/websocket-example.png" /></p
></q-tab-panel>
{% endraw %}
</q-tab-panels> </q-tab-panels>
</template> </template>
</div> </div>
@@ -296,6 +383,8 @@
data: function () { data: function () {
return { return {
///// Declare models/variables ///// ///// Declare models/variables /////
protocol: window.location.protocol,
location: '//' + window.location.hostname,
thingDialog: { thingDialog: {
show: false, show: false,
data: {} data: {}
@@ -310,7 +399,7 @@
}, },
///// Where functions live ///// ///// Where functions live /////
methods: { methods: {
exampleFunction(data) { exampleFunction: function (data) {
var theData = data var theData = data
LNbits.api LNbits.api
.request( .request(
@@ -325,6 +414,28 @@
LNbits.utils.notifyApiError(error) // Error will be passed to the frontend LNbits.utils.notifyApiError(error) // Error will be passed to the frontend
}) })
}, },
initWs: async function () {
if (location.protocol !== 'http:') {
localUrl =
'wss://' +
document.domain +
':' +
location.port +
'/api/v1/ws/32872r23g29'
} else {
localUrl =
'ws://' +
document.domain +
':' +
location.port +
'/api/v1/ws/32872r23g29'
}
this.ws = new WebSocket(localUrl)
this.ws.addEventListener('message', async ({data}) => {
const res = data.toString()
document.getElementById('text-to-change').innerHTML = res
})
},
sendThingDialog() { sendThingDialog() {
console.log(this.thingDialog) console.log(this.thingDialog)
} }
@@ -333,6 +444,7 @@
created: function () { created: function () {
self = this // Often used to run a real object, rather than the event (all a bit confusing really) self = this // Often used to run a real object, rather than the event (all a bit confusing really)
self.exampleFunction('lorum') self.exampleFunction('lorum')
self.initWs()
} }
}) })
</script> </script>

View File

@@ -55,8 +55,16 @@
></q-select> ></q-select>
<!-- </div> --> <!-- </div> -->
<!-- </div> --> <!-- </div> -->
<q-input
v-if="productDialog.url"
filled
dense
v-model.trim="productDialog.data.image"
type="url"
label="Image URL"
></q-input>
<q-file <q-file
v-else
class="q-pr-md" class="q-pr-md"
filled filled
dense dense
@@ -79,6 +87,10 @@
/> />
</template> </template>
</q-file> </q-file>
<q-toggle
:label="`${productDialog.url ? 'Insert image URL' : 'Upload image file'}`"
v-model="productDialog.url"
></q-toggle>
<q-input <q-input
filled filled
dense dense

View File

@@ -200,7 +200,10 @@
:href="props.row.wallet" :href="props.row.wallet"
target="_blank" target="_blank"
></q-btn> ></q-btn>
<q-tooltip> Link to pass to stall relay </q-tooltip> <q-tooltip
>Disabled: link to pass to stall relays when using
nostr</q-tooltip
>
</q-td> </q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props"> <q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }} {{ col.value }}

View File

@@ -498,6 +498,7 @@
}, },
productDialog: { productDialog: {
show: false, show: false,
url: true,
data: {} data: {}
}, },
stallDialog: { stallDialog: {
@@ -536,6 +537,9 @@
methods: { methods: {
resetDialog(dialog) { resetDialog(dialog) {
this[dialog].show = false this[dialog].show = false
if (dialog == 'productDialog') {
this[dialog].url = true
}
this[dialog].data = {} this[dialog].data = {}
}, },
toggleDA(value, evt) { toggleDA(value, evt) {
@@ -798,11 +802,17 @@
var link = _.findWhere(self.products, {id: linkId}) var link = _.findWhere(self.products, {id: linkId})
self.productDialog.data = _.clone(link._data) self.productDialog.data = _.clone(link._data)
if (self.productDialog.data.categories) {
self.productDialog.data.categories = self.productDialog.data.categories.split( self.productDialog.data.categories = self.productDialog.data.categories.split(
',' ','
) )
}
if (self.productDialog.data.image.startsWith('data:')) {
self.productDialog.url = false
}
self.productDialog.show = true self.productDialog.show = true
console.log(self.productDialog)
}, },
sendProductFormData: function () { sendProductFormData: function () {
let _data = {...this.productDialog.data} let _data = {...this.productDialog.data}
@@ -831,14 +841,8 @@
let canvas = document.createElement('canvas') let canvas = document.createElement('canvas')
canvas.setAttribute('width', fit.width) canvas.setAttribute('width', fit.width)
canvas.setAttribute('height', fit.height) canvas.setAttribute('height', fit.height)
await pica.resize(image, canvas, { output = await pica.resize(image, canvas)
quality: 0, this.productDialog.data.image = output.toDataURL('image/jpeg', 0.4)
alpha: true,
unsharpAmount: 95,
unsharpRadius: 0.9,
unsharpThreshold: 70
})
this.productDialog.data.image = canvas.toDataURL()
this.productDialog = {...this.productDialog} this.productDialog = {...this.productDialog}
} }
}, },

View File

@@ -113,6 +113,23 @@ async def api_market_product_create(
if stall.currency != "sat": if stall.currency != "sat":
data.price *= settings.fiat_base_multiplier data.price *= settings.fiat_base_multiplier
if data.image:
image_is_url = data.image.startswith("https://") or data.image.startswith(
"http://"
)
if not image_is_url:
def size(b64string):
return int((len(b64string) * 3) / 4 - b64string.count("=", -2))
image_size = size(data.image) / 1024
if image_size > 100:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=f"Image size is too big, {int(image_size)}Kb. Max: 100kb, Compress the image at https://tinypng.com, or use an URL.",
)
if product_id: if product_id:
product = await get_market_product(product_id) product = await get_market_product(product_id)
if not product: if not product: