feat: code quality
format add ci fixup ci
This commit is contained in:
parent
09bb033f85
commit
42b5edaf5d
26 changed files with 3199 additions and 295 deletions
34
.github/workflows/ci.yml
vendored
Normal file
34
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
name: CI
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
uses: lnbits/lnbits/.github/workflows/lint.yml@dev
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [lint]
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ['3.9', '3.10']
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: lnbits/lnbits/.github/actions/prepare@dev
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
- name: Run pytest
|
||||||
|
uses: pavelzw/pytest-action@v2
|
||||||
|
env:
|
||||||
|
LNBITS_BACKEND_WALLET_CLASS: FakeWallet
|
||||||
|
PYTHONUNBUFFERED: 1
|
||||||
|
DEBUG: true
|
||||||
|
with:
|
||||||
|
verbose: true
|
||||||
|
job-summary: true
|
||||||
|
emoji: false
|
||||||
|
click-to-expand: true
|
||||||
|
custom-pytest: poetry run pytest
|
||||||
|
report-title: 'test (${{ matrix.python-version }})'
|
||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -55,4 +55,4 @@ jobs:
|
||||||
# check if pr exists before creating it
|
# check if pr exists before creating it
|
||||||
gh config set pager cat
|
gh config set pager cat
|
||||||
check=$(gh pr list -H $branch | wc -l)
|
check=$(gh pr list -H $branch | wc -l)
|
||||||
test $check -ne 0 || gh pr create --title "$title" --body "$body" --repo lnbits/lnbits-extensions
|
test $check -ne 0 || gh pr create --title "$title" --body "$body" --repo lnbits/lnbits-extensions
|
||||||
|
|
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -1 +1,4 @@
|
||||||
__pycache__
|
__pycache__
|
||||||
|
node_modules
|
||||||
|
.mypy_cache
|
||||||
|
.venv
|
||||||
|
|
|
||||||
12
.prettierrc
Normal file
12
.prettierrc
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"semi": false,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"insertPragma": false,
|
||||||
|
"printWidth": 80,
|
||||||
|
"proseWrap": "preserve",
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"useTabs": false,
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"bracketSpacing": false
|
||||||
|
}
|
||||||
47
Makefile
Normal file
47
Makefile
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
all: format check
|
||||||
|
|
||||||
|
format: prettier black ruff
|
||||||
|
|
||||||
|
check: mypy pyright checkblack checkruff checkprettier
|
||||||
|
|
||||||
|
prettier:
|
||||||
|
poetry run ./node_modules/.bin/prettier --write .
|
||||||
|
pyright:
|
||||||
|
poetry run ./node_modules/.bin/pyright
|
||||||
|
|
||||||
|
mypy:
|
||||||
|
poetry run mypy .
|
||||||
|
|
||||||
|
black:
|
||||||
|
poetry run black .
|
||||||
|
|
||||||
|
ruff:
|
||||||
|
poetry run ruff check . --fix
|
||||||
|
|
||||||
|
checkruff:
|
||||||
|
poetry run ruff check .
|
||||||
|
|
||||||
|
checkprettier:
|
||||||
|
poetry run ./node_modules/.bin/prettier --check .
|
||||||
|
|
||||||
|
checkblack:
|
||||||
|
poetry run black --check .
|
||||||
|
|
||||||
|
checkeditorconfig:
|
||||||
|
editorconfig-checker
|
||||||
|
|
||||||
|
test:
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
DEBUG=true \
|
||||||
|
poetry run pytest
|
||||||
|
install-pre-commit-hook:
|
||||||
|
@echo "Installing pre-commit hook to git"
|
||||||
|
@echo "Uninstall the hook with poetry run pre-commit uninstall"
|
||||||
|
poetry run pre-commit install
|
||||||
|
|
||||||
|
pre-commit:
|
||||||
|
poetry run pre-commit run --all-files
|
||||||
|
|
||||||
|
|
||||||
|
checkbundle:
|
||||||
|
@echo "skipping checkbundle"
|
||||||
|
|
@ -4,15 +4,16 @@
|
||||||
|
|
||||||
## A Starter Template for Your Own Extension
|
## A Starter Template for Your Own Extension
|
||||||
|
|
||||||
Ready to start hacking? Once you've forked this extension, you can incorporate functions from other extensions as needed.
|
Ready to start hacking? Once you've forked this extension, you can incorporate functions from other extensions as needed.
|
||||||
|
|
||||||
### How to Use This Template
|
### How to Use This Template
|
||||||
|
|
||||||
> This guide assumes you're using this extension as a base for a new one, and have installed LNbits using https://github.com/lnbits/lnbits/blob/main/docs/guide/installation.md#option-1-recommended-poetry.
|
> This guide assumes you're using this extension as a base for a new one, and have installed LNbits using https://github.com/lnbits/lnbits/blob/main/docs/guide/installation.md#option-1-recommended-poetry.
|
||||||
|
|
||||||
1. Install and enable the extension either through the official LNbits manifest or by adding https://raw.githubusercontent.com/lnbits/myextension/main/manifest.json to `"Server"/"Server"/"Extension Sources"`.  
|
1. Install and enable the extension either through the official LNbits manifest or by adding https://raw.githubusercontent.com/lnbits/myextension/main/manifest.json to `"Server"/"Server"/"Extension Sources"`.  
|
||||||
|
|
||||||
2. `Ctrl c` shut down your LNbits installation.
|
2. `Ctrl c` shut down your LNbits installation.
|
||||||
3. Download the extension files from https://github.com/lnbits/myextension to a folder outside of `/lnbits`, and initialize the folder with `git`. Alternatively, create a repo, copy the myextension extension files into it, then `git clone` the extension to a location outside of `/lnbits`.
|
3. Download the extension files from https://github.com/lnbits/myextension to a folder outside of `/lnbits`, and initialize the folder with `git`. Alternatively, create a repo, copy the myextension extension files into it, then `git clone` the extension to a location outside of `/lnbits`.
|
||||||
4. Remove the installed extension from `lnbits/lnbits/extensions`.
|
4. Remove the installed extension from `lnbits/lnbits/extensions`.
|
||||||
5. Create a symbolic link using `ln -s /home/ben/Projects/<name of your extension> /home/ben/Projects/lnbits/lnbits/extensions`.
|
5. Create a symbolic link using `ln -s /home/ben/Projects/<name of your extension> /home/ben/Projects/lnbits/lnbits/extensions`.
|
||||||
6. Restart your LNbits installation. You can now modify your extension and `git push` changes to a repo.
|
6. Restart your LNbits installation. You can now modify your extension and `git push` changes to a repo.
|
||||||
|
|
|
||||||
38
__init__.py
38
__init__.py
|
|
@ -1,19 +1,24 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from lnbits.db import Database
|
|
||||||
from lnbits.helpers import template_renderer
|
|
||||||
from lnbits.tasks import create_permanent_unique_task
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from .crud import db
|
||||||
|
from .tasks import wait_for_paid_invoices
|
||||||
|
from .views import myextension_generic_router
|
||||||
|
from .views_api import myextension_api_router
|
||||||
|
from .views_lnurl import myextension_lnurl_router
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"This logged message is from myextension/__init__.py, you can debug in your extension using 'import logger from loguru' and 'logger.debug(<thing-to-log>)'."
|
"This logged message is from myextension/__init__.py, you can debug in your "
|
||||||
|
"extension using 'import logger from loguru' and 'logger.debug(<thing-to-log>)'."
|
||||||
)
|
)
|
||||||
|
|
||||||
db = Database("ext_myextension")
|
|
||||||
|
|
||||||
myextension_ext: APIRouter = APIRouter(prefix="/myextension", tags=["MyExtension"])
|
myextension_ext: APIRouter = APIRouter(prefix="/myextension", tags=["MyExtension"])
|
||||||
|
myextension_ext.include_router(myextension_generic_router)
|
||||||
|
myextension_ext.include_router(myextension_api_router)
|
||||||
|
myextension_ext.include_router(myextension_lnurl_router)
|
||||||
|
|
||||||
myextension_static_files = [
|
myextension_static_files = [
|
||||||
{
|
{
|
||||||
|
|
@ -22,16 +27,6 @@ myextension_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 *
|
|
||||||
|
|
||||||
scheduled_tasks: list[asyncio.Task] = []
|
scheduled_tasks: list[asyncio.Task] = []
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -44,5 +39,16 @@ def myextension_stop():
|
||||||
|
|
||||||
|
|
||||||
def myextension_start():
|
def myextension_start():
|
||||||
|
from lnbits.tasks import create_permanent_unique_task
|
||||||
|
|
||||||
task = create_permanent_unique_task("ext_myextension", wait_for_paid_invoices)
|
task = create_permanent_unique_task("ext_myextension", wait_for_paid_invoices)
|
||||||
scheduled_tasks.append(task)
|
scheduled_tasks.append(task)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"db",
|
||||||
|
"myextension_ext",
|
||||||
|
"myextension_static_files",
|
||||||
|
"myextension_start",
|
||||||
|
"myextension_stop",
|
||||||
|
]
|
||||||
|
|
|
||||||
128
crud.py
128
crud.py
|
|
@ -1,99 +1,77 @@
|
||||||
from typing import List, Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
from lnbits.helpers import urlsafe_short_hash
|
from lnbits.db import Database
|
||||||
from lnbits.lnurl import encode as lnurl_encode
|
from lnbits.helpers import insert_query, update_query
|
||||||
from . import db
|
|
||||||
from .models import CreateMyExtensionData, MyExtension
|
from .models import MyExtension
|
||||||
from fastapi import Request
|
|
||||||
from lnurl import encode as lnurl_encode
|
db = Database("ext_myextension")
|
||||||
import shortuuid
|
table_name = "myextension.maintable"
|
||||||
|
|
||||||
|
|
||||||
async def create_myextension(
|
async def create_myextension(data: MyExtension) -> MyExtension:
|
||||||
wallet_id: str, data: CreateMyExtensionData, req: Request
|
|
||||||
) -> MyExtension:
|
|
||||||
myextension_id = urlsafe_short_hash()
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
insert_query(table_name, data),
|
||||||
INSERT INTO myextension.maintable (id, wallet, name, lnurlpayamount, lnurlwithdrawamount)
|
(*data.dict().values(),),
|
||||||
VALUES (?, ?, ?, ?, ?)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
myextension_id,
|
|
||||||
wallet_id,
|
|
||||||
data.name,
|
|
||||||
data.lnurlpayamount,
|
|
||||||
data.lnurlwithdrawamount,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
myextension = await get_myextension(myextension_id, req)
|
return data
|
||||||
assert myextension, "Newly created table couldn't be retrieved"
|
|
||||||
return myextension
|
# this is how we used to do it
|
||||||
|
|
||||||
|
# myextension_id = urlsafe_short_hash()
|
||||||
|
# await db.execute(
|
||||||
|
# """
|
||||||
|
# INSERT INTO myextension.maintable
|
||||||
|
# (id, wallet, name, lnurlpayamount, lnurlwithdrawamount)
|
||||||
|
# VALUES (?, ?, ?, ?, ?)
|
||||||
|
# """,
|
||||||
|
# (
|
||||||
|
# myextension_id,
|
||||||
|
# wallet_id,
|
||||||
|
# data.name,
|
||||||
|
# data.lnurlpayamount,
|
||||||
|
# data.lnurlwithdrawamount,
|
||||||
|
# ),
|
||||||
|
# )
|
||||||
|
# myextension = await get_myextension(myextension_id)
|
||||||
|
# assert myextension, "Newly created table couldn't be retrieved"
|
||||||
|
|
||||||
|
|
||||||
async def get_myextension(
|
async def get_myextension(myextension_id: str) -> Optional[MyExtension]:
|
||||||
myextension_id: str, req: Optional[Request] = None
|
|
||||||
) -> Optional[MyExtension]:
|
|
||||||
row = await db.fetchone(
|
row = await db.fetchone(
|
||||||
"SELECT * FROM myextension.maintable WHERE id = ?", (myextension_id,)
|
f"SELECT * FROM {table_name} WHERE id = ?", (myextension_id,)
|
||||||
)
|
)
|
||||||
if not row:
|
return MyExtension(**row) if row else None
|
||||||
return None
|
|
||||||
rowAmended = MyExtension(**row)
|
|
||||||
if req:
|
|
||||||
rowAmended.lnurlpay = lnurl_encode(
|
|
||||||
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
|
|
||||||
)
|
|
||||||
return rowAmended
|
|
||||||
|
|
||||||
|
|
||||||
async def get_myextensions(
|
async def get_myextensions(wallet_ids: Union[str, list[str]]) -> list[MyExtension]:
|
||||||
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]
|
||||||
|
|
||||||
q = ",".join(["?"] * len(wallet_ids))
|
q = ",".join(["?"] * len(wallet_ids))
|
||||||
rows = await db.fetchall(
|
rows = await db.fetchall(
|
||||||
f"SELECT * FROM myextension.maintable WHERE wallet IN ({q})", (*wallet_ids,)
|
f"SELECT * FROM {table_name} WHERE wallet IN ({q})", (*wallet_ids,)
|
||||||
)
|
)
|
||||||
tempRows = [MyExtension(**row) for row in rows]
|
return [MyExtension(**row) for row in rows]
|
||||||
if req:
|
|
||||||
for row in tempRows:
|
|
||||||
row.lnurlpay = lnurl_encode(
|
|
||||||
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
|
|
||||||
)
|
|
||||||
return tempRows
|
|
||||||
|
|
||||||
|
|
||||||
async def update_myextension(
|
async def update_myextension(data: MyExtension) -> MyExtension:
|
||||||
myextension_id: str, req: Optional[Request] = None, **kwargs
|
|
||||||
) -> MyExtension:
|
|
||||||
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
f"UPDATE myextension.maintable SET {q} WHERE id = ?",
|
update_query(table_name, data),
|
||||||
(*kwargs.values(), myextension_id),
|
(
|
||||||
|
*data.dict().values(),
|
||||||
|
data.id,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
myextension = await get_myextension(myextension_id, req)
|
return data
|
||||||
assert myextension, "Newly updated myextension couldn't be retrieved"
|
# this is how we used to do it
|
||||||
return myextension
|
|
||||||
|
# q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
|
||||||
|
# await db.execute(
|
||||||
|
# f"UPDATE myextension.maintable SET {q} WHERE id = ?",
|
||||||
|
# (*kwargs.values(), myextension_id),
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
async def delete_myextension(myextension_id: str) -> None:
|
async def delete_myextension(myextension_id: str) -> None:
|
||||||
await db.execute(
|
await db.execute(f"DELETE FROM {table_name} WHERE id = ?", (myextension_id,))
|
||||||
"DELETE FROM myextension.maintable WHERE id = ?", (myextension_id,)
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ This is a longform description that will be used in the advanced description whe
|
||||||
|
|
||||||
Adding some bullets is nice covering:
|
Adding some bullets is nice covering:
|
||||||
|
|
||||||
* Functionality
|
- Functionality
|
||||||
* Use cases
|
- Use cases
|
||||||
|
|
||||||
...and some other text about just how great this etension is.
|
...and some other text about just how great this etension is.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
# the migration file is where you build your database tables
|
# the migration file is where you build your database tables
|
||||||
# If you create a new release for your extension , remeember the migration file is like a blockchain, never edit only add!
|
# If you create a new release for your extension ,
|
||||||
|
# remember the migration file is like a blockchain, never edit only add!
|
||||||
|
|
||||||
|
|
||||||
async def m001_initial(db):
|
async def m001_initial(db):
|
||||||
|
|
@ -20,17 +21,3 @@ async def m001_initial(db):
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Here we add another field to the database
|
|
||||||
|
|
||||||
|
|
||||||
async def m002_addtip_wallet(db):
|
|
||||||
"""
|
|
||||||
Add total to templates table
|
|
||||||
"""
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
ALTER TABLE myextension.maintable ADD ticker INTEGER DEFAULT 1;
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
|
||||||
28
models.py
28
models.py
|
|
@ -1,30 +1,24 @@
|
||||||
# Data models for your extension
|
# Data models for your extension
|
||||||
|
|
||||||
from sqlite3 import Row
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class CreateMyExtensionData(BaseModel):
|
class CreateMyExtensionData(BaseModel):
|
||||||
wallet: Optional[str]
|
name: str
|
||||||
name: Optional[str]
|
lnurlpayamount: int
|
||||||
total: Optional[int]
|
lnurlwithdrawamount: int
|
||||||
lnurlpayamount: Optional[int]
|
wallet: Optional[str] = None
|
||||||
lnurlwithdrawamount: Optional[int]
|
total: int = 0
|
||||||
ticker: Optional[int]
|
|
||||||
|
|
||||||
|
|
||||||
class MyExtension(BaseModel):
|
class MyExtension(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
wallet: Optional[str]
|
wallet: str
|
||||||
name: Optional[str]
|
lnurlpayamount: int
|
||||||
total: Optional[int]
|
name: str
|
||||||
lnurlpayamount: Optional[int]
|
lnurlwithdrawamount: int
|
||||||
lnurlwithdrawamount: Optional[int]
|
total: int
|
||||||
lnurlpay: Optional[str]
|
lnurlpay: Optional[str]
|
||||||
lnurlwithdraw: Optional[str]
|
lnurlwithdraw: Optional[str]
|
||||||
ticker: Optional[int]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_row(cls, row: Row) -> "MyExtension":
|
|
||||||
return cls(**dict(row))
|
|
||||||
|
|
|
||||||
59
package-lock.json
generated
Normal file
59
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
{
|
||||||
|
"name": "lnurlp",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "lnurlp",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"pyright": "^1.1.358"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fsevents": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prettier": {
|
||||||
|
"version": "3.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
|
||||||
|
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
|
||||||
|
"bin": {
|
||||||
|
"prettier": "bin/prettier.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pyright": {
|
||||||
|
"version": "1.1.375",
|
||||||
|
"resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.375.tgz",
|
||||||
|
"integrity": "sha512-DeSxwNWSFXPr079RFtvUW8O30XC80tuQf7KLMlmj5aks7isDEzcjSQkvMKiXd+a4Ueo+JTq0WsDlEoN/2BYKJA==",
|
||||||
|
"bin": {
|
||||||
|
"pyright": "index.js",
|
||||||
|
"pyright-langserver": "langserver.index.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "~2.3.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
package.json
Normal file
15
package.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"name": "lnurlp",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"pyright": "^1.1.358"
|
||||||
|
}
|
||||||
|
}
|
||||||
2534
poetry.lock
generated
Normal file
2534
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
89
pyproject.toml
Normal file
89
pyproject.toml
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
[tool.poetry]
|
||||||
|
name = "myextension"
|
||||||
|
version = "0.0.0"
|
||||||
|
description = "Eightball is a simple API that allows you to create a random number generator."
|
||||||
|
authors = ["benarc", "dni <dni@lnbits.com>"]
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.10 | ^3.9"
|
||||||
|
lnbits = "*"
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
black = "^24.3.0"
|
||||||
|
pytest-asyncio = "^0.21.0"
|
||||||
|
pytest = "^7.3.2"
|
||||||
|
mypy = "^1.5.1"
|
||||||
|
pre-commit = "^3.2.2"
|
||||||
|
ruff = "^0.3.2"
|
||||||
|
pytest-md = "^0.2.0"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
[[tool.mypy.overrides]]
|
||||||
|
module = [
|
||||||
|
"lnbits.*",
|
||||||
|
"loguru.*",
|
||||||
|
"fastapi.*",
|
||||||
|
"pydantic.*",
|
||||||
|
]
|
||||||
|
ignore_missing_imports = "True"
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
log_cli = false
|
||||||
|
testpaths = [
|
||||||
|
"tests"
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
line-length = 88
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
# Same as Black. + 10% rule of black
|
||||||
|
line-length = 88
|
||||||
|
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
# Enable:
|
||||||
|
# F - pyflakes
|
||||||
|
# E - pycodestyle errors
|
||||||
|
# W - pycodestyle warnings
|
||||||
|
# I - isort
|
||||||
|
# A - flake8-builtins
|
||||||
|
# C - mccabe
|
||||||
|
# N - naming
|
||||||
|
# UP - pyupgrade
|
||||||
|
# RUF - ruff
|
||||||
|
# B - bugbear
|
||||||
|
select = ["F", "E", "W", "I", "A", "C", "N", "UP", "RUF", "B"]
|
||||||
|
# UP007: pyupgrade: use X | Y instead of Optional. (python3.10)
|
||||||
|
ignore = ["UP007"]
|
||||||
|
|
||||||
|
# Allow autofix for all enabled rules (when `--fix`) is provided.
|
||||||
|
fixable = ["ALL"]
|
||||||
|
unfixable = []
|
||||||
|
|
||||||
|
# Allow unused variables when underscore-prefixed.
|
||||||
|
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||||
|
|
||||||
|
# needed for pydantic
|
||||||
|
[tool.ruff.lint.pep8-naming]
|
||||||
|
classmethod-decorators = [
|
||||||
|
"root_validator",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Ignore unused imports in __init__.py files.
|
||||||
|
# [tool.ruff.lint.extend-per-file-ignores]
|
||||||
|
# "views_api.py" = ["F401"]
|
||||||
|
|
||||||
|
# [tool.ruff.lint.mccabe]
|
||||||
|
# max-complexity = 10
|
||||||
|
|
||||||
|
[tool.ruff.lint.flake8-bugbear]
|
||||||
|
# Allow default arguments like, e.g., `data: List[str] = fastapi.Query(None)`.
|
||||||
|
extend-immutable-calls = [
|
||||||
|
"fastapi.Depends",
|
||||||
|
"fastapi.Query",
|
||||||
|
]
|
||||||
12
tasks.py
12
tasks.py
|
|
@ -7,7 +7,6 @@ from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
from .crud import get_myextension, update_myextension
|
from .crud import get_myextension, update_myextension
|
||||||
|
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
########## RUN YOUR TASKS HERE ########
|
########## RUN YOUR TASKS HERE ########
|
||||||
#######################################
|
#######################################
|
||||||
|
|
@ -31,19 +30,22 @@ async def on_invoice_paid(payment: Payment) -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
myextension_id = payment.extra.get("myextensionId")
|
myextension_id = payment.extra.get("myextensionId")
|
||||||
|
assert myextension_id, "myextensionId not set in invoice"
|
||||||
myextension = await get_myextension(myextension_id)
|
myextension = await get_myextension(myextension_id)
|
||||||
|
assert myextension, "MyExtension does not exist"
|
||||||
|
|
||||||
# update something in the db
|
# update something in the db
|
||||||
if payment.extra.get("lnurlwithdraw"):
|
if payment.extra.get("lnurlwithdraw"):
|
||||||
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 = {"total": total}
|
|
||||||
|
|
||||||
await update_myextension(myextension_id=myextension_id, **data_to_update)
|
myextension.total = total
|
||||||
|
await update_myextension(myextension)
|
||||||
|
|
||||||
# here we could send some data to a websocket on wss://<your-lnbits>/api/v1/ws/<myextension_id>
|
# here we could send some data to a websocket on
|
||||||
# and then listen to it on the frontend, which we do with index.html connectWebocket()
|
# 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 = {
|
some_payment_data = {
|
||||||
"name": myextension.name,
|
"name": myextension.name,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
<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-btn flat label="Swagger API" type="a" href="../docs#/MyExtension"></q-btn>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,25 @@
|
||||||
<q-expansion-item group="extras" icon="info" label="More info">
|
<q-expansion-item group="extras" icon="info" label="More info">
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<p>
|
<p>Some more info about my excellent extension.</p>
|
||||||
Some more info about my excellent extension.
|
<small
|
||||||
</p>
|
>Created by
|
||||||
<small>Created by
|
<a
|
||||||
<a class="text-secondary" href="https://github.com/benarc" target="_blank">Ben Arc</a>.</small>
|
class="text-secondary"
|
||||||
<small>Repo
|
href="https://github.com/benarc"
|
||||||
<a class="text-secondary" href="https://github.com/lnbits/myextension" target="_blank">MyExtension</a>.</small>
|
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>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@
|
||||||
<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">New MyExtension</q-btn>
|
<q-btn unelevated color="primary" @click="formDialog.show = true"
|
||||||
|
>New MyExtension</q-btn
|
||||||
|
>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
||||||
|
|
@ -22,8 +24,14 @@
|
||||||
<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 dense flat :data="myex" row-key="id" :columns="myexTable.columns"
|
<q-table
|
||||||
:pagination.sync="myexTable.pagination">
|
dense
|
||||||
|
flat
|
||||||
|
:data="myex"
|
||||||
|
row-key="id"
|
||||||
|
:columns="myexTable.columns"
|
||||||
|
:pagination.sync="myexTable.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">
|
||||||
|
|
@ -39,28 +47,57 @@
|
||||||
<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 unelevated dense size="sm" icon="qr_code" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
<q-btn
|
||||||
class="q-mr-sm" @click="openUrlDialog(props.row.id)"></q-btn></q-td>
|
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-td auto-width>
|
||||||
<q-btn unelevated dense size="sm" icon="launch" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
<q-btn
|
||||||
type="a" :href="props.row.myextension" target="_blank"><q-tooltip>Open public
|
unelevated
|
||||||
page</q-tooltip></q-btn></q-td>
|
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-td>
|
||||||
<q-btn flat dense size="xs" @click="updateMyExtensionForm(props.row.id)" icon="edit" color="light-blue">
|
<q-btn
|
||||||
|
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 flat dense size="xs" @click="deleteMyExtension(props.row.id)" icon="cancel" color="pink">
|
<q-btn
|
||||||
|
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>
|
||||||
|
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</q-table>
|
</q-table>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
@ -69,9 +106,13 @@
|
||||||
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md">
|
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md">
|
||||||
<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">
|
||||||
<p>Simple extension you can use as a base for your own extension. <br /> Includes very simple LNURL-pay and
|
{{SITE_TITLE}} MyExtension extension
|
||||||
LNURL-withdraw example.</p>
|
</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>
|
||||||
</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>
|
||||||
|
|
@ -91,20 +132,54 @@
|
||||||
<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 filled dense v-model.trim="formDialog.data.name" label="Name"
|
<q-input
|
||||||
placeholder="Name for your record"></q-input>
|
filled
|
||||||
<q-select filled dense emit-value v-model="formDialog.data.wallet" :options="g.user.walletOptions"
|
dense
|
||||||
label="Wallet *"></q-select>
|
v-model.trim="formDialog.data.name"
|
||||||
<q-input filled dense type="number" v-model.trim="formDialog.data.lnurlwithdrawamount"
|
label="Name"
|
||||||
label="LNURL-withdraw amount"></q-input>
|
placeholder="Name for your record"
|
||||||
<q-input filled dense type="number" v-model.trim="formDialog.data.lnurlpayamount"
|
></q-input>
|
||||||
label="LNURL-pay amount"></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">
|
<div class="row q-mt-lg">
|
||||||
<q-btn v-if="formDialog.data.id" unelevated color="primary" type="submit">Update MyExtension</q-btn>
|
<q-btn
|
||||||
<q-btn v-else unelevated color="primary"
|
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"
|
:disable="formDialog.data.name == null || formDialog.data.wallet == null || formDialog.data.lnurlwithdrawamount == null || formDialog.data.lnurlpayamount == null"
|
||||||
type="submit">Create MyExtension</q-btn>
|
type="submit"
|
||||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
|
>Create MyExtension</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>
|
||||||
|
|
@ -119,29 +194,39 @@
|
||||||
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
|
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
|
||||||
<lnbits-qrcode :value="qrValue"></lnbits-qrcode>
|
<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>
|
|
||||||
|
|
||||||
<q-separator></q-separator>
|
<q-separator></q-separator>
|
||||||
|
|
||||||
<div class="row justify-start q-mt-lg">
|
<div class="row justify-start q-mt-lg">
|
||||||
<div class="col col-md-auto">
|
<div class="col col-md-auto">
|
||||||
<q-btn outline style="color: primmary;" @click="qrValue = urlDialog.data.lnurlpay">lnurlpay</q-btn>
|
<q-btn
|
||||||
|
outline
|
||||||
|
style="color: primmary"
|
||||||
|
@click="qrValue = urlDialog.data.lnurlpay"
|
||||||
|
>lnurlpay</q-btn
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-md-auto">
|
<div class="col col-md-auto">
|
||||||
<q-btn outline style="color: primmary;" @click="qrValue = urlDialog.data.lnurlwithdraw">lnurlwithdraw</q-btn>
|
<q-btn
|
||||||
|
outline
|
||||||
|
style="color: primmary"
|
||||||
|
@click="qrValue = urlDialog.data.lnurlwithdraw"
|
||||||
|
>lnurlwithdraw</q-btn
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<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
|
<q-btn
|
||||||
icon="add_circle" />
|
round
|
||||||
</template>
|
@click="createInvoice(urlDialog.data.wallet, urlDialog.data.id)"
|
||||||
<template v-slot:hint>
|
color="primary"
|
||||||
Create an invoice
|
flat
|
||||||
|
icon="add_circle"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-slot:hint> Create an invoice </template>
|
||||||
</q-input>
|
</q-input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -150,12 +235,10 @@
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||||
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.4.0/dist/confetti.browser.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.4.0/dist/confetti.browser.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
///////////////////////////////////////////////////
|
///////////////////////////////////////////////////
|
||||||
//////////an object we can update with data////////
|
//////////an object we can update with data////////
|
||||||
///////////////////////////////////////////////////
|
///////////////////////////////////////////////////
|
||||||
|
|
@ -178,15 +261,20 @@
|
||||||
myex: [],
|
myex: [],
|
||||||
myexTable: {
|
myexTable: {
|
||||||
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
|
||||||
|
|
@ -247,7 +335,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateMyExtensionForm(tempId) {
|
updateMyExtensionForm(tempId) {
|
||||||
const myextension = _.findWhere(this.myex, { id: tempId })
|
const myextension = _.findWhere(this.myex, {id: tempId})
|
||||||
this.formDialog.data = {
|
this.formDialog.data = {
|
||||||
...myextension
|
...myextension
|
||||||
}
|
}
|
||||||
|
|
@ -291,7 +379,7 @@
|
||||||
},
|
},
|
||||||
deleteMyExtension: function (tempId) {
|
deleteMyExtension: function (tempId) {
|
||||||
var self = this
|
var self = this
|
||||||
var myextension = _.findWhere(this.myex, { id: tempId })
|
var myextension = _.findWhere(this.myex, {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?')
|
||||||
|
|
@ -300,7 +388,8 @@
|
||||||
.request(
|
.request(
|
||||||
'DELETE',
|
'DELETE',
|
||||||
'/myextension/api/v1/myex/' + tempId,
|
'/myextension/api/v1/myex/' + 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.myex = _.reject(self.myex, function (obj) {
|
self.myex = _.reject(self.myex, function (obj) {
|
||||||
|
|
@ -316,12 +405,12 @@
|
||||||
LNbits.utils.exportCSV(this.myexTable.columns, this.myex)
|
LNbits.utils.exportCSV(this.myexTable.columns, this.myex)
|
||||||
},
|
},
|
||||||
itemsArray(tempId) {
|
itemsArray(tempId) {
|
||||||
const myextension = _.findWhere(this.myex, { id: tempId })
|
const myextension = _.findWhere(this.myex, {id: tempId})
|
||||||
return [...myextension.itemsMap.values()]
|
return [...myextension.itemsMap.values()]
|
||||||
},
|
},
|
||||||
openformDialog(id) {
|
openformDialog(id) {
|
||||||
const [tempId, itemId] = id.split(':')
|
const [tempId, itemId] = id.split(':')
|
||||||
const myextension = _.findWhere(this.myex, { id: tempId })
|
const myextension = _.findWhere(this.myex, {id: tempId})
|
||||||
if (itemId) {
|
if (itemId) {
|
||||||
const item = myextension.itemsMap.get(id)
|
const item = myextension.itemsMap.get(id)
|
||||||
this.formDialog.data = {
|
this.formDialog.data = {
|
||||||
|
|
@ -339,7 +428,7 @@
|
||||||
this.formDialog.data = {}
|
this.formDialog.data = {}
|
||||||
},
|
},
|
||||||
openUrlDialog(id) {
|
openUrlDialog(id) {
|
||||||
this.urlDialog.data = _.findWhere(this.myex, { id })
|
this.urlDialog.data = _.findWhere(this.myex, {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)
|
||||||
|
|
@ -363,12 +452,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.request(
|
.request('POST', `/api/v1/payments`, wallet.inkey, dataToSend)
|
||||||
'POST',
|
|
||||||
`/api/v1/payments`,
|
|
||||||
wallet.inkey,
|
|
||||||
dataToSend
|
|
||||||
)
|
|
||||||
.then(response => {
|
.then(response => {
|
||||||
this.qrValue = response.data.payment_request
|
this.qrValue = response.data.payment_request
|
||||||
})
|
})
|
||||||
|
|
@ -377,15 +461,15 @@
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
makeItRain() {
|
makeItRain() {
|
||||||
document.getElementById("vue").disabled = true
|
document.getElementById('vue').disabled = true
|
||||||
var end = Date.now() + (2 * 1000)
|
var end = Date.now() + 2 * 1000
|
||||||
var colors = ['#FFD700', '#ffffff']
|
var colors = ['#FFD700', '#ffffff']
|
||||||
function frame() {
|
function frame() {
|
||||||
confetti({
|
confetti({
|
||||||
particleCount: 2,
|
particleCount: 2,
|
||||||
angle: 60,
|
angle: 60,
|
||||||
spread: 55,
|
spread: 55,
|
||||||
origin: { x: 0 },
|
origin: {x: 0},
|
||||||
colors: colors,
|
colors: colors,
|
||||||
zIndex: 999999
|
zIndex: 999999
|
||||||
})
|
})
|
||||||
|
|
@ -393,15 +477,14 @@
|
||||||
particleCount: 2,
|
particleCount: 2,
|
||||||
angle: 120,
|
angle: 120,
|
||||||
spread: 55,
|
spread: 55,
|
||||||
origin: { x: 1 },
|
origin: {x: 1},
|
||||||
colors: colors,
|
colors: colors,
|
||||||
zIndex: 999999
|
zIndex: 999999
|
||||||
})
|
})
|
||||||
if (Date.now() < end) {
|
if (Date.now() < end) {
|
||||||
requestAnimationFrame(frame)
|
requestAnimationFrame(frame)
|
||||||
}
|
} else {
|
||||||
else {
|
document.getElementById('vue').disabled = false
|
||||||
document.getElementById("vue").disabled = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
frame()
|
frame()
|
||||||
|
|
@ -445,4 +528,4 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,20 @@
|
||||||
<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 :value="qrValue" :options="{width: 800}" class="rounded-borders"></qrcode>
|
<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)">Copy LNURL </q-btn>
|
<q-btn outline color="grey" @click="copyText(qrValue)"
|
||||||
|
>Copy LNURL
|
||||||
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-sm-6 col-md-5 col-lg-4 q-gutter-y-md">
|
<div class="col-12 col-sm-6 col-md-5 col-lg-4 q-gutter-y-md">
|
||||||
|
|
@ -26,13 +31,15 @@
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<h6 class="text-subtitle1 q-mb-sm q-mt-none">Public page</h6>
|
<h6 class="text-subtitle1 q-mb-sm q-mt-none">Public page</h6>
|
||||||
<p class="q-my-none">
|
<p class="q-my-none">
|
||||||
Most extensions have a public page that can be shared
|
Most extensions have a public page that can be shared (this page will
|
||||||
(this page will still be accessible even if you have restricted
|
still be accessible even if you have restricted access to your LNbits
|
||||||
access to your LNbits install).
|
install).
|
||||||
<br /><br />
|
<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
|
||||||
which you can subscribe to somewhere using wss://{your-lnbits}/api/v1/ws/{the-id-of-this-record}
|
a websocket waiting for the payment, which you can subscribe to
|
||||||
</q-card-section>
|
somewhere using wss://{your-lnbits}/api/v1/ws/{the-id-of-this-record}
|
||||||
|
</p></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>
|
||||||
<q-list> </q-list>
|
<q-list> </q-list>
|
||||||
|
|
@ -57,8 +64,7 @@
|
||||||
// Will trigger payment reaction when payment received, sent from tasks.py
|
// Will trigger payment reaction when payment received, sent from tasks.py
|
||||||
eventReactionWebocket(this.myExtensionID)
|
eventReactionWebocket(this.myExtensionID)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
11
tests/test_init.py
Normal file
11
tests/test_init.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import pytest
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from .. import myextension_ext
|
||||||
|
|
||||||
|
|
||||||
|
# just import router and add it to a test router
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_router():
|
||||||
|
router = APIRouter()
|
||||||
|
router.include_router(myextension_ext)
|
||||||
7
toc.md
7
toc.md
|
|
@ -1,22 +1,29 @@
|
||||||
# Terms and Conditions for LNbits Extension
|
# Terms and Conditions for LNbits Extension
|
||||||
|
|
||||||
## 1. Acceptance of Terms
|
## 1. Acceptance of Terms
|
||||||
|
|
||||||
By installing and using the LNbits extension ("Extension"), you agree to be bound by these terms and conditions ("Terms"). If you do not agree to these Terms, do not use the Extension.
|
By installing and using the LNbits extension ("Extension"), you agree to be bound by these terms and conditions ("Terms"). If you do not agree to these Terms, do not use the Extension.
|
||||||
|
|
||||||
## 2. License
|
## 2. License
|
||||||
|
|
||||||
The Extension is free and open-source software, released under [specify the FOSS license here, e.g., GPL-3.0, MIT, etc.]. You are permitted to use, copy, modify, and distribute the Extension under the terms of that license.
|
The Extension is free and open-source software, released under [specify the FOSS license here, e.g., GPL-3.0, MIT, etc.]. You are permitted to use, copy, modify, and distribute the Extension under the terms of that license.
|
||||||
|
|
||||||
## 3. No Warranty
|
## 3. No Warranty
|
||||||
|
|
||||||
The Extension is provided "as is" and with all faults, and the developer expressly disclaims all warranties of any kind, whether express, implied, statutory, or otherwise, including but not limited to warranties of merchantability, fitness for a particular purpose, non-infringement, and any warranties arising out of course of dealing or usage of trade. No advice or information, whether oral or written, obtained from the developer or elsewhere will create any warranty not expressly stated in this Terms.
|
The Extension is provided "as is" and with all faults, and the developer expressly disclaims all warranties of any kind, whether express, implied, statutory, or otherwise, including but not limited to warranties of merchantability, fitness for a particular purpose, non-infringement, and any warranties arising out of course of dealing or usage of trade. No advice or information, whether oral or written, obtained from the developer or elsewhere will create any warranty not expressly stated in this Terms.
|
||||||
|
|
||||||
## 4. Limitation of Liability
|
## 4. Limitation of Liability
|
||||||
|
|
||||||
In no event will the developer be liable to you or any third party for any direct, indirect, incidental, special, consequential, or punitive damages, including lost profit, lost revenue, loss of data, or other damages arising out of or in connection with your use of the Extension, even if the developer has been advised of the possibility of such damages. The foregoing limitation of liability shall apply to the fullest extent permitted by law in the applicable jurisdiction.
|
In no event will the developer be liable to you or any third party for any direct, indirect, incidental, special, consequential, or punitive damages, including lost profit, lost revenue, loss of data, or other damages arising out of or in connection with your use of the Extension, even if the developer has been advised of the possibility of such damages. The foregoing limitation of liability shall apply to the fullest extent permitted by law in the applicable jurisdiction.
|
||||||
|
|
||||||
## 5. Modification of Terms
|
## 5. Modification of Terms
|
||||||
|
|
||||||
The developer reserves the right to modify these Terms at any time. You are advised to review these Terms periodically for any changes. Changes to these Terms are effective when they are posted on the appropriate location within or associated with the Extension.
|
The developer reserves the right to modify these Terms at any time. You are advised to review these Terms periodically for any changes. Changes to these Terms are effective when they are posted on the appropriate location within or associated with the Extension.
|
||||||
|
|
||||||
## 6. General Provisions
|
## 6. General Provisions
|
||||||
|
|
||||||
If any provision of these Terms is held to be invalid or unenforceable, that provision will be enforced to the maximum extent permissible, and the other provisions of these Terms will remain in full force and effect. These Terms constitute the entire agreement between you and the developer regarding the use of the Extension.
|
If any provision of these Terms is held to be invalid or unenforceable, that provision will be enforced to the maximum extent permissible, and the other provisions of these Terms will remain in full force and effect. These Terms constitute the entire agreement between you and the developer regarding the use of the Extension.
|
||||||
|
|
||||||
## 7. Contact Information
|
## 7. Contact Information
|
||||||
|
|
||||||
If you have any questions about these Terms, please contact the developer at [developer's contact information].
|
If you have any questions about these Terms, please contact the developer at [developer's contact information].
|
||||||
|
|
|
||||||
34
views.py
34
views.py
|
|
@ -1,18 +1,20 @@
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
from fastapi import Depends, Request
|
from fastapi import APIRouter, Depends, Request
|
||||||
from fastapi.templating import Jinja2Templates
|
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.exceptions import HTTPException
|
||||||
from starlette.responses import HTMLResponse
|
from starlette.responses import HTMLResponse
|
||||||
|
|
||||||
from lnbits.core.models import User
|
|
||||||
from lnbits.decorators import check_user_exists
|
|
||||||
from lnbits.settings import settings
|
|
||||||
|
|
||||||
from . import myextension_ext, myextension_renderer
|
|
||||||
from .crud import get_myextension
|
from .crud import get_myextension
|
||||||
|
|
||||||
myex = Jinja2Templates(directory="myex")
|
myextension_generic_router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
def myextension_renderer():
|
||||||
|
return template_renderer(["myextension/templates"])
|
||||||
|
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
|
|
@ -23,7 +25,7 @@ myex = Jinja2Templates(directory="myex")
|
||||||
# Backend admin page
|
# Backend admin page
|
||||||
|
|
||||||
|
|
||||||
@myextension_ext.get("/", response_class=HTMLResponse)
|
@myextension_generic_router.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(
|
||||||
"myextension/index.html", {"request": request, "user": user.dict()}
|
"myextension/index.html", {"request": request, "user": user.dict()}
|
||||||
|
|
@ -33,9 +35,9 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
|
||||||
# Frontend shareable page
|
# Frontend shareable page
|
||||||
|
|
||||||
|
|
||||||
@myextension_ext.get("/{myextension_id}")
|
@myextension_generic_router.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)
|
||||||
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."
|
||||||
|
|
@ -54,7 +56,7 @@ 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_generic_router.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:
|
||||||
|
|
@ -67,9 +69,11 @@ async def manifest(myextension_id: str):
|
||||||
"name": myextension.name + " - " + settings.lnbits_site_title,
|
"name": myextension.name + " - " + settings.lnbits_site_title,
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": settings.lnbits_custom_logo
|
"src": (
|
||||||
if settings.lnbits_custom_logo
|
settings.lnbits_custom_logo
|
||||||
else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png",
|
if settings.lnbits_custom_logo
|
||||||
|
else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png"
|
||||||
|
),
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"sizes": "900x900",
|
"sizes": "900x900",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
93
views_api.py
93
views_api.py
|
|
@ -1,24 +1,28 @@
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from fastapi import Depends, Query, Request
|
|
||||||
from starlette.exceptions import HTTPException
|
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, Query, Request
|
||||||
from lnbits.core.crud import get_user
|
from lnbits.core.crud import get_user
|
||||||
|
from lnbits.core.models import WalletTypeInfo
|
||||||
from lnbits.core.services import create_invoice
|
from lnbits.core.services import create_invoice
|
||||||
from lnbits.decorators import (
|
from lnbits.decorators import (
|
||||||
WalletTypeInfo,
|
|
||||||
get_key_type,
|
get_key_type,
|
||||||
require_admin_key,
|
require_admin_key,
|
||||||
|
require_invoice_key,
|
||||||
)
|
)
|
||||||
|
from lnbits.helpers import urlsafe_short_hash
|
||||||
|
from lnurl import encode as lnurl_encode
|
||||||
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
from . import myextension_ext
|
|
||||||
from .crud import (
|
from .crud import (
|
||||||
create_myextension,
|
create_myextension,
|
||||||
update_myextension,
|
|
||||||
delete_myextension,
|
delete_myextension,
|
||||||
get_myextension,
|
get_myextension,
|
||||||
get_myextensions,
|
get_myextensions,
|
||||||
|
update_myextension,
|
||||||
)
|
)
|
||||||
from .models import CreateMyExtensionData
|
from .models import CreateMyExtensionData, MyExtension
|
||||||
|
|
||||||
|
myextension_api_router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
|
|
@ -28,9 +32,8 @@ 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/myex", status_code=HTTPStatus.OK)
|
@myextension_api_router.get("/api/v1/myex", status_code=HTTPStatus.OK)
|
||||||
async def api_myextensions(
|
async def api_myextensions(
|
||||||
req: Request,
|
|
||||||
all_wallets: bool = Query(False),
|
all_wallets: bool = Query(False),
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
wallet: WalletTypeInfo = Depends(get_key_type),
|
||||||
):
|
):
|
||||||
|
|
@ -38,19 +41,19 @@ async def api_myextensions(
|
||||||
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 [
|
return [myextension.dict() for myextension in await get_myextensions(wallet_ids)]
|
||||||
myextension.dict() for myextension in await get_myextensions(wallet_ids, req)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
## Get a single record
|
## Get a single record
|
||||||
|
|
||||||
|
|
||||||
@myextension_ext.get("/api/v1/myex/{myextension_id}", status_code=HTTPStatus.OK)
|
@myextension_api_router.get(
|
||||||
async def api_myextension(
|
"/api/v1/myex/{myextension_id}",
|
||||||
req: Request, myextension_id: str, WalletTypeInfo=Depends(get_key_type)
|
status_code=HTTPStatus.OK,
|
||||||
):
|
dependencies=[Depends(require_invoice_key)],
|
||||||
myextension = await get_myextension(myextension_id, req)
|
)
|
||||||
|
async def api_myextension(myextension_id: str):
|
||||||
|
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."
|
||||||
|
|
@ -61,49 +64,64 @@ async def api_myextension(
|
||||||
## update a record
|
## update a record
|
||||||
|
|
||||||
|
|
||||||
@myextension_ext.put("/api/v1/myex/{myextension_id}")
|
@myextension_api_router.put("/api/v1/myex/{myextension_id}")
|
||||||
async def api_myextension_update(
|
async def api_myextension_update(
|
||||||
req: Request,
|
|
||||||
data: CreateMyExtensionData,
|
data: CreateMyExtensionData,
|
||||||
myextension_id: str,
|
myextension_id: str,
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
wallet: WalletTypeInfo = Depends(get_key_type),
|
||||||
):
|
) -> MyExtension:
|
||||||
if not myextension_id:
|
if not myextension_id:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist."
|
status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist."
|
||||||
)
|
)
|
||||||
myextension = await get_myextension(myextension_id, req)
|
myextension = await get_myextension(myextension_id)
|
||||||
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(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.FORBIDDEN, detail="Not your MyExtension."
|
status_code=HTTPStatus.FORBIDDEN, detail="Not your MyExtension."
|
||||||
)
|
)
|
||||||
myextension = await update_myextension(
|
|
||||||
myextension_id=myextension_id, **data.dict(), req=req
|
for key, value in data.dict().items():
|
||||||
)
|
setattr(myextension, key, value)
|
||||||
return myextension.dict()
|
|
||||||
|
return await update_myextension(myextension)
|
||||||
|
|
||||||
|
|
||||||
## Create a new record
|
## Create a new record
|
||||||
|
|
||||||
|
|
||||||
@myextension_ext.post("/api/v1/myex", status_code=HTTPStatus.CREATED)
|
@myextension_api_router.post("/api/v1/myex", status_code=HTTPStatus.CREATED)
|
||||||
async def api_myextension_create(
|
async def api_myextension_create(
|
||||||
req: Request,
|
request: Request,
|
||||||
data: CreateMyExtensionData,
|
data: CreateMyExtensionData,
|
||||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
key_type: WalletTypeInfo = Depends(require_admin_key),
|
||||||
):
|
) -> MyExtension:
|
||||||
myextension = await create_myextension(
|
myextension_id = urlsafe_short_hash()
|
||||||
wallet_id=wallet.wallet.id, data=data, req=req
|
lnurlpay = lnurl_encode(
|
||||||
|
str(request.url_for("myextension.api_lnurl_pay", myextension_id=myextension_id))
|
||||||
)
|
)
|
||||||
return myextension.dict()
|
lnurlwithdraw = lnurl_encode(
|
||||||
|
str(
|
||||||
|
request.url_for(
|
||||||
|
"myextension.api_lnurl_withdraw", myextension_id=myextension_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
data.wallet = data.wallet or key_type.wallet.id
|
||||||
|
myext = MyExtension(
|
||||||
|
id=myextension_id,
|
||||||
|
lnurlpay=lnurlpay,
|
||||||
|
lnurlwithdraw=lnurlwithdraw,
|
||||||
|
**data.dict(),
|
||||||
|
)
|
||||||
|
return await create_myextension(myext)
|
||||||
|
|
||||||
|
|
||||||
## Delete a record
|
## Delete a record
|
||||||
|
|
||||||
|
|
||||||
@myextension_ext.delete("/api/v1/myex/{myextension_id}")
|
@myextension_api_router.delete("/api/v1/myex/{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)
|
||||||
):
|
):
|
||||||
|
|
@ -128,7 +146,7 @@ async def api_myextension_delete(
|
||||||
## This endpoint creates a payment
|
## This endpoint creates a payment
|
||||||
|
|
||||||
|
|
||||||
@myextension_ext.post(
|
@myextension_api_router.post(
|
||||||
"/api/v1/myex/payment/{myextension_id}", status_code=HTTPStatus.CREATED
|
"/api/v1/myex/payment/{myextension_id}", status_code=HTTPStatus.CREATED
|
||||||
)
|
)
|
||||||
async def api_myextension_create_invoice(
|
async def api_myextension_create_invoice(
|
||||||
|
|
@ -141,7 +159,8 @@ async def api_myextension_create_invoice(
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist."
|
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
|
# we create a payment and add some tags,
|
||||||
|
# so tasks.py can grab the payment once its paid
|
||||||
|
|
||||||
try:
|
try:
|
||||||
payment_hash, payment_request = await create_invoice(
|
payment_hash, payment_request = await create_invoice(
|
||||||
|
|
@ -153,7 +172,9 @@ async def api_myextension_create_invoice(
|
||||||
"amount": amount,
|
"amount": amount,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as exc:
|
||||||
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(exc)
|
||||||
|
) from exc
|
||||||
|
|
||||||
return {"payment_hash": payment_hash, "payment_request": payment_request}
|
return {"payment_hash": payment_hash, "payment_request": payment_request}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,14 @@
|
||||||
# Feel free to delete this file if you don't need it.
|
# Feel free to delete this file if you don't need it.
|
||||||
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from fastapi import Depends, Query, Request
|
from typing import Optional
|
||||||
from . import myextension_ext
|
|
||||||
from .crud import get_myextension
|
import shortuuid
|
||||||
|
from fastapi import APIRouter, Query, Request
|
||||||
from lnbits.core.services import create_invoice, pay_invoice
|
from lnbits.core.services import create_invoice, pay_invoice
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from typing import Optional
|
|
||||||
from .crud import update_myextension
|
from .crud import get_myextension
|
||||||
from .models import MyExtension
|
|
||||||
import shortuuid
|
|
||||||
|
|
||||||
#################################################
|
#################################################
|
||||||
########### A very simple LNURLpay ##############
|
########### A very simple LNURLpay ##############
|
||||||
|
|
@ -19,8 +18,10 @@ import shortuuid
|
||||||
#################################################
|
#################################################
|
||||||
#################################################
|
#################################################
|
||||||
|
|
||||||
|
myextension_lnurl_router = APIRouter()
|
||||||
|
|
||||||
@myextension_ext.get(
|
|
||||||
|
@myextension_lnurl_router.get(
|
||||||
"/api/v1/lnurl/pay/{myextension_id}",
|
"/api/v1/lnurl/pay/{myextension_id}",
|
||||||
status_code=HTTPStatus.OK,
|
status_code=HTTPStatus.OK,
|
||||||
name="myextension.api_lnurl_pay",
|
name="myextension.api_lnurl_pay",
|
||||||
|
|
@ -45,7 +46,7 @@ async def api_lnurl_pay(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@myextension_ext.get(
|
@myextension_lnurl_router.get(
|
||||||
"/api/v1/lnurl/paycb/{myextension_id}",
|
"/api/v1/lnurl/paycb/{myextension_id}",
|
||||||
status_code=HTTPStatus.OK,
|
status_code=HTTPStatus.OK,
|
||||||
name="myextension.api_lnurl_pay_callback",
|
name="myextension.api_lnurl_pay_callback",
|
||||||
|
|
@ -60,7 +61,7 @@ async def api_lnurl_pay_cb(
|
||||||
if not myextension:
|
if not myextension:
|
||||||
return {"status": "ERROR", "reason": "No myextension found"}
|
return {"status": "ERROR", "reason": "No myextension found"}
|
||||||
|
|
||||||
payment_hash, payment_request = await create_invoice(
|
_, payment_request = await create_invoice(
|
||||||
wallet_id=myextension.wallet,
|
wallet_id=myextension.wallet,
|
||||||
amount=int(amount / 1000),
|
amount=int(amount / 1000),
|
||||||
memo=myextension.name,
|
memo=myextension.name,
|
||||||
|
|
@ -82,28 +83,24 @@ async def api_lnurl_pay_cb(
|
||||||
######## 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 #
|
||||||
#################################################
|
#################################################
|
||||||
## withdraws are unique, removing 'tickerhash' ##
|
## withdraw is unlimited, look at withdraw ext ##
|
||||||
## here and crud.py will allow muliple pulls ####
|
## for more advanced withdraw options ##
|
||||||
#################################################
|
#################################################
|
||||||
|
|
||||||
|
|
||||||
@myextension_ext.get(
|
@myextension_lnurl_router.get(
|
||||||
"/api/v1/lnurl/withdraw/{myextension_id}/{tickerhash}",
|
"/api/v1/lnurl/withdraw/{myextension_id}",
|
||||||
status_code=HTTPStatus.OK,
|
status_code=HTTPStatus.OK,
|
||||||
name="myextension.api_lnurl_withdraw",
|
name="myextension.api_lnurl_withdraw",
|
||||||
)
|
)
|
||||||
async def api_lnurl_withdraw(
|
async def api_lnurl_withdraw(
|
||||||
request: Request,
|
request: Request,
|
||||||
myextension_id: str,
|
myextension_id: str,
|
||||||
tickerhash: str,
|
|
||||||
):
|
):
|
||||||
myextension = await get_myextension(myextension_id)
|
myextension = await get_myextension(myextension_id)
|
||||||
if not myextension:
|
if not myextension:
|
||||||
return {"status": "ERROR", "reason": "No myextension found"}
|
return {"status": "ERROR", "reason": "No myextension found"}
|
||||||
k1 = shortuuid.uuid(name=myextension.id + str(myextension.ticker))
|
k1 = shortuuid.uuid(name=myextension.id)
|
||||||
if k1 != tickerhash:
|
|
||||||
return {"status": "ERROR", "reason": "LNURLw already used"}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"tag": "withdrawRequest",
|
"tag": "withdrawRequest",
|
||||||
"callback": str(
|
"callback": str(
|
||||||
|
|
@ -118,7 +115,7 @@ async def api_lnurl_withdraw(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@myextension_ext.get(
|
@myextension_lnurl_router.get(
|
||||||
"/api/v1/lnurl/withdrawcb/{myextension_id}",
|
"/api/v1/lnurl/withdrawcb/{myextension_id}",
|
||||||
status_code=HTTPStatus.OK,
|
status_code=HTTPStatus.OK,
|
||||||
name="myextension.api_lnurl_withdraw_callback",
|
name="myextension.api_lnurl_withdraw_callback",
|
||||||
|
|
@ -134,13 +131,10 @@ async def api_lnurl_withdraw_cb(
|
||||||
if not myextension:
|
if not myextension:
|
||||||
return {"status": "ERROR", "reason": "No myextension found"}
|
return {"status": "ERROR", "reason": "No myextension found"}
|
||||||
|
|
||||||
k1Check = shortuuid.uuid(name=myextension.id + str(myextension.ticker))
|
k1_check = shortuuid.uuid(name=myextension.id)
|
||||||
if k1Check != k1:
|
if k1_check != 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 pay_invoice(
|
await pay_invoice(
|
||||||
wallet_id=myextension.wallet,
|
wallet_id=myextension.wallet,
|
||||||
payment_request=pr,
|
payment_request=pr,
|
||||||
Loading…
Add table
Add a link
Reference in a new issue