diff --git a/lnbits/extensions/copilot/crud.py b/lnbits/extensions/copilot/crud.py index 955561e4f..a7c36168e 100644 --- a/lnbits/extensions/copilot/crud.py +++ b/lnbits/extensions/copilot/crud.py @@ -13,7 +13,7 @@ async def create_copilot( copilot_id = urlsafe_short_hash() await db.execute( """ - INSERT INTO copilot.copilots ( + INSERT INTO copilot.newer_copilots ( id, user, lnurl_toggle, @@ -71,24 +71,26 @@ async def update_copilot( q = ", ".join([f"{field[0]} = ?" for field in data]) items = [f"{field[1]}" for field in data] 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( - "SELECT * FROM copilot.copilots WHERE id = ?", (copilot_id,) + "SELECT * FROM copilot.newer_copilots WHERE id = ?", (copilot_id,) ) return Copilots(**row) if row else None async def get_copilot(copilot_id: str) -> Copilots: 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 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] 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,)) diff --git a/lnbits/extensions/copilot/migrations.py b/lnbits/extensions/copilot/migrations.py index 7b23c9368..b1c16dcc8 100644 --- a/lnbits/extensions/copilot/migrations.py +++ b/lnbits/extensions/copilot/migrations.py @@ -23,7 +23,7 @@ async def m001_initial(db): lnurl_title TEXT, show_message INTEGER, show_ack INTEGER, - show_price TEXT, + show_price INTEGER, amount_made INTEGER, fullscreen_cam INTEGER, iframe_url TEXT, @@ -43,37 +43,37 @@ async def m002_fix_data_types(db): "ALTER TABLE copilot.copilots ALTER COLUMN show_price TYPE TEXT;" ) - # 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;") + +async def m003_fix_data_types(db): + await db.execute( + f""" + CREATE TABLE copilot.newer_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.newer_copilots SELECT * FROM copilot.copilots" + ) diff --git a/lnbits/extensions/copilot/views_api.py b/lnbits/extensions/copilot/views_api.py index d9342a30a..91b0572a5 100644 --- a/lnbits/extensions/copilot/views_api.py +++ b/lnbits/extensions/copilot/views_api.py @@ -5,7 +5,7 @@ from fastapi.param_functions import Query from fastapi.params import Depends 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 .crud import ( @@ -54,7 +54,7 @@ async def api_copilot_retrieve( async def api_copilot_create_or_update( data: CreateCopilotData, copilot_id: str = Query(None), - wallet: WalletTypeInfo = Depends(get_key_type), + wallet: WalletTypeInfo = Depends(require_admin_key), ): data.user = wallet.wallet.user data.wallet = wallet.wallet.id @@ -67,7 +67,7 @@ async def api_copilot_create_or_update( @copilot_ext.delete("/api/v1/copilot/{copilot_id}") 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) diff --git a/lnbits/extensions/events/__init__.py b/lnbits/extensions/events/__init__.py index da29358b6..9d59a8b6e 100644 --- a/lnbits/extensions/events/__init__.py +++ b/lnbits/extensions/events/__init__.py @@ -16,4 +16,3 @@ def events_renderer(): from .views import * # noqa from .views_api import * # noqa - diff --git a/lnbits/extensions/events/crud.py b/lnbits/extensions/events/crud.py index 4a24b7970..dc7db93ad 100644 --- a/lnbits/extensions/events/crud.py +++ b/lnbits/extensions/events/crud.py @@ -74,6 +74,9 @@ async def get_tickets(wallet_ids: Union[str, List[str]]) -> List[Tickets]: async def delete_ticket(payment_hash: str) -> None: 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 diff --git a/lnbits/extensions/events/templates/events/index.html b/lnbits/extensions/events/templates/events/index.html index c2d81960e..409ed2af3 100644 --- a/lnbits/extensions/events/templates/events/index.html +++ b/lnbits/extensions/events/templates/events/index.html @@ -380,14 +380,14 @@ methods: { getTickets: function () { var self = this - console.log('obj') LNbits.api - .request( - 'GET', - '/events/api/v1/tickets?all_wallets', - this.g.user.wallets[0].inkey + .request( + 'GET', + '/events/api/v1/tickets?all_wallets=true', + this.g.user.wallets[0].inkey ) .then(function (response) { + console.log(response) self.tickets = response.data.map(function (obj) { console.log(obj) return mapEvents(obj) diff --git a/lnbits/extensions/events/views_api.py b/lnbits/extensions/events/views_api.py index 5dae31e1d..4ff93c31e 100644 --- a/lnbits/extensions/events/views_api.py +++ b/lnbits/extensions/events/views_api.py @@ -15,6 +15,7 @@ from .crud import ( create_event, create_ticket, delete_event, + delete_event_tickets, delete_ticket, get_event, 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_tickets(event_id) raise HTTPException(status_code=HTTPStatus.NO_CONTENT) diff --git a/lnbits/extensions/jukebox/views_api.py b/lnbits/extensions/jukebox/views_api.py index e6403d007..ed92efc9e 100644 --- a/lnbits/extensions/jukebox/views_api.py +++ b/lnbits/extensions/jukebox/views_api.py @@ -7,11 +7,11 @@ from fastapi import Request from fastapi.param_functions import Query from fastapi.params import Depends 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.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 .crud import ( @@ -30,7 +30,7 @@ from .models import CreateJukeboxPayment, CreateJukeLinkData @jukebox_ext.get("/api/v1/jukebox") async def api_get_jukeboxs( req: Request, - wallet: WalletTypeInfo = Depends(get_key_type), + wallet: WalletTypeInfo = Depends(require_admin_key), all_wallets: bool = Query(False), ): wallet_user = wallet.wallet.user @@ -72,7 +72,7 @@ async def api_check_credentials_callbac( @jukebox_ext.get("/api/v1/jukebox/{juke_id}") 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) jukebox = await get_jukebox(juke_id) @@ -85,7 +85,7 @@ async def api_check_credentials_check( async def api_create_update_jukebox( data: CreateJukeLinkData, juke_id: str = Query(None), - wallet: WalletTypeInfo = Depends(get_key_type), + wallet: WalletTypeInfo = Depends(require_admin_key), ): if 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}") -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) try: return [{**jukebox} for jukebox in await get_jukeboxs(wallet.wallet.user)] diff --git a/lnbits/extensions/lnurlp/__init__.py b/lnbits/extensions/lnurlp/__init__.py index ea8e509a0..41b7a7b6c 100644 --- a/lnbits/extensions/lnurlp/__init__.py +++ b/lnbits/extensions/lnurlp/__init__.py @@ -1,8 +1,7 @@ import asyncio -from fastapi import APIRouter, FastAPI +from fastapi import APIRouter from fastapi.staticfiles import StaticFiles -from starlette.routing import Mount from lnbits.db import Database from lnbits.helpers import template_renderer @@ -29,10 +28,10 @@ def lnurlp_renderer(): 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 .tasks import wait_for_paid_invoices +from .views import * # noqa +from .views_api import * # noqa def lnurlp_start(): diff --git a/lnbits/extensions/lnurlp/lnurl.py b/lnbits/extensions/lnurlp/lnurl.py index f7a615a4b..173b4823a 100644 --- a/lnbits/extensions/lnurlp/lnurl.py +++ b/lnbits/extensions/lnurlp/lnurl.py @@ -1,13 +1,14 @@ import hashlib import math from http import HTTPStatus -from fastapi import FastAPI, Request -from starlette.exceptions import HTTPException -from lnurl import ( - LnurlPayResponse, - LnurlPayActionResponse, + +from fastapi import Request +from lnurl import ( # type: ignore LnurlErrorResponse, -) # type: ignore + LnurlPayActionResponse, + LnurlPayResponse, +) +from starlette.exceptions import HTTPException from lnbits.core.services import create_invoice from lnbits.utils.exchange_rates import get_fiat_rate_satoshis diff --git a/lnbits/extensions/lnurlp/views.py b/lnbits/extensions/lnurlp/views.py index d39a5ebfc..4e9f487ca 100644 --- a/lnbits/extensions/lnurlp/views.py +++ b/lnbits/extensions/lnurlp/views.py @@ -1,23 +1,21 @@ 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 . import lnurlp_ext, lnurlp_renderer 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") @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)): return lnurlp_renderer().TemplateResponse( "lnurlp/index.html", {"request": request, "user": user.dict()} @@ -31,7 +29,6 @@ async def display(request: Request, link_id): raise HTTPException( 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)} return lnurlp_renderer().TemplateResponse("lnurlp/display.html", ctx) @@ -43,6 +40,5 @@ async def print_qr(request: Request, link_id): raise HTTPException( 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)} return lnurlp_renderer().TemplateResponse("lnurlp/print_qr.html", ctx) diff --git a/lnbits/extensions/lnurlp/views_api.py b/lnbits/extensions/lnurlp/views_api.py index 7558006f9..94e15e3ab 100644 --- a/lnbits/extensions/lnurlp/views_api.py +++ b/lnbits/extensions/lnurlp/views_api.py @@ -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 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 fastapi 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.utils.exchange_rates import currencies, get_fiat_rate_satoshis -from .models import CreatePayLinkData from . import lnurlp_ext from .crud import ( create_pay_link, + delete_pay_link, get_pay_link, get_pay_links, update_pay_link, - delete_pay_link, ) +from .models import CreatePayLinkData @lnurlp_ext.get("/api/v1/currencies") diff --git a/lnbits/extensions/lnurlpos/views_api.py b/lnbits/extensions/lnurlpos/views_api.py index 21c8dd124..f2adcf7b3 100644 --- a/lnbits/extensions/lnurlpos/views_api.py +++ b/lnbits/extensions/lnurlpos/views_api.py @@ -1,26 +1,23 @@ -import hashlib -from fastapi import FastAPI, Request -from fastapi.params import Depends 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.utils.exchange_rates import currencies + +from . import lnurlpos_ext from .crud import ( create_lnurlpos, - update_lnurlpos, + delete_lnurlpos, get_lnurlpos, get_lnurlposs, - delete_lnurlpos, + update_lnurlpos, ) -from lnbits.utils.exchange_rates import currencies from .models import createLnurlpos @@ -37,7 +34,7 @@ async def api_list_currencies_available(): async def api_lnurlpos_create_or_update( request: Request, data: createLnurlpos, - wallet: WalletTypeInfo = Depends(get_key_type), + wallet: WalletTypeInfo = Depends(require_admin_key), lnurlpos_id: str = Query(None), ): if not lnurlpos_id: @@ -79,7 +76,7 @@ async def api_lnurlpos_retrieve( @lnurlpos_ext.delete("/api/v1/lnurlpos/{lnurlpos_id}") async def api_lnurlpos_delete( request: Request, - wallet: WalletTypeInfo = Depends(get_key_type), + wallet: WalletTypeInfo = Depends(require_admin_key), lnurlpos_id: str = Query(None), ): lnurlpos = await get_lnurlpos(lnurlpos_id) diff --git a/lnbits/extensions/ngrok/config.json b/lnbits/extensions/ngrok/config.json.example similarity index 100% rename from lnbits/extensions/ngrok/config.json rename to lnbits/extensions/ngrok/config.json.example diff --git a/lnbits/extensions/offlineshop/views_api.py b/lnbits/extensions/offlineshop/views_api.py index f3968948f..906526517 100644 --- a/lnbits/extensions/offlineshop/views_api.py +++ b/lnbits/extensions/offlineshop/views_api.py @@ -1,27 +1,26 @@ import json - -from typing import List, Optional -from fastapi.params import Depends -from pydantic.main import BaseModel - from http import HTTPStatus +from typing import Optional + +from fastapi.params import Depends from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl +from pydantic.main import BaseModel from starlette.exceptions import HTTPException 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.utils.exchange_rates import currencies from lnbits.requestvars import g +from lnbits.utils.exchange_rates import currencies from . import offlineshop_ext from .crud import ( + add_item, + delete_item_from_shop, + get_items, get_or_create_shop_by_wallet, set_method, - add_item, update_item, - get_items, - delete_item_from_shop, ) from .models import ShopCounter diff --git a/lnbits/extensions/satsdice/views_api.py b/lnbits/extensions/satsdice/views_api.py index 315d823ce..91ce62dfb 100644 --- a/lnbits/extensions/satsdice/views_api.py +++ b/lnbits/extensions/satsdice/views_api.py @@ -7,7 +7,7 @@ from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl # type: ignore from starlette.exceptions import HTTPException 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 .crud import ( @@ -67,7 +67,7 @@ async def api_link_retrieve( 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) @@ -112,7 +112,7 @@ async def api_link_delete( 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( status_code=HTTPStatus.FORBIDDEN, detail="Not your pay link." ) @@ -125,117 +125,6 @@ async def api_link_delete( ##########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}") async def api_withdraw_hash_retrieve( wallet: WalletTypeInfo = Depends(get_key_type), diff --git a/lnbits/extensions/satspay/views_api.py b/lnbits/extensions/satspay/views_api.py index 2ca16370c..733e63e6c 100644 --- a/lnbits/extensions/satspay/views_api.py +++ b/lnbits/extensions/satspay/views_api.py @@ -1,29 +1,22 @@ -import hashlib - from http import HTTPStatus -import httpx +import httpx from fastapi import Query from fastapi.params import Depends - 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 .models import CreateCharge + from .crud import ( + check_address_balance, create_charge, - update_charge, + delete_charge, get_charge, get_charges, - delete_charge, - check_address_balance, + update_charge, ) +from .models import CreateCharge #############################CHARGES########################## @@ -31,7 +24,7 @@ from .crud import ( @satspay_ext.post("/api/v1/charge") @satspay_ext.put("/api/v1/charge/{charge_id}") 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: charge = await create_charge(user=wallet.wallet.user, data=data) diff --git a/lnbits/extensions/tipjar/README.md b/lnbits/extensions/tipjar/README.md new file mode 100644 index 000000000..4965ec936 --- /dev/null +++ b/lnbits/extensions/tipjar/README.md @@ -0,0 +1,15 @@ +

