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