All extensions semi-switched

This commit is contained in:
Ben Arc 2021-08-21 00:34:48 +01:00
parent 8deea85999
commit a9dc087f61
17 changed files with 282 additions and 372 deletions

View File

@ -8,7 +8,7 @@ from .exchange_rates import exchange_rate_providers_serializable, fiat_currencie
from .helpers import get_callback_url
@bleskomat_ext.route("/")
@bleskomat_ext.get("/")
@validate_uuids(["usr"], required=True)
@check_user_exists()
async def index():

View File

@ -20,7 +20,7 @@ from .exchange_rates import (
)
@bleskomat_ext.route("/api/v1/bleskomats", methods=["GET"])
@bleskomat_ext.get("/api/v1/bleskomats")
@api_check_wallet_key("admin")
async def api_bleskomats():
wallet_ids = [g.wallet.id]
@ -29,14 +29,12 @@ async def api_bleskomats():
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
return (
jsonify(
[bleskomat._asdict() for bleskomat in await get_bleskomats(wallet_ids)]
),
[bleskomat._asdict() for bleskomat in await get_bleskomats(wallet_ids)],
HTTPStatus.OK,
)
@bleskomat_ext.route("/api/v1/bleskomat/<bleskomat_id>", methods=["GET"])
@bleskomat_ext.get("/api/v1/bleskomat/<bleskomat_id>")
@api_check_wallet_key("admin")
async def api_bleskomat_retrieve(bleskomat_id):
bleskomat = await get_bleskomat(bleskomat_id)
@ -50,40 +48,28 @@ async def api_bleskomat_retrieve(bleskomat_id):
return jsonify(bleskomat._asdict()), HTTPStatus.OK
@bleskomat_ext.route("/api/v1/bleskomat", methods=["POST"])
@bleskomat_ext.route("/api/v1/bleskomat/<bleskomat_id>", methods=["PUT"])
class CreateData(BaseModel):
name: str
fiat_currency: str = fiat_currencies.keys()
exchange_rate_provider: str = exchange_rate_providers.keys()
fee: Optional[str, int, float] = Query(...)
@bleskomat_ext.post("/api/v1/bleskomat")
@bleskomat_ext.put("/api/v1/bleskomat/<bleskomat_id>")
@api_check_wallet_key("admin")
@api_validate_post_request(
schema={
"name": {"type": "string", "empty": False, "required": True},
"fiat_currency": {
"type": "string",
"allowed": fiat_currencies.keys(),
"required": True,
},
"exchange_rate_provider": {
"type": "string",
"allowed": exchange_rate_providers.keys(),
"required": True,
},
"fee": {"type": ["string", "float", "number", "integer"], "required": True},
}
)
async def api_bleskomat_create_or_update(bleskomat_id=None):
async def api_bleskomat_create_or_update(data: CreateData, bleskomat_id=None):
try:
fiat_currency = g.data["fiat_currency"]
exchange_rate_provider = g.data["exchange_rate_provider"]
fiat_currency = data.fiat_currency
exchange_rate_provider = data.exchange_rate_provider
await fetch_fiat_exchange_rate(
currency=fiat_currency, provider=exchange_rate_provider
)
except Exception as e:
print(e)
return (
jsonify(
{
"message": f'Failed to fetch BTC/{fiat_currency} currency pair from "{exchange_rate_provider}"'
}
),
},
HTTPStatus.INTERNAL_SERVER_ERROR,
)
@ -94,17 +80,17 @@ async def api_bleskomat_create_or_update(bleskomat_id=None):
jsonify({"message": "Bleskomat configuration not found."}),
HTTPStatus.NOT_FOUND,
)
bleskomat = await update_bleskomat(bleskomat_id, **g.data)
bleskomat = await update_bleskomat(bleskomat_id, **data)
else:
bleskomat = await create_bleskomat(wallet_id=g.wallet.id, **g.data)
bleskomat = await create_bleskomat(wallet_id=g.wallet.id, **data)
return (
jsonify(bleskomat._asdict()),
bleskomat._asdict(),
HTTPStatus.OK if bleskomat_id else HTTPStatus.CREATED,
)
@bleskomat_ext.route("/api/v1/bleskomat/<bleskomat_id>", methods=["DELETE"])
@bleskomat_ext.delete("/api/v1/bleskomat/<bleskomat_id>")
@api_check_wallet_key("admin")
async def api_bleskomat_delete(bleskomat_id):
bleskomat = await get_bleskomat(bleskomat_id)

View File