Tip Jars

+

Accept tips in Bitcoin, with small messages attached!

+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) + +

How to set it up

+ +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) + + +

And that's it already! Let the sats flow!

diff --git a/lnbits/extensions/tipjar/__init__.py b/lnbits/extensions/tipjar/__init__.py new file mode 100644 index 000000000..a4b50c041 --- /dev/null +++ b/lnbits/extensions/tipjar/__init__.py @@ -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 diff --git a/lnbits/extensions/tipjar/config.json b/lnbits/extensions/tipjar/config.json new file mode 100644 index 000000000..e48eb4ea1 --- /dev/null +++ b/lnbits/extensions/tipjar/config.json @@ -0,0 +1,6 @@ +{ + "name": "Tip Jar", + "short_description": "Accept Bitcoin donations, with messages attached!", + "icon": "favorite", + "contributors": ["Fittiboy"] +} diff --git a/lnbits/extensions/tipjar/crud.py b/lnbits/extensions/tipjar/crud.py new file mode 100644 index 000000000..e9fe53f9c --- /dev/null +++ b/lnbits/extensions/tipjar/crud.py @@ -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) diff --git a/lnbits/extensions/tipjar/helpers.py b/lnbits/extensions/tipjar/helpers.py new file mode 100644 index 000000000..540614057 --- /dev/null +++ b/lnbits/extensions/tipjar/helpers.py @@ -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 diff --git a/lnbits/extensions/tipjar/migrations.py b/lnbits/extensions/tipjar/migrations.py new file mode 100644 index 000000000..6b58fbca2 --- /dev/null +++ b/lnbits/extensions/tipjar/migrations.py @@ -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) + ); + """ + ) diff --git a/lnbits/extensions/tipjar/models.py b/lnbits/extensions/tipjar/models.py new file mode 100644 index 000000000..3e68f846a --- /dev/null +++ b/lnbits/extensions/tipjar/models.py @@ -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)) diff --git a/lnbits/extensions/tipjar/templates/tipjar/_api_docs.html b/lnbits/extensions/tipjar/templates/tipjar/_api_docs.html new file mode 100644 index 000000000..42788bad6 --- /dev/null +++ b/lnbits/extensions/tipjar/templates/tipjar/_api_docs.html @@ -0,0 +1,16 @@ + + +

+ Tip Jar: Receive tips with messages! +

+

+ Your personal Bitcoin tip page, which supports + lightning and on-chain payments. + Notifications, including a donation message, + can be sent via webhook. + + Created by, Fitti +

+
+
diff --git a/lnbits/extensions/tipjar/templates/tipjar/display.html b/lnbits/extensions/tipjar/templates/tipjar/display.html new file mode 100644 index 000000000..80e5c6fe3 --- /dev/null +++ b/lnbits/extensions/tipjar/templates/tipjar/display.html @@ -0,0 +1,94 @@ +{% extends "public.html" %} {% block page %} +
+
+ + +
Tip {{ donatee }} some sats!
+
+ + + + +
+ Submit +
+
+
+
+
+
+ +{% endblock %} {% block scripts %} + +{% endblock %} diff --git a/lnbits/extensions/tipjar/templates/tipjar/index.html b/lnbits/extensions/tipjar/templates/tipjar/index.html new file mode 100644 index 000000000..7c58a74fd --- /dev/null +++ b/lnbits/extensions/tipjar/templates/tipjar/index.html @@ -0,0 +1,447 @@ +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block page %} +
+
+ + + New TipJar + + + + + +
+
+
TipJars
+
+
+ Export to CSV +
+
+ + {% raw %} + + + {% endraw %} + +
+
+ + + +
+
+
Tips
+
+
+ Export to CSV +
+
+ + {% raw %} + + + {% endraw %} + +
+
+
+
+ + +
+ {{SITE_TITLE}} TipJar extension +
+
+ + + {% include "tipjar/_api_docs.html" %} + +
+
+ + + + + + +
+
+
+ +
+
+ + + Watch-Only extension MUST be activated and have a wallet + + +
+
+
+
+ +
+ + +
+ Update TipJar + + Create TipJar + Cancel +
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }} + +{% endblock %} diff --git a/lnbits/extensions/tipjar/views.py b/lnbits/extensions/tipjar/views.py new file mode 100644 index 000000000..7038862dc --- /dev/null +++ b/lnbits/extensions/tipjar/views.py @@ -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}, + ) diff --git a/lnbits/extensions/tipjar/views_api.py b/lnbits/extensions/tipjar/views_api.py new file mode 100644 index 000000000..2f89ec996 --- /dev/null +++ b/lnbits/extensions/tipjar/views_api.py @@ -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 diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py index 8d640a8a4..d7008c2cd 100644 --- a/lnbits/extensions/tpos/views_api.py +++ b/lnbits/extensions/tpos/views_api.py @@ -6,7 +6,7 @@ from starlette.exceptions import HTTPException from lnbits.core.crud import get_user, get_wallet 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 .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}") -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) if not tpos: diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index 61978b743..97641c7ff 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -4,13 +4,20 @@ from fastapi 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 +from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key from lnbits.extensions.watchonly import watchonly_ext -from .crud import (create_mempool, create_watch_wallet, delete_watch_wallet, - get_addresses, get_fresh_address, get_mempool, - get_watch_wallet, get_watch_wallets, update_mempool) +from .crud import ( + create_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 ###################WALLETS############################# @@ -41,7 +48,7 @@ async def api_wallet_retrieve( @watchonly_ext.post("/api/v1/wallet") 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: wallet = await create_watch_wallet( @@ -57,7 +64,7 @@ async def api_wallet_create_or_update( @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) 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") 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) return mempool.dict() @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) if not mempool: mempool = await create_mempool(user=w.wallet.user) diff --git a/lnbits/extensions/withdraw/views_api.py b/lnbits/extensions/withdraw/views_api.py index 678346f95..d287b6cc6 100644 --- a/lnbits/extensions/withdraw/views_api.py +++ b/lnbits/extensions/withdraw/views_api.py @@ -7,20 +7,21 @@ from starlette.exceptions import HTTPException from starlette.requests import Request 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 .crud import (create_withdraw_link, - delete_withdraw_link, get_hash_check, get_withdraw_link, - get_withdraw_links, update_withdraw_link) +from .crud import ( + create_withdraw_link, + delete_withdraw_link, + get_hash_check, + get_withdraw_link, + get_withdraw_links, + update_withdraw_link, +) from .models import CreateWithdrawData -# from fastapi import FastAPI, Query, Response - - @withdraw_ext.get("/api/v1/links", status_code=HTTPStatus.OK) -# @api_check_wallet_key("invoice") async def api_links( req: Request, wallet: WalletTypeInfo = Depends(get_key_type), @@ -42,58 +43,37 @@ async def api_links( status_code=HTTPStatus.UPGRADE_REQUIRED, 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) -# @api_check_wallet_key("invoice") -async def api_link_retrieve(link_id, wallet: WalletTypeInfo = Depends(get_key_type)): +async def api_link_retrieve(link_id, request: Request, wallet: WalletTypeInfo = Depends(get_key_type)): link = await get_withdraw_link(link_id, 0) if not link: raise HTTPException( 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: raise HTTPException( 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)}} -# 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.put("/api/v1/links/{link_id}", status_code=HTTPStatus.OK) -# @api_check_wallet_key("admin") async def api_link_create_or_update( req: Request, data: CreateWithdrawData, link_id: str = None, - wallet: WalletTypeInfo = Depends(get_key_type), + wallet: WalletTypeInfo = Depends(require_admin_key), ): if data.max_withdrawable < data.min_withdrawable: raise HTTPException( detail="`max_withdrawable` needs to be at least `min_withdrawable`.", status_code=HTTPStatus.BAD_REQUEST, ) - # response.status_code = HTTPStatus.BAD_REQUEST - # return { - # "message": "`max_withdrawable` needs to be at least `min_withdrawable`." - # } usescsv = "" for i in range(data.uses): @@ -109,50 +89,37 @@ async def api_link_create_or_update( raise HTTPException( 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: raise HTTPException( 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) else: link = await create_withdraw_link( wallet_id=wallet.wallet.id, data=data, usescsv=usescsv ) - # if link_id: - # response.status_code = HTTPStatus.OK return {**link.dict(), **{"lnurl": link.lnurl(req)}} @withdraw_ext.delete("/api/v1/links/{link_id}") -# @api_check_wallet_key("admin") -async def api_link_delete(link_id, wallet: WalletTypeInfo = Depends(get_key_type)): +async def api_link_delete(link_id, wallet: WalletTypeInfo = Depends(require_admin_key)): link = await get_withdraw_link(link_id) if not link: raise HTTPException( 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: raise HTTPException( 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) raise HTTPException(status_code=HTTPStatus.NO_CONTENT) - # return "" @withdraw_ext.get("/api/v1/links/{the_hash}/{lnurl_id}", status_code=HTTPStatus.OK) -# @api_check_wallet_key("invoice") async def api_hash_retrieve( the_hash, lnurl_id, wallet: WalletTypeInfo = Depends(get_key_type) ):