commit
055e91dadc
|
@ -13,7 +13,7 @@ async def create_copilot(
|
||||||
copilot_id = urlsafe_short_hash()
|
copilot_id = urlsafe_short_hash()
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO copilot.copilots (
|
INSERT INTO copilot.newer_copilots (
|
||||||
id,
|
id,
|
||||||
user,
|
user,
|
||||||
lnurl_toggle,
|
lnurl_toggle,
|
||||||
|
@ -71,24 +71,26 @@ async def update_copilot(
|
||||||
q = ", ".join([f"{field[0]} = ?" for field in data])
|
q = ", ".join([f"{field[0]} = ?" for field in data])
|
||||||
items = [f"{field[1]}" for field in data]
|
items = [f"{field[1]}" for field in data]
|
||||||
items.append(copilot_id)
|
items.append(copilot_id)
|
||||||
await db.execute(f"UPDATE copilot.copilots SET {q} WHERE id = ?", (items))
|
await db.execute(f"UPDATE copilot.newer_copilots SET {q} WHERE id = ?", (items))
|
||||||
row = await db.fetchone(
|
row = await db.fetchone(
|
||||||
"SELECT * FROM copilot.copilots WHERE id = ?", (copilot_id,)
|
"SELECT * FROM copilot.newer_copilots WHERE id = ?", (copilot_id,)
|
||||||
)
|
)
|
||||||
return Copilots(**row) if row else None
|
return Copilots(**row) if row else None
|
||||||
|
|
||||||
|
|
||||||
async def get_copilot(copilot_id: str) -> Copilots:
|
async def get_copilot(copilot_id: str) -> Copilots:
|
||||||
row = await db.fetchone(
|
row = await db.fetchone(
|
||||||
"SELECT * FROM copilot.copilots WHERE id = ?", (copilot_id,)
|
"SELECT * FROM copilot.newer_copilots WHERE id = ?", (copilot_id,)
|
||||||
)
|
)
|
||||||
return Copilots(**row) if row else None
|
return Copilots(**row) if row else None
|
||||||
|
|
||||||
|
|
||||||
async def get_copilots(user: str) -> List[Copilots]:
|
async def get_copilots(user: str) -> List[Copilots]:
|
||||||
rows = await db.fetchall("SELECT * FROM copilot.copilots WHERE user = ?", (user,))
|
rows = await db.fetchall(
|
||||||
|
"SELECT * FROM copilot.newer_copilots WHERE user = ?", (user,)
|
||||||
|
)
|
||||||
return [Copilots(**row) for row in rows]
|
return [Copilots(**row) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
async def delete_copilot(copilot_id: str) -> None:
|
async def delete_copilot(copilot_id: str) -> None:
|
||||||
await db.execute("DELETE FROM copilot.copilots WHERE id = ?", (copilot_id,))
|
await db.execute("DELETE FROM copilot.newer_copilots WHERE id = ?", (copilot_id,))
|
||||||
|
|
|
@ -6,6 +6,48 @@ async def m001_initial(db):
|
||||||
await db.execute(
|
await db.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE TABLE copilot.copilots (
|
CREATE TABLE copilot.copilots (
|
||||||
|
id TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"user" TEXT,
|
||||||
|
title TEXT,
|
||||||
|
lnurl_toggle INTEGER,
|
||||||
|
wallet TEXT,
|
||||||
|
animation1 TEXT,
|
||||||
|
animation2 TEXT,
|
||||||
|
animation3 TEXT,
|
||||||
|
animation1threshold INTEGER,
|
||||||
|
animation2threshold INTEGER,
|
||||||
|
animation3threshold INTEGER,
|
||||||
|
animation1webhook TEXT,
|
||||||
|
animation2webhook TEXT,
|
||||||
|
animation3webhook TEXT,
|
||||||
|
lnurl_title TEXT,
|
||||||
|
show_message INTEGER,
|
||||||
|
show_ack INTEGER,
|
||||||
|
show_price INTEGER,
|
||||||
|
amount_made INTEGER,
|
||||||
|
fullscreen_cam INTEGER,
|
||||||
|
iframe_url TEXT,
|
||||||
|
timestamp TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def m002_fix_data_types(db):
|
||||||
|
"""
|
||||||
|
Fix data types.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if db.type != "SQLITE":
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE copilot.copilots ALTER COLUMN show_price TYPE TEXT;"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def m003_fix_data_types(db):
|
||||||
|
await db.execute(
|
||||||
|
f"""
|
||||||
|
CREATE TABLE copilot.newer_copilots (
|
||||||
id TEXT NOT NULL PRIMARY KEY,
|
id TEXT NOT NULL PRIMARY KEY,
|
||||||
"user" TEXT,
|
"user" TEXT,
|
||||||
title TEXT,
|
title TEXT,
|
||||||
|
@ -32,48 +74,6 @@ async def m001_initial(db):
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def m002_fix_data_types(db):
|
|
||||||
"""
|
|
||||||
Fix data types.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if db.type != "SQLITE":
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"ALTER TABLE copilot.copilots ALTER COLUMN show_price TYPE TEXT;"
|
"INSERT INTO copilot.newer_copilots SELECT * FROM copilot.copilots"
|
||||||
)
|
)
|
||||||
|
|
||||||
# If needed, migration for SQLite (RENAME not working properly)
|
|
||||||
#
|
|
||||||
# await db.execute(
|
|
||||||
# f"""
|
|
||||||
# CREATE TABLE copilot.new_copilots (
|
|
||||||
# id TEXT NOT NULL PRIMARY KEY,
|
|
||||||
# "user" TEXT,
|
|
||||||
# title TEXT,
|
|
||||||
# lnurl_toggle INTEGER,
|
|
||||||
# wallet TEXT,
|
|
||||||
# animation1 TEXT,
|
|
||||||
# animation2 TEXT,
|
|
||||||
# animation3 TEXT,
|
|
||||||
# animation1threshold INTEGER,
|
|
||||||
# animation2threshold INTEGER,
|
|
||||||
# animation3threshold INTEGER,
|
|
||||||
# animation1webhook TEXT,
|
|
||||||
# animation2webhook TEXT,
|
|
||||||
# animation3webhook TEXT,
|
|
||||||
# lnurl_title TEXT,
|
|
||||||
# show_message INTEGER,
|
|
||||||
# show_ack INTEGER,
|
|
||||||
# show_price TEXT,
|
|
||||||
# amount_made INTEGER,
|
|
||||||
# fullscreen_cam INTEGER,
|
|
||||||
# iframe_url TEXT,
|
|
||||||
# timestamp TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
|
|
||||||
# );
|
|
||||||
# """
|
|
||||||
# )
|
|
||||||
#
|
|
||||||
# await db.execute("INSERT INTO copilot.new_copilots SELECT * FROM copilot.copilots;")
|
|
||||||
# await db.execute("DROP TABLE IF EXISTS copilot.copilots;")
|
|
||||||
# await db.execute("ALTER TABLE copilot.new_copilots RENAME TO copilot.copilots;")
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ from fastapi.param_functions import Query
|
||||||
from fastapi.params import Depends
|
from fastapi.params import Depends
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
from lnbits.decorators import WalletTypeInfo, get_key_type
|
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
|
||||||
|
|
||||||
from . import copilot_ext
|
from . import copilot_ext
|
||||||
from .crud import (
|
from .crud import (
|
||||||
|
@ -54,7 +54,7 @@ async def api_copilot_retrieve(
|
||||||
async def api_copilot_create_or_update(
|
async def api_copilot_create_or_update(
|
||||||
data: CreateCopilotData,
|
data: CreateCopilotData,
|
||||||
copilot_id: str = Query(None),
|
copilot_id: str = Query(None),
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
):
|
):
|
||||||
data.user = wallet.wallet.user
|
data.user = wallet.wallet.user
|
||||||
data.wallet = wallet.wallet.id
|
data.wallet = wallet.wallet.id
|
||||||
|
@ -67,7 +67,7 @@ async def api_copilot_create_or_update(
|
||||||
|
|
||||||
@copilot_ext.delete("/api/v1/copilot/{copilot_id}")
|
@copilot_ext.delete("/api/v1/copilot/{copilot_id}")
|
||||||
async def api_copilot_delete(
|
async def api_copilot_delete(
|
||||||
copilot_id: str = Query(None), wallet: WalletTypeInfo = Depends(get_key_type)
|
copilot_id: str = Query(None), wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||||
):
|
):
|
||||||
copilot = await get_copilot(copilot_id)
|
copilot = await get_copilot(copilot_id)
|
||||||
|
|
||||||
|
|
|
@ -16,4 +16,3 @@ def events_renderer():
|
||||||
|
|
||||||
from .views import * # noqa
|
from .views import * # noqa
|
||||||
from .views_api import * # noqa
|
from .views_api import * # noqa
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,9 @@ async def get_tickets(wallet_ids: Union[str, List[str]]) -> List[Tickets]:
|
||||||
async def delete_ticket(payment_hash: str) -> None:
|
async def delete_ticket(payment_hash: str) -> None:
|
||||||
await db.execute("DELETE FROM events.ticket WHERE id = ?", (payment_hash,))
|
await db.execute("DELETE FROM events.ticket WHERE id = ?", (payment_hash,))
|
||||||
|
|
||||||
|
async def delete_event_tickets(event_id: str) -> None:
|
||||||
|
await db.execute("DELETE FROM events.tickets WHERE event = ?", (event_id,))
|
||||||
|
|
||||||
|
|
||||||
# EVENTS
|
# EVENTS
|
||||||
|
|
||||||
|
|
|
@ -380,14 +380,14 @@
|
||||||
methods: {
|
methods: {
|
||||||
getTickets: function () {
|
getTickets: function () {
|
||||||
var self = this
|
var self = this
|
||||||
console.log('obj')
|
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.request(
|
.request(
|
||||||
'GET',
|
'GET',
|
||||||
'/events/api/v1/tickets?all_wallets',
|
'/events/api/v1/tickets?all_wallets=true',
|
||||||
this.g.user.wallets[0].inkey
|
this.g.user.wallets[0].inkey
|
||||||
)
|
)
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
|
console.log(response)
|
||||||
self.tickets = response.data.map(function (obj) {
|
self.tickets = response.data.map(function (obj) {
|
||||||
console.log(obj)
|
console.log(obj)
|
||||||
return mapEvents(obj)
|
return mapEvents(obj)
|
||||||
|
|
|
@ -15,6 +15,7 @@ from .crud import (
|
||||||
create_event,
|
create_event,
|
||||||
create_ticket,
|
create_ticket,
|
||||||
delete_event,
|
delete_event,
|
||||||
|
delete_event_tickets,
|
||||||
delete_ticket,
|
delete_ticket,
|
||||||
get_event,
|
get_event,
|
||||||
get_event_tickets,
|
get_event_tickets,
|
||||||
|
@ -81,6 +82,7 @@ async def api_form_delete(event_id, wallet: WalletTypeInfo = Depends(get_key_typ
|
||||||
)
|
)
|
||||||
|
|
||||||
await delete_event(event_id)
|
await delete_event(event_id)
|
||||||
|
await delete_event_tickets(event_id)
|
||||||
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,11 @@ from fastapi import Request
|
||||||
from fastapi.param_functions import Query
|
from fastapi.param_functions import Query
|
||||||
from fastapi.params import Depends
|
from fastapi.params import Depends
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
from starlette.responses import HTMLResponse, JSONResponse # type: ignore
|
from starlette.responses import HTMLResponse # type: ignore
|
||||||
|
|
||||||
from lnbits.core.crud import get_wallet
|
from lnbits.core.crud import get_wallet
|
||||||
from lnbits.core.services import check_invoice_status, create_invoice
|
from lnbits.core.services import check_invoice_status, create_invoice
|
||||||
from lnbits.decorators import WalletTypeInfo, get_key_type
|
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
|
||||||
|
|
||||||
from . import jukebox_ext
|
from . import jukebox_ext
|
||||||
from .crud import (
|
from .crud import (
|
||||||
|
@ -30,7 +30,7 @@ from .models import CreateJukeboxPayment, CreateJukeLinkData
|
||||||
@jukebox_ext.get("/api/v1/jukebox")
|
@jukebox_ext.get("/api/v1/jukebox")
|
||||||
async def api_get_jukeboxs(
|
async def api_get_jukeboxs(
|
||||||
req: Request,
|
req: Request,
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
all_wallets: bool = Query(False),
|
all_wallets: bool = Query(False),
|
||||||
):
|
):
|
||||||
wallet_user = wallet.wallet.user
|
wallet_user = wallet.wallet.user
|
||||||
|
@ -72,7 +72,7 @@ async def api_check_credentials_callbac(
|
||||||
|
|
||||||
@jukebox_ext.get("/api/v1/jukebox/{juke_id}")
|
@jukebox_ext.get("/api/v1/jukebox/{juke_id}")
|
||||||
async def api_check_credentials_check(
|
async def api_check_credentials_check(
|
||||||
juke_id: str = Query(None), wallet: WalletTypeInfo = Depends(get_key_type)
|
juke_id: str = Query(None), wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||||
):
|
):
|
||||||
print(juke_id)
|
print(juke_id)
|
||||||
jukebox = await get_jukebox(juke_id)
|
jukebox = await get_jukebox(juke_id)
|
||||||
|
@ -85,7 +85,7 @@ async def api_check_credentials_check(
|
||||||
async def api_create_update_jukebox(
|
async def api_create_update_jukebox(
|
||||||
data: CreateJukeLinkData,
|
data: CreateJukeLinkData,
|
||||||
juke_id: str = Query(None),
|
juke_id: str = Query(None),
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
):
|
):
|
||||||
if juke_id:
|
if juke_id:
|
||||||
jukebox = await update_jukebox(data, juke_id=juke_id)
|
jukebox = await update_jukebox(data, juke_id=juke_id)
|
||||||
|
@ -95,7 +95,7 @@ async def api_create_update_jukebox(
|
||||||
|
|
||||||
|
|
||||||
@jukebox_ext.delete("/api/v1/jukebox/{juke_id}")
|
@jukebox_ext.delete("/api/v1/jukebox/{juke_id}")
|
||||||
async def api_delete_item(juke_id=None, wallet: WalletTypeInfo = Depends(get_key_type)):
|
async def api_delete_item(juke_id=None, wallet: WalletTypeInfo = Depends(require_admin_key)):
|
||||||
await delete_jukebox(juke_id)
|
await delete_jukebox(juke_id)
|
||||||
try:
|
try:
|
||||||
return [{**jukebox} for jukebox in await get_jukeboxs(wallet.wallet.user)]
|
return [{**jukebox} for jukebox in await get_jukeboxs(wallet.wallet.user)]
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from fastapi import APIRouter, FastAPI
|
from fastapi import APIRouter
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from starlette.routing import Mount
|
|
||||||
|
|
||||||
from lnbits.db import Database
|
from lnbits.db import Database
|
||||||
from lnbits.helpers import template_renderer
|
from lnbits.helpers import template_renderer
|
||||||
|
@ -29,10 +28,10 @@ def lnurlp_renderer():
|
||||||
return template_renderer(["lnbits/extensions/lnurlp/templates"])
|
return template_renderer(["lnbits/extensions/lnurlp/templates"])
|
||||||
|
|
||||||
|
|
||||||
from .views_api import * # noqa
|
|
||||||
from .views import * # noqa
|
|
||||||
from .tasks import wait_for_paid_invoices
|
|
||||||
from .lnurl import * # noqa
|
from .lnurl import * # noqa
|
||||||
|
from .tasks import wait_for_paid_invoices
|
||||||
|
from .views import * # noqa
|
||||||
|
from .views_api import * # noqa
|
||||||
|
|
||||||
|
|
||||||
def lnurlp_start():
|
def lnurlp_start():
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
import math
|
import math
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from fastapi import FastAPI, Request
|
|
||||||
from starlette.exceptions import HTTPException
|
from fastapi import Request
|
||||||
from lnurl import (
|
from lnurl import ( # type: ignore
|
||||||
LnurlPayResponse,
|
|
||||||
LnurlPayActionResponse,
|
|
||||||
LnurlErrorResponse,
|
LnurlErrorResponse,
|
||||||
) # type: ignore
|
LnurlPayActionResponse,
|
||||||
|
LnurlPayResponse,
|
||||||
|
)
|
||||||
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
from lnbits.core.services import create_invoice
|
from lnbits.core.services import create_invoice
|
||||||
from lnbits.utils.exchange_rates import get_fiat_rate_satoshis
|
from lnbits.utils.exchange_rates import get_fiat_rate_satoshis
|
||||||
|
|
|
@ -1,23 +1,21 @@
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
|
from fastapi import Request
|
||||||
|
from fastapi.params import Depends
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from starlette.exceptions import HTTPException
|
||||||
|
from starlette.responses import HTMLResponse
|
||||||
|
|
||||||
|
from lnbits.core.models import User
|
||||||
from lnbits.decorators import check_user_exists
|
from lnbits.decorators import check_user_exists
|
||||||
|
|
||||||
from . import lnurlp_ext, lnurlp_renderer
|
from . import lnurlp_ext, lnurlp_renderer
|
||||||
from .crud import get_pay_link
|
from .crud import get_pay_link
|
||||||
from fastapi import FastAPI, Request
|
|
||||||
from fastapi.params import Depends
|
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
|
|
||||||
from starlette.exceptions import HTTPException
|
|
||||||
from starlette.responses import HTMLResponse
|
|
||||||
from lnbits.core.models import User
|
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="templates")
|
templates = Jinja2Templates(directory="templates")
|
||||||
|
|
||||||
|
|
||||||
@lnurlp_ext.get("/", response_class=HTMLResponse)
|
@lnurlp_ext.get("/", response_class=HTMLResponse)
|
||||||
# @validate_uuids(["usr"], required=True)
|
|
||||||
# @check_user_exists()
|
|
||||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
async def index(request: Request, user: User = Depends(check_user_exists)):
|
||||||
return lnurlp_renderer().TemplateResponse(
|
return lnurlp_renderer().TemplateResponse(
|
||||||
"lnurlp/index.html", {"request": request, "user": user.dict()}
|
"lnurlp/index.html", {"request": request, "user": user.dict()}
|
||||||
|
@ -31,7 +29,6 @@ async def display(request: Request, link_id):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist."
|
status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist."
|
||||||
)
|
)
|
||||||
# abort(HTTPStatus.NOT_FOUND, "Pay link does not exist.")
|
|
||||||
ctx = {"request": request, "lnurl": link.lnurl(req=request)}
|
ctx = {"request": request, "lnurl": link.lnurl(req=request)}
|
||||||
return lnurlp_renderer().TemplateResponse("lnurlp/display.html", ctx)
|
return lnurlp_renderer().TemplateResponse("lnurlp/display.html", ctx)
|
||||||
|
|
||||||
|
@ -43,6 +40,5 @@ async def print_qr(request: Request, link_id):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist."
|
status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist."
|
||||||
)
|
)
|
||||||
# abort(HTTPStatus.NOT_FOUND, "Pay link does not exist.")
|
|
||||||
ctx = {"request": request, "lnurl": link.lnurl(req=request)}
|
ctx = {"request": request, "lnurl": link.lnurl(req=request)}
|
||||||
return lnurlp_renderer().TemplateResponse("lnurlp/print_qr.html", ctx)
|
return lnurlp_renderer().TemplateResponse("lnurlp/print_qr.html", ctx)
|
||||||
|
|
|
@ -1,27 +1,24 @@
|
||||||
from typing import Optional
|
|
||||||
from fastapi.params import Depends
|
|
||||||
from fastapi.param_functions import Query
|
|
||||||
from pydantic.main import BaseModel
|
|
||||||
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
|
from fastapi import Request
|
||||||
|
from fastapi.param_functions import Query
|
||||||
|
from fastapi.params import Depends
|
||||||
from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl # type: ignore
|
from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl # type: ignore
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
from fastapi import Request
|
|
||||||
from starlette.responses import HTMLResponse, JSONResponse # type: ignore
|
|
||||||
|
|
||||||
from lnbits.core.crud import get_user
|
from lnbits.core.crud import get_user
|
||||||
from lnbits.decorators import WalletTypeInfo, get_key_type
|
from lnbits.decorators import WalletTypeInfo, get_key_type
|
||||||
from lnbits.utils.exchange_rates import currencies, get_fiat_rate_satoshis
|
from lnbits.utils.exchange_rates import currencies, get_fiat_rate_satoshis
|
||||||
from .models import CreatePayLinkData
|
|
||||||
|
|
||||||
from . import lnurlp_ext
|
from . import lnurlp_ext
|
||||||
from .crud import (
|
from .crud import (
|
||||||
create_pay_link,
|
create_pay_link,
|
||||||
|
delete_pay_link,
|
||||||
get_pay_link,
|
get_pay_link,
|
||||||
get_pay_links,
|
get_pay_links,
|
||||||
update_pay_link,
|
update_pay_link,
|
||||||
delete_pay_link,
|
|
||||||
)
|
)
|
||||||
|
from .models import CreatePayLinkData
|
||||||
|
|
||||||
|
|
||||||
@lnurlp_ext.get("/api/v1/currencies")
|
@lnurlp_ext.get("/api/v1/currencies")
|
||||||
|
|
|
@ -1,26 +1,23 @@
|
||||||
import hashlib
|
|
||||||
from fastapi import FastAPI, Request
|
|
||||||
from fastapi.params import Depends
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
from starlette.exceptions import HTTPException
|
|
||||||
from starlette.responses import HTMLResponse
|
|
||||||
from fastapi.params import Depends
|
|
||||||
from fastapi.param_functions import Query
|
|
||||||
from lnbits.decorators import check_user_exists, WalletTypeInfo, get_key_type
|
|
||||||
from lnbits.core.crud import get_user
|
|
||||||
from lnbits.core.models import User, Payment
|
|
||||||
from . import lnurlpos_ext
|
|
||||||
|
|
||||||
|
from fastapi import Request
|
||||||
|
from fastapi.param_functions import Query
|
||||||
|
from fastapi.params import Depends
|
||||||
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
|
from lnbits.core.crud import get_user
|
||||||
|
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
|
||||||
from lnbits.extensions.lnurlpos import lnurlpos_ext
|
from lnbits.extensions.lnurlpos import lnurlpos_ext
|
||||||
|
from lnbits.utils.exchange_rates import currencies
|
||||||
|
|
||||||
|
from . import lnurlpos_ext
|
||||||
from .crud import (
|
from .crud import (
|
||||||
create_lnurlpos,
|
create_lnurlpos,
|
||||||
update_lnurlpos,
|
delete_lnurlpos,
|
||||||
get_lnurlpos,
|
get_lnurlpos,
|
||||||
get_lnurlposs,
|
get_lnurlposs,
|
||||||
delete_lnurlpos,
|
update_lnurlpos,
|
||||||
)
|
)
|
||||||
from lnbits.utils.exchange_rates import currencies
|
|
||||||
from .models import createLnurlpos
|
from .models import createLnurlpos
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,7 +34,7 @@ async def api_list_currencies_available():
|
||||||
async def api_lnurlpos_create_or_update(
|
async def api_lnurlpos_create_or_update(
|
||||||
request: Request,
|
request: Request,
|
||||||
data: createLnurlpos,
|
data: createLnurlpos,
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
lnurlpos_id: str = Query(None),
|
lnurlpos_id: str = Query(None),
|
||||||
):
|
):
|
||||||
if not lnurlpos_id:
|
if not lnurlpos_id:
|
||||||
|
@ -79,7 +76,7 @@ async def api_lnurlpos_retrieve(
|
||||||
@lnurlpos_ext.delete("/api/v1/lnurlpos/{lnurlpos_id}")
|
@lnurlpos_ext.delete("/api/v1/lnurlpos/{lnurlpos_id}")
|
||||||
async def api_lnurlpos_delete(
|
async def api_lnurlpos_delete(
|
||||||
request: Request,
|
request: Request,
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
lnurlpos_id: str = Query(None),
|
lnurlpos_id: str = Query(None),
|
||||||
):
|
):
|
||||||
lnurlpos = await get_lnurlpos(lnurlpos_id)
|
lnurlpos = await get_lnurlpos(lnurlpos_id)
|
||||||
|
|
|
@ -1,27 +1,26 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from typing import List, Optional
|
|
||||||
from fastapi.params import Depends
|
|
||||||
from pydantic.main import BaseModel
|
|
||||||
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from fastapi.params import Depends
|
||||||
from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl
|
from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl
|
||||||
|
from pydantic.main import BaseModel
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
from starlette.responses import HTMLResponse, JSONResponse # type: ignore
|
from starlette.responses import HTMLResponse # type: ignore
|
||||||
|
|
||||||
from lnbits.decorators import WalletTypeInfo, get_key_type
|
from lnbits.decorators import WalletTypeInfo, get_key_type
|
||||||
from lnbits.utils.exchange_rates import currencies
|
|
||||||
from lnbits.requestvars import g
|
from lnbits.requestvars import g
|
||||||
|
from lnbits.utils.exchange_rates import currencies
|
||||||
|
|
||||||
from . import offlineshop_ext
|
from . import offlineshop_ext
|
||||||
from .crud import (
|
from .crud import (
|
||||||
|
add_item,
|
||||||
|
delete_item_from_shop,
|
||||||
|
get_items,
|
||||||
get_or_create_shop_by_wallet,
|
get_or_create_shop_by_wallet,
|
||||||
set_method,
|
set_method,
|
||||||
add_item,
|
|
||||||
update_item,
|
update_item,
|
||||||
get_items,
|
|
||||||
delete_item_from_shop,
|
|
||||||
)
|
)
|
||||||
from .models import ShopCounter
|
from .models import ShopCounter
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl # type: ignore
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
from lnbits.core.crud import get_user
|
from lnbits.core.crud import get_user
|
||||||
from lnbits.decorators import WalletTypeInfo, get_key_type
|
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
|
||||||
|
|
||||||
from . import satsdice_ext
|
from . import satsdice_ext
|
||||||
from .crud import (
|
from .crud import (
|
||||||
|
@ -67,7 +67,7 @@ async def api_link_retrieve(
|
||||||
status_code=HTTPStatus.FORBIDDEN, detail="Not your pay link."
|
status_code=HTTPStatus.FORBIDDEN, detail="Not your pay link."
|
||||||
)
|
)
|
||||||
|
|
||||||
return {**link._asdict(), **{"lnurl": link.lnurl}}
|
return {**link.dict(), **{"lnurl": link.lnurl}}
|
||||||
|
|
||||||
|
|
||||||
@satsdice_ext.post("/api/v1/links", status_code=HTTPStatus.CREATED)
|
@satsdice_ext.post("/api/v1/links", status_code=HTTPStatus.CREATED)
|
||||||
|
@ -112,7 +112,7 @@ async def api_link_delete(
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist."
|
status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist."
|
||||||
)
|
)
|
||||||
|
|
||||||
if link.wallet != g.wallet.id:
|
if link.wallet != wallet.wallet.id:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.FORBIDDEN, detail="Not your pay link."
|
status_code=HTTPStatus.FORBIDDEN, detail="Not your pay link."
|
||||||
)
|
)
|
||||||
|
@ -125,117 +125,6 @@ async def api_link_delete(
|
||||||
##########LNURL withdraw
|
##########LNURL withdraw
|
||||||
|
|
||||||
|
|
||||||
@satsdice_ext.get("/api/v1/withdraws")
|
|
||||||
async def api_withdraws(
|
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type), all_wallets: str = Query(None)
|
|
||||||
):
|
|
||||||
wallet_ids = [wallet.wallet.id]
|
|
||||||
|
|
||||||
if all_wallets:
|
|
||||||
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
|
|
||||||
try:
|
|
||||||
return (
|
|
||||||
jsonify(
|
|
||||||
[
|
|
||||||
{**withdraw._asdict(), **{"lnurl": withdraw.lnurl}}
|
|
||||||
for withdraw in await get_satsdice_withdraws(wallet_ids)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
HTTPStatus.OK,
|
|
||||||
)
|
|
||||||
except LnurlInvalidUrl:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.UPGRADE_REQUIRED,
|
|
||||||
detail="LNURLs need to be delivered over a publically accessible `https` domain or Tor.",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@satsdice_ext.get("/api/v1/withdraws/{withdraw_id}")
|
|
||||||
async def api_withdraw_retrieve(
|
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type), withdraw_id: str = Query(None)
|
|
||||||
):
|
|
||||||
withdraw = await get_satsdice_withdraw(withdraw_id, 0)
|
|
||||||
|
|
||||||
if not withdraw:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="satsdice withdraw does not exist."
|
|
||||||
)
|
|
||||||
|
|
||||||
if withdraw.wallet != wallet.wallet.id:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.FORBIDDEN, detail="Not your satsdice withdraw."
|
|
||||||
)
|
|
||||||
|
|
||||||
return {**withdraw._asdict(), **{"lnurl": withdraw.lnurl}}, HTTPStatus.OK
|
|
||||||
|
|
||||||
|
|
||||||
@satsdice_ext.post("/api/v1/withdraws", status_code=HTTPStatus.CREATED)
|
|
||||||
@satsdice_ext.put("/api/v1/withdraws/{withdraw_id}", status_code=HTTPStatus.OK)
|
|
||||||
async def api_withdraw_create_or_update(
|
|
||||||
data: CreateSatsDiceWithdraws,
|
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
|
||||||
withdraw_id: str = Query(None),
|
|
||||||
):
|
|
||||||
if data.max_satsdiceable < data.min_satsdiceable:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.BAD_REQUEST,
|
|
||||||
detail="`max_satsdiceable` needs to be at least `min_satsdiceable`.",
|
|
||||||
)
|
|
||||||
|
|
||||||
usescsv = ""
|
|
||||||
for i in range(data.uses):
|
|
||||||
if data.is_unique:
|
|
||||||
usescsv += "," + str(i + 1)
|
|
||||||
else:
|
|
||||||
usescsv += "," + str(1)
|
|
||||||
usescsv = usescsv[1:]
|
|
||||||
|
|
||||||
if withdraw_id:
|
|
||||||
withdraw = await get_satsdice_withdraw(withdraw_id, 0)
|
|
||||||
if not withdraw:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND,
|
|
||||||
detail="satsdice withdraw does not exist.",
|
|
||||||
)
|
|
||||||
if withdraw.wallet != wallet.wallet.id:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.FORBIDDEN, detail="Not your satsdice withdraw."
|
|
||||||
)
|
|
||||||
|
|
||||||
withdraw = await update_satsdice_withdraw(
|
|
||||||
withdraw_id, **data, usescsv=usescsv, used=0
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
withdraw = await create_satsdice_withdraw(
|
|
||||||
wallet_id=wallet.wallet.id, **data, usescsv=usescsv
|
|
||||||
)
|
|
||||||
|
|
||||||
return {**withdraw._asdict(), **{"lnurl": withdraw.lnurl}}
|
|
||||||
|
|
||||||
|
|
||||||
@satsdice_ext.delete("/api/v1/withdraws/{withdraw_id}")
|
|
||||||
async def api_withdraw_delete(
|
|
||||||
data: CreateSatsDiceWithdraws,
|
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
|
||||||
withdraw_id: str = Query(None),
|
|
||||||
):
|
|
||||||
withdraw = await get_satsdice_withdraw(withdraw_id)
|
|
||||||
|
|
||||||
if not withdraw:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="satsdice withdraw does not exist."
|
|
||||||
)
|
|
||||||
|
|
||||||
if withdraw.wallet != wallet.wallet.id:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.FORBIDDEN, detail="Not your satsdice withdraw."
|
|
||||||
)
|
|
||||||
|
|
||||||
await delete_satsdice_withdraw(withdraw_id)
|
|
||||||
|
|
||||||
return "", HTTPStatus.NO_CONTENT
|
|
||||||
|
|
||||||
|
|
||||||
@satsdice_ext.get("/api/v1/withdraws/{the_hash}/{lnurl_id}")
|
@satsdice_ext.get("/api/v1/withdraws/{the_hash}/{lnurl_id}")
|
||||||
async def api_withdraw_hash_retrieve(
|
async def api_withdraw_hash_retrieve(
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
wallet: WalletTypeInfo = Depends(get_key_type),
|
||||||
|
|
|
@ -1,29 +1,22 @@
|
||||||
import hashlib
|
|
||||||
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
import httpx
|
|
||||||
|
|
||||||
|
import httpx
|
||||||
from fastapi import Query
|
from fastapi import Query
|
||||||
from fastapi.params import Depends
|
from fastapi.params import Depends
|
||||||
|
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
from starlette.requests import Request
|
|
||||||
from starlette.responses import HTMLResponse, JSONResponse # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
from lnbits.core.crud import get_user
|
|
||||||
from lnbits.decorators import WalletTypeInfo, get_key_type
|
|
||||||
|
|
||||||
|
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
|
||||||
from lnbits.extensions.satspay import satspay_ext
|
from lnbits.extensions.satspay import satspay_ext
|
||||||
from .models import CreateCharge
|
|
||||||
from .crud import (
|
from .crud import (
|
||||||
|
check_address_balance,
|
||||||
create_charge,
|
create_charge,
|
||||||
update_charge,
|
delete_charge,
|
||||||
get_charge,
|
get_charge,
|
||||||
get_charges,
|
get_charges,
|
||||||
delete_charge,
|
update_charge,
|
||||||
check_address_balance,
|
|
||||||
)
|
)
|
||||||
|
from .models import CreateCharge
|
||||||
|
|
||||||
#############################CHARGES##########################
|
#############################CHARGES##########################
|
||||||
|
|
||||||
|
@ -31,7 +24,7 @@ from .crud import (
|
||||||
@satspay_ext.post("/api/v1/charge")
|
@satspay_ext.post("/api/v1/charge")
|
||||||
@satspay_ext.put("/api/v1/charge/{charge_id}")
|
@satspay_ext.put("/api/v1/charge/{charge_id}")
|
||||||
async def api_charge_create_or_update(
|
async def api_charge_create_or_update(
|
||||||
data: CreateCharge, wallet: WalletTypeInfo = Depends(get_key_type), charge_id=None
|
data: CreateCharge, wallet: WalletTypeInfo = Depends(require_admin_key), charge_id=None
|
||||||
):
|
):
|
||||||
if not charge_id:
|
if not charge_id:
|
||||||
charge = await create_charge(user=wallet.wallet.user, data=data)
|
charge = await create_charge(user=wallet.wallet.user, data=data)
|
||||||
|
|
15
lnbits/extensions/tipjar/README.md
Normal file
15
lnbits/extensions/tipjar/README.md
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<h1>Tip Jars</h1>
|
||||||
|
<h2>Accept tips in Bitcoin, with small messages attached!</h2>
|
||||||
|
The TipJar extension allows you to integrate Bitcoin Lightning (and on-chain) tips into your website or social media!
|
||||||
|
|
||||||
|
![image](https://user-images.githubusercontent.com/28876473/134997129-c2f3f13c-a65d-42ed-a9c4-8a1da569d74f.png)
|
||||||
|
|
||||||
|
<h2>How to set it up</h2>
|
||||||
|
|
||||||
|
1. Simply create a new Tip Jar with the desired details (onchain optional):
|
||||||
|
![image](https://user-images.githubusercontent.com/28876473/134996842-ec2f2783-2eef-4671-8eaf-023713865512.png)
|
||||||
|
1. Share the URL you get from this little button:
|
||||||
|
![image](https://user-images.githubusercontent.com/28876473/134996973-f8ed4632-ea2f-4b62-83f1-1e4c6b6c91fa.png)
|
||||||
|
|
||||||
|
|
||||||
|
<h3>And that's it already! Let the sats flow!</h3>
|
16
lnbits/extensions/tipjar/__init__.py
Normal file
16
lnbits/extensions/tipjar/__init__.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from lnbits.db import Database
|
||||||
|
from lnbits.helpers import template_renderer
|
||||||
|
|
||||||
|
db = Database("ext_tipjar")
|
||||||
|
|
||||||
|
tipjar_ext: APIRouter = APIRouter(prefix="/tipjar", tags=["tipjar"])
|
||||||
|
|
||||||
|
|
||||||
|
def tipjar_renderer():
|
||||||
|
return template_renderer(["lnbits/extensions/tipjar/templates"])
|
||||||
|
|
||||||
|
|
||||||
|
from .views import * # noqa
|
||||||
|
from .views_api import * # noqa
|
6
lnbits/extensions/tipjar/config.json
Normal file
6
lnbits/extensions/tipjar/config.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "Tip Jar",
|
||||||
|
"short_description": "Accept Bitcoin donations, with messages attached!",
|
||||||
|
"icon": "favorite",
|
||||||
|
"contributors": ["Fittiboy"]
|
||||||
|
}
|
130
lnbits/extensions/tipjar/crud.py
Normal file
130
lnbits/extensions/tipjar/crud.py
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
from . import db
|
||||||
|
from .models import Tip, TipJar, createTip, createTipJar
|
||||||
|
|
||||||
|
from ..satspay.crud import delete_charge # type: ignore
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from lnbits.db import SQLITE
|
||||||
|
|
||||||
|
|
||||||
|
async def create_tip(
|
||||||
|
id: int,
|
||||||
|
wallet: str,
|
||||||
|
message: str,
|
||||||
|
name: str,
|
||||||
|
sats: int,
|
||||||
|
tipjar: str,
|
||||||
|
) -> Tip:
|
||||||
|
"""Create a new Tip"""
|
||||||
|
await db.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO tipjar.Tips (
|
||||||
|
id,
|
||||||
|
wallet,
|
||||||
|
name,
|
||||||
|
message,
|
||||||
|
sats,
|
||||||
|
tipjar
|
||||||
|
)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
""",
|
||||||
|
(id, wallet, name, message, sats, tipjar),
|
||||||
|
)
|
||||||
|
|
||||||
|
tip = await get_tip(id)
|
||||||
|
assert tip, "Newly created tip couldn't be retrieved"
|
||||||
|
return tip
|
||||||
|
|
||||||
|
|
||||||
|
async def create_tipjar(data: createTipJar) -> TipJar:
|
||||||
|
"""Create a new TipJar"""
|
||||||
|
|
||||||
|
returning = "" if db.type == SQLITE else "RETURNING ID"
|
||||||
|
method = db.execute if db.type == SQLITE else db.fetchone
|
||||||
|
|
||||||
|
result = await (method)(
|
||||||
|
f"""
|
||||||
|
INSERT INTO tipjar.TipJars (
|
||||||
|
name,
|
||||||
|
wallet,
|
||||||
|
webhook,
|
||||||
|
onchain
|
||||||
|
)
|
||||||
|
VALUES (?, ?, ?, ?)
|
||||||
|
{returning}
|
||||||
|
""",
|
||||||
|
(data.name, data.wallet, data.webhook, data.onchain),
|
||||||
|
)
|
||||||
|
if db.type == SQLITE:
|
||||||
|
tipjar_id = result._result_proxy.lastrowid
|
||||||
|
else:
|
||||||
|
tipjar_id = result[0]
|
||||||
|
|
||||||
|
tipjar = await get_tipjar(tipjar_id)
|
||||||
|
assert tipjar
|
||||||
|
return tipjar
|
||||||
|
|
||||||
|
|
||||||
|
async def get_tipjar(tipjar_id: int) -> Optional[TipJar]:
|
||||||
|
"""Return a tipjar by ID"""
|
||||||
|
row = await db.fetchone("SELECT * FROM tipjar.TipJars WHERE id = ?", (tipjar_id,))
|
||||||
|
return TipJar(**row) if row else None
|
||||||
|
|
||||||
|
|
||||||
|
async def get_tipjars(wallet_id: str) -> Optional[list]:
|
||||||
|
"""Return all TipJars belonging assigned to the wallet_id"""
|
||||||
|
rows = await db.fetchall(
|
||||||
|
"SELECT * FROM tipjar.TipJars WHERE wallet = ?", (wallet_id,)
|
||||||
|
)
|
||||||
|
return [TipJar(**row) for row in rows] if rows else None
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_tipjar(tipjar_id: int) -> None:
|
||||||
|
"""Delete a TipJar and all corresponding Tips"""
|
||||||
|
await db.execute("DELETE FROM tipjar.TipJars WHERE id = ?", (tipjar_id,))
|
||||||
|
rows = await db.fetchall("SELECT * FROM tipjar.Tips WHERE tipjar = ?", (tipjar_id,))
|
||||||
|
for row in rows:
|
||||||
|
await delete_tip(row["id"])
|
||||||
|
|
||||||
|
|
||||||
|
async def get_tip(tip_id: str) -> Optional[Tip]:
|
||||||
|
"""Return a Tip"""
|
||||||
|
row = await db.fetchone("SELECT * FROM tipjar.Tips WHERE id = ?", (tip_id,))
|
||||||
|
return Tip(**row) if row else None
|
||||||
|
|
||||||
|
|
||||||
|
async def get_tips(wallet_id: str) -> Optional[list]:
|
||||||
|
"""Return all Tips assigned to wallet_id"""
|
||||||
|
rows = await db.fetchall("SELECT * FROM tipjar.Tips WHERE wallet = ?", (wallet_id,))
|
||||||
|
return [Tip(**row) for row in rows] if rows else None
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_tip(tip_id: str) -> None:
|
||||||
|
"""Delete a Tip and its corresponding statspay charge"""
|
||||||
|
await db.execute("DELETE FROM tipjar.Tips WHERE id = ?", (tip_id,))
|
||||||
|
await delete_charge(tip_id)
|
||||||
|
|
||||||
|
|
||||||
|
async def update_tip(tip_id: str, **kwargs) -> Tip:
|
||||||
|
"""Update a Tip"""
|
||||||
|
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
|
||||||
|
await db.execute(
|
||||||
|
f"UPDATE tipjar.Tips SET {q} WHERE id = ?",
|
||||||
|
(*kwargs.values(), tip_id),
|
||||||
|
)
|
||||||
|
row = await db.fetchone("SELECT * FROM tipjar.Tips WHERE id = ?", (tip_id,))
|
||||||
|
assert row, "Newly updated tip couldn't be retrieved"
|
||||||
|
return Tip(**row)
|
||||||
|
|
||||||
|
|
||||||
|
async def update_tipjar(tipjar_id: str, **kwargs) -> TipJar:
|
||||||
|
"""Update a tipjar"""
|
||||||
|
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
|
||||||
|
await db.execute(
|
||||||
|
f"UPDATE tipjar.TipJars SET {q} WHERE id = ?",
|
||||||
|
(*kwargs.values(), tipjar_id),
|
||||||
|
)
|
||||||
|
row = await db.fetchone("SELECT * FROM tipjar.TipJars WHERE id = ?", (tipjar_id,))
|
||||||
|
assert row, "Newly updated tipjar couldn't be retrieved"
|
||||||
|
return TipJar(**row)
|
20
lnbits/extensions/tipjar/helpers.py
Normal file
20
lnbits/extensions/tipjar/helpers.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
from lnbits.core.crud import get_wallet
|
||||||
|
from .crud import get_tipjar
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
async def get_charge_details(tipjar_id):
|
||||||
|
"""Return the default details for a satspay charge"""
|
||||||
|
tipjar = await get_tipjar(tipjar_id)
|
||||||
|
wallet_id = tipjar.wallet
|
||||||
|
wallet = await get_wallet(wallet_id)
|
||||||
|
user = wallet.user
|
||||||
|
details = {
|
||||||
|
"time": 1440,
|
||||||
|
"user": user,
|
||||||
|
"lnbitswallet": wallet_id,
|
||||||
|
"onchainwallet": tipjar.onchain,
|
||||||
|
"completelink": "/tipjar/" + str(tipjar_id),
|
||||||
|
"completelinktext": "Thanks for the tip!",
|
||||||
|
}
|
||||||
|
return details
|
27
lnbits/extensions/tipjar/migrations.py
Normal file
27
lnbits/extensions/tipjar/migrations.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
async def m001_initial(db):
|
||||||
|
|
||||||
|
await db.execute(
|
||||||
|
f"""
|
||||||
|
CREATE TABLE IF NOT EXISTS tipjar.TipJars (
|
||||||
|
id {db.serial_primary_key},
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
wallet TEXT NOT NULL,
|
||||||
|
onchain TEXT,
|
||||||
|
webhook TEXT
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
await db.execute(
|
||||||
|
f"""
|
||||||
|
CREATE TABLE IF NOT EXISTS tipjar.Tips (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
wallet TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
message TEXT NOT NULL,
|
||||||
|
sats INT NOT NULL,
|
||||||
|
tipjar INT NOT NULL,
|
||||||
|
FOREIGN KEY(tipjar) REFERENCES {db.references_schema}TipJars(id)
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
)
|
64
lnbits/extensions/tipjar/models.py
Normal file
64
lnbits/extensions/tipjar/models.py
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import json
|
||||||
|
from lnurl import Lnurl, LnurlWithdrawResponse, encode as lnurl_encode # type: ignore
|
||||||
|
from urllib.parse import urlparse, urlunparse, parse_qs, urlencode, ParseResult
|
||||||
|
from lnurl.types import LnurlPayMetadata # type: ignore
|
||||||
|
from sqlite3 import Row
|
||||||
|
from typing import NamedTuple, Optional, Dict
|
||||||
|
import shortuuid # type: ignore
|
||||||
|
from fastapi.param_functions import Query
|
||||||
|
from pydantic.main import BaseModel
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional, NamedTuple
|
||||||
|
from fastapi import FastAPI, Request
|
||||||
|
|
||||||
|
|
||||||
|
class createTip(BaseModel):
|
||||||
|
id: str
|
||||||
|
wallet: str
|
||||||
|
sats: int
|
||||||
|
tipjar: int
|
||||||
|
name: str = "Anonymous"
|
||||||
|
message: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
class Tip(NamedTuple):
|
||||||
|
"""A Tip represents a single donation"""
|
||||||
|
|
||||||
|
id: str # This ID always corresponds to a satspay charge ID
|
||||||
|
wallet: str
|
||||||
|
name: str # Name of the donor
|
||||||
|
message: str # Donation message
|
||||||
|
sats: int
|
||||||
|
tipjar: int # The ID of the corresponding tip jar
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_row(cls, row: Row) -> "Tip":
|
||||||
|
return cls(**dict(row))
|
||||||
|
|
||||||
|
|
||||||
|
class createTipJar(BaseModel):
|
||||||
|
name: str
|
||||||
|
wallet: str
|
||||||
|
webhook: str = None
|
||||||
|
onchain: str = None
|
||||||
|
|
||||||
|
|
||||||
|
class createTips(BaseModel):
|
||||||
|
name: str
|
||||||
|
sats: str
|
||||||
|
tipjar: str
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
class TipJar(NamedTuple):
|
||||||
|
"""A TipJar represents a user's tip jar"""
|
||||||
|
|
||||||
|
id: int
|
||||||
|
name: str # The name of the donatee
|
||||||
|
wallet: str # Lightning wallet
|
||||||
|
onchain: Optional[str] # Watchonly wallet
|
||||||
|
webhook: Optional[str] # URL to POST tips to
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_row(cls, row: Row) -> "TipJar":
|
||||||
|
return cls(**dict(row))
|
16
lnbits/extensions/tipjar/templates/tipjar/_api_docs.html
Normal file
16
lnbits/extensions/tipjar/templates/tipjar/_api_docs.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<h4 class="text-subtitle1 q-my-none">
|
||||||
|
Tip Jar: Receive tips with messages!
|
||||||
|
</h4>
|
||||||
|
<p>
|
||||||
|
Your personal Bitcoin tip page, which supports
|
||||||
|
lightning and on-chain payments.
|
||||||
|
Notifications, including a donation message,
|
||||||
|
can be sent via webhook.
|
||||||
|
<small>
|
||||||
|
Created by, <a href="https://github.com/Fittiboy">Fitti</a></small
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
94
lnbits/extensions/tipjar/templates/tipjar/display.html
Normal file
94
lnbits/extensions/tipjar/templates/tipjar/display.html
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
{% extends "public.html" %} {% block page %}
|
||||||
|
<div class="row q-col-gutter-md justify-center">
|
||||||
|
<div class="col-12 col-md-7 col-lg-6 q-gutter-y-md">
|
||||||
|
<q-card class="q-pa-lg">
|
||||||
|
<q-card-section class="q-pa-none">
|
||||||
|
<h5 class="q-my-none">Tip {{ donatee }} some sats!</h5>
|
||||||
|
<br />
|
||||||
|
<q-form @submit="Invoice()" class="q-gutter-md">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="tipDialog.data.name"
|
||||||
|
maxlength="25"
|
||||||
|
type="name"
|
||||||
|
label="Your Name (or contact info, leave blank for anonymous tip)"
|
||||||
|
></q-input>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.number="tipDialog.data.sats"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="2100000000000000"
|
||||||
|
suffix="sats"
|
||||||
|
:rules="[val => val > 0 || 'Choose a positive number of sats!']"
|
||||||
|
label="Amount of sats"
|
||||||
|
></q-input>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="tipDialog.data.message"
|
||||||
|
maxlength="144"
|
||||||
|
type="textarea"
|
||||||
|
label="Tip Message (you can leave this blank too)"
|
||||||
|
></q-input>
|
||||||
|
<div class="row q-mt-lg">
|
||||||
|
<q-btn
|
||||||
|
unelevated
|
||||||
|
color="primary"
|
||||||
|
:disable="tipDialog.data.sats < 1 || !tipDialog.data.sats"
|
||||||
|
type="submit"
|
||||||
|
>Submit</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</q-form>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %} {% block scripts %}
|
||||||
|
<script>
|
||||||
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: '#vue',
|
||||||
|
mixins: [windowMixin],
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
paymentReq: null,
|
||||||
|
redirectUrl: null,
|
||||||
|
tipDialog: {
|
||||||
|
show: false,
|
||||||
|
data: {
|
||||||
|
name: '',
|
||||||
|
sats: '',
|
||||||
|
message: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
Invoice: function () {
|
||||||
|
var self = this
|
||||||
|
console.log('{{ tipjar }}')
|
||||||
|
axios
|
||||||
|
.post('/tipjar/api/v1/tips', {
|
||||||
|
tipjar: '{{ tipjar }}',
|
||||||
|
name: self.tipDialog.data.name,
|
||||||
|
sats: self.tipDialog.data.sats,
|
||||||
|
message: self.tipDialog.data.message
|
||||||
|
})
|
||||||
|
.then(function (response) {
|
||||||
|
console.log(response.data)
|
||||||
|
self.redirect_url = response.data.redirect_url
|
||||||
|
console.log(self.redirect_url)
|
||||||
|
window.location.href = self.redirect_url
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
447
lnbits/extensions/tipjar/templates/tipjar/index.html
Normal file
447
lnbits/extensions/tipjar/templates/tipjar/index.html
Normal file
|
@ -0,0 +1,447 @@
|
||||||
|
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
||||||
|
%} {% block page %}
|
||||||
|
<div class="row q-col-gutter-md">
|
||||||
|
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<q-btn unelevated color="primary" @click="tipjarDialog.show = true"
|
||||||
|
>New TipJar</q-btn
|
||||||
|
>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<div class="row items-center no-wrap q-mb-md">
|
||||||
|
<div class="col">
|
||||||
|
<h5 class="text-subtitle1 q-my-none">TipJars</h5>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<q-btn flat color="grey" @click="exporttipjarsCSV"
|
||||||
|
>Export to CSV</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-table
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
:data="tipjars"
|
||||||
|
row-key="id"
|
||||||
|
:columns="tipjarsTable.columns"
|
||||||
|
:pagination.sync="tipjarsTable.pagination"
|
||||||
|
>
|
||||||
|
{% raw %}
|
||||||
|
<template v-slot:header="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-th auto-width></q-th>
|
||||||
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
|
{{ col.label }}
|
||||||
|
</q-th>
|
||||||
|
<q-th auto-width></q-th>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
<template v-slot:body="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-td auto-width>
|
||||||
|
<q-btn
|
||||||
|
unelevated
|
||||||
|
dense
|
||||||
|
size="xs"
|
||||||
|
icon="send"
|
||||||
|
:color="($q.dark.isActive) ? 'grey-8' : 'grey-6'"
|
||||||
|
type="a"
|
||||||
|
:href="props.row.displayUrl"
|
||||||
|
target="_blank"
|
||||||
|
></q-btn>
|
||||||
|
</q-td>
|
||||||
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
|
{{ col.value }}
|
||||||
|
</q-td>
|
||||||
|
<q-td auto-width>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="xs"
|
||||||
|
@click="deleteTipJar(props.row.id)"
|
||||||
|
icon="cancel"
|
||||||
|
color="pink"
|
||||||
|
></q-btn>
|
||||||
|
</q-td>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
{% endraw %}
|
||||||
|
</q-table>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<div class="row items-center no-wrap q-mb-md">
|
||||||
|
<div class="col">
|
||||||
|
<h5 class="text-subtitle1 q-my-none">Tips</h5>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<q-btn flat color="grey" @click="exporttipsCSV"
|
||||||
|
>Export to CSV</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-table
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
:data="tips"
|
||||||
|
:columns="tipsTable.columns"
|
||||||
|
:pagination.sync="tipsTable.pagination"
|
||||||
|
>
|
||||||
|
{% raw %}
|
||||||
|
<template v-slot:header="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
|
{{ col.label }}
|
||||||
|
</q-th>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
<template v-slot:body="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
|
{{ col.value }}
|
||||||
|
</q-td>
|
||||||
|
<q-td auto-width>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="xs"
|
||||||
|
@click="deleteTip(props.row.id)"
|
||||||
|
icon="cancel"
|
||||||
|
color="pink"
|
||||||
|
></q-btn>
|
||||||
|
</q-td>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
{% endraw %}
|
||||||
|
</q-table>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<h6 class="text-subtitle1 q-my-none">
|
||||||
|
{{SITE_TITLE}} TipJar extension
|
||||||
|
</h6>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section class="q-pa-none">
|
||||||
|
<q-separator></q-separator>
|
||||||
|
<q-list> {% include "tipjar/_api_docs.html" %} </q-list>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-dialog v-model="tipjarDialog.show" position="top">
|
||||||
|
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||||
|
<q-form @submit="sendTipJarData" class="q-gutter-md">
|
||||||
|
<q-select
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
emit-value
|
||||||
|
v-model="tipjarDialog.data.wallet"
|
||||||
|
:options="g.user.walletOptions"
|
||||||
|
label="Wallet *"
|
||||||
|
>
|
||||||
|
</q-select>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div v-if="walletLinks.length > 0">
|
||||||
|
<q-checkbox v-model="tipjarDialog.data.chain" label="Chain" />
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<q-checkbox :value="false" label="Chain" disabled>
|
||||||
|
<q-tooltip>
|
||||||
|
Watch-Only extension MUST be activated and have a wallet
|
||||||
|
</q-tooltip>
|
||||||
|
</q-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="tipjarDialog.data.chain">
|
||||||
|
<q-select
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
emit-value
|
||||||
|
v-model="tipjarDialog.data.onchain"
|
||||||
|
:options="walletLinks"
|
||||||
|
label="Chain Wallet"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="tipjarDialog.data.name"
|
||||||
|
type="text"
|
||||||
|
label="Donatee name *"
|
||||||
|
></q-input>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="tipjarDialog.data.webhook"
|
||||||
|
type="url"
|
||||||
|
label="Webhook (URL to send tip details to once paid)"
|
||||||
|
></q-input>
|
||||||
|
<div class="row q-mt-lg">
|
||||||
|
<q-btn
|
||||||
|
v-if="tipjarDialog.data.id"
|
||||||
|
unelevated
|
||||||
|
color="primary"
|
||||||
|
type="submit"
|
||||||
|
>Update TipJar</q-btn
|
||||||
|
>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
v-else
|
||||||
|
unelevated
|
||||||
|
color="primary"
|
||||||
|
:disable="tipjarDialog.data.name == null"
|
||||||
|
type="submit"
|
||||||
|
>Create TipJar</q-btn
|
||||||
|
>
|
||||||
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
|
||||||
|
>Cancel</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</q-form>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</div>
|
||||||
|
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||||
|
<script>
|
||||||
|
var mapTipJar = function (obj) {
|
||||||
|
obj.date = Quasar.utils.date.formatDate(
|
||||||
|
new Date(obj.time * 1000),
|
||||||
|
'YYYY-MM-DD HH:mm'
|
||||||
|
)
|
||||||
|
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount)
|
||||||
|
obj.displayUrl = ['/tipjar/', obj.id].join('')
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: '#vue',
|
||||||
|
mixins: [windowMixin],
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
tipjars: [],
|
||||||
|
tips: [],
|
||||||
|
walletLinks: [],
|
||||||
|
tipjarsTable: {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
align: 'left',
|
||||||
|
label: 'ID',
|
||||||
|
field: 'id'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Donatee',
|
||||||
|
field: 'name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Wallet',
|
||||||
|
field: 'wallet'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'onchain address',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Onchain Address',
|
||||||
|
field: 'onchain'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'webhook',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Webhook',
|
||||||
|
field: 'webhook'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
pagination: {
|
||||||
|
rowsPerPage: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tipsTable: {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'tipjar',
|
||||||
|
align: 'left',
|
||||||
|
label: 'TipJar',
|
||||||
|
field: 'tipjar'
|
||||||
|
},
|
||||||
|
{name: 'id', align: 'left', label: 'Charge ID', field: 'id'},
|
||||||
|
{name: 'name', align: 'left', label: 'Donor', field: 'name'},
|
||||||
|
{
|
||||||
|
name: 'message',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Message',
|
||||||
|
field: 'message'
|
||||||
|
},
|
||||||
|
{name: 'sats', align: 'left', label: 'Sats', field: 'sats'}
|
||||||
|
],
|
||||||
|
pagination: {
|
||||||
|
rowsPerPage: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tipjarDialog: {
|
||||||
|
show: false,
|
||||||
|
chain: false,
|
||||||
|
data: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getWalletLinks: function () {
|
||||||
|
var self = this
|
||||||
|
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'GET',
|
||||||
|
'/watchonly/api/v1/wallet',
|
||||||
|
this.g.user.wallets[0].inkey
|
||||||
|
)
|
||||||
|
.then(function (response) {
|
||||||
|
for (i = 0; i < response.data.length; i++) {
|
||||||
|
self.walletLinks.push(response.data[i].id)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getTips: function () {
|
||||||
|
var self = this
|
||||||
|
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'GET',
|
||||||
|
'/tipjar/api/v1/tips',
|
||||||
|
this.g.user.wallets[0].inkey
|
||||||
|
)
|
||||||
|
.then(function (response) {
|
||||||
|
self.tips = response.data.map(function (obj) {
|
||||||
|
return mapTipJar(obj)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deleteTip: function (tipId) {
|
||||||
|
var self = this
|
||||||
|
var tips = _.findWhere(this.tips, {id: tipId})
|
||||||
|
|
||||||
|
LNbits.utils
|
||||||
|
.confirmDialog('Are you sure you want to delete this tip?')
|
||||||
|
.onOk(function () {
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'DELETE',
|
||||||
|
'/tipjar/api/v1/tips/' + tipId,
|
||||||
|
_.findWhere(self.g.user.wallets, {id: tips.wallet}).inkey
|
||||||
|
)
|
||||||
|
.then(function (response) {
|
||||||
|
self.tips = _.reject(self.tips, function (obj) {
|
||||||
|
return obj.id == ticketId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
exporttipsCSV: function () {
|
||||||
|
LNbits.utils.exportCSV(this.tipsTable.columns, this.tips)
|
||||||
|
},
|
||||||
|
|
||||||
|
getTipJars: function () {
|
||||||
|
var self = this
|
||||||
|
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'GET',
|
||||||
|
'/tipjar/api/v1/tipjars',
|
||||||
|
this.g.user.wallets[0].inkey
|
||||||
|
)
|
||||||
|
.then(function (response) {
|
||||||
|
self.tipjars = response.data.map(function (obj) {
|
||||||
|
return mapTipJar(obj)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
sendTipJarData: function () {
|
||||||
|
var wallet = _.findWhere(this.g.user.wallets, {
|
||||||
|
id: this.tipjarDialog.data.wallet
|
||||||
|
})
|
||||||
|
var data = this.tipjarDialog.data
|
||||||
|
|
||||||
|
this.createTipJar(wallet, data)
|
||||||
|
},
|
||||||
|
|
||||||
|
createTipJar: function (wallet, data) {
|
||||||
|
var self = this
|
||||||
|
LNbits.api
|
||||||
|
.request('POST', '/tipjar/api/v1/tipjars', wallet.inkey, data)
|
||||||
|
.then(function (response) {
|
||||||
|
self.tipjars.push(mapTipJar(response.data))
|
||||||
|
self.tipjarDialog.show = false
|
||||||
|
self.tipjarDialog.data = {}
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
updatetipjarDialog: function (tipjarId) {
|
||||||
|
var link = _.findWhere(this.tipjars, {id: tipjarId})
|
||||||
|
console.log(link.id)
|
||||||
|
this.tipjarDialog.data.id = link.id
|
||||||
|
this.tipjarDialog.data.wallet = link.wallet
|
||||||
|
this.tipjarDialog.data.name = link.name
|
||||||
|
this.tipjarDialog.data.webhook = link.webhook
|
||||||
|
this.tipjarDialog.show = true
|
||||||
|
},
|
||||||
|
deleteTipJar: function (tipjarsId) {
|
||||||
|
var self = this
|
||||||
|
var tipjars = _.findWhere(this.tipjars, {id: tipjarsId})
|
||||||
|
|
||||||
|
LNbits.utils
|
||||||
|
.confirmDialog('Are you sure you want to delete this tipjar link?')
|
||||||
|
.onOk(function () {
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'DELETE',
|
||||||
|
'/tipjar/api/v1/tipjars/' + tipjarsId,
|
||||||
|
_.findWhere(self.g.user.wallets, {id: tipjars.wallet}).inkey
|
||||||
|
)
|
||||||
|
.then(function (response) {
|
||||||
|
self.tipjars = _.reject(self.tipjars, function (obj) {
|
||||||
|
return obj.id == tipjarsId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
exporttipjarsCSV: function () {
|
||||||
|
LNbits.utils.exportCSV(this.tipjarsTable.columns, this.tipjars)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created: function () {
|
||||||
|
if (this.g.user.wallets.length) {
|
||||||
|
this.getWalletLinks()
|
||||||
|
this.getTipJars()
|
||||||
|
this.getTips()
|
||||||
|
this.getServices()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
48
lnbits/extensions/tipjar/views.py
Normal file
48
lnbits/extensions/tipjar/views.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
from .crud import get_tipjar
|
||||||
|
|
||||||
|
from http import HTTPStatus
|
||||||
|
import httpx
|
||||||
|
from collections import defaultdict
|
||||||
|
from lnbits.decorators import check_user_exists
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
import hashlib
|
||||||
|
from lnbits.core.services import check_invoice_status
|
||||||
|
from lnbits.core.crud import update_payment_status, get_standalone_payment
|
||||||
|
from fastapi import FastAPI, Request
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from starlette.exceptions import HTTPException
|
||||||
|
from starlette.responses import HTMLResponse
|
||||||
|
from fastapi.params import Depends
|
||||||
|
from fastapi.param_functions import Query
|
||||||
|
import random
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from http import HTTPStatus
|
||||||
|
from . import tipjar_ext, tipjar_renderer
|
||||||
|
from lnbits.core.models import User, Payment
|
||||||
|
|
||||||
|
templates = Jinja2Templates(directory="templates")
|
||||||
|
|
||||||
|
|
||||||
|
@tipjar_ext.get("/")
|
||||||
|
async def index(request: Request, user: User = Depends(check_user_exists)):
|
||||||
|
return tipjar_renderer().TemplateResponse(
|
||||||
|
"tipjar/index.html", {"request": request, "user": user.dict()}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@tipjar_ext.get("/{tipjar_id}")
|
||||||
|
async def tip(request: Request, tipjar_id: int = Query(None)):
|
||||||
|
"""Return the donation form for the Tipjar corresponding to id"""
|
||||||
|
tipjar = await get_tipjar(tipjar_id)
|
||||||
|
print(tipjar_id)
|
||||||
|
if not tipjar:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.NOT_FOUND, detail="TipJar does not exist."
|
||||||
|
)
|
||||||
|
|
||||||
|
return tipjar_renderer().TemplateResponse(
|
||||||
|
"tipjar/display.html",
|
||||||
|
{"request": request, "donatee": tipjar.name, "tipjar": tipjar.id},
|
||||||
|
)
|
208
lnbits/extensions/tipjar/views_api.py
Normal file
208
lnbits/extensions/tipjar/views_api.py
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
from http import HTTPStatus
|
||||||
|
import json
|
||||||
|
from fastapi import Request
|
||||||
|
from fastapi.param_functions import Query
|
||||||
|
from fastapi.params import Depends
|
||||||
|
from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl # type: ignore
|
||||||
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
|
from lnbits.decorators import (
|
||||||
|
WalletTypeInfo,
|
||||||
|
get_key_type,
|
||||||
|
)
|
||||||
|
from lnbits.core.crud import get_user
|
||||||
|
|
||||||
|
from . import tipjar_ext
|
||||||
|
from .helpers import get_charge_details
|
||||||
|
from .crud import (
|
||||||
|
create_tipjar,
|
||||||
|
get_tipjar,
|
||||||
|
create_tip,
|
||||||
|
get_tipjars,
|
||||||
|
get_tip,
|
||||||
|
get_tips,
|
||||||
|
update_tip,
|
||||||
|
update_tipjar,
|
||||||
|
delete_tip,
|
||||||
|
delete_tipjar,
|
||||||
|
)
|
||||||
|
from ..satspay.crud import create_charge
|
||||||
|
from .models import createTipJar, createTips, createTip
|
||||||
|
|
||||||
|
|
||||||
|
@tipjar_ext.post("/api/v1/tipjars")
|
||||||
|
async def api_create_tipjar(data: createTipJar):
|
||||||
|
"""Create a tipjar, which holds data about how/where to post tips"""
|
||||||
|
try:
|
||||||
|
tipjar = await create_tipjar(data)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
|
||||||
|
|
||||||
|
return tipjar.dict()
|
||||||
|
|
||||||
|
|
||||||
|
async def user_from_wallet(wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||||
|
return wallet.wallet.user
|
||||||
|
|
||||||
|
|
||||||
|
@tipjar_ext.post("/api/v1/tips")
|
||||||
|
async def api_create_tip(data: createTips):
|
||||||
|
"""Take data from tip form and return satspay charge"""
|
||||||
|
sats = data.sats
|
||||||
|
message = data.message
|
||||||
|
if not message:
|
||||||
|
message = "No message"
|
||||||
|
tipjar_id = data.tipjar
|
||||||
|
tipjar = await get_tipjar(tipjar_id)
|
||||||
|
|
||||||
|
webhook = tipjar.webhook
|
||||||
|
charge_details = await get_charge_details(tipjar.id)
|
||||||
|
print(charge_details["time"])
|
||||||
|
name = data.name
|
||||||
|
# Ensure that description string can be split reliably
|
||||||
|
name = name.replace('"', "''")
|
||||||
|
if not name:
|
||||||
|
name = "Anonymous"
|
||||||
|
description = f'"{name}": {message}'
|
||||||
|
|
||||||
|
charge = await create_charge(
|
||||||
|
user=charge_details["user"],
|
||||||
|
data={
|
||||||
|
"amount": sats,
|
||||||
|
"webhook": webhook,
|
||||||
|
"description": description,
|
||||||
|
"onchainwallet": charge_details["onchainwallet"],
|
||||||
|
"lnbitswallet": charge_details["lnbitswallet"],
|
||||||
|
"completelink": charge_details["completelink"],
|
||||||
|
"completelinktext": charge_details["completelinktext"],
|
||||||
|
"time": charge_details["time"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await create_tip(
|
||||||
|
id=charge.id,
|
||||||
|
wallet=tipjar.wallet,
|
||||||
|
message=message,
|
||||||
|
name=name,
|
||||||
|
sats=data.sats,
|
||||||
|
tipjar=data.tipjar,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"redirect_url": f"/satspay/{charge.id}"}
|
||||||
|
|
||||||
|
|
||||||
|
@tipjar_ext.get("/api/v1/tipjars")
|
||||||
|
async def api_get_tipjars(wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||||
|
"""Return list of all tipjars assigned to wallet with given invoice key"""
|
||||||
|
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
|
||||||
|
tipjars = []
|
||||||
|
for wallet_id in wallet_ids:
|
||||||
|
new_tipjars = await get_tipjars(wallet_id)
|
||||||
|
tipjars += new_tipjars if new_tipjars else []
|
||||||
|
return [tipjar._asdict() for tipjar in tipjars] if tipjars else []
|
||||||
|
|
||||||
|
|
||||||
|
@tipjar_ext.get("/api/v1/tips")
|
||||||
|
async def api_get_tips(wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||||
|
"""Return list of all tips assigned to wallet with given invoice key"""
|
||||||
|
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
|
||||||
|
tips = []
|
||||||
|
for wallet_id in wallet_ids:
|
||||||
|
new_tips = await get_tips(wallet_id)
|
||||||
|
tips += new_tips if new_tips else []
|
||||||
|
return [tip._asdict() for tip in tips] if tips else []
|
||||||
|
|
||||||
|
|
||||||
|
@tipjar_ext.put("/api/v1/tips/{tip_id}")
|
||||||
|
async def api_update_tip(
|
||||||
|
wallet: WalletTypeInfo = Depends(get_key_type), tip_id: str = Query(None)
|
||||||
|
):
|
||||||
|
"""Update a tip with the data given in the request"""
|
||||||
|
if tip_id:
|
||||||
|
tip = await get_tip(tip_id)
|
||||||
|
|
||||||
|
if not tip:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.NOT_FOUND, detail="Tip does not exist."
|
||||||
|
)
|
||||||
|
|
||||||
|
if tip.wallet != wallet.wallet.id:
|
||||||
|
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.FORBIDDEN, detail="Not your tip."
|
||||||
|
)
|
||||||
|
|
||||||
|
tip = await update_tip(tip_id, **g.data)
|
||||||
|
else:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST, detail="No tip ID specified"
|
||||||
|
)
|
||||||
|
return tip.dict()
|
||||||
|
|
||||||
|
|
||||||
|
@tipjar_ext.put("/api/v1/tipjars/{tipjar_id}")
|
||||||
|
async def api_update_tipjar(
|
||||||
|
wallet: WalletTypeInfo = Depends(get_key_type), tipjar_id: str = Query(None)
|
||||||
|
):
|
||||||
|
"""Update a tipjar with the data given in the request"""
|
||||||
|
if tipjar_id:
|
||||||
|
tipjar = await get_tipjar(tipjar_id)
|
||||||
|
|
||||||
|
if not tipjar:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.NOT_FOUND, detail="TipJar does not exist."
|
||||||
|
)
|
||||||
|
|
||||||
|
if tipjar.wallet != wallet.wallet.id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.FORBIDDEN, detail="Not your tipjar."
|
||||||
|
)
|
||||||
|
|
||||||
|
tipjar = await update_tipjar(tipjar_id, **data)
|
||||||
|
else:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST, detail="No tipjar ID specified"
|
||||||
|
)
|
||||||
|
return tipjar.dict()
|
||||||
|
|
||||||
|
|
||||||
|
@tipjar_ext.delete("/api/v1/tips/{tip_id}")
|
||||||
|
async def api_delete_tip(
|
||||||
|
wallet: WalletTypeInfo = Depends(get_key_type), tip_id: str = Query(None)
|
||||||
|
):
|
||||||
|
"""Delete the tip with the given tip_id"""
|
||||||
|
tip = await get_tip(tip_id)
|
||||||
|
if not tip:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.NOT_FOUND, detail="No tip with this ID!"
|
||||||
|
)
|
||||||
|
if tip.wallet != g.wallet.id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.FORBIDDEN,
|
||||||
|
detail="Not authorized to delete this tip!",
|
||||||
|
)
|
||||||
|
await delete_tip(tip_id)
|
||||||
|
|
||||||
|
return "", HTTPStatus.NO_CONTENT
|
||||||
|
|
||||||
|
|
||||||
|
@tipjar_ext.delete("/api/v1/tipjars/{tipjar_id}")
|
||||||
|
async def api_delete_tipjar(
|
||||||
|
wallet: WalletTypeInfo = Depends(get_key_type), tipjar_id: str = Query(None)
|
||||||
|
):
|
||||||
|
"""Delete the tipjar with the given tipjar_id"""
|
||||||
|
tipjar = await get_tipjar(tipjar_id)
|
||||||
|
if not tipjar:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.NOT_FOUND,
|
||||||
|
detail="No tipjar with this ID!",
|
||||||
|
)
|
||||||
|
if tipjar.wallet != g.wallet.id:
|
||||||
|
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.FORBIDDEN,
|
||||||
|
detail="Not authorized to delete this tipjar!",
|
||||||
|
)
|
||||||
|
await delete_tipjar(tipjar_id)
|
||||||
|
|
||||||
|
return "", HTTPStatus.NO_CONTENT
|
|
@ -6,7 +6,7 @@ from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
from lnbits.core.crud import get_user, get_wallet
|
from lnbits.core.crud import get_user, get_wallet
|
||||||
from lnbits.core.services import check_invoice_status, create_invoice
|
from lnbits.core.services import check_invoice_status, create_invoice
|
||||||
from lnbits.decorators import WalletTypeInfo, get_key_type
|
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
|
||||||
|
|
||||||
from . import tpos_ext
|
from . import tpos_ext
|
||||||
from .crud import create_tpos, delete_tpos, get_tpos, get_tposs
|
from .crud import create_tpos, delete_tpos, get_tpos, get_tposs
|
||||||
|
@ -33,7 +33,7 @@ async def api_tpos_create(
|
||||||
|
|
||||||
|
|
||||||
@tpos_ext.delete("/api/v1/tposs/{tpos_id}")
|
@tpos_ext.delete("/api/v1/tposs/{tpos_id}")
|
||||||
async def api_tpos_delete(tpos_id: str, wallet: WalletTypeInfo = Depends(get_key_type)):
|
async def api_tpos_delete(tpos_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)):
|
||||||
tpos = await get_tpos(tpos_id)
|
tpos = await get_tpos(tpos_id)
|
||||||
|
|
||||||
if not tpos:
|
if not tpos:
|
||||||
|
|
|
@ -4,13 +4,20 @@ from fastapi import Query
|
||||||
from fastapi.params import Depends
|
from fastapi.params import Depends
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
from lnbits.core.crud import get_user
|
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
|
||||||
from lnbits.decorators import WalletTypeInfo, get_key_type
|
|
||||||
from lnbits.extensions.watchonly import watchonly_ext
|
from lnbits.extensions.watchonly import watchonly_ext
|
||||||
|
|
||||||
from .crud import (create_mempool, create_watch_wallet, delete_watch_wallet,
|
from .crud import (
|
||||||
get_addresses, get_fresh_address, get_mempool,
|
create_mempool,
|
||||||
get_watch_wallet, get_watch_wallets, update_mempool)
|
create_watch_wallet,
|
||||||
|
delete_watch_wallet,
|
||||||
|
get_addresses,
|
||||||
|
get_fresh_address,
|
||||||
|
get_mempool,
|
||||||
|
get_watch_wallet,
|
||||||
|
get_watch_wallets,
|
||||||
|
update_mempool,
|
||||||
|
)
|
||||||
from .models import CreateWallet
|
from .models import CreateWallet
|
||||||
|
|
||||||
###################WALLETS#############################
|
###################WALLETS#############################
|
||||||
|
@ -41,7 +48,7 @@ async def api_wallet_retrieve(
|
||||||
|
|
||||||
@watchonly_ext.post("/api/v1/wallet")
|
@watchonly_ext.post("/api/v1/wallet")
|
||||||
async def api_wallet_create_or_update(
|
async def api_wallet_create_or_update(
|
||||||
data: CreateWallet, wallet_id=None, w: WalletTypeInfo = Depends(get_key_type)
|
data: CreateWallet, wallet_id=None, w: WalletTypeInfo = Depends(require_admin_key)
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
wallet = await create_watch_wallet(
|
wallet = await create_watch_wallet(
|
||||||
|
@ -57,7 +64,7 @@ async def api_wallet_create_or_update(
|
||||||
|
|
||||||
|
|
||||||
@watchonly_ext.delete("/api/v1/wallet/{wallet_id}")
|
@watchonly_ext.delete("/api/v1/wallet/{wallet_id}")
|
||||||
async def api_wallet_delete(wallet_id, w: WalletTypeInfo = Depends(get_key_type)):
|
async def api_wallet_delete(wallet_id, w: WalletTypeInfo = Depends(require_admin_key)):
|
||||||
wallet = await get_watch_wallet(wallet_id)
|
wallet = await get_watch_wallet(wallet_id)
|
||||||
|
|
||||||
if not wallet:
|
if not wallet:
|
||||||
|
@ -105,14 +112,14 @@ async def api_get_addresses(wallet_id, w: WalletTypeInfo = Depends(get_key_type)
|
||||||
|
|
||||||
@watchonly_ext.put("/api/v1/mempool")
|
@watchonly_ext.put("/api/v1/mempool")
|
||||||
async def api_update_mempool(
|
async def api_update_mempool(
|
||||||
endpoint: str = Query(...), w: WalletTypeInfo = Depends(get_key_type)
|
endpoint: str = Query(...), w: WalletTypeInfo = Depends(require_admin_key)
|
||||||
):
|
):
|
||||||
mempool = await update_mempool(endpoint, user=w.wallet.user)
|
mempool = await update_mempool(endpoint, user=w.wallet.user)
|
||||||
return mempool.dict()
|
return mempool.dict()
|
||||||
|
|
||||||
|
|
||||||
@watchonly_ext.get("/api/v1/mempool")
|
@watchonly_ext.get("/api/v1/mempool")
|
||||||
async def api_get_mempool(w: WalletTypeInfo = Depends(get_key_type)):
|
async def api_get_mempool(w: WalletTypeInfo = Depends(require_admin_key)):
|
||||||
mempool = await get_mempool(w.wallet.user)
|
mempool = await get_mempool(w.wallet.user)
|
||||||
if not mempool:
|
if not mempool:
|
||||||
mempool = await create_mempool(user=w.wallet.user)
|
mempool = await create_mempool(user=w.wallet.user)
|
||||||
|
|
|
@ -7,20 +7,21 @@ from starlette.exceptions import HTTPException
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
|
|
||||||
from lnbits.core.crud import get_user
|
from lnbits.core.crud import get_user
|
||||||
from lnbits.decorators import WalletTypeInfo, get_key_type
|
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
|
||||||
|
|
||||||
from . import withdraw_ext
|
from . import withdraw_ext
|
||||||
from .crud import (create_withdraw_link,
|
from .crud import (
|
||||||
delete_withdraw_link, get_hash_check, get_withdraw_link,
|
create_withdraw_link,
|
||||||
get_withdraw_links, update_withdraw_link)
|
delete_withdraw_link,
|
||||||
|
get_hash_check,
|
||||||
|
get_withdraw_link,
|
||||||
|
get_withdraw_links,
|
||||||
|
update_withdraw_link,
|
||||||
|
)
|
||||||
from .models import CreateWithdrawData
|
from .models import CreateWithdrawData
|
||||||
|
|
||||||
# from fastapi import FastAPI, Query, Response
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@withdraw_ext.get("/api/v1/links", status_code=HTTPStatus.OK)
|
@withdraw_ext.get("/api/v1/links", status_code=HTTPStatus.OK)
|
||||||
# @api_check_wallet_key("invoice")
|
|
||||||
async def api_links(
|
async def api_links(
|
||||||
req: Request,
|
req: Request,
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
wallet: WalletTypeInfo = Depends(get_key_type),
|
||||||
|
@ -42,58 +43,37 @@ async def api_links(
|
||||||
status_code=HTTPStatus.UPGRADE_REQUIRED,
|
status_code=HTTPStatus.UPGRADE_REQUIRED,
|
||||||
detail="LNURLs need to be delivered over a publically accessible `https` domain or Tor.",
|
detail="LNURLs need to be delivered over a publically accessible `https` domain or Tor.",
|
||||||
)
|
)
|
||||||
# response.status_code = HTTPStatus.UPGRADE_REQUIRED
|
|
||||||
# return { "message": "LNURLs need to be delivered over a publically accessible `https` domain or Tor." }
|
|
||||||
|
|
||||||
|
|
||||||
@withdraw_ext.get("/api/v1/links/{link_id}", status_code=HTTPStatus.OK)
|
@withdraw_ext.get("/api/v1/links/{link_id}", status_code=HTTPStatus.OK)
|
||||||
# @api_check_wallet_key("invoice")
|
async def api_link_retrieve(link_id, request: Request, wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||||
async def api_link_retrieve(link_id, wallet: WalletTypeInfo = Depends(get_key_type)):
|
|
||||||
link = await get_withdraw_link(link_id, 0)
|
link = await get_withdraw_link(link_id, 0)
|
||||||
|
|
||||||
if not link:
|
if not link:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
detail="Withdraw link does not exist.", status_code=HTTPStatus.NOT_FOUND
|
detail="Withdraw link does not exist.", status_code=HTTPStatus.NOT_FOUND
|
||||||
)
|
)
|
||||||
# response.status_code = HTTPStatus.NOT_FOUND
|
|
||||||
# return {"message": "Withdraw link does not exist."}
|
|
||||||
|
|
||||||
if link.wallet != wallet.wallet.id:
|
if link.wallet != wallet.wallet.id:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
detail="Not your withdraw link.", status_code=HTTPStatus.FORBIDDEN
|
detail="Not your withdraw link.", status_code=HTTPStatus.FORBIDDEN
|
||||||
)
|
)
|
||||||
# response.status_code = HTTPStatus.FORBIDDEN
|
|
||||||
# return {"message": "Not your withdraw link."}
|
|
||||||
return {**link, **{"lnurl": link.lnurl(request)}}
|
return {**link, **{"lnurl": link.lnurl(request)}}
|
||||||
|
|
||||||
|
|
||||||
# class CreateData(BaseModel):
|
|
||||||
# title: str = Query(...)
|
|
||||||
# min_withdrawable: int = Query(..., ge=1)
|
|
||||||
# max_withdrawable: int = Query(..., ge=1)
|
|
||||||
# uses: int = Query(..., ge=1)
|
|
||||||
# wait_time: int = Query(..., ge=1)
|
|
||||||
# is_unique: bool
|
|
||||||
|
|
||||||
|
|
||||||
@withdraw_ext.post("/api/v1/links", status_code=HTTPStatus.CREATED)
|
@withdraw_ext.post("/api/v1/links", status_code=HTTPStatus.CREATED)
|
||||||
@withdraw_ext.put("/api/v1/links/{link_id}", status_code=HTTPStatus.OK)
|
@withdraw_ext.put("/api/v1/links/{link_id}", status_code=HTTPStatus.OK)
|
||||||
# @api_check_wallet_key("admin")
|
|
||||||
async def api_link_create_or_update(
|
async def api_link_create_or_update(
|
||||||
req: Request,
|
req: Request,
|
||||||
data: CreateWithdrawData,
|
data: CreateWithdrawData,
|
||||||
link_id: str = None,
|
link_id: str = None,
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
):
|
):
|
||||||
if data.max_withdrawable < data.min_withdrawable:
|
if data.max_withdrawable < data.min_withdrawable:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
detail="`max_withdrawable` needs to be at least `min_withdrawable`.",
|
detail="`max_withdrawable` needs to be at least `min_withdrawable`.",
|
||||||
status_code=HTTPStatus.BAD_REQUEST,
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
)
|
)
|
||||||
# response.status_code = HTTPStatus.BAD_REQUEST
|
|
||||||
# return {
|
|
||||||
# "message": "`max_withdrawable` needs to be at least `min_withdrawable`."
|
|
||||||
# }
|
|
||||||
|
|
||||||
usescsv = ""
|
usescsv = ""
|
||||||
for i in range(data.uses):
|
for i in range(data.uses):
|
||||||
|
@ -109,50 +89,37 @@ async def api_link_create_or_update(
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
detail="Withdraw link does not exist.", status_code=HTTPStatus.NOT_FOUND
|
detail="Withdraw link does not exist.", status_code=HTTPStatus.NOT_FOUND
|
||||||
)
|
)
|
||||||
# response.status_code = HTTPStatus.NOT_FOUND
|
|
||||||
# return {"message": "Withdraw link does not exist."}
|
|
||||||
if link.wallet != wallet.wallet.id:
|
if link.wallet != wallet.wallet.id:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
detail="Not your withdraw link.", status_code=HTTPStatus.FORBIDDEN
|
detail="Not your withdraw link.", status_code=HTTPStatus.FORBIDDEN
|
||||||
)
|
)
|
||||||
# response.status_code = HTTPStatus.FORBIDDEN
|
|
||||||
# return {"message": "Not your withdraw link."}
|
|
||||||
link = await update_withdraw_link(link_id, data=data, usescsv=usescsv, used=0)
|
link = await update_withdraw_link(link_id, data=data, usescsv=usescsv, used=0)
|
||||||
else:
|
else:
|
||||||
link = await create_withdraw_link(
|
link = await create_withdraw_link(
|
||||||
wallet_id=wallet.wallet.id, data=data, usescsv=usescsv
|
wallet_id=wallet.wallet.id, data=data, usescsv=usescsv
|
||||||
)
|
)
|
||||||
# if link_id:
|
|
||||||
# response.status_code = HTTPStatus.OK
|
|
||||||
return {**link.dict(), **{"lnurl": link.lnurl(req)}}
|
return {**link.dict(), **{"lnurl": link.lnurl(req)}}
|
||||||
|
|
||||||
|
|
||||||
@withdraw_ext.delete("/api/v1/links/{link_id}")
|
@withdraw_ext.delete("/api/v1/links/{link_id}")
|
||||||
# @api_check_wallet_key("admin")
|
async def api_link_delete(link_id, wallet: WalletTypeInfo = Depends(require_admin_key)):
|
||||||
async def api_link_delete(link_id, wallet: WalletTypeInfo = Depends(get_key_type)):
|
|
||||||
link = await get_withdraw_link(link_id)
|
link = await get_withdraw_link(link_id)
|
||||||
|
|
||||||
if not link:
|
if not link:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
detail="Withdraw link does not exist.", status_code=HTTPStatus.NOT_FOUND
|
detail="Withdraw link does not exist.", status_code=HTTPStatus.NOT_FOUND
|
||||||
)
|
)
|
||||||
# response.status_code = HTTPStatus.NOT_FOUND
|
|
||||||
# return {"message": "Withdraw link does not exist."}
|
|
||||||
|
|
||||||
if link.wallet != wallet.wallet.id:
|
if link.wallet != wallet.wallet.id:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
detail="Not your withdraw link.", status_code=HTTPStatus.FORBIDDEN
|
detail="Not your withdraw link.", status_code=HTTPStatus.FORBIDDEN
|
||||||
)
|
)
|
||||||
# response.status_code = HTTPStatus.FORBIDDEN
|
|
||||||
# return {"message": "Not your withdraw link."}
|
|
||||||
|
|
||||||
await delete_withdraw_link(link_id)
|
await delete_withdraw_link(link_id)
|
||||||
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
||||||
# return ""
|
|
||||||
|
|
||||||
|
|
||||||
@withdraw_ext.get("/api/v1/links/{the_hash}/{lnurl_id}", status_code=HTTPStatus.OK)
|
@withdraw_ext.get("/api/v1/links/{the_hash}/{lnurl_id}", status_code=HTTPStatus.OK)
|
||||||
# @api_check_wallet_key("invoice")
|
|
||||||
async def api_hash_retrieve(
|
async def api_hash_retrieve(
|
||||||
the_hash, lnurl_id, wallet: WalletTypeInfo = Depends(get_key_type)
|
the_hash, lnurl_id, wallet: WalletTypeInfo = Depends(get_key_type)
|
||||||
):
|
):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user