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 f78e85ffc..b1bbccebe 100644 --- a/lnbits/extensions/satspay/crud.py +++ b/lnbits/extensions/satspay/crud.py @@ -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,)) diff --git a/lnbits/extensions/satspay/migrations.py b/lnbits/extensions/satspay/migrations.py index 96fcc32ba..73fa8f3d6 100644 --- a/lnbits/extensions/satspay/migrations.py +++ b/lnbits/extensions/satspay/migrations.py @@ -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;") diff --git a/lnbits/extensions/satspay/models.py b/lnbits/extensions/satspay/models.py index 942263574..86e28c15e 100644 --- a/lnbits/extensions/satspay/models.py +++ b/lnbits/extensions/satspay/models.py @@ -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)) diff --git a/lnbits/extensions/satspay/static/js/utils.js b/lnbits/extensions/satspay/static/js/utils.js index 9b4abbfca..20dbae730 100644 --- a/lnbits/extensions/satspay/static/js/utils.js +++ b/lnbits/extensions/satspay/static/js/utils.js @@ -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) : '' 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 a6375d360..019e7dec0 100644 --- a/lnbits/extensions/satspay/templates/satspay/display.html +++ b/lnbits/extensions/satspay/templates/satspay/display.html @@ -291,7 +291,7 @@ {% endblock %} {% block styles %} @@ -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() diff --git a/lnbits/extensions/satspay/templates/satspay/index.html b/lnbits/extensions/satspay/templates/satspay/index.html index ad80c2fc9..b10c67260 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 @@ -261,6 +261,63 @@ + + + +
+
+
Themes
+
+
+ + {% raw %} + + + + {% endraw %} + +
+
@@ -379,6 +436,15 @@ label="Wallet *" > + +
- + - + + - Custom CSS to apply styles to your SatsPay invoice
- Save Settings - Update CSS theme + Save CSS theme + Cancel
@@ -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() diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py index 6b14c2788..88e4238cd 100644 --- a/lnbits/extensions/satspay/views.py +++ b/lnbits/extensions/satspay/views.py @@ -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 diff --git a/lnbits/extensions/satspay/views_api.py b/lnbits/extensions/satspay/views_api.py index f6aa18149..8c8299e6a 100644 --- a/lnbits/extensions/satspay/views_api.py +++ b/lnbits/extensions/satspay/views_api.py @@ -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