Merge pull request #55 from lnbits/add_client_for_dm

Add client for dm
This commit is contained in:
Vlad Stan 2023-05-04 12:23:37 +03:00 committed by GitHub
commit a1ec9f3e13
7 changed files with 138 additions and 14 deletions

View file

@ -4,6 +4,7 @@ import secrets
from typing import Any, Optional, Tuple
import secp256k1
from bech32 import bech32_decode, convertbits
from cffi import FFI
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
@ -80,3 +81,21 @@ def order_from_json(s: str) -> Tuple[Optional[Any], Optional[str]]:
return (order, s) if (type(order) is dict) and "items" in order else (None, s)
except ValueError:
return None, s
def normalize_public_key(pubkey: str) -> str:
if pubkey.startswith("npub1"):
_, decoded_data = bech32_decode(pubkey)
if not decoded_data:
raise ValueError("Public Key is not valid npub")
decoded_data_bits = convertbits(decoded_data, 5, 8, False)
if not decoded_data_bits:
raise ValueError("Public Key is not valid npub")
return bytes(decoded_data_bits).hex()
# check if valid hex
if len(pubkey) != 64:
raise ValueError("Public Key is not valid hex")
int(pubkey, 16)
return pubkey

View file

@ -352,7 +352,7 @@ async def _handle_new_order(order: PartialOrder) -> Optional[str]:
return None
async def _handle_new_customer(event, merchant):
async def _handle_new_customer(event, merchant: Merchant):
await create_customer(
merchant.id, Customer(merchant_id=merchant.id, public_key=event.pubkey)
)

View file

@ -26,6 +26,8 @@
<q-separator></q-separator>
</q-card-section>
<q-card-section>
<div class="row">
<div class="col-10">
<q-select
v-model="activePublicKey"
:options="customers.map(c => ({label: buildCustomerLabel(c), value: c.public_key}))"
@ -34,6 +36,21 @@
@input="selectActiveCustomer()"
>
</q-select>
</div>
<div class="col-2">
<q-btn
label="Add"
color="green"
class="float-right q-mt-md"
@click="showAddPublicKey = true"
>
<q-tooltip>
Add a public key to chat with
</q-tooltip>
</q-btn>
</div>
</div>
</q-card-section>
<q-card-section>
<div class="chat-container" ref="chatCard">
@ -76,4 +93,30 @@
</div>
</q-card-section>
</q-card>
<div>
<q-dialog v-model="showAddPublicKey" position="top">
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
<q-form @submit="addPublicKey" class="q-gutter-md">
<q-input
filled
dense
v-model.trim="newPublicKey"
label="Public Key (hex or nsec)"
></q-input>
<div class="row q-mt-lg">
<q-btn
unelevated
color="primary"
:disable="!newPublicKey"
type="submit"
>Add</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
</div>
</div>

View file

@ -2,7 +2,7 @@ async function directMessages(path) {
const template = await loadTemplateAsync(path)
Vue.component('direct-messages', {
name: 'direct-messages',
props: ['active-chat-customer', 'adminkey', 'inkey'],
props: ['active-chat-customer', 'merchant-id', 'adminkey', 'inkey'],
template,
watch: {
@ -19,7 +19,9 @@ async function directMessages(path) {
unreadMessages: 0,
activePublicKey: null,
messages: [],
newMessage: ''
newMessage: '',
showAddPublicKey: false,
newPublicKey: null
}
},
methods: {
@ -56,7 +58,7 @@ async function directMessages(path) {
try {
const {data} = await LNbits.api.request(
'GET',
'/nostrmarket/api/v1/customers',
'/nostrmarket/api/v1/customer',
this.inkey
)
this.customers = data
@ -83,6 +85,27 @@ async function directMessages(path) {
LNbits.utils.notifyApiError(error)
}
},
addPublicKey: async function(){
try {
const {data} = await LNbits.api.request(
'POST',
'/nostrmarket/api/v1/customer',
this.adminkey,
{
public_key: this.newPublicKey,
merchant_id: this.merchantId,
unread_messages: 0
}
)
this.newPublicKey = null
this.activePublicKey = data.public_key
await this.selectActiveCustomer()
} catch (error) {
LNbits.utils.notifyApiError(error)
} finally {
this.showAddPublicKey = false
}
},
handleNewMessage: async function (data) {
if (data.customerPubkey === this.activePublicKey) {
await this.getDirectMessages(this.activePublicKey)

View file

@ -227,7 +227,7 @@ async function orderList(path) {
try {
const {data} = await LNbits.api.request(
'GET',
'/nostrmarket/api/v1/customers',
'/nostrmarket/api/v1/customer',
this.inkey
)
this.customers = data

View file

@ -167,6 +167,7 @@
:inkey="g.user.wallets[0].inkey"
:adminkey="g.user.wallets[0].adminkey"
:active-chat-customer="activeChatCustomer"
:merchant-id="merchant.id"
@customer-selected="filterOrdersForCustomer"
>
</direct-messages>

View file

@ -13,10 +13,12 @@ from lnbits.decorators import (
require_admin_key,
require_invoice_key,
)
from lnbits.extensions.nostrmarket.helpers import normalize_public_key
from lnbits.utils.exchange_rates import currencies
from . import nostr_client, nostrmarket_ext, scheduled_tasks
from .crud import (
create_customer,
create_direct_message,
create_merchant,
create_product,
@ -31,6 +33,7 @@ from .crud import (
delete_product,
delete_stall,
delete_zone,
get_customer,
get_customers,
get_direct_messages,
get_merchant_by_pubkey,
@ -796,7 +799,7 @@ async def api_create_message(
######################################## CUSTOMERS ########################################
@nostrmarket_ext.get("/api/v1/customers")
@nostrmarket_ext.get("/api/v1/customer")
async def api_get_customers(
wallet: WalletTypeInfo = Depends(get_key_type),
) -> List[Customer]:
@ -818,6 +821,41 @@ async def api_get_customers(
)
@nostrmarket_ext.post("/api/v1/customer")
async def api_createcustomer(
data: Customer,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Customer:
try:
pubkey = normalize_public_key(data.public_key)
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "A merchant does not exists for this user"
assert merchant.id == data.merchant_id, "Invalid merchant id for user"
existing_customer = await get_customer(merchant.id, pubkey)
assert existing_customer == None, "This public key already exists"
customer = await create_customer(
merchant.id, Customer(merchant_id=merchant.id, public_key=pubkey)
)
await nostr_client.subscribe_to_user_profile(pubkey, 0)
return customer
except (ValueError, AssertionError) as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot create customer",
)
######################################## OTHER ########################################