Now you can have multiple themes

This commit is contained in:
ben
2022-11-24 14:54:19 +00:00
parent b53ecdac17
commit f56ed40c77
10 changed files with 304 additions and 94 deletions

View File

@@ -2,7 +2,5 @@
"name": "SatsPay Server",
"short_description": "Create onchain and LN charges",
"icon": "payment",
"contributors": [
"arcbtc"
]
"contributors": ["arcbtc"]
}

View File

@@ -10,7 +10,8 @@ from ..watchonly.crud import get_config, get_fresh_address
# from lnbits.db import open_ext_db
from . import db
from .models import Charges, CreateCharge, SatsPaySettings
from .models import Charges, CreateCharge, SatsPayThemes
###############CHARGES##########################
@@ -48,9 +49,10 @@ async def create_charge(user: str, data: CreateCharge) -> Charges:
completelinktext,
time,
amount,
custom_css,
balance
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
charge_id,
@@ -66,6 +68,7 @@ async def create_charge(user: str, data: CreateCharge) -> Charges:
data.completelinktext,
data.time,
data.amount,
data.custom_css,
0,
),
)
@@ -132,41 +135,51 @@ async def get_charge_config(charge_id: str):
################## SETTINGS ###################
async def save_settings(user_id: str, data: SatsPaySettings):
async def save_theme(data: SatsPayThemes, css_id: str = None):
# insert or update
row = await db.fetchone(
"""SELECT user_id FROM satspay.settings WHERE user_id = ?""", (user_id,)
)
if row:
if css_id:
await db.execute(
"""
UPDATE satspay.settings SET custom_css = ? WHERE user_id = ?
UPDATE satspay.themes SET custom_css = ?, title = ? WHERE css_id = ?
""",
(data.custom_css, user_id),
(data.custom_css, data.title, css_id),
)
else:
css_id = urlsafe_short_hash()
await db.execute(
"""
INSERT INTO satspay.settings (
user_id,
INSERT INTO satspay.themes (
css_id,
title,
user,
custom_css
)
VALUES (?, ?)
VALUES (?, ?, ?, ?)
""",
(
user_id,
css_id,
data.title,
data.user,
data.custom_css,
),
)
return True
return await get_theme(css_id)
async def get_settings(user_id: str) -> SatsPaySettings:
row = await db.fetchone(
"""SELECT * FROM satspay.settings WHERE user_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,),
)
if row:
return SatsPaySettings.from_row(row)
else:
return None
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,))

View File

@@ -28,16 +28,26 @@ async def m001_initial(db):
)
async def m002_add_settings_table(db):
async def m002_add_themes_table(db):
"""
Settings table
Themes table
"""
await db.execute(
"""
CREATE TABLE satspay.settings (
user_id TEXT,
CREATE TABLE satspay.themes (
css_id TEXT,
user TEXT,
title TEXT,
custom_css TEXT
);
"""
)
async def m003_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;")

View File

@@ -13,6 +13,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)
@@ -28,6 +29,7 @@ class Charges(BaseModel):
webhook: Optional[str]
completelink: Optional[str]
completelinktext: Optional[str] = "Back to Merchant"
custom_css: Optional[str]
time: int
amount: int
balance: int
@@ -56,9 +58,12 @@ class Charges(BaseModel):
return False
class SatsPaySettings(BaseModel):
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) -> "SatsPaySettings":
def from_row(cls, row: Row) -> "SatsPayThemes":
return cls(**dict(row))

View File

@@ -27,5 +27,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) : ''

View File

@@ -5,7 +5,13 @@
WatchOnly extension, we highly reccomend using a fresh extended public Key
specifically for SatsPayServer!<br />
<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>
<br />

View File

@@ -291,7 +291,7 @@
</div>
{% endblock %} {% block styles %}
<link
href="/satspay/css/{{ charge_data.id }}"
href="/satspay/css/{{ charge_data.custom_css }}"
rel="stylesheet"
type="text/css"
/>
@@ -315,6 +315,7 @@
customCss: '',
charge: JSON.parse('{{charge_data | tojson}}'),
mempool_endpoint: '{{mempool_endpoint}}',
css_id: '{{ charge_data.css_id }}',
pendingFunds: 0,
ws: null,
newProgress: 0.4,
@@ -440,8 +441,10 @@
},
created: async function () {
// Remove a user defined theme
document.body.setAttribute('data-theme', '')
console.log(this.charge.custom_css)
if (this.charge.custom_css) {
document.body.setAttribute('data-theme', '')
}
if (this.charge.lnbitswallet) this.payInvoice()
else this.payOnchain()
await this.checkBalances()

View File

@@ -12,8 +12,8 @@
<q-btn
unelevated
color="primary"
@click="getSettings();formDialogSettings.show = true"
>SatsPay settings
@click="getThemes();formDialogThemes.show = true"
>New CSS Theme
</q-btn>
</q-card-section>
</q-card>
@@ -261,6 +261,63 @@
</q-table>
</q-card-section>
</q-card>
<q-card>
<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 class="col-12 col-md-5 q-gutter-y-md">
@@ -379,6 +436,15 @@
label="Wallet *"
>
</q-select>
<q-select
filled
dense
emit-value
v-model="formDialogCharge.data.custom_css"
:options="themeOptions"
label="Custom CSS theme (optional)"
>
</q-select>
<div class="row q-mt-lg">
<q-btn
unelevated
@@ -397,23 +463,36 @@
</q-card>
</q-dialog>
<q-dialog v-model="formDialogSettings.show" position="top">
<q-dialog v-model="formDialogThemes.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendFormDataSettings" class="q-gutter-md">
<q-form @submit="sendFormDataThemes" class="q-gutter-md">
<q-input
filled
dense
v-model.trim="formDialogSettings.data.custom_css"
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-tooltip
>Custom CSS to apply styles to your SatsPay invoice</q-tooltip
>
</q-input>
<div class="row q-mt-lg">
<q-btn unelevated color="primary" type="submit">Save Settings</q-btn>
<q-btn @click="cancelSettings" flat color="grey" class="q-ml-auto"
<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>
@@ -441,6 +520,8 @@
balance: null,
walletLinks: [],
chargeLinks: [],
themeLinks: [],
themeOptions: [],
onchainwallet: '',
rescanning: false,
mempool: {
@@ -520,7 +601,25 @@
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: {
show: false,
data: {
@@ -528,11 +627,12 @@
onchainwallet: '',
lnbits: false,
description: '',
custom_css: '',
time: null,
amount: null
}
},
formDialogSettings: {
formDialogThemes: {
show: false,
data: {
custom_css: ''
@@ -541,9 +641,9 @@
}
},
methods: {
cancelSettings: function (data) {
cancelThemes: function (data) {
this.formDialogCharge.data.custom_css = ''
this.formDialogSettings.show = false
this.formDialogThemes.show = false
},
cancelCharge: function (data) {
this.formDialogCharge.data.description = ''
@@ -552,6 +652,7 @@
this.formDialogCharge.data.time = null
this.formDialogCharge.data.amount = null
this.formDialogCharge.data.webhook = ''
this.formDialogCharge.data.custom_css = ''
this.formDialogCharge.data.completelink = ''
this.formDialogCharge.show = false
},
@@ -615,30 +716,38 @@
}
},
getSettings: async function () {
getThemes: async function () {
try {
const {data} = await LNbits.api.request(
'GET',
'/satspay/api/v1/settings',
'/satspay/api/v1/themes',
this.g.user.wallets[0].inkey
)
if (data) {
this.formDialogSettings.data.custom_css = data.custom_css
}
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)
}
},
sendFormDataSettings: function () {
sendFormDataThemes: function () {
const wallet = this.g.user.wallets[0].inkey
const data = this.formDialogSettings.data
data.custom_css = data.custom_css
this.saveSettings(wallet, data)
const data = this.formDialogThemes.data
this.createTheme(wallet, data)
},
sendFormDataCharge: function () {
const wallet = this.g.user.wallets[0].inkey
this.formDialogCharge.data.custom_css = this.formDialogCharge.data.custom_css.id
const data = this.formDialogCharge.data
const wallet = this.g.user.wallets[0].inkey
data.amount = parseInt(data.amount)
data.time = parseInt(data.time)
data.onchainwallet = this.onchainwallet?.id
@@ -706,23 +815,68 @@
this.rescanning = false
}
},
saveSettings: async function (wallet, data) {
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 {
const resp = await LNbits.api.request(
'POST',
'/satspay/api/v1/settings',
wallet,
data
)
this.formDialogSettings.show = false
this.formDialogSettings.data = {
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) {
try {
const resp = await LNbits.api.request(
@@ -775,7 +929,7 @@
}
},
created: async function () {
await this.getSettings()
await this.getThemes()
await this.getCharges()
await this.getWalletLinks()
await this.getWalletConfig()

View File

@@ -12,7 +12,7 @@ from lnbits.core.models import User
from lnbits.decorators import check_user_exists
from . import satspay_ext, satspay_renderer
from .crud import get_charge, get_charge_config, get_settings
from .crud import get_charge, get_charge_config, get_themes, get_theme
templates = Jinja2Templates(directory="templates")
@@ -48,16 +48,10 @@ async def display(request: Request, charge_id: str):
)
@satspay_ext.get("/css/{charge_id}")
async def display(charge_id: str, response: Response):
charge = await get_charge(charge_id)
if not charge:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist."
)
wallet = await get_wallet(charge.lnbitswallet)
settings = await get_settings(wallet.user)
if settings:
return Response(content=settings.custom_css, media_type="text/css")
@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

View File

@@ -20,12 +20,14 @@ from .crud import (
delete_charge,
get_charge,
get_charges,
get_settings,
save_settings,
get_theme,
get_themes,
delete_theme,
save_theme,
update_charge,
)
from .helpers import compact_charge
from .models import CreateCharge, SatsPaySettings
from .models import CreateCharge, SatsPayThemes
#############################CHARGES##########################
@@ -141,22 +143,42 @@ async def api_charge_balance(charge_id):
}
#############################CHARGES##########################
#############################THEMES##########################
@satspay_ext.post("/api/v1/settings")
async def api_settings_save(
data: SatsPaySettings, wallet: WalletTypeInfo = Depends(require_invoice_key)
@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,
):
await save_settings(user_id=wallet.wallet.user, data=data)
return True
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/settings")
async def api_settings_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
@satspay_ext.get("/api/v1/themes")
async def api_themes_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
try:
return await get_settings(wallet.wallet.user)
return await get_themes(wallet.wallet.user)
except HTTPException:
logger.error("Error loading satspay settings")
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