@ -15,13 +15,12 @@ from .crud import (
delete_pay_link,
)
@lnurlp_ext.route("/api/v1/currencies", methods=["GET"])
@lnurlp_ext.get("/api/v1/currencies")
async def api_list_currencies_available():
return jsonify(list(currencies.keys()))
@lnurlp_ext.route("/api/v1/links", methods=["GET"])
@lnurlp_ext.get("/api/v1/links")
@api_check_wallet_key("invoice")
async def api_links():
wallet_ids = [g.wallet.id]
@ -31,66 +30,59 @@ async def api_links():
try:
return (
jsonify(
[
{**link._asdict(), **{"lnurl": link.lnurl}}
for link in await get_pay_links(wallet_ids)
]
),
],
HTTPStatus.OK,
)
except LnurlInvalidUrl:
return (
jsonify(
{
"message": "LNURLs need to be delivered over a publically accessible `https` domain or Tor."
}
),
},
HTTPStatus.UPGRADE_REQUIRED,
)
@lnurlp_ext.route("/api/v1/links/<link_id>", methods=["GET"])
@lnurlp_ext.get("/api/v1/links/<link_id>")
@api_check_wallet_key("invoice")
async def api_link_retrieve(link_id):
link = await get_pay_link(link_id)
if not link:
return jsonify({"message": "Pay link does not exist."}), HTTPStatus.NOT_FOUND
return {"message": "Pay link does not exist."}, HTTPStatus.NOT_FOUND
if link.wallet != g.wallet.id:
return jsonify({"message": "Not your pay link."}), HTTPStatus.FORBIDDEN
return {"message": "Not your pay link."}, HTTPStatus.FORBIDDEN
return jsonify({**link._asdict(), **{"lnurl": link.lnurl}}), HTTPStatus.OK
return {**link._asdict(), **{"lnurl": link.lnurl}}, HTTPStatus.OK
class CreateData(BaseModel):
description: str
min: int = Query(ge=0.01)
max: int = Query(ge=0.01)
currency: Optional[str]
comment_chars: int = Query(ge=0, lt=800)
webhook_url: Optional[str]
success_text: Optional[str]
success_url: Optional[str]
@lnurlp_ext.route("/api/v1/links", methods=["POST"])
@lnurlp_ext.route("/api/v1/links/<link_id>", methods=["PUT"])
@lnurlp_ext.post("/api/v1/links")
@lnurlp_ext.put("/api/v1/links/<link_id>")
@api_check_wallet_key("invoice")
@api_validate_post_request(
schema={
"description": {"type": "string", "empty": False, "required": True},
"min": {"type": "number", "min": 0.01, "required": True},
"max": {"type": "number", "min": 0.01, "required": True},
"currency": {"type": "string", "nullable": True, "required": False},
"comment_chars": {"type": "integer", "required": True, "min": 0, "max": 800},
"webhook_url": {"type": "string", "required": False},
"success_text": {"type": "string", "required": False},
"success_url": {"type": "string", "required": False},
}
)
async def api_link_create_or_update(link_id=None):
if g.data["min"] > g.data["max"]:
return jsonify({"message": "Min is greater than max."}), HTTPStatus.BAD_REQUEST
async def api_link_create_or_update(data: CreateData, link_id=None):
if data.min > data.max:
return {"message": "Min is greater than max."}, HTTPStatus.BAD_REQUEST
if g.data.get("currency") == None and (
round(g.data["min"]) != g.data["min"] or round(g.data["max"]) != g.data["max"]
if data.currency == None and (
round(data.min) != data.min or round(data.max) != data.max
):
return jsonify({"message": "Must use full satoshis."}), HTTPStatus.BAD_REQUEST
return {"message": "Must use full satoshis."}, HTTPStatus.BAD_REQUEST
if "success_url" in g.data and g.data["success_url"][:8] != "https://":
if "success_url" in data and data.success_url[:8] != "https://":
return (
jsonify({"message": "Success URL must be secure https://..."}),
{"message": "Success URL must be secure https://..."},
HTTPStatus.BAD_REQUEST,
)
@ -99,44 +91,44 @@ async def api_link_create_or_update(link_id=None):
if not link:
return (
jsonify({"message": "Pay link does not exist."}),
{"message": "Pay link does not exist."},
HTTPStatus.NOT_FOUND,
)
if link.wallet != g.wallet.id:
return jsonify({"message": "Not your pay link."}), HTTPStatus.FORBIDDEN
return {"message": "Not your pay link."}, HTTPStatus.FORBIDDEN
link = await update_pay_link(link_id, **g.data)
link = await update_pay_link(link_id, **data)
else:
link = await create_pay_link(wallet_id=g.wallet.id, **g.data)
link = await create_pay_link(wallet_id=g.wallet.id, **data)
return (
jsonify({**link._asdict(), **{"lnurl": link.lnurl}}),
{**link._asdict(), **{"lnurl": link.lnurl}},
HTTPStatus.OK if link_id else HTTPStatus.CREATED,
)
@lnurlp_ext.route("/api/v1/links/<link_id>", methods=["DELETE"])
@lnurlp_ext.delete("/api/v1/links/<link_id>")
@api_check_wallet_key("invoice")
async def api_link_delete(link_id):
link = await get_pay_link(link_id)
if not link:
return jsonify({"message": "Pay link does not exist."}), HTTPStatus.NOT_FOUND
return {"message": "Pay link does not exist."}, HTTPStatus.NOT_FOUND
if link.wallet != g.wallet.id:
return jsonify({"message": "Not your pay link."}), HTTPStatus.FORBIDDEN
return {"message": "Not your pay link."}, HTTPStatus.FORBIDDEN
await delete_pay_link(link_id)
return "", HTTPStatus.NO_CONTENT
@lnurlp_ext.route("/api/v1/rate/<currency>", methods=["GET"])
@lnurlp_ext.get("/api/v1/rate/<currency>")
async def api_check_fiat_rate(currency):
try:
rate = await get_fiat_rate_satoshis(currency)
except AssertionError:
rate = None
return jsonify({"rate": rate}), HTTPStatus.OK
return {"rate": rate}, HTTPStatus.OK

View File

@ -23,7 +23,7 @@ port = getenv("PORT")
ngrok_tunnel = ngrok.connect(port)
@ngrok_ext.route("/")
@ngrok_ext.get("/")
@validate_uuids(["usr"], required=True)
@check_user_exists()
async def index():

View File

@ -9,14 +9,14 @@ from . import offlineshop_ext
from .crud import get_shop, get_item
@offlineshop_ext.route("/lnurl/<item_id>", methods=["GET"])
@offlineshop_ext.get("/lnurl/<item_id>")
async def lnurl_response(item_id):
item = await get_item(item_id)
if not item:
return jsonify({"status": "ERROR", "reason": "Item not found."})
return {"status": "ERROR", "reason": "Item not found."}
if not item.enabled:
return jsonify({"status": "ERROR", "reason": "Item disabled."})
return {"status": "ERROR", "reason": "Item disabled."}
price_msat = (
await fiat_amount_as_satoshis(item.price, item.unit)
@ -31,14 +31,14 @@ async def lnurl_response(item_id):
metadata=await item.lnurlpay_metadata(),
)
return jsonify(resp.dict())
return resp.dict()
@offlineshop_ext.route("/lnurl/cb/<item_id>", methods=["GET"])
@offlineshop_ext.get("/lnurl/cb/<item_id>")
async def lnurl_callback(item_id):
item = await get_item(item_id)
if not item:
return jsonify({"status": "ERROR", "reason": "Couldn't find item."})
return {"status": "ERROR", "reason": "Couldn't find item."}
if item.unit == "sat":
min = item.price * 1000
@ -51,17 +51,13 @@ async def lnurl_callback(item_id):
amount_received = int(request.args.get("amount") or 0)
if amount_received < min:
return jsonify(
LnurlErrorResponse(
return LnurlErrorResponse(
reason=f"Amount {amount_received} is smaller than minimum {min}."
).dict()
)
elif amount_received > max:
return jsonify(
LnurlErrorResponse(
return LnurlErrorResponse(
reason=f"Amount {amount_received} is greater than maximum {max}."
).dict()
)
shop = await get_shop(item.shop)
@ -76,7 +72,7 @@ async def lnurl_callback(item_id):
extra={"tag": "offlineshop", "item": item.id},
)
except Exception as exc:
return jsonify(LnurlErrorResponse(reason=exc.message).dict())
return LnurlErrorResponse(reason=exc.message).dict()
resp = LnurlPayActionResponse(
pr=payment_request,
@ -84,4 +80,4 @@ async def lnurl_callback(item_id):
routes=[],
)
return jsonify(resp.dict())
return resp.dict()

View File

@ -11,14 +11,14 @@ from . import offlineshop_ext
from .crud import get_item, get_shop
@offlineshop_ext.route("/")
@offlineshop_ext.get("/")
@validate_uuids(["usr"], required=True)
@check_user_exists()
async def index():
return await render_template("offlineshop/index.html", user=g.user)
@offlineshop_ext.route("/print")
@offlineshop_ext.get("/print")
async def print_qr_codes():
items = []
for item_id in request.args.get("items").split(","):
@ -35,7 +35,7 @@ async def print_qr_codes():
return await render_template("offlineshop/print.html", items=items)
@offlineshop_ext.route("/confirmation")
@offlineshop_ext.get("/confirmation")
async def confirmation_code():
style = "<style>* { font-size: 100px}</style>"

View File

@ -17,12 +17,12 @@ from .crud import (
from .models import ShopCounter
@offlineshop_ext.route("/api/v1/currencies", methods=["GET"])
@offlineshop_ext.get("/api/v1/currencies")
async def api_list_currencies_available():
return jsonify(list(currencies.keys()))
@offlineshop_ext.route("/api/v1/offlineshop", methods=["GET"])
@offlineshop_ext.get("/api/v1/offlineshop")
@api_check_wallet_key("invoice")
async def api_shop_from_wallet():
shop = await get_or_create_shop_by_wallet(g.wallet.id)
@ -30,66 +30,59 @@ async def api_shop_from_wallet():
try:
return (
jsonify(
{
**shop._asdict(),
**{
"otp_key": shop.otp_key,
"items": [item.values() for item in items],
},
}
),
},
HTTPStatus.OK,
)
except LnurlInvalidUrl:
return (
jsonify(
{
"message": "LNURLs need to be delivered over a publically accessible `https` domain or Tor."
}
),
},
HTTPStatus.UPGRADE_REQUIRED,
)
class CreateItemsData(BaseModel):
name: str
description: str
image: Optional[str]
price: int
unit: str
@offlineshop_ext.route("/api/v1/offlineshop/items", methods=["POST"])
@offlineshop_ext.route("/api/v1/offlineshop/items/<item_id>", methods=["PUT"])
@offlineshop_ext.post("/api/v1/offlineshop/items")
@offlineshop_ext.put("/api/v1/offlineshop/items/<item_id>")
@api_check_wallet_key("invoice")
@api_validate_post_request(
schema={
"name": {"type": "string", "empty": False, "required": True},
"description": {"type": "string", "empty": False, "required": True},
"image": {"type": "string", "required": False, "nullable": True},
"price": {"type": "number", "required": True},
"unit": {"type": "string", "required": True},
}
)
async def api_add_or_update_item(item_id=None):
async def api_add_or_update_item(data: CreateItemsData, item_id=None):
shop = await get_or_create_shop_by_wallet(g.wallet.id)
if item_id == None:
await add_item(
shop.id,
g.data["name"],
g.data["description"],
g.data.get("image"),
g.data["price"],
g.data["unit"],
data.name,
data.description,
data.image,
data.price,
data.unit,
)
return "", HTTPStatus.CREATED
else:
await update_item(
shop.id,
item_id,
g.data["name"],
g.data["description"],
g.data.get("image"),
g.data["price"],
g.data["unit"],
data.name,
data.description,
data.image,
data.price,
data.unit,
)
return "", HTTPStatus.OK
@offlineshop_ext.route("/api/v1/offlineshop/items/<item_id>", methods=["DELETE"])
@offlineshop_ext.delete("/api/v1/offlineshop/items/<item_id>")
@api_check_wallet_key("invoice")
async def api_delete_item(item_id):
shop = await get_or_create_shop_by_wallet(g.wallet.id)
@ -97,23 +90,16 @@ async def api_delete_item(item_id):
return "", HTTPStatus.NO_CONTENT
@offlineshop_ext.route("/api/v1/offlineshop/method", methods=["PUT"])
@api_check_wallet_key("invoice")
@api_validate_post_request(
schema={
"method": {"type": "string", "required": True, "nullable": False},
"wordlist": {
"type": "string",
"empty": True,
"nullable": True,
"required": False,
},
}
)
async def api_set_method():
method = g.data["method"]
class CreateMethodData(BaseModel):
method: str
wordlist: Optional[str]
wordlist = g.data["wordlist"].split("\n") if g.data["wordlist"] else None
@offlineshop_ext.put("/api/v1/offlineshop/method")
@api_check_wallet_key("invoice")
async def api_set_method(data: CreateMethodData):
method = data.method
wordlist = data.wordlist.split("\n") if data.wordlist else None
wordlist = [word.strip() for word in wordlist if word.strip()]
shop = await get_or_create_shop_by_wallet(g.wallet.id)

View File

@ -25,13 +25,12 @@ async def api_paywalls():
HTTPStatus.OK,
)
class CreateData(BaseModel):
url: Optional[str] = Query(...)
memo: Optional[str] = Query(...)
description: str = Query(None)
amount: int = Query(None)
remembers: bool = Query(None)
description: str
amount: int
remembers: bool
@paywall_ext.post("/api/v1/paywalls")
@api_check_wallet_key("invoice")

View File

@ -5,7 +5,7 @@ from lnbits.decorators import check_user_exists, validate_uuids
from . import splitpayments_ext
@splitpayments_ext.route("/")
@splitpayments_ext.get("/")
@validate_uuids(["usr"], required=True)
@check_user_exists()
async def index():

View File

@ -9,52 +9,41 @@ from .crud import get_targets, set_targets
from .models import Target
@splitpayments_ext.route("/api/v1/targets", methods=["GET"])
@splitpayments_ext.get("/api/v1/targets")
@api_check_wallet_key("admin")
async def api_targets_get():
targets = await get_targets(g.wallet.id)
return jsonify([target._asdict() for target in targets] or [])
return [target._asdict() for target in targets] or []
class SchemaData(BaseModel):
wallet: str
alias: str
percent: int
@splitpayments_ext.route("/api/v1/targets", methods=["PUT"])
@splitpayments_ext.put("/api/v1/targets")
@api_check_wallet_key("admin")
@api_validate_post_request(
schema={
"targets": {
"type": "list",
"schema": {
"type": "dict",
"schema": {
"wallet": {"type": "string"},
"alias": {"type": "string"},
"percent": {"type": "integer"},
},
},
}
}
)
async def api_targets_set():
async def api_targets_set(targets: Optional(list[SchemaData]) = None):
targets = []
for entry in g.data["targets"]:
for entry in targets:
wallet = await get_wallet(entry["wallet"])
if not wallet:
wallet = await get_wallet_for_key(entry["wallet"], "invoice")
if not wallet:
return (
jsonify({"message": f"Invalid wallet '{entry['wallet']}'."}),
{"message": f"Invalid wallet '{entry['wallet']}'."},
HTTPStatus.BAD_REQUEST,
)
if wallet.id == g.wallet.id:
return (
jsonify({"message": "Can't split to itself."}),
{"message": "Can't split to itself."},
HTTPStatus.BAD_REQUEST,
)
if entry["percent"] < 0:
return (
jsonify({"message": f"Invalid percent '{entry['percent']}'."}),
{"message": f"Invalid percent '{entry['percent']}'."},
HTTPStatus.BAD_REQUEST,
)
@ -64,7 +53,7 @@ async def api_targets_set():
percent_sum = sum([target.percent for target in targets])
if percent_sum > 100:
return jsonify({"message": "Splitting over 100%."}), HTTPStatus.BAD_REQUEST
return {"message": "Splitting over 100%."}, HTTPStatus.BAD_REQUEST
await set_targets(g.wallet.id, targets)
return "", HTTPStatus.OK

View File

@ -7,7 +7,7 @@ from . import streamalerts_ext
from .crud import get_service
@streamalerts_ext.route("/")
@streamalerts_ext.get("/")
@validate_uuids(["usr"], required=True)
@check_user_exists()
async def index():
@ -15,7 +15,7 @@ async def index():
return await render_template("streamalerts/index.html", user=g.user)
@streamalerts_ext.route("/<state>")
@streamalerts_ext.get("/<state>")
async def donation(state):
"""Return the donation form for the Service corresponding to state"""
service = await get_service(0, by_state=state)

View File

@ -24,30 +24,27 @@ from .crud import (
)
from ..satspay.crud import create_charge, get_charge
class CreateServicesData(BaseModel):
twitchuser: str
client_id: str
client_secret: str
wallet: str
servicename: str
onchain: Optional[str]
@streamalerts_ext.route("/api/v1/services", methods=["POST"])
@streamalerts_ext.post("/api/v1/services")
@api_check_wallet_key("invoice")
@api_validate_post_request(
schema={
"twitchuser": {"type": "string", "required": True},
"client_id": {"type": "string", "required": True},
"client_secret": {"type": "string", "required": True},
"wallet": {"type": "string", "required": True},
"servicename": {"type": "string", "required": True},
"onchain": {"type": "string"},
}
)
async def api_create_service():
async def api_create_service(data: CreateData):
"""Create a service, which holds data about how/where to post donations"""
try:
service = await create_service(**g.data)
service = await create_service(**data)
except Exception as e:
return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR
return {"message": str(e)}, HTTPStatus.INTERNAL_SERVER_ERROR
return jsonify(service._asdict()), HTTPStatus.CREATED
return service._asdict(), HTTPStatus.CREATED
@streamalerts_ext.route("/api/v1/getaccess/<service_id>", methods=["GET"])
@streamalerts_ext.get("/api/v1/getaccess/<service_id>")
async def api_get_access(service_id):
"""Redirect to Streamlabs' Approve/Decline page for API access for Service
with service_id
@ -67,56 +64,52 @@ async def api_get_access(service_id):
redirect_url = endpoint_url + querystring
return redirect(redirect_url)
else:
return (jsonify({"message": "Service does not exist!"}), HTTPStatus.BAD_REQUEST)
return ({"message": "Service does not exist!"}, HTTPStatus.BAD_REQUEST)
@streamalerts_ext.route("/api/v1/authenticate/<service_id>", methods=["GET"])
async def api_authenticate_service(service_id):
@streamalerts_ext.get("/api/v1/authenticate/<service_id>")
async def api_authenticate_service(Code: str, State: str, service_id):
"""Endpoint visited via redirect during third party API authentication
If successful, an API access token will be added to the service, and
the user will be redirected to index.html.
"""
code = request.args.get("code")
state = request.args.get("state")
code = Code
state = State
service = await get_service(service_id)
if service.state != state:
return (jsonify({"message": "State doesn't match!"}), HTTPStatus.BAD_Request)
return ({"message": "State doesn't match!"}, HTTPStatus.BAD_Request)
redirect_uri = request.scheme + "://" + request.headers["Host"]
redirect_uri += f"/streamalerts/api/v1/authenticate/{service_id}"
url, success = await authenticate_service(service_id, code, redirect_uri)
if success:
return redirect(url)
else:
return (
jsonify({"message": "Service already authenticated!"}),
return ({"message": "Service already authenticated!"},
HTTPStatus.BAD_REQUEST,
)
class CreateDonationsData(BaseModel):
name: str
sats: int
service: int
message: str
@streamalerts_ext.route("/api/v1/donations", methods=["POST"])
@api_validate_post_request(
schema={
"name": {"type": "string"},
"sats": {"type": "integer", "required": True},
"service": {"type": "integer", "required": True},
"message": {"type": "string"},
}
)
async def api_create_donation():
@streamalerts_ext.post("/api/v1/donations")
async def api_create_donation(data:CreateDonationsData):
"""Take data from donation form and return satspay charge"""
# Currency is hardcoded while frotnend is limited
cur_code = "USD"
sats = g.data["sats"]
message = g.data.get("message", "")
sats = data.sats
message = data.message
# Fiat amount is calculated here while frontend is limited
price = await btc_price(cur_code)
amount = sats * (10 ** (-8)) * price
webhook_base = request.scheme + "://" + request.headers["Host"]
service_id = g.data["service"]
service_id = data.service
service = await get_service(service_id)
charge_details = await get_charge_details(service.id)
name = g.data.get("name", "")
name = data.name
if not name:
name = "Anonymous"
description = f"{sats} sats donation from {name} to {service.twitchuser}"
@ -134,33 +127,29 @@ async def api_create_donation():
message=message,
name=name,
cur_code=cur_code,
sats=g.data["sats"],
sats=data.sats,
amount=amount,
service=g.data["service"],
service=data.service,
)
return (jsonify({"redirect_url": f"/satspay/{charge.id}"}), HTTPStatus.OK)
return ({"redirect_url": f"/satspay/{charge.id}"}), HTTPStatus.OK
@streamalerts_ext.route("/api/v1/postdonation", methods=["POST"])
@api_validate_post_request(
schema={
"id": {"type": "string", "required": True},
}
)
async def api_post_donation():
@streamalerts_ext.post("/api/v1/postdonation")
async def api_post_donation(id: str):
"""Post a paid donation to Stremalabs/StreamElements.
This endpoint acts as a webhook for the SatsPayServer extension."""
data = await request.get_json(force=True)
donation_id = data.get("id", "No ID")
donation_id = id
charge = await get_charge(donation_id)
if charge and charge.paid:
return await post_donation(donation_id)
else:
return (jsonify({"message": "Not a paid charge!"}), HTTPStatus.BAD_REQUEST)
return ({"message": "Not a paid charge!"}, HTTPStatus.BAD_REQUEST)
@streamalerts_ext.route("/api/v1/services", methods=["GET"])
@streamalerts_ext.get("/api/v1/services")
@api_check_wallet_key("invoice")
async def api_get_services():
"""Return list of all services assigned to wallet with given invoice key"""
@ -170,12 +159,12 @@ async def api_get_services():
new_services = await get_services(wallet_id)
services += new_services if new_services else []
return (
jsonify([service._asdict() for service in services] if services else []),
[service._asdict() for service in services] if services else [],
HTTPStatus.OK,
)
@streamalerts_ext.route("/api/v1/donations", methods=["GET"])
@streamalerts_ext.get("/api/v1/donations")
@api_check_wallet_key("invoice")
async def api_get_donations():
"""Return list of all donations assigned to wallet with given invoice
@ -187,12 +176,12 @@ async def api_get_donations():
new_donations = await get_donations(wallet_id)
donations += new_donations if new_donations else []
return (
jsonify([donation._asdict() for donation in donations] if donations else []),
[donation._asdict() for donation in donations] if donations else [],
HTTPStatus.OK,
)
@streamalerts_ext.route("/api/v1/donations/<donation_id>", methods=["PUT"])
@streamalerts_ext.put("/api/v1/donations/<donation_id>")
@api_check_wallet_key("invoice")
async def api_update_donation(donation_id=None):
"""Update a donation with the data given in the request"""
@ -201,23 +190,23 @@ async def api_update_donation(donation_id=None):
if not donation:
return (
jsonify({"message": "Donation does not exist."}),
{"message": "Donation does not exist."},
HTTPStatus.NOT_FOUND,
)
if donation.wallet != g.wallet.id:
return (jsonify({"message": "Not your donation."}), HTTPStatus.FORBIDDEN)
return ({"message": "Not your donation."}, HTTPStatus.FORBIDDEN)
donation = await update_donation(donation_id, **g.data)
else:
return (
jsonify({"message": "No donation ID specified"}),
{"message": "No donation ID specified"},
HTTPStatus.BAD_REQUEST,
)
return jsonify(donation._asdict()), HTTPStatus.CREATED
return donation._asdict(), HTTPStatus.CREATED
@streamalerts_ext.route("/api/v1/services/<service_id>", methods=["PUT"])
@streamalerts_ext.put("/api/v1/services/<service_id>")
@api_check_wallet_key("invoice")
async def api_update_service(service_id=None):
"""Update a service with the data given in the request"""
@ -225,30 +214,28 @@ async def api_update_service(service_id=None):
service = await get_service(service_id)
if not service:
return (
jsonify({"message": "Service does not exist."}),
return ({"message": "Service does not exist."},
HTTPStatus.NOT_FOUND,
)
if service.wallet != g.wallet.id:
return (jsonify({"message": "Not your service."}), HTTPStatus.FORBIDDEN)
return ({"message": "Not your service."}), HTTPStatus.FORBIDDEN
service = await update_service(service_id, **g.data)
else:
return (jsonify({"message": "No service ID specified"}), HTTPStatus.BAD_REQUEST)
return jsonify(service._asdict()), HTTPStatus.CREATED
return ({"message": "No service ID specified"}), HTTPStatus.BAD_REQUEST
return service._asdict(), HTTPStatus.CREATED
@streamalerts_ext.route("/api/v1/donations/<donation_id>", methods=["DELETE"])
@streamalerts_ext.delete("/api/v1/donations/<donation_id>")
@api_check_wallet_key("invoice")
async def api_delete_donation(donation_id):
"""Delete the donation with the given donation_id"""
donation = await get_donation(donation_id)
if not donation:
return (jsonify({"message": "No donation with this ID!"}), HTTPStatus.NOT_FOUND)
return ({"message": "No donation with this ID!"}, HTTPStatus.NOT_FOUND)
if donation.wallet != g.wallet.id:
return (
jsonify({"message": "Not authorized to delete this donation!"}),
return ({"message": "Not authorized to delete this donation!"},
HTTPStatus.FORBIDDEN,
)
await delete_donation(donation_id)
@ -256,16 +243,16 @@ async def api_delete_donation(donation_id):
return "", HTTPStatus.NO_CONTENT
@streamalerts_ext.route("/api/v1/services/<service_id>", methods=["DELETE"])
@streamalerts_ext.delete("/api/v1/services/<service_id>")
@api_check_wallet_key("invoice")
async def api_delete_service(service_id):
"""Delete the service with the given service_id"""
service = await get_service(service_id)
if not service:
return (jsonify({"message": "No service with this ID!"}), HTTPStatus.NOT_FOUND)
return ({"message": "No service with this ID!"}, HTTPStatus.NOT_FOUND)
if service.wallet != g.wallet.id:
return (
jsonify({"message": "Not authorized to delete this service!"}),
{"message": "Not authorized to delete this service!"},
HTTPStatus.FORBIDDEN,
)
await delete_service(service_id)

View File

@ -7,14 +7,14 @@ from . import subdomains_ext
from .crud import get_domain
@subdomains_ext.route("/")
@subdomains_ext.get("/")
@validate_uuids(["usr"], required=True)
@check_user_exists()
async def index():
return await render_template("subdomains/index.html", user=g.user)
@subdomains_ext.route("/<domain_id>")
@subdomains_ext.get("/<domain_id>")
async def display(domain_id):
domain = await get_domain(domain_id)
if not domain:

View File

@ -24,7 +24,7 @@ from .cloudflare import cloudflare_create_subdomain, cloudflare_deletesubdomain
# domainS
@subdomains_ext.route("/api/v1/domains", methods=["GET"])
@subdomains_ext.get("/api/v1/domains")
@api_check_wallet_key("invoice")
async def api_domains():
wallet_ids = [g.wallet.id]
@ -33,52 +33,49 @@ async def api_domains():
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
return (
jsonify([domain._asdict() for domain in await get_domains(wallet_ids)]),
[domain._asdict() for domain in await get_domains(wallet_ids)],
HTTPStatus.OK,
)
class CreateDomainsData(BaseModel):
wallet: str
domain: str
cf_token: str
cf_zone_id: str
webhook: optional[str]
description: str
cost: int
allowed_record_types: str
@subdomains_ext.route("/api/v1/domains", methods=["POST"])
@subdomains_ext.route("/api/v1/domains/<domain_id>", methods=["PUT"])
@subdomains_ext.post("/api/v1/domains")
@subdomains_ext.put("/api/v1/domains/<domain_id>")
@api_check_wallet_key("invoice")
@api_validate_post_request(
schema={
"wallet": {"type": "string", "empty": False, "required": True},
"domain": {"type": "string", "empty": False, "required": True},
"cf_token": {"type": "string", "empty": False, "required": True},
"cf_zone_id": {"type": "string", "empty": False, "required": True},
"webhook": {"type": "string", "empty": False, "required": False},
"description": {"type": "string", "min": 0, "required": True},
"cost": {"type": "integer", "min": 0, "required": True},
"allowed_record_types": {"type": "string", "required": True},
}
)
async def api_domain_create(domain_id=None):
async def api_domain_create(data: CreateDomainsData, domain_id=None):
if domain_id:
domain = await get_domain(domain_id)
if not domain:
return jsonify({"message": "domain does not exist."}), HTTPStatus.NOT_FOUND
return {"message": "domain does not exist."}, HTTPStatus.NOT_FOUND
if domain.wallet != g.wallet.id:
return jsonify({"message": "Not your domain."}), HTTPStatus.FORBIDDEN
return {"message": "Not your domain."}, HTTPStatus.FORBIDDEN
domain = await update_domain(domain_id, **g.data)
domain = await update_domain(domain_id, **data)
else:
domain = await create_domain(**g.data)
domain = await create_domain(**data)
return jsonify(domain._asdict()), HTTPStatus.CREATED
@subdomains_ext.route("/api/v1/domains/<domain_id>", methods=["DELETE"])
@subdomains_ext.delete("/api/v1/domains/<domain_id>")
@api_check_wallet_key("invoice")
async def api_domain_delete(domain_id):
domain = await get_domain(domain_id)
if not domain:
return jsonify({"message": "domain does not exist."}), HTTPStatus.NOT_FOUND
return {"message": "domain does not exist."}, HTTPStatus.NOT_FOUND
if domain.wallet != g.wallet.id:
return jsonify({"message": "Not your domain."}), HTTPStatus.FORBIDDEN
return {"message": "Not your domain."}, HTTPStatus.FORBIDDEN
await delete_domain(domain_id)
@ -88,7 +85,7 @@ async def api_domain_delete(domain_id):
#########subdomains##########
@subdomains_ext.route("/api/v1/subdomains", methods=["GET"])
@subdomains_ext.get("/api/v1/subdomains")
@api_check_wallet_key("invoice")
async def api_subdomains():
wallet_ids = [g.wallet.id]
@ -97,24 +94,22 @@ async def api_subdomains():
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
return (
jsonify([domain._asdict() for domain in await get_subdomains(wallet_ids)]),
[domain._asdict() for domain in await get_subdomains(wallet_ids)],
HTTPStatus.OK,
)
class CreateDomainsData(BaseModel):
domain: str
subdomain: str
email: str
ip: str
sats: int = Query(ge=0)
duration: int
record_type: str
@subdomains_ext.route("/api/v1/subdomains/<domain_id>", methods=["POST"])
@api_validate_post_request(
schema={
"domain": {"type": "string", "empty": False, "required": True},
"subdomain": {"type": "string", "empty": False, "required": True},
"email": {"type": "string", "empty": True, "required": True},
"ip": {"type": "string", "empty": False, "required": True},
"sats": {"type": "integer", "min": 0, "required": True},
"duration": {"type": "integer", "empty": False, "required": True},
"record_type": {"type": "string", "empty": False, "required": True},
}
)
async def api_subdomain_make_subdomain(domain_id):
@subdomains_ext.post("/api/v1/subdomains/<domain_id>")
async def api_subdomain_make_subdomain(data: CreateDomainsData, domain_id):
domain = await get_domain(domain_id)
# If the request is coming for the non-existant domain
@ -122,100 +117,95 @@ async def api_subdomain_make_subdomain(domain_id):
return jsonify({"message": "LNsubdomain does not exist."}), HTTPStatus.NOT_FOUND
## If record_type is not one of the allowed ones reject the request
if g.data["record_type"] not in domain.allowed_record_types:
return (
jsonify({"message": g.data["record_type"] + "Not a valid record"}),
if data.record_type not in domain.allowed_record_types:
return ({"message": data.record_type + "Not a valid record"},
HTTPStatus.BAD_REQUEST,
)
## If domain already exist in our database reject it
if await get_subdomainBySubdomain(g.data["subdomain"]) is not None:
if await get_subdomainBySubdomain(data.subdomain) is not None:
return (
jsonify(
{
"message": g.data["subdomain"]
"message": data.subdomain
+ "."
+ domain.domain
+ " domain already taken"
}
),
},
HTTPStatus.BAD_REQUEST,
)
## Dry run cloudflare... (create and if create is sucessful delete it)
cf_response = await cloudflare_create_subdomain(
domain=domain,
subdomain=g.data["subdomain"],
record_type=g.data["record_type"],
ip=g.data["ip"],
subdomain=data.subdomain,
record_type=data.record_type,
ip=data.ip,
)
if cf_response["success"] == True:
cloudflare_deletesubdomain(domain=domain, domain_id=cf_response["result"]["id"])
else:
return (
jsonify(
{
"message": "Problem with cloudflare: "
+ cf_response["errors"][0]["message"]
}
),
},
HTTPStatus.BAD_REQUEST,
)
## ALL OK - create an invoice and return it to the user
sats = g.data["sats"]
sats = data.sats
try:
payment_hash, payment_request = await create_invoice(
wallet_id=domain.wallet,
amount=sats,
memo=f"subdomain {g.data['subdomain']}.{domain.domain} for {sats} sats for {g.data['duration']} days",
memo=f"subdomain {data.subdomain}.{domain.domain} for {sats} sats for {data.duration} days",
extra={"tag": "lnsubdomain"},
)
except Exception as e:
return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR
return {"message": str(e)}, HTTPStatus.INTERNAL_SERVER_ERROR
subdomain = await create_subdomain(
payment_hash=payment_hash, wallet=domain.wallet, **g.data
payment_hash=payment_hash, wallet=domain.wallet, **data
)
if not subdomain:
return (
jsonify({"message": "LNsubdomain could not be fetched."}),
{"message": "LNsubdomain could not be fetched."},
HTTPStatus.NOT_FOUND,
)
return (
jsonify({"payment_hash": payment_hash, "payment_request": payment_request}),
{"payment_hash": payment_hash, "payment_request": payment_request},
HTTPStatus.OK,
)
@subdomains_ext.route("/api/v1/subdomains/<payment_hash>", methods=["GET"])
@subdomains_ext.get("/api/v1/subdomains/<payment_hash>")
async def api_subdomain_send_subdomain(payment_hash):
subdomain = await get_subdomain(payment_hash)
try:
status = await check_invoice_status(subdomain.wallet, payment_hash)
is_paid = not status.pending
except Exception:
return jsonify({"paid": False}), HTTPStatus.OK
return {"paid": False}, HTTPStatus.OK
if is_paid:
return jsonify({"paid": True}), HTTPStatus.OK
return {"paid": True}, HTTPStatus.OK
return jsonify({"paid": False}), HTTPStatus.OK
return {"paid": False}, HTTPStatus.OK
@subdomains_ext.route("/api/v1/subdomains/<subdomain_id>", methods=["DELETE"])
@subdomains_ext.delete("/api/v1/subdomains/<subdomain_id>")
@api_check_wallet_key("invoice")
async def api_subdomain_delete(subdomain_id):
subdomain = await get_subdomain(subdomain_id)
if not subdomain:
return jsonify({"message": "Paywall does not exist."}), HTTPStatus.NOT_FOUND
return {"message": "Paywall does not exist."}, HTTPStatus.NOT_FOUND
if subdomain.wallet != g.wallet.id:
return jsonify({"message": "Not your subdomain."}), HTTPStatus.FORBIDDEN
return {"message": "Not your subdomain."}, HTTPStatus.FORBIDDEN
await delete_subdomain(subdomain_id)

View File

@ -5,7 +5,7 @@ from lnbits.decorators import check_user_exists, validate_uuids
from . import usermanager_ext
@usermanager_ext.route("/")
@usermanager_ext.get("/")
@validate_uuids(["usr"], required=True)
@check_user_exists()
async def index():

View File

@ -23,45 +23,40 @@ from lnbits.core import update_user_extension
### Users
@usermanager_ext.route("/api/v1/users", methods=["GET"])
@usermanager_ext.get("/api/v1/users")
@api_check_wallet_key(key_type="invoice")
async def api_usermanager_users():
user_id = g.wallet.user
return (
jsonify([user._asdict() for user in await get_usermanager_users(user_id)]),
return ([user._asdict() for user in await get_usermanager_users(user_id)],
HTTPStatus.OK,
)
@usermanager_ext.route("/api/v1/users/<user_id>", methods=["GET"])
@usermanager_ext.get("/api/v1/users/<user_id>")
@api_check_wallet_key(key_type="invoice")
async def api_usermanager_user(user_id):
user = await get_usermanager_user(user_id)
return (
jsonify(user._asdict()),
return (user._asdict(),
HTTPStatus.OK,
)
class CreateUsersData(BaseModel):
domain: str
subdomain: str
email: str
ip: Optional[str]
sats: Optional[str]
@usermanager_ext.route("/api/v1/users", methods=["POST"])
@usermanager_ext.post("/api/v1/users")
@api_check_wallet_key(key_type="invoice")
@api_validate_post_request(
schema={
"user_name": {"type": "string", "empty": False, "required": True},
"wallet_name": {"type": "string", "empty": False, "required": True},
"admin_id": {"type": "string", "empty": False, "required": True},
"email": {"type": "string", "required": False},
"password": {"type": "string", "required": False},
}
)
async def api_usermanager_users_create():
user = await create_usermanager_user(**g.data)
async def api_usermanager_users_create(data: CreateUsersData):
user = await create_usermanager_user(**data)
full = user._asdict()
full["wallets"] = [wallet._asdict() for wallet in await get_usermanager_users_wallets(user.id)]
return jsonify(full), HTTPStatus.CREATED
@usermanager_ext.route("/api/v1/users/<user_id>", methods=["DELETE"])
@usermanager_ext.delete("/api/v1/users/<user_id>")
@api_check_wallet_key(key_type="invoice")
async def api_usermanager_users_delete(user_id):
user = await get_usermanager_user(user_id)
@ -73,84 +68,75 @@ async def api_usermanager_users_delete(user_id):
###Activate Extension
class CreateUsersData(BaseModel):
extension: str
userid: str
active: bool
@usermanager_ext.route("/api/v1/extensions", methods=["POST"])
@usermanager_ext.post("/api/v1/extensions")
@api_check_wallet_key(key_type="invoice")
@api_validate_post_request(
schema={
"extension": {"type": "string", "empty": False, "required": True},
"userid": {"type": "string", "empty": False, "required": True},
"active": {"type": "boolean", "required": True},
}
)
async def api_usermanager_activate_extension():
user = await get_user(g.data["userid"])
async def api_usermanager_activate_extension(data: CreateUsersData):
user = await get_user(data.userid)
if not user:
return jsonify({"message": "no such user"}), HTTPStatus.NOT_FOUND
return {"message": "no such user"}, HTTPStatus.NOT_FOUND
update_user_extension(
user_id=g.data["userid"], extension=g.data["extension"], active=g.data["active"]
user_id=data.userid, extension=data.extension, active=data.active
)
return jsonify({"extension": "updated"}), HTTPStatus.CREATED
return {"extension": "updated"}, HTTPStatus.CREATED
###Wallets
class CreateWalletsData(BaseModel):
user_id: str
wallet_name: str
admin_id: str
@usermanager_ext.route("/api/v1/wallets", methods=["POST"])
@usermanager_ext.post("/api/v1/wallets")
@api_check_wallet_key(key_type="invoice")
@api_validate_post_request(
schema={
"user_id": {"type": "string", "empty": False, "required": True},
"wallet_name": {"type": "string", "empty": False, "required": True},
"admin_id": {"type": "string", "empty": False, "required": True},
}
)
async def api_usermanager_wallets_create():
async def api_usermanager_wallets_create(data: CreateWalletsData):
user = await create_usermanager_wallet(
g.data["user_id"], g.data["wallet_name"], g.data["admin_id"]
data.user_id, data.wallet_name, data.admin_id
)
return jsonify(user._asdict()), HTTPStatus.CREATED
return user._asdict(), HTTPStatus.CREATED
@usermanager_ext.route("/api/v1/wallets", methods=["GET"])
@usermanager_ext.get("/api/v1/wallets")
@api_check_wallet_key(key_type="invoice")
async def api_usermanager_wallets():
admin_id = g.wallet.user
return (
jsonify(
[wallet._asdict() for wallet in await get_usermanager_wallets(admin_id)]
),
[wallet._asdict() for wallet in await get_usermanager_wallets(admin_id)],
HTTPStatus.OK,
)
@usermanager_ext.route("/api/v1/wallets<wallet_id>", methods=["GET"])
@usermanager_ext.get("/api/v1/wallets<wallet_id>")
@api_check_wallet_key(key_type="invoice")
async def api_usermanager_wallet_transactions(wallet_id):
return jsonify(await get_usermanager_wallet_transactions(wallet_id)), HTTPStatus.OK
return await get_usermanager_wallet_transactions(wallet_id), HTTPStatus.OK
@usermanager_ext.route("/api/v1/wallets/<user_id>", methods=["GET"])
@usermanager_ext.get("/api/v1/wallets/<user_id>")
@api_check_wallet_key(key_type="invoice")
async def api_usermanager_users_wallets(user_id):
wallet = await get_usermanager_users_wallets(user_id)
return (
jsonify(
[
wallet._asdict()
for wallet in await get_usermanager_users_wallets(user_id)
]
),
],
HTTPStatus.OK,
)
@usermanager_ext.route("/api/v1/wallets/<wallet_id>", methods=["DELETE"])
@usermanager_ext.delete("/api/v1/wallets/<wallet_id>")
@api_check_wallet_key(key_type="invoice")
async def api_usermanager_wallets_delete(wallet_id):
wallet = await get_usermanager_wallet(wallet_id)
if not wallet:
return jsonify({"message": "Wallet does not exist."}), HTTPStatus.NOT_FOUND
return {"message": "Wallet does not exist."}, HTTPStatus.NOT_FOUND
await delete_usermanager_wallet(wallet_id, wallet.user)
return "", HTTPStatus.NO_CONTENT

View File

@ -6,7 +6,6 @@ from lnbits.core.crud import get_user
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
from pydantic import BaseModel
from fastapi import FastAPI, Query
from fastapi.encoders import jsonable_encoder
from . import withdraw_ext
from .crud import (