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 6ed324a4c..113b0cb44 100644 --- a/lnbits/extensions/satspay/crud.py +++ b/lnbits/extensions/satspay/crud.py @@ -10,7 +10,8 @@ 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, SatsPaySettings +from .models import Charges, CreateCharge, SatsPayThemes + ###############CHARGES########################## @@ -53,7 +54,8 @@ async def create_charge(user: str, data: CreateCharge) -> Charges: time, amount, balance, - extra + extra, + custom_css, ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, @@ -71,6 +73,7 @@ async def create_charge(user: str, data: CreateCharge) -> Charges: data.completelinktext, data.time, data.amount, + data.custom_css, 0, data.extra, ), @@ -124,41 +127,51 @@ async def check_address_balance(charge_id: str) -> Optional[Charges]: ################## 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,)) diff --git a/lnbits/extensions/satspay/migrations.py b/lnbits/extensions/satspay/migrations.py index fe0a9d9c6..8787cc8b3 100644 --- a/lnbits/extensions/satspay/migrations.py +++ b/lnbits/extensions/satspay/migrations.py @@ -38,16 +38,26 @@ async def m002_add_charge_extra_data(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;") diff --git a/lnbits/extensions/satspay/models.py b/lnbits/extensions/satspay/models.py index 94c0938c0..4227cabc3 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 @@ -73,9 +75,12 @@ class Charges(BaseModel): def must_call_webhook(self): return self.webhook and self.paid and self.config.webhook_success == 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)) 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 6d34a386f..437f81c7f 100644 --- a/lnbits/extensions/satspay/templates/satspay/display.html +++ b/lnbits/extensions/satspay/templates/satspay/display.html @@ -299,7 +299,7 @@ {% endblock %} {% block styles %} @@ -324,6 +324,7 @@ charge: JSON.parse('{{charge_data | tojson}}'), mempoolEndpoint: '{{mempool_endpoint}}', network: '{{network}}', + css_id: '{{ charge_data.css_id }}', pendingFunds: 0, ws: null, newProgress: 0.4, @@ -462,8 +463,10 @@ await this.getCustomCss() if (this.charge.payment_request) this.payInvoice() // 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() diff --git a/lnbits/extensions/satspay/templates/satspay/index.html b/lnbits/extensions/satspay/templates/satspay/index.html index fb7c42855..08bf785dc 100644 --- a/lnbits/extensions/satspay/templates/satspay/index.html +++ b/lnbits/extensions/satspay/templates/satspay/index.html @@ -12,8 +12,8 @@ SatsPay settings + @click="getThemes();formDialogThemes.show = true" + >New CSS Theme @@ -266,6 +266,63 @@ + + + +
+
+
Themes
+
+
+ + {% raw %} + + + + {% endraw %} + +
+
@@ -384,6 +441,15 @@ label="Wallet *" > + +
- + - + + - Custom CSS to apply styles to your SatsPay invoice
- Save Settings - Update CSS theme + Save CSS theme + Cancel
@@ -446,7 +525,9 @@ balance: null, walletLinks: [], chargeLinks: [], - onchainwallet: null, + themeLinks: [], + themeOptions: [], + onchainwallet: '', rescanning: false, mempool: { endpoint: '', @@ -526,7 +607,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: { @@ -534,11 +633,12 @@ onchainwallet: '', lnbits: false, description: '', + custom_css: '', time: null, amount: null } }, - formDialogSettings: { + formDialogThemes: { show: false, data: { custom_css: '' @@ -547,9 +647,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 = '' @@ -559,6 +659,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 }, @@ -623,30 +724,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.lnbitswallet = data.lnbits ? data.lnbitswallet : null @@ -715,23 +824,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( @@ -784,7 +938,7 @@ } }, created: async function () { - await this.getSettings() + await this.getThemes() await this.getCharges() await this.getWalletConfig() await this.getWalletLinks() diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py index 0618ce1bb..72362f862 100644 --- a/lnbits/extensions/satspay/views.py +++ b/lnbits/extensions/satspay/views.py @@ -12,7 +12,7 @@ from lnbits.decorators import check_user_exists from lnbits.extensions.satspay.helpers import public_charge 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") @@ -43,16 +43,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 diff --git a/lnbits/extensions/satspay/views_api.py b/lnbits/extensions/satspay/views_api.py index 429453df4..d5b510ab1 100644 --- a/lnbits/extensions/satspay/views_api.py +++ b/lnbits/extensions/satspay/views_api.py @@ -21,13 +21,15 @@ 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 call_webhook, public_charge from .helpers import compact_charge -from .models import CreateCharge, SatsPaySettings +from .models import CreateCharge, SatsPayThemes #############################CHARGES########################## @@ -145,22 +147,42 @@ async def api_charge_balance(charge_id): return {**public_charge(charge)} -#############################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