diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 11075370b..ae3e6a5ef 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -12,7 +12,7 @@ from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse import async_timeout import httpx import pyqrcode -from fastapi import Depends, Header, Query, Request +from fastapi import Depends, Header, Query, Request, Response from fastapi.exceptions import HTTPException from fastapi.params import Body from loguru import logger @@ -584,8 +584,8 @@ class DecodePayment(BaseModel): data: str -@core_app.post("/api/v1/payments/decode") -async def api_payments_decode(data: DecodePayment): +@core_app.post("/api/v1/payments/decode", status_code=HTTPStatus.OK) +async def api_payments_decode(data: DecodePayment, response: Response): payment_str = data.data try: if payment_str[:5] == "LNURL": @@ -606,6 +606,7 @@ async def api_payments_decode(data: DecodePayment): "min_final_cltv_expiry": invoice.min_final_cltv_expiry, } except: + response.status_code = HTTPStatus.BAD_REQUEST return {"message": "Failed to decode"} diff --git a/lnbits/db.py b/lnbits/db.py index f52b03914..321b23d0a 100644 --- a/lnbits/db.py +++ b/lnbits/db.py @@ -1,6 +1,7 @@ import asyncio import datetime import os +import re import time from contextlib import asynccontextmanager from typing import Optional @@ -73,18 +74,39 @@ class Connection(Compat): query = query.replace("?", "%s") return query + def rewrite_values(self, values): + # strip html + CLEANR = re.compile("<.*?>|&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});") + + def cleanhtml(raw_html): + if isinstance(raw_html, str): + cleantext = re.sub(CLEANR, "", raw_html) + return cleantext + else: + return raw_html + + # tuple to list and back to tuple + values = tuple([cleanhtml(l) for l in list(values)]) + return values + async def fetchall(self, query: str, values: tuple = ()) -> list: - result = await self.conn.execute(self.rewrite_query(query), values) + result = await self.conn.execute( + self.rewrite_query(query), self.rewrite_values(values) + ) return await result.fetchall() async def fetchone(self, query: str, values: tuple = ()): - result = await self.conn.execute(self.rewrite_query(query), values) + result = await self.conn.execute( + self.rewrite_query(query), self.rewrite_values(values) + ) row = await result.fetchone() await result.close() return row async def execute(self, query: str, values: tuple = ()): - return await self.conn.execute(self.rewrite_query(query), values) + return await self.conn.execute( + self.rewrite_query(query), self.rewrite_values(values) + ) class Database(Compat): diff --git a/lnbits/extensions/lnurlp/migrations.py b/lnbits/extensions/lnurlp/migrations.py index 5258471da..c4edd3aa3 100644 --- a/lnbits/extensions/lnurlp/migrations.py +++ b/lnbits/extensions/lnurlp/migrations.py @@ -8,7 +8,7 @@ async def m001_initial(db): id {db.serial_primary_key}, wallet TEXT NOT NULL, description TEXT NOT NULL, - amount INTEGER NOT NULL, + amount {db.big_int} NOT NULL, served_meta INTEGER NOT NULL, served_pr INTEGER NOT NULL ); diff --git a/lnbits/extensions/offlineshop/migrations.py b/lnbits/extensions/offlineshop/migrations.py index 84aea27e2..4e668668a 100644 --- a/lnbits/extensions/offlineshop/migrations.py +++ b/lnbits/extensions/offlineshop/migrations.py @@ -22,7 +22,7 @@ async def m001_initial(db): description TEXT NOT NULL, image TEXT, -- image/png;base64,... enabled BOOLEAN NOT NULL DEFAULT true, - price INTEGER NOT NULL, + price {db.big_int} NOT NULL, unit TEXT NOT NULL DEFAULT 'sat' ); """ diff --git a/lnbits/extensions/paywall/migrations.py b/lnbits/extensions/paywall/migrations.py index fa91e409f..9b3341fd4 100644 --- a/lnbits/extensions/paywall/migrations.py +++ b/lnbits/extensions/paywall/migrations.py @@ -3,14 +3,14 @@ async def m001_initial(db): Initial paywalls table. """ await db.execute( - """ + f""" CREATE TABLE paywall.paywalls ( id TEXT PRIMARY KEY, wallet TEXT NOT NULL, secret TEXT NOT NULL, url TEXT NOT NULL, memo TEXT NOT NULL, - amount INTEGER NOT NULL, + amount {db.big_int} NOT NULL, time TIMESTAMP NOT NULL DEFAULT """ + db.timestamp_now + """ @@ -25,14 +25,14 @@ async def m002_redux(db): """ await db.execute("ALTER TABLE paywall.paywalls RENAME TO paywalls_old") await db.execute( - """ + f""" CREATE TABLE paywall.paywalls ( id TEXT PRIMARY KEY, wallet TEXT NOT NULL, url TEXT NOT NULL, memo TEXT NOT NULL, description TEXT NULL, - amount INTEGER DEFAULT 0, + amount {db.big_int} DEFAULT 0, time TIMESTAMP NOT NULL DEFAULT """ + db.timestamp_now + """, diff --git a/lnbits/extensions/satsdice/migrations.py b/lnbits/extensions/satsdice/migrations.py index 612982411..82ab35ba7 100644 --- a/lnbits/extensions/satsdice/migrations.py +++ b/lnbits/extensions/satsdice/migrations.py @@ -3,14 +3,14 @@ async def m001_initial(db): Creates an improved satsdice table and migrates the existing data. """ await db.execute( - """ + f""" CREATE TABLE satsdice.satsdice_pay ( id TEXT PRIMARY KEY, wallet TEXT, title TEXT, min_bet INTEGER, max_bet INTEGER, - amount INTEGER DEFAULT 0, + amount {db.big_int} DEFAULT 0, served_meta INTEGER NOT NULL, served_pr INTEGER NOT NULL, multiplier FLOAT, @@ -28,11 +28,11 @@ async def m002_initial(db): Creates an improved satsdice table and migrates the existing data. """ await db.execute( - """ + f""" CREATE TABLE satsdice.satsdice_withdraw ( id TEXT PRIMARY KEY, satsdice_pay TEXT, - value INTEGER DEFAULT 1, + value {db.big_int} DEFAULT 1, unique_hash TEXT UNIQUE, k1 TEXT, open_time INTEGER, @@ -47,11 +47,11 @@ async def m003_initial(db): Creates an improved satsdice table and migrates the existing data. """ await db.execute( - """ + f""" CREATE TABLE satsdice.satsdice_payment ( payment_hash TEXT PRIMARY KEY, satsdice_pay TEXT, - value INTEGER, + value {db.big_int}, paid BOOL DEFAULT FALSE, lost BOOL DEFAULT FALSE ); diff --git a/lnbits/extensions/satspay/config.json b/lnbits/extensions/satspay/config.json index beb0071cb..fe9e3df49 100644 --- a/lnbits/extensions/satspay/config.json +++ b/lnbits/extensions/satspay/config.json @@ -2,7 +2,5 @@ "name": "SatsPay Server", "short_description": "Create onchain and LN charges", "icon": "payment", - "contributors": [ - "arcbtc" - ] + "contributors": ["arcbtc"] } diff --git a/lnbits/extensions/satspay/crud.py b/lnbits/extensions/satspay/crud.py index 968c9ab01..7e34f6f80 100644 --- a/lnbits/extensions/satspay/crud.py +++ b/lnbits/extensions/satspay/crud.py @@ -10,7 +10,7 @@ from lnbits.helpers import urlsafe_short_hash from ..watchonly.crud import get_config, get_fresh_address from . import db from .helpers import fetch_onchain_balance -from .models import Charges, CreateCharge +from .models import Charges, CreateCharge, SatsPayThemes ###############CHARGES########################## @@ -53,9 +53,10 @@ async def create_charge(user: str, data: CreateCharge) -> Charges: time, amount, balance, - extra + extra, + custom_css ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( charge_id, @@ -73,6 +74,7 @@ async def create_charge(user: str, data: CreateCharge) -> Charges: data.amount, 0, data.extra, + data.custom_css, ), ) return await get_charge(charge_id) @@ -121,3 +123,101 @@ async def check_address_balance(charge_id: str) -> Optional[Charges]: if invoice_status["paid"]: return await update_charge(charge_id=charge_id, balance=charge.amount) return await get_charge(charge_id) + + +################## SETTINGS ################### + + +async def save_theme(data: SatsPayThemes, css_id: str = None): + # insert or update + if css_id: + await db.execute( + """ + UPDATE satspay.themes SET custom_css = ?, title = ? WHERE css_id = ? + """, + (data.custom_css, data.title, css_id), + ) + else: + css_id = urlsafe_short_hash() + await db.execute( + """ + INSERT INTO satspay.themes ( + css_id, + title, + user, + custom_css + ) + VALUES (?, ?, ?, ?) + """, + ( + css_id, + data.title, + data.user, + data.custom_css, + ), + ) + return await get_theme(css_id) + + +async def get_theme(css_id: str) -> SatsPayThemes: + row = await db.fetchone("SELECT * FROM satspay.themes WHERE css_id = ?", (css_id,)) + return SatsPayThemes.from_row(row) if row else None + + +async def get_themes(user_id: str) -> List[SatsPayThemes]: + rows = await db.fetchall( + """SELECT * FROM satspay.themes WHERE "user" = ? ORDER BY "timestamp" DESC """, + (user_id,), + ) + return await get_config(row.user) + + +################## SETTINGS ################### + + +async def save_theme(data: SatsPayThemes, css_id: str = None): + # insert or update + if css_id: + await db.execute( + """ + UPDATE satspay.themes SET custom_css = ?, title = ? WHERE css_id = ? + """, + (data.custom_css, data.title, css_id), + ) + else: + css_id = urlsafe_short_hash() + await db.execute( + """ + INSERT INTO satspay.themes ( + css_id, + title, + "user", + custom_css + ) + VALUES (?, ?, ?, ?) + """, + ( + css_id, + data.title, + data.user, + data.custom_css, + ), + ) + return await get_theme(css_id) + + +async def get_theme(css_id: str) -> SatsPayThemes: + row = await db.fetchone("SELECT * FROM satspay.themes WHERE css_id = ?", (css_id,)) + return SatsPayThemes.from_row(row) if row else None + + +async def get_themes(user_id: str) -> List[SatsPayThemes]: + rows = await db.fetchall( + """SELECT * FROM satspay.themes WHERE "user" = ? ORDER BY "title" DESC """, + (user_id,), + ) + return [SatsPayThemes.from_row(row) for row in rows] + + +async def delete_theme(theme_id: str) -> None: + await db.execute("DELETE FROM satspay.themes WHERE css_id = ?", (theme_id,)) diff --git a/lnbits/extensions/satspay/helpers.py b/lnbits/extensions/satspay/helpers.py index 2aa83e1f5..60c5ba4ab 100644 --- a/lnbits/extensions/satspay/helpers.py +++ b/lnbits/extensions/satspay/helpers.py @@ -19,10 +19,12 @@ def public_charge(charge: Charges): "time_elapsed": charge.time_elapsed, "time_left": charge.time_left, "paid": charge.paid, + "custom_css": charge.custom_css, } if charge.paid: c["completelink"] = charge.completelink + c["completelinktext"] = charge.completelinktext return c diff --git a/lnbits/extensions/satspay/migrations.py b/lnbits/extensions/satspay/migrations.py index 2579961f5..e23bd413b 100644 --- a/lnbits/extensions/satspay/migrations.py +++ b/lnbits/extensions/satspay/migrations.py @@ -4,7 +4,7 @@ async def m001_initial(db): """ await db.execute( - """ + f""" CREATE TABLE satspay.charges ( id TEXT NOT NULL PRIMARY KEY, "user" TEXT, @@ -18,8 +18,8 @@ async def m001_initial(db): completelink TEXT, completelinktext TEXT, time INTEGER, - amount INTEGER, - balance INTEGER DEFAULT 0, + amount {db.big_int}, + balance {db.big_int} DEFAULT 0, timestamp TIMESTAMP NOT NULL DEFAULT """ + db.timestamp_now + """ @@ -37,3 +37,28 @@ async def m002_add_charge_extra_data(db): ADD COLUMN extra TEXT DEFAULT '{"mempool_endpoint": "https://mempool.space", "network": "Mainnet"}'; """ ) + + +async def m003_add_themes_table(db): + """ + Themes table + """ + + await db.execute( + """ + CREATE TABLE satspay.themes ( + css_id TEXT NOT NULL PRIMARY KEY, + "user" TEXT, + title TEXT, + custom_css TEXT + ); + """ + ) + + +async def m004_add_custom_css_to_charges(db): + """ + Add custom css option column to the 'charges' table + """ + + await db.execute("ALTER TABLE satspay.charges ADD COLUMN custom_css TEXT;") diff --git a/lnbits/extensions/satspay/models.py b/lnbits/extensions/satspay/models.py index 1e7c95c99..cfb3c7aca 100644 --- a/lnbits/extensions/satspay/models.py +++ b/lnbits/extensions/satspay/models.py @@ -14,6 +14,7 @@ class CreateCharge(BaseModel): webhook: str = Query(None) completelink: str = Query(None) completelinktext: str = Query(None) + custom_css: Optional[str] time: int = Query(..., ge=1) amount: int = Query(..., ge=1) extra: str = "{}" @@ -38,6 +39,7 @@ class Charges(BaseModel): completelink: Optional[str] completelinktext: Optional[str] = "Back to Merchant" extra: str = "{}" + custom_css: Optional[str] time: int amount: int balance: int @@ -72,3 +74,14 @@ class Charges(BaseModel): def must_call_webhook(self): return self.webhook and self.paid and self.config.webhook_success == False + + +class SatsPayThemes(BaseModel): + css_id: str = Query(None) + title: str = Query(None) + custom_css: str = Query(None) + user: Optional[str] + + @classmethod + def from_row(cls, row: Row) -> "SatsPayThemes": + return cls(**dict(row)) diff --git a/lnbits/extensions/satspay/static/js/utils.js b/lnbits/extensions/satspay/static/js/utils.js index 929279554..2b1be8bdc 100644 --- a/lnbits/extensions/satspay/static/js/utils.js +++ b/lnbits/extensions/satspay/static/js/utils.js @@ -26,5 +26,10 @@ const mapCharge = (obj, oldObj = {}) => { return charge } +const mapCSS = (obj, oldObj = {}) => { + const theme = _.clone(obj) + return theme +} + const minutesToTime = min => min > 0 ? new Date(min * 1000).toISOString().substring(14, 19) : '' diff --git a/lnbits/extensions/satspay/templates/satspay/_api_docs.html b/lnbits/extensions/satspay/templates/satspay/_api_docs.html index ed6587357..6d5ae661e 100644 --- a/lnbits/extensions/satspay/templates/satspay/_api_docs.html +++ b/lnbits/extensions/satspay/templates/satspay/_api_docs.html @@ -5,7 +5,13 @@ WatchOnly extension, we highly reccomend using a fresh extended public Key specifically for SatsPayServer!
- Created by, Ben ArcBen Arc, + motorina0


