diff --git a/models.py b/models.py index 73e6687..58842d5 100644 --- a/models.py +++ b/models.py @@ -37,6 +37,7 @@ class MerchantProfile(BaseModel): about: str | None = None picture: str | None = None banner: str | None = None + website: str | None = None nip05: str | None = None lud16: str | None = None @@ -90,11 +91,23 @@ class Merchant(PartialMerchant, Nostrable): return merchant def to_nostr_event(self, pubkey: str) -> NostrEvent: - content = { - "name": self.config.name, - "about": self.config.about, - "picture": self.config.picture, - } + content: dict[str, str] = {} + if self.config.name: + content["name"] = self.config.name + if self.config.display_name: + content["display_name"] = self.config.display_name + if self.config.about: + content["about"] = self.config.about + if self.config.picture: + content["picture"] = self.config.picture + if self.config.banner: + content["banner"] = self.config.banner + if self.config.website: + content["website"] = self.config.website + if self.config.nip05: + content["nip05"] = self.config.nip05 + if self.config.lud16: + content["lud16"] = self.config.lud16 event = NostrEvent( pubkey=pubkey, created_at=round(time.time()), diff --git a/services.py b/services.py index 4039dbb..b978e39 100644 --- a/services.py +++ b/services.py @@ -149,9 +149,8 @@ async def update_merchant_to_nostr( stall.event_id = event.id stall.event_created_at = event.created_at await update_stall(merchant.id, stall) - if delete_merchant: - # merchant profile updates not supported yet - event = await sign_and_send_to_nostr(merchant, merchant, delete_merchant) + # Always publish merchant profile (kind 0) + event = await sign_and_send_to_nostr(merchant, merchant, delete_merchant) assert event merchant.config.event_id = event.id return merchant diff --git a/static/components/edit-profile-dialog.js b/static/components/edit-profile-dialog.js new file mode 100644 index 0000000..aacfefc --- /dev/null +++ b/static/components/edit-profile-dialog.js @@ -0,0 +1,85 @@ +window.app.component('edit-profile-dialog', { + name: 'edit-profile-dialog', + template: '#edit-profile-dialog', + delimiters: ['${', '}'], + props: ['model-value', 'merchant-id', 'merchant-config', 'adminkey'], + emits: ['update:model-value', 'profile-updated'], + data: function () { + return { + saving: false, + formData: { + name: '', + display_name: '', + about: '', + picture: '', + banner: '', + website: '', + nip05: '', + lud16: '' + } + } + }, + computed: { + show: { + get() { + return this.modelValue + }, + set(value) { + this.$emit('update:model-value', value) + } + } + }, + methods: { + saveProfile: async function () { + this.saving = true + try { + const config = { + ...this.merchantConfig, + name: this.formData.name || null, + display_name: this.formData.display_name || null, + about: this.formData.about || null, + picture: this.formData.picture || null, + banner: this.formData.banner || null, + website: this.formData.website || null, + nip05: this.formData.nip05 || null, + lud16: this.formData.lud16 || null + } + await LNbits.api.request( + 'PATCH', + `/nostrmarket/api/v1/merchant/${this.merchantId}`, + this.adminkey, + config + ) + this.show = false + this.$q.notify({ + type: 'positive', + message: 'Profile updated!' + }) + this.$emit('profile-updated') + } catch (error) { + LNbits.utils.notifyApiError(error) + } finally { + this.saving = false + } + }, + loadFormData: function () { + if (this.merchantConfig) { + this.formData.name = this.merchantConfig.name || '' + this.formData.display_name = this.merchantConfig.display_name || '' + this.formData.about = this.merchantConfig.about || '' + this.formData.picture = this.merchantConfig.picture || '' + this.formData.banner = this.merchantConfig.banner || '' + this.formData.website = this.merchantConfig.website || '' + this.formData.nip05 = this.merchantConfig.nip05 || '' + this.formData.lud16 = this.merchantConfig.lud16 || '' + } + } + }, + watch: { + modelValue(newVal) { + if (newVal) { + this.loadFormData() + } + } + } +}) diff --git a/static/components/key-pair.js b/static/components/key-pair.js deleted file mode 100644 index d324bc0..0000000 --- a/static/components/key-pair.js +++ /dev/null @@ -1,11 +0,0 @@ -window.app.component('key-pair', { - name: 'key-pair', - template: '#key-pair', - delimiters: ['${', '}'], - props: ['public-key', 'private-key', 'merchant-config'], - methods: { - handleImageError: function (event) { - event.target.style.display = 'none' - } - } -}) diff --git a/static/components/merchant-tab.js b/static/components/merchant-tab.js index 3c48281..cd2e2b2 100644 --- a/static/components/merchant-tab.js +++ b/static/components/merchant-tab.js @@ -13,12 +13,41 @@ window.app.component('merchant-tab', { 'is-admin', 'merchant-config' ], + emits: [ + 'toggle-show-keys', + 'hide-keys', + 'merchant-deleted', + 'toggle-merchant-state', + 'restart-nostr-connection', + 'profile-updated' + ], + data: function () { + return { + showEditProfileDialog: false, + showKeysDialog: false + } + }, computed: { marketClientUrl: function () { return '/nostrmarket/market' } }, methods: { + publishProfile: async function () { + try { + await LNbits.api.request( + 'PUT', + `/nostrmarket/api/v1/merchant/${this.merchantId}/nostr`, + this.adminkey + ) + this.$q.notify({ + type: 'positive', + message: 'Profile published to Nostr!' + }) + } catch (error) { + LNbits.utils.notifyApiError(error) + } + }, toggleShowKeys: function () { this.$emit('toggle-show-keys') }, @@ -33,6 +62,9 @@ window.app.component('merchant-tab', { }, restartNostrConnection: function () { this.$emit('restart-nostr-connection') + }, + handleImageError: function (e) { + e.target.style.display = 'none' } } }) diff --git a/static/components/nostr-keys-dialog.js b/static/components/nostr-keys-dialog.js new file mode 100644 index 0000000..81c451f --- /dev/null +++ b/static/components/nostr-keys-dialog.js @@ -0,0 +1,56 @@ +window.app.component('nostr-keys-dialog', { + name: 'nostr-keys-dialog', + template: '#nostr-keys-dialog', + delimiters: ['${', '}'], + props: ['public-key', 'private-key', 'model-value'], + emits: ['update:model-value'], + data: function () { + return { + showNsec: false + } + }, + computed: { + show: { + get() { + return this.modelValue + }, + set(value) { + this.$emit('update:model-value', value) + } + }, + npub: function () { + if (!this.publicKey) return '' + try { + return window.NostrTools.nip19.npubEncode(this.publicKey) + } catch (e) { + return this.publicKey + } + }, + nsec: function () { + if (!this.privateKey) return '' + try { + return window.NostrTools.nip19.nsecEncode(this.privateKey) + } catch (e) { + return this.privateKey + } + } + }, + methods: { + copyText: function (text, message) { + var notify = this.$q.notify + Quasar.copyToClipboard(text).then(function () { + notify({ + message: message || 'Copied to clipboard!', + position: 'bottom' + }) + }) + } + }, + watch: { + modelValue(newVal) { + if (!newVal) { + this.showNsec = false + } + } + } +}) diff --git a/templates/nostrmarket/components/edit-profile-dialog.html b/templates/nostrmarket/components/edit-profile-dialog.html new file mode 100644 index 0000000..27a4b1e --- /dev/null +++ b/templates/nostrmarket/components/edit-profile-dialog.html @@ -0,0 +1,63 @@ + + + +
Edit Profile
+ + + + + + + + +
+ Save + Cancel +
+
+
+
diff --git a/templates/nostrmarket/components/key-pair.html b/templates/nostrmarket/components/key-pair.html deleted file mode 100644 index 2becf66..0000000 --- a/templates/nostrmarket/components/key-pair.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - -
- -
- - - - -
- - -
-
-
-
- -
-
(No name set)
-
-
-
- -
-
-
- - -
-
- - -
-
-
-
-
-
diff --git a/templates/nostrmarket/components/merchant-tab.html b/templates/nostrmarket/components/merchant-tab.html index 63fe48f..cbd41ed 100644 --- a/templates/nostrmarket/components/merchant-tab.html +++ b/templates/nostrmarket/components/merchant-tab.html @@ -1,11 +1,255 @@
- + + +
+
+ Merchant Profile +
+
+
+ Edit + + Show Keys + + Publish + + + Saved Profiles + + + + + + + + + + + + + + + Multi-profile support coming soon + + + + + + + + + + Add New Profile + Import a different nsec + + + + + + + + + + + + Accepting Orders + Orders Paused + + New orders will be processed + + + New orders will be ignored + + + + + + + + + + Pause Orders + Resume Orders + + Stop accepting new orders + + + Start accepting new orders + + + + + +
+
+ + +
+ + +
+ + + +
+ +
+ + + + +
+ + +
+
+
+
+ +
+
+ (No display name set) +
+
+
+
+
+ 0 Following + Not implemented yet +
+
+ 0 Followers + Not implemented yet +
+
+
+
+
+ +
+
+
+ + +
+
+ + +
+
+
+
+
+ +
+ + + New Post (Coming soon) + +
+ + + +
+ +
Coming Soon
+
+ Merchant posts will appear here +
+
+
+
@@ -43,4 +287,20 @@
+ + + + + +
diff --git a/templates/nostrmarket/components/nostr-keys-dialog.html b/templates/nostrmarket/components/nostr-keys-dialog.html new file mode 100644 index 0000000..dde7069 --- /dev/null +++ b/templates/nostrmarket/components/nostr-keys-dialog.html @@ -0,0 +1,75 @@ + + + +
Nostr Keys
+
+ + +
+ +
+ + +
+ + Public Key (npub) +
+ + + + + +
+ + Private Key (nsec) +
+ + + +
+ + Never share your private key! +
+
+ + + +
+
diff --git a/templates/nostrmarket/index.html b/templates/nostrmarket/index.html index fbb2def..50b148f 100644 --- a/templates/nostrmarket/index.html +++ b/templates/nostrmarket/index.html @@ -158,6 +158,8 @@ @merchant-deleted="handleMerchantDeleted" @toggle-merchant-state="toggleMerchantState" @restart-nostr-connection="restartNostrConnection" + @import-key="showImportKeysDialog" + @profile-updated="getMerchant" > @@ -424,8 +426,11 @@ } -{% include("nostrmarket/components/nostr-keys-dialog.html") %} + - + + diff --git a/views_api.py b/views_api.py index af675cd..ad7659e 100644 --- a/views_api.py +++ b/views_api.py @@ -62,6 +62,7 @@ from .models import ( DirectMessage, DirectMessageType, Merchant, + MerchantConfig, Order, OrderReissue, OrderStatusUpdate, @@ -192,6 +193,35 @@ async def api_delete_merchant( await subscribe_to_all_merchants() +@nostrmarket_ext.patch("/api/v1/merchant/{merchant_id}") +async def api_update_merchant( + merchant_id: str, + config: MerchantConfig, + wallet: WalletTypeInfo = Depends(require_admin_key), +): + try: + merchant = await get_merchant_for_user(wallet.wallet.user) + assert merchant, "Merchant cannot be found" + assert merchant.id == merchant_id, "Wrong merchant ID" + + updated_merchant = await update_merchant( + wallet.wallet.user, merchant_id, config + ) + return updated_merchant + + except AssertionError as ex: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail=str(ex), + ) from ex + except Exception as ex: + logger.warning(ex) + raise HTTPException( + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + detail="Cannot update merchant", + ) from ex + + @nostrmarket_ext.put("/api/v1/merchant/{merchant_id}/nostr") async def api_republish_merchant( merchant_id: str,