mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-02-24 14:51:05 +01:00
Merge branch 'main' of githubblackcoffeexbt:blackcoffeexbt/lnbits-legend
This commit is contained in:
commit
be9b4bf5b8
20 changed files with 663 additions and 127 deletions
|
@ -12,7 +12,7 @@ from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
|
||||||
import async_timeout
|
import async_timeout
|
||||||
import httpx
|
import httpx
|
||||||
import pyqrcode
|
import pyqrcode
|
||||||
from fastapi import Depends, Header, Query, Request
|
from fastapi import Depends, Header, Query, Request, Response
|
||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
from fastapi.params import Body
|
from fastapi.params import Body
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
@ -584,8 +584,8 @@ class DecodePayment(BaseModel):
|
||||||
data: str
|
data: str
|
||||||
|
|
||||||
|
|
||||||
@core_app.post("/api/v1/payments/decode")
|
@core_app.post("/api/v1/payments/decode", status_code=HTTPStatus.OK)
|
||||||
async def api_payments_decode(data: DecodePayment):
|
async def api_payments_decode(data: DecodePayment, response: Response):
|
||||||
payment_str = data.data
|
payment_str = data.data
|
||||||
try:
|
try:
|
||||||
if payment_str[:5] == "LNURL":
|
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,
|
"min_final_cltv_expiry": invoice.min_final_cltv_expiry,
|
||||||
}
|
}
|
||||||
except:
|
except:
|
||||||
|
response.status_code = HTTPStatus.BAD_REQUEST
|
||||||
return {"message": "Failed to decode"}
|
return {"message": "Failed to decode"}
|
||||||
|
|
||||||
|
|
||||||
|
|
28
lnbits/db.py
28
lnbits/db.py
|
@ -1,6 +1,7 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
@ -73,18 +74,39 @@ class Connection(Compat):
|
||||||
query = query.replace("?", "%s")
|
query = query.replace("?", "%s")
|
||||||
return query
|
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:
|
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()
|
return await result.fetchall()
|
||||||
|
|
||||||
async def fetchone(self, query: str, values: tuple = ()):
|
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()
|
row = await result.fetchone()
|
||||||
await result.close()
|
await result.close()
|
||||||
return row
|
return row
|
||||||
|
|
||||||
async def execute(self, query: str, values: tuple = ()):
|
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):
|
class Database(Compat):
|
||||||
|
|
|
@ -8,7 +8,7 @@ async def m001_initial(db):
|
||||||
id {db.serial_primary_key},
|
id {db.serial_primary_key},
|
||||||
wallet TEXT NOT NULL,
|
wallet TEXT NOT NULL,
|
||||||
description TEXT NOT NULL,
|
description TEXT NOT NULL,
|
||||||
amount INTEGER NOT NULL,
|
amount {db.big_int} NOT NULL,
|
||||||
served_meta INTEGER NOT NULL,
|
served_meta INTEGER NOT NULL,
|
||||||
served_pr INTEGER NOT NULL
|
served_pr INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
|
@ -22,7 +22,7 @@ async def m001_initial(db):
|
||||||
description TEXT NOT NULL,
|
description TEXT NOT NULL,
|
||||||
image TEXT, -- image/png;base64,...
|
image TEXT, -- image/png;base64,...
|
||||||
enabled BOOLEAN NOT NULL DEFAULT true,
|
enabled BOOLEAN NOT NULL DEFAULT true,
|
||||||
price INTEGER NOT NULL,
|
price {db.big_int} NOT NULL,
|
||||||
unit TEXT NOT NULL DEFAULT 'sat'
|
unit TEXT NOT NULL DEFAULT 'sat'
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -3,14 +3,14 @@ async def m001_initial(db):
|
||||||
Initial paywalls table.
|
Initial paywalls table.
|
||||||
"""
|
"""
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
CREATE TABLE paywall.paywalls (
|
CREATE TABLE paywall.paywalls (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
wallet TEXT NOT NULL,
|
wallet TEXT NOT NULL,
|
||||||
secret TEXT NOT NULL,
|
secret TEXT NOT NULL,
|
||||||
url TEXT NOT NULL,
|
url TEXT NOT NULL,
|
||||||
memo TEXT NOT NULL,
|
memo TEXT NOT NULL,
|
||||||
amount INTEGER NOT NULL,
|
amount {db.big_int} NOT NULL,
|
||||||
time TIMESTAMP NOT NULL DEFAULT """
|
time TIMESTAMP NOT NULL DEFAULT """
|
||||||
+ db.timestamp_now
|
+ 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("ALTER TABLE paywall.paywalls RENAME TO paywalls_old")
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
CREATE TABLE paywall.paywalls (
|
CREATE TABLE paywall.paywalls (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
wallet TEXT NOT NULL,
|
wallet TEXT NOT NULL,
|
||||||
url TEXT NOT NULL,
|
url TEXT NOT NULL,
|
||||||
memo TEXT NOT NULL,
|
memo TEXT NOT NULL,
|
||||||
description TEXT NULL,
|
description TEXT NULL,
|
||||||
amount INTEGER DEFAULT 0,
|
amount {db.big_int} DEFAULT 0,
|
||||||
time TIMESTAMP NOT NULL DEFAULT """
|
time TIMESTAMP NOT NULL DEFAULT """
|
||||||
+ db.timestamp_now
|
+ db.timestamp_now
|
||||||
+ """,
|
+ """,
|
||||||
|
|
|
@ -3,14 +3,14 @@ async def m001_initial(db):
|
||||||
Creates an improved satsdice table and migrates the existing data.
|
Creates an improved satsdice table and migrates the existing data.
|
||||||
"""
|
"""
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
CREATE TABLE satsdice.satsdice_pay (
|
CREATE TABLE satsdice.satsdice_pay (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
wallet TEXT,
|
wallet TEXT,
|
||||||
title TEXT,
|
title TEXT,
|
||||||
min_bet INTEGER,
|
min_bet INTEGER,
|
||||||
max_bet INTEGER,
|
max_bet INTEGER,
|
||||||
amount INTEGER DEFAULT 0,
|
amount {db.big_int} DEFAULT 0,
|
||||||
served_meta INTEGER NOT NULL,
|
served_meta INTEGER NOT NULL,
|
||||||
served_pr INTEGER NOT NULL,
|
served_pr INTEGER NOT NULL,
|
||||||
multiplier FLOAT,
|
multiplier FLOAT,
|
||||||
|
@ -28,11 +28,11 @@ async def m002_initial(db):
|
||||||
Creates an improved satsdice table and migrates the existing data.
|
Creates an improved satsdice table and migrates the existing data.
|
||||||
"""
|
"""
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
CREATE TABLE satsdice.satsdice_withdraw (
|
CREATE TABLE satsdice.satsdice_withdraw (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
satsdice_pay TEXT,
|
satsdice_pay TEXT,
|
||||||
value INTEGER DEFAULT 1,
|
value {db.big_int} DEFAULT 1,
|
||||||
unique_hash TEXT UNIQUE,
|
unique_hash TEXT UNIQUE,
|
||||||
k1 TEXT,
|
k1 TEXT,
|
||||||
open_time INTEGER,
|
open_time INTEGER,
|
||||||
|
@ -47,11 +47,11 @@ async def m003_initial(db):
|
||||||
Creates an improved satsdice table and migrates the existing data.
|
Creates an improved satsdice table and migrates the existing data.
|
||||||
"""
|
"""
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
CREATE TABLE satsdice.satsdice_payment (
|
CREATE TABLE satsdice.satsdice_payment (
|
||||||
payment_hash TEXT PRIMARY KEY,
|
payment_hash TEXT PRIMARY KEY,
|
||||||
satsdice_pay TEXT,
|
satsdice_pay TEXT,
|
||||||
value INTEGER,
|
value {db.big_int},
|
||||||
paid BOOL DEFAULT FALSE,
|
paid BOOL DEFAULT FALSE,
|
||||||
lost BOOL DEFAULT FALSE
|
lost BOOL DEFAULT FALSE
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,7 +2,5 @@
|
||||||
"name": "SatsPay Server",
|
"name": "SatsPay Server",
|
||||||
"short_description": "Create onchain and LN charges",
|
"short_description": "Create onchain and LN charges",
|
||||||
"icon": "payment",
|
"icon": "payment",
|
||||||
"contributors": [
|
"contributors": ["arcbtc"]
|
||||||
"arcbtc"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ from lnbits.helpers import urlsafe_short_hash
|
||||||
from ..watchonly.crud import get_config, get_fresh_address
|
from ..watchonly.crud import get_config, get_fresh_address
|
||||||
from . import db
|
from . import db
|
||||||
from .helpers import fetch_onchain_balance
|
from .helpers import fetch_onchain_balance
|
||||||
from .models import Charges, CreateCharge
|
from .models import Charges, CreateCharge, SatsPayThemes
|
||||||
|
|
||||||
###############CHARGES##########################
|
###############CHARGES##########################
|
||||||
|
|
||||||
|
@ -53,9 +53,10 @@ async def create_charge(user: str, data: CreateCharge) -> Charges:
|
||||||
time,
|
time,
|
||||||
amount,
|
amount,
|
||||||
balance,
|
balance,
|
||||||
extra
|
extra,
|
||||||
|
custom_css
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
charge_id,
|
charge_id,
|
||||||
|
@ -73,6 +74,7 @@ async def create_charge(user: str, data: CreateCharge) -> Charges:
|
||||||
data.amount,
|
data.amount,
|
||||||
0,
|
0,
|
||||||
data.extra,
|
data.extra,
|
||||||
|
data.custom_css,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return await get_charge(charge_id)
|
return await get_charge(charge_id)
|
||||||
|
@ -121,3 +123,101 @@ async def check_address_balance(charge_id: str) -> Optional[Charges]:
|
||||||
if invoice_status["paid"]:
|
if invoice_status["paid"]:
|
||||||
return await update_charge(charge_id=charge_id, balance=charge.amount)
|
return await update_charge(charge_id=charge_id, balance=charge.amount)
|
||||||
return await get_charge(charge_id)
|
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,))
|
||||||
|
|
|
@ -19,10 +19,12 @@ def public_charge(charge: Charges):
|
||||||
"time_elapsed": charge.time_elapsed,
|
"time_elapsed": charge.time_elapsed,
|
||||||
"time_left": charge.time_left,
|
"time_left": charge.time_left,
|
||||||
"paid": charge.paid,
|
"paid": charge.paid,
|
||||||
|
"custom_css": charge.custom_css,
|
||||||
}
|
}
|
||||||
|
|
||||||
if charge.paid:
|
if charge.paid:
|
||||||
c["completelink"] = charge.completelink
|
c["completelink"] = charge.completelink
|
||||||
|
c["completelinktext"] = charge.completelinktext
|
||||||
|
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ async def m001_initial(db):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
CREATE TABLE satspay.charges (
|
CREATE TABLE satspay.charges (
|
||||||
id TEXT NOT NULL PRIMARY KEY,
|
id TEXT NOT NULL PRIMARY KEY,
|
||||||
"user" TEXT,
|
"user" TEXT,
|
||||||
|
@ -18,8 +18,8 @@ async def m001_initial(db):
|
||||||
completelink TEXT,
|
completelink TEXT,
|
||||||
completelinktext TEXT,
|
completelinktext TEXT,
|
||||||
time INTEGER,
|
time INTEGER,
|
||||||
amount INTEGER,
|
amount {db.big_int},
|
||||||
balance INTEGER DEFAULT 0,
|
balance {db.big_int} DEFAULT 0,
|
||||||
timestamp TIMESTAMP NOT NULL DEFAULT """
|
timestamp TIMESTAMP NOT NULL DEFAULT """
|
||||||
+ db.timestamp_now
|
+ 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"}';
|
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;")
|
||||||
|
|
|
@ -14,6 +14,7 @@ class CreateCharge(BaseModel):
|
||||||
webhook: str = Query(None)
|
webhook: str = Query(None)
|
||||||
completelink: str = Query(None)
|
completelink: str = Query(None)
|
||||||
completelinktext: str = Query(None)
|
completelinktext: str = Query(None)
|
||||||
|
custom_css: Optional[str]
|
||||||
time: int = Query(..., ge=1)
|
time: int = Query(..., ge=1)
|
||||||
amount: int = Query(..., ge=1)
|
amount: int = Query(..., ge=1)
|
||||||
extra: str = "{}"
|
extra: str = "{}"
|
||||||
|
@ -38,6 +39,7 @@ class Charges(BaseModel):
|
||||||
completelink: Optional[str]
|
completelink: Optional[str]
|
||||||
completelinktext: Optional[str] = "Back to Merchant"
|
completelinktext: Optional[str] = "Back to Merchant"
|
||||||
extra: str = "{}"
|
extra: str = "{}"
|
||||||
|
custom_css: Optional[str]
|
||||||
time: int
|
time: int
|
||||||
amount: int
|
amount: int
|
||||||
balance: int
|
balance: int
|
||||||
|
@ -72,3 +74,14 @@ class Charges(BaseModel):
|
||||||
|
|
||||||
def must_call_webhook(self):
|
def must_call_webhook(self):
|
||||||
return self.webhook and self.paid and self.config.webhook_success == False
|
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))
|
||||||
|
|
|
@ -26,5 +26,10 @@ const mapCharge = (obj, oldObj = {}) => {
|
||||||
return charge
|
return charge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapCSS = (obj, oldObj = {}) => {
|
||||||
|
const theme = _.clone(obj)
|
||||||
|
return theme
|
||||||
|
}
|
||||||
|
|
||||||
const minutesToTime = min =>
|
const minutesToTime = min =>
|
||||||
min > 0 ? new Date(min * 1000).toISOString().substring(14, 19) : ''
|
min > 0 ? new Date(min * 1000).toISOString().substring(14, 19) : ''
|
||||||
|
|
|
@ -5,7 +5,13 @@
|
||||||
WatchOnly extension, we highly reccomend using a fresh extended public Key
|
WatchOnly extension, we highly reccomend using a fresh extended public Key
|
||||||
specifically for SatsPayServer!<br />
|
specifically for SatsPayServer!<br />
|
||||||
<small>
|
<small>
|
||||||
Created by, <a href="https://github.com/benarc">Ben Arc</a></small
|
Created by, <a href="https://github.com/benarc">Ben Arc</a>,
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
style="color: unset"
|
||||||
|
href="https://github.com/motorina0"
|
||||||
|
>motorina0</a
|
||||||
|
></small
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -174,7 +174,7 @@
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
<q-btn
|
<q-btn
|
||||||
outline
|
outline
|
||||||
v-if="charge.webhook"
|
v-if="charge.completelink"
|
||||||
type="a"
|
type="a"
|
||||||
:href="charge.completelink"
|
:href="charge.completelink"
|
||||||
:label="charge.completelinktext"
|
:label="charge.completelinktext"
|
||||||
|
@ -297,7 +297,17 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg- 4 col-md-3 col-sm-1"></div>
|
<div class="col-lg- 4 col-md-3 col-sm-1"></div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %} {% block styles %}
|
||||||
|
<link
|
||||||
|
href="/satspay/css/{{ charge_data.custom_css }}"
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
/>
|
||||||
|
<style>
|
||||||
|
header button.q-btn-dropdown {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
|
|
||||||
<script src="https://mempool.space/mempool.js"></script>
|
<script src="https://mempool.space/mempool.js"></script>
|
||||||
|
@ -438,6 +448,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: async function () {
|
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()
|
if (this.charge.payment_request) this.payInvoice()
|
||||||
else this.payOnchain()
|
else this.payOnchain()
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,26 @@
|
||||||
<q-btn unelevated color="primary" @click="formDialogCharge.show = true"
|
<q-btn unelevated color="primary" @click="formDialogCharge.show = true"
|
||||||
>New charge
|
>New charge
|
||||||
</q-btn>
|
</q-btn>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
v-if="admin == 'True'"
|
||||||
|
unelevated
|
||||||
|
color="primary"
|
||||||
|
@click="getThemes();formDialogThemes.show = true"
|
||||||
|
>New CSS Theme
|
||||||
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
v-else
|
||||||
|
disable
|
||||||
|
unelevated
|
||||||
|
color="primary"
|
||||||
|
@click="getThemes();formDialogThemes.show = true"
|
||||||
|
>New CSS Theme
|
||||||
|
<q-tooltip
|
||||||
|
>For security reason, custom css is only available to server
|
||||||
|
admins.</q-tooltip
|
||||||
|
></q-btn
|
||||||
|
>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
||||||
|
@ -259,6 +279,63 @@
|
||||||
</q-table>
|
</q-table>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
||||||
|
<q-card v-if="admin == 'True'">
|
||||||
|
<q-card-section>
|
||||||
|
<div class="row items-center no-wrap q-mb-md">
|
||||||
|
<div class="col">
|
||||||
|
<h5 class="text-subtitle1 q-my-none">Themes</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-table
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
:data="themeLinks"
|
||||||
|
row-key="id"
|
||||||
|
:columns="customCSSTable.columns"
|
||||||
|
:pagination.sync="customCSSTable.pagination"
|
||||||
|
>
|
||||||
|
{% raw %}
|
||||||
|
<template v-slot:header="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
|
{{ col.label }}
|
||||||
|
</q-th>
|
||||||
|
<q-th auto-width></q-th>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:body="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
|
{{ col.value }}
|
||||||
|
</q-td>
|
||||||
|
<q-td auto-width>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="xs"
|
||||||
|
@click="updateformDialog(props.row.css_id)"
|
||||||
|
icon="edit"
|
||||||
|
color="light-blue"
|
||||||
|
></q-btn>
|
||||||
|
</q-td>
|
||||||
|
<q-td auto-width>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="xs"
|
||||||
|
@click="deleteTheme(props.row.css_id)"
|
||||||
|
icon="cancel"
|
||||||
|
color="pink"
|
||||||
|
></q-btn>
|
||||||
|
</q-td>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
{% endraw %}
|
||||||
|
</q-table>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 col-md-5 q-gutter-y-md">
|
<div class="col-12 col-md-5 q-gutter-y-md">
|
||||||
|
@ -303,32 +380,6 @@
|
||||||
>
|
>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.trim="formDialogCharge.data.webhook"
|
|
||||||
type="url"
|
|
||||||
label="Webhook (URL to send transaction data to once paid)"
|
|
||||||
>
|
|
||||||
</q-input>
|
|
||||||
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.trim="formDialogCharge.data.completelink"
|
|
||||||
type="url"
|
|
||||||
label="Completed button URL"
|
|
||||||
>
|
|
||||||
</q-input>
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.trim="formDialogCharge.data.completelinktext"
|
|
||||||
type="text"
|
|
||||||
label="Completed button text (ie 'Back to merchant')"
|
|
||||||
>
|
|
||||||
</q-input>
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div v-if="walletLinks.length > 0">
|
<div v-if="walletLinks.length > 0">
|
||||||
|
@ -377,6 +428,52 @@
|
||||||
label="Wallet *"
|
label="Wallet *"
|
||||||
>
|
>
|
||||||
</q-select>
|
</q-select>
|
||||||
|
<q-toggle
|
||||||
|
v-model="showAdvanced"
|
||||||
|
label="Show advanced options"
|
||||||
|
></q-toggle>
|
||||||
|
<div v-if="showAdvanced" class="row">
|
||||||
|
<div class="col">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialogCharge.data.webhook"
|
||||||
|
type="url"
|
||||||
|
label="Webhook (URL to send transaction data to once paid)"
|
||||||
|
class="q-mt-lg"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialogCharge.data.completelink"
|
||||||
|
type="url"
|
||||||
|
label="Completed button URL"
|
||||||
|
class="q-mt-lg"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialogCharge.data.completelinktext"
|
||||||
|
type="text"
|
||||||
|
label="Completed button text (ie 'Back to merchant')"
|
||||||
|
class="q-mt-lg"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
|
<q-select
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
emit-value
|
||||||
|
v-model="formDialogCharge.data.custom_css"
|
||||||
|
:options="themeOptions"
|
||||||
|
label="Custom CSS theme (optional)"
|
||||||
|
class="q-mt-lg"
|
||||||
|
>
|
||||||
|
</q-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<q-btn
|
<q-btn
|
||||||
unelevated
|
unelevated
|
||||||
|
@ -394,6 +491,43 @@
|
||||||
</q-form>
|
</q-form>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
||||||
|
<q-dialog v-model="formDialogThemes.show" position="top">
|
||||||
|
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||||
|
<q-form @submit="sendFormDataThemes" class="q-gutter-md">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialogThemes.data.title"
|
||||||
|
type="text"
|
||||||
|
label="*Title"
|
||||||
|
></q-input>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialogThemes.data.custom_css"
|
||||||
|
type="textarea"
|
||||||
|
label="Custom CSS"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
|
<div class="row q-mt-lg">
|
||||||
|
<q-btn
|
||||||
|
v-if="formDialogThemes.data.css_id"
|
||||||
|
unelevated
|
||||||
|
color="primary"
|
||||||
|
type="submit"
|
||||||
|
>Update CSS theme</q-btn
|
||||||
|
>
|
||||||
|
<q-btn v-else unelevated color="primary" type="submit"
|
||||||
|
>Save CSS theme</q-btn
|
||||||
|
>
|
||||||
|
<q-btn @click="cancelThemes" flat color="grey" class="q-ml-auto"
|
||||||
|
>Cancel</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</q-form>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||||
<!-- lnbits/static/vendor
|
<!-- lnbits/static/vendor
|
||||||
|
@ -410,16 +544,21 @@
|
||||||
mixins: [windowMixin],
|
mixins: [windowMixin],
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
|
settings: {},
|
||||||
filter: '',
|
filter: '',
|
||||||
|
admin: '{{ admin }}',
|
||||||
balance: null,
|
balance: null,
|
||||||
walletLinks: [],
|
walletLinks: [],
|
||||||
chargeLinks: [],
|
chargeLinks: [],
|
||||||
onchainwallet: null,
|
themeLinks: [],
|
||||||
|
themeOptions: [],
|
||||||
|
onchainwallet: '',
|
||||||
rescanning: false,
|
rescanning: false,
|
||||||
mempool: {
|
mempool: {
|
||||||
endpoint: '',
|
endpoint: '',
|
||||||
network: 'Mainnet'
|
network: 'Mainnet'
|
||||||
},
|
},
|
||||||
|
showAdvanced: false,
|
||||||
|
|
||||||
chargesTable: {
|
chargesTable: {
|
||||||
columns: [
|
columns: [
|
||||||
|
@ -494,7 +633,25 @@
|
||||||
rowsPerPage: 10
|
rowsPerPage: 10
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
customCSSTable: {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'css_id',
|
||||||
|
align: 'left',
|
||||||
|
label: 'ID',
|
||||||
|
field: 'css_id'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Title',
|
||||||
|
field: 'title'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
pagination: {
|
||||||
|
rowsPerPage: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
formDialogCharge: {
|
formDialogCharge: {
|
||||||
show: false,
|
show: false,
|
||||||
data: {
|
data: {
|
||||||
|
@ -502,13 +659,24 @@
|
||||||
onchainwallet: '',
|
onchainwallet: '',
|
||||||
lnbits: false,
|
lnbits: false,
|
||||||
description: '',
|
description: '',
|
||||||
|
custom_css: '',
|
||||||
time: null,
|
time: null,
|
||||||
amount: null
|
amount: null
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
formDialogThemes: {
|
||||||
|
show: false,
|
||||||
|
data: {
|
||||||
|
custom_css: ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
cancelThemes: function (data) {
|
||||||
|
this.formDialogCharge.data.custom_css = ''
|
||||||
|
this.formDialogThemes.show = false
|
||||||
|
},
|
||||||
cancelCharge: function (data) {
|
cancelCharge: function (data) {
|
||||||
this.formDialogCharge.data.description = ''
|
this.formDialogCharge.data.description = ''
|
||||||
this.formDialogCharge.data.onchain = false
|
this.formDialogCharge.data.onchain = false
|
||||||
|
@ -517,6 +685,7 @@
|
||||||
this.formDialogCharge.data.time = null
|
this.formDialogCharge.data.time = null
|
||||||
this.formDialogCharge.data.amount = null
|
this.formDialogCharge.data.amount = null
|
||||||
this.formDialogCharge.data.webhook = ''
|
this.formDialogCharge.data.webhook = ''
|
||||||
|
this.formDialogCharge.data.custom_css = ''
|
||||||
this.formDialogCharge.data.completelink = ''
|
this.formDialogCharge.data.completelink = ''
|
||||||
this.formDialogCharge.show = false
|
this.formDialogCharge.show = false
|
||||||
},
|
},
|
||||||
|
@ -580,9 +749,39 @@
|
||||||
LNbits.utils.notifyApiError(error)
|
LNbits.utils.notifyApiError(error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sendFormDataCharge: function () {
|
|
||||||
|
getThemes: async function () {
|
||||||
|
try {
|
||||||
|
const {data} = await LNbits.api.request(
|
||||||
|
'GET',
|
||||||
|
'/satspay/api/v1/themes',
|
||||||
|
this.g.user.wallets[0].inkey
|
||||||
|
)
|
||||||
|
console.log(data)
|
||||||
|
this.themeLinks = data.map(c =>
|
||||||
|
mapCSS(
|
||||||
|
c,
|
||||||
|
this.themeLinks.find(old => old.css_id === c.css_id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
this.themeOptions = data.map(w => ({
|
||||||
|
id: w.css_id,
|
||||||
|
label: w.title + ' - ' + w.css_id
|
||||||
|
}))
|
||||||
|
} catch (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
sendFormDataThemes: function () {
|
||||||
const wallet = this.g.user.wallets[0].inkey
|
const wallet = this.g.user.wallets[0].inkey
|
||||||
|
const data = this.formDialogThemes.data
|
||||||
|
this.createTheme(wallet, data)
|
||||||
|
},
|
||||||
|
sendFormDataCharge: function () {
|
||||||
|
this.formDialogCharge.data.custom_css = this.formDialogCharge.data.custom_css?.id
|
||||||
const data = this.formDialogCharge.data
|
const data = this.formDialogCharge.data
|
||||||
|
const wallet = this.g.user.wallets[0].inkey
|
||||||
data.amount = parseInt(data.amount)
|
data.amount = parseInt(data.amount)
|
||||||
data.time = parseInt(data.time)
|
data.time = parseInt(data.time)
|
||||||
data.lnbitswallet = data.lnbits ? data.lnbitswallet : null
|
data.lnbitswallet = data.lnbits ? data.lnbitswallet : null
|
||||||
|
@ -651,6 +850,68 @@
|
||||||
this.rescanning = false
|
this.rescanning = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updateformDialog: function (themeId) {
|
||||||
|
const theme = _.findWhere(this.themeLinks, {css_id: themeId})
|
||||||
|
console.log(theme.css_id)
|
||||||
|
this.formDialogThemes.data.css_id = theme.css_id
|
||||||
|
this.formDialogThemes.data.title = theme.title
|
||||||
|
this.formDialogThemes.data.custom_css = theme.custom_css
|
||||||
|
this.formDialogThemes.show = true
|
||||||
|
},
|
||||||
|
createTheme: async function (wallet, data) {
|
||||||
|
console.log(data.css_id)
|
||||||
|
try {
|
||||||
|
if (data.css_id) {
|
||||||
|
const resp = await LNbits.api.request(
|
||||||
|
'POST',
|
||||||
|
'/satspay/api/v1/themes/' + data.css_id,
|
||||||
|
wallet,
|
||||||
|
data
|
||||||
|
)
|
||||||
|
this.themeLinks = _.reject(this.themeLinks, function (obj) {
|
||||||
|
return obj.css_id === data.css_id
|
||||||
|
})
|
||||||
|
this.themeLinks.unshift(mapCSS(resp.data))
|
||||||
|
} else {
|
||||||
|
const resp = await LNbits.api.request(
|
||||||
|
'POST',
|
||||||
|
'/satspay/api/v1/themes',
|
||||||
|
wallet,
|
||||||
|
data
|
||||||
|
)
|
||||||
|
this.themeLinks.unshift(mapCSS(resp.data))
|
||||||
|
}
|
||||||
|
this.formDialogThemes.show = false
|
||||||
|
this.formDialogThemes.data = {
|
||||||
|
title: '',
|
||||||
|
custom_css: ''
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('cun')
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteTheme: function (themeId) {
|
||||||
|
const theme = _.findWhere(this.themeLinks, {id: themeId})
|
||||||
|
LNbits.utils
|
||||||
|
.confirmDialog('Are you sure you want to delete this theme?')
|
||||||
|
.onOk(async () => {
|
||||||
|
try {
|
||||||
|
const response = await LNbits.api.request(
|
||||||
|
'DELETE',
|
||||||
|
'/satspay/api/v1/themes/' + themeId,
|
||||||
|
this.g.user.wallets[0].adminkey
|
||||||
|
)
|
||||||
|
|
||||||
|
this.themeLinks = _.reject(this.themeLinks, function (obj) {
|
||||||
|
return obj.css_id === themeId
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
createCharge: async function (wallet, data) {
|
createCharge: async function (wallet, data) {
|
||||||
try {
|
try {
|
||||||
const resp = await LNbits.api.request(
|
const resp = await LNbits.api.request(
|
||||||
|
@ -703,6 +964,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: async function () {
|
created: async function () {
|
||||||
|
if (this.admin == 'True') {
|
||||||
|
await this.getThemes()
|
||||||
|
}
|
||||||
await this.getCharges()
|
await this.getCharges()
|
||||||
await this.getWalletConfig()
|
await this.getWalletConfig()
|
||||||
await this.getWalletLinks()
|
await this.getWalletLinks()
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
import json
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
|
from fastapi import Response
|
||||||
from fastapi.param_functions import Depends
|
from fastapi.param_functions import Depends
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from loguru import logger
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
from starlette.responses import HTMLResponse
|
from starlette.responses import HTMLResponse
|
||||||
|
@ -9,17 +12,21 @@ from starlette.responses import HTMLResponse
|
||||||
from lnbits.core.models import User
|
from lnbits.core.models import User
|
||||||
from lnbits.decorators import check_user_exists
|
from lnbits.decorators import check_user_exists
|
||||||
from lnbits.extensions.satspay.helpers import public_charge
|
from lnbits.extensions.satspay.helpers import public_charge
|
||||||
|
from lnbits.settings import LNBITS_ADMIN_USERS
|
||||||
|
|
||||||
from . import satspay_ext, satspay_renderer
|
from . import satspay_ext, satspay_renderer
|
||||||
from .crud import get_charge
|
from .crud import get_charge, get_theme
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="templates")
|
templates = Jinja2Templates(directory="templates")
|
||||||
|
|
||||||
|
|
||||||
@satspay_ext.get("/", response_class=HTMLResponse)
|
@satspay_ext.get("/", response_class=HTMLResponse)
|
||||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
async def index(request: Request, user: User = Depends(check_user_exists)):
|
||||||
|
admin = False
|
||||||
|
if LNBITS_ADMIN_USERS and user.id in LNBITS_ADMIN_USERS:
|
||||||
|
admin = True
|
||||||
return satspay_renderer().TemplateResponse(
|
return satspay_renderer().TemplateResponse(
|
||||||
"satspay/index.html", {"request": request, "user": user.dict()}
|
"satspay/index.html", {"request": request, "user": user.dict(), "admin": admin}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,3 +47,11 @@ async def display(request: Request, charge_id: str):
|
||||||
"network": charge.config.network,
|
"network": charge.config.network,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@satspay_ext.get("/css/{css_id}")
|
||||||
|
async def display(css_id: str, response: Response):
|
||||||
|
theme = await get_theme(css_id)
|
||||||
|
if theme:
|
||||||
|
return Response(content=theme.custom_css, media_type="text/css")
|
||||||
|
return None
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import json
|
import json
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
|
import httpx
|
||||||
from fastapi.params import Depends
|
from fastapi.params import Depends
|
||||||
|
from loguru import logger
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
|
from lnbits.core.crud import get_wallet
|
||||||
from lnbits.decorators import (
|
from lnbits.decorators import (
|
||||||
WalletTypeInfo,
|
WalletTypeInfo,
|
||||||
get_key_type,
|
get_key_type,
|
||||||
|
@ -11,17 +14,22 @@ from lnbits.decorators import (
|
||||||
require_invoice_key,
|
require_invoice_key,
|
||||||
)
|
)
|
||||||
from lnbits.extensions.satspay import satspay_ext
|
from lnbits.extensions.satspay import satspay_ext
|
||||||
|
from lnbits.settings import LNBITS_ADMIN_EXTENSIONS, LNBITS_ADMIN_USERS
|
||||||
|
|
||||||
from .crud import (
|
from .crud import (
|
||||||
check_address_balance,
|
check_address_balance,
|
||||||
create_charge,
|
create_charge,
|
||||||
delete_charge,
|
delete_charge,
|
||||||
|
delete_theme,
|
||||||
get_charge,
|
get_charge,
|
||||||
get_charges,
|
get_charges,
|
||||||
|
get_theme,
|
||||||
|
get_themes,
|
||||||
|
save_theme,
|
||||||
update_charge,
|
update_charge,
|
||||||
)
|
)
|
||||||
from .helpers import call_webhook, public_charge
|
from .helpers import call_webhook, public_charge
|
||||||
from .models import CreateCharge
|
from .models import CreateCharge, SatsPayThemes
|
||||||
|
|
||||||
#############################CHARGES##########################
|
#############################CHARGES##########################
|
||||||
|
|
||||||
|
@ -126,3 +134,49 @@ async def api_charge_balance(charge_id):
|
||||||
await update_charge(charge_id=charge.id, extra=json.dumps(extra))
|
await update_charge(charge_id=charge.id, extra=json.dumps(extra))
|
||||||
|
|
||||||
return {**public_charge(charge)}
|
return {**public_charge(charge)}
|
||||||
|
|
||||||
|
|
||||||
|
#############################THEMES##########################
|
||||||
|
|
||||||
|
|
||||||
|
@satspay_ext.post("/api/v1/themes")
|
||||||
|
@satspay_ext.post("/api/v1/themes/{css_id}")
|
||||||
|
async def api_themes_save(
|
||||||
|
data: SatsPayThemes,
|
||||||
|
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
||||||
|
css_id: str = None,
|
||||||
|
):
|
||||||
|
if LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.FORBIDDEN,
|
||||||
|
detail="Only server admins can create themes.",
|
||||||
|
)
|
||||||
|
if css_id:
|
||||||
|
theme = await save_theme(css_id=css_id, data=data)
|
||||||
|
else:
|
||||||
|
data.user = wallet.wallet.user
|
||||||
|
theme = await save_theme(data=data)
|
||||||
|
return theme
|
||||||
|
|
||||||
|
|
||||||
|
@satspay_ext.get("/api/v1/themes")
|
||||||
|
async def api_themes_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||||
|
try:
|
||||||
|
return await get_themes(wallet.wallet.user)
|
||||||
|
except HTTPException:
|
||||||
|
logger.error("Error loading satspay themes")
|
||||||
|
logger.error(HTTPException)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
@satspay_ext.delete("/api/v1/themes/{theme_id}")
|
||||||
|
async def api_charge_delete(theme_id, wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||||
|
theme = await get_theme(theme_id)
|
||||||
|
|
||||||
|
if not theme:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.NOT_FOUND, detail="Theme does not exist."
|
||||||
|
)
|
||||||
|
|
||||||
|
await delete_theme(theme_id)
|
||||||
|
return "", HTTPStatus.NO_CONTENT
|
||||||
|
|
|
@ -3,25 +3,25 @@ async def m001_initial(db):
|
||||||
Initial wallet table.
|
Initial wallet table.
|
||||||
"""
|
"""
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
CREATE TABLE watchonly.wallets (
|
CREATE TABLE watchonly.wallets (
|
||||||
id TEXT NOT NULL PRIMARY KEY,
|
id TEXT NOT NULL PRIMARY KEY,
|
||||||
"user" TEXT,
|
"user" TEXT,
|
||||||
masterpub TEXT NOT NULL,
|
masterpub TEXT NOT NULL,
|
||||||
title TEXT NOT NULL,
|
title TEXT NOT NULL,
|
||||||
address_no INTEGER NOT NULL DEFAULT 0,
|
address_no INTEGER NOT NULL DEFAULT 0,
|
||||||
balance INTEGER NOT NULL
|
balance {db.big_int} NOT NULL
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
CREATE TABLE watchonly.addresses (
|
CREATE TABLE watchonly.addresses (
|
||||||
id TEXT NOT NULL PRIMARY KEY,
|
id TEXT NOT NULL PRIMARY KEY,
|
||||||
address TEXT NOT NULL,
|
address TEXT NOT NULL,
|
||||||
wallet TEXT NOT NULL,
|
wallet TEXT NOT NULL,
|
||||||
amount INTEGER NOT NULL
|
amount {db.big_int} NOT NULL
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,13 +3,13 @@ async def m001_initial(db):
|
||||||
Creates an improved withdraw table and migrates the existing data.
|
Creates an improved withdraw table and migrates the existing data.
|
||||||
"""
|
"""
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
CREATE TABLE withdraw.withdraw_links (
|
CREATE TABLE withdraw.withdraw_links (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
wallet TEXT,
|
wallet TEXT,
|
||||||
title TEXT,
|
title TEXT,
|
||||||
min_withdrawable INTEGER DEFAULT 1,
|
min_withdrawable {db.big_int} DEFAULT 1,
|
||||||
max_withdrawable INTEGER DEFAULT 1,
|
max_withdrawable {db.big_int} DEFAULT 1,
|
||||||
uses INTEGER DEFAULT 1,
|
uses INTEGER DEFAULT 1,
|
||||||
wait_time INTEGER,
|
wait_time INTEGER,
|
||||||
is_unique INTEGER DEFAULT 0,
|
is_unique INTEGER DEFAULT 0,
|
||||||
|
@ -28,13 +28,13 @@ async def m002_change_withdraw_table(db):
|
||||||
Creates an improved withdraw table and migrates the existing data.
|
Creates an improved withdraw table and migrates the existing data.
|
||||||
"""
|
"""
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
CREATE TABLE withdraw.withdraw_link (
|
CREATE TABLE withdraw.withdraw_link (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
wallet TEXT,
|
wallet TEXT,
|
||||||
title TEXT,
|
title TEXT,
|
||||||
min_withdrawable INTEGER DEFAULT 1,
|
min_withdrawable {db.big_int} DEFAULT 1,
|
||||||
max_withdrawable INTEGER DEFAULT 1,
|
max_withdrawable {db.big_int} DEFAULT 1,
|
||||||
uses INTEGER DEFAULT 1,
|
uses INTEGER DEFAULT 1,
|
||||||
wait_time INTEGER,
|
wait_time INTEGER,
|
||||||
is_unique INTEGER DEFAULT 0,
|
is_unique INTEGER DEFAULT 0,
|
||||||
|
|
127
requirements.txt
127
requirements.txt
|
@ -1,55 +1,72 @@
|
||||||
aiofiles==0.8.0
|
aiofiles==0.8.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
anyio==3.6.1
|
anyio==3.6.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
asyncio==3.4.3
|
asgiref==3.4.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
attrs==21.4.0
|
asn1crypto==1.5.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
bech32==1.2.0
|
async-timeout==4.0.2 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
bitstring==3.1.9
|
attrs==21.2.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
cerberus==1.3.4
|
base58==2.1.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
certifi==2022.6.15
|
bech32==1.2.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
cffi==1.15.0
|
bitstring==3.1.9 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
click==8.1.3
|
cerberus==1.3.4 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
ecdsa==0.18.0
|
certifi==2021.5.30 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
embit==0.5.0
|
cffi==1.15.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
environs==9.5.0
|
charset-normalizer==2.0.6 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
fastapi==0.79.0
|
click==8.0.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
h11==0.12.0
|
coincurve==17.0.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
httpcore==0.15.0
|
colorama==0.4.5 ; python_version >= "3.7" and python_version < "4.0" and platform_system == "Windows" or python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32"
|
||||||
httptools==0.4.0
|
cryptography==36.0.2 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
httpx==0.23.0
|
ecdsa==0.17.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
idna==3.3
|
embit==0.4.9 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
jinja2==3.0.1
|
enum34==1.1.10 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
lnurl==0.3.6
|
environs==9.3.3 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
loguru==0.6.0
|
fastapi==0.78.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
markupsafe==2.1.1
|
grpcio==1.49.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
marshmallow==3.17.0
|
h11==0.12.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
outcome==1.2.0
|
httpcore==0.15.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
packaging==21.3
|
httptools==0.4.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
psycopg2-binary==2.9.3
|
httpx==0.23.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
pycparser==2.21
|
idna==3.2 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
pycryptodomex==3.15.0
|
importlib-metadata==4.8.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
pydantic==1.9.1
|
jinja2==3.0.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
pyngrok==5.1.0
|
lnurl==0.3.6 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
pyparsing==3.0.9
|
loguru==0.5.3 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
pypng==0.20220715.0
|
markupsafe==2.0.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
pyqrcode==1.2.1
|
marshmallow==3.17.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
pyscss==1.4.0
|
outcome==1.1.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
python-dotenv==0.20.0
|
packaging==21.3 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
pyyaml==6.0
|
pathlib2==2.3.7.post1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
represent==1.6.0.post0
|
protobuf==4.21.7 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
rfc3986==1.5.0
|
psycopg2-binary==2.9.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
secp256k1==0.14.0
|
pycparser==2.21 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
shortuuid==1.0.9
|
pycryptodomex==3.14.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
six==1.16.0
|
pydantic==1.8.2 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
sniffio==1.2.0
|
pyln-bolt7==1.0.246 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
sqlalchemy-aio==0.17.0
|
pyln-client==0.11.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
sqlalchemy==1.3.23
|
pyln-proto==0.11.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
sse-starlette==0.10.3
|
pyparsing==3.0.9 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
starlette==0.19.1
|
pypng==0.0.21 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
typing-extensions==4.3.0
|
pyqrcode==1.2.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
uvicorn==0.18.2
|
pyscss==1.4.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
uvloop==0.16.0
|
pysocks==1.7.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
watchfiles==0.16.0
|
python-dotenv==0.19.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
websockets==10.3
|
pyyaml==5.4.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
websocket-client==1.3.3
|
represent==1.6.0.post0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
async-timeout==4.0.2
|
rfc3986==1.5.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
setuptools==65.4.0
|
rfc3986[idna2008]==1.5.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
|
secp256k1==0.14.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
|
setuptools==65.4.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
|
shortuuid==1.0.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
|
six==1.16.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
|
sniffio==1.2.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
|
sqlalchemy-aio==0.17.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
|
sqlalchemy==1.3.23 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
|
sse-starlette==0.6.2 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
|
starlette==0.19.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
|
typing-extensions==3.10.0.2 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
|
uvicorn==0.18.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
|
uvloop==0.16.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
|
watchgod==0.7 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
|
websocket-client==1.3.3 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
|
websockets==10.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
|
win32-setctime==1.1.0 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32"
|
||||||
|
zipp==3.5.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||||
|
|
Loading…
Add table
Reference in a new issue