diff --git a/lnbits/extensions/satspay/templates/satspay/display.html b/lnbits/extensions/satspay/templates/satspay/display.html index a24ed84c7..8ea218bdd 100644 --- a/lnbits/extensions/satspay/templates/satspay/display.html +++ b/lnbits/extensions/satspay/templates/satspay/display.html @@ -174,7 +174,7 @@
- +{% endblock %} {% block styles %} + + {% endblock %} {% block scripts %} @@ -438,6 +448,10 @@ } }, created: async function () { + // Remove a user defined theme + if (this.charge.custom_css) { + document.body.setAttribute('data-theme', '') + } if (this.charge.payment_request) this.payInvoice() else this.payOnchain() diff --git a/lnbits/extensions/satspay/templates/satspay/index.html b/lnbits/extensions/satspay/templates/satspay/index.html index 60c4d5199..602b1a288 100644 --- a/lnbits/extensions/satspay/templates/satspay/index.html +++ b/lnbits/extensions/satspay/templates/satspay/index.html @@ -8,6 +8,26 @@ New charge + + New CSS Theme + + New CSS Theme + For security reason, custom css is only available to server + admins. @@ -259,6 +279,63 @@ + + + +
+
+
Themes
+
+
+ + {% raw %} + + + + {% endraw %} + +
+
@@ -303,32 +380,6 @@ > - - - - - - - -
@@ -377,6 +428,52 @@ label="Wallet *" > + +
+
+ + + + + + + + + +
+
+ + + + + + + +
+ Update CSS theme + Save CSS theme + Cancel +
+
+
+
{% endblock %} {% block scripts %} {{ window_vars(user) }}