make format && generate keys with schnorr
This commit is contained in:
parent
9b69851155
commit
70a051a413
|
@ -59,17 +59,13 @@ async def update_shop_product(product_id: str, **kwargs) -> Optional[Stalls]:
|
|||
f"UPDATE shop.products SET {q} WHERE id = ?",
|
||||
(*kwargs.values(), product_id),
|
||||
)
|
||||
row = await db.fetchone(
|
||||
"SELECT * FROM shop.products WHERE id = ?", (product_id,)
|
||||
)
|
||||
row = await db.fetchone("SELECT * FROM shop.products WHERE id = ?", (product_id,))
|
||||
|
||||
return Products(**row) if row else None
|
||||
|
||||
|
||||
async def get_shop_product(product_id: str) -> Optional[Products]:
|
||||
row = await db.fetchone(
|
||||
"SELECT * FROM shop.products WHERE id = ?", (product_id,)
|
||||
)
|
||||
row = await db.fetchone("SELECT * FROM shop.products WHERE id = ?", (product_id,))
|
||||
return Products(**row) if row else None
|
||||
|
||||
|
||||
|
@ -132,9 +128,7 @@ async def get_shop_zone(zone_id: str) -> Optional[Zones]:
|
|||
|
||||
|
||||
async def get_shop_zones(user: str) -> List[Zones]:
|
||||
rows = await db.fetchall(
|
||||
'SELECT * FROM shop.zones WHERE "user" = ?', (user,)
|
||||
)
|
||||
rows = await db.fetchall('SELECT * FROM shop.zones WHERE "user" = ?', (user,))
|
||||
return [Zones(**row) for row in rows]
|
||||
|
||||
|
||||
|
@ -182,16 +176,12 @@ async def update_shop_stall(stall_id: str, **kwargs) -> Optional[Stalls]:
|
|||
f"UPDATE shop.stalls SET {q} WHERE id = ?",
|
||||
(*kwargs.values(), stall_id),
|
||||
)
|
||||
row = await db.fetchone(
|
||||
"SELECT * FROM shop.stalls WHERE id = ?", (stall_id,)
|
||||
)
|
||||
row = await db.fetchone("SELECT * FROM shop.stalls WHERE id = ?", (stall_id,))
|
||||
return Stalls(**row) if row else None
|
||||
|
||||
|
||||
async def get_shop_stall(stall_id: str) -> Optional[Stalls]:
|
||||
row = await db.fetchone(
|
||||
"SELECT * FROM shop.stalls WHERE id = ?", (stall_id,)
|
||||
)
|
||||
row = await db.fetchone("SELECT * FROM shop.stalls WHERE id = ?", (stall_id,))
|
||||
return Stalls(**row) if row else None
|
||||
|
||||
|
||||
|
@ -203,9 +193,7 @@ async def get_shop_stalls(wallet_ids: Union[str, List[str]]) -> List[Stalls]:
|
|||
return [Stalls(**row) for row in rows]
|
||||
|
||||
|
||||
async def get_shop_stalls_by_ids(
|
||||
stall_ids: Union[str, List[str]]
|
||||
) -> List[Stalls]:
|
||||
async def get_shop_stalls_by_ids(stall_ids: Union[str, List[str]]) -> List[Stalls]:
|
||||
q = ",".join(["?"] * len(stall_ids))
|
||||
rows = await db.fetchall(
|
||||
f"SELECT * FROM shop.stalls WHERE id IN ({q})", (*stall_ids,)
|
||||
|
@ -250,9 +238,7 @@ async def create_shop_order(data: createOrder, invoiceid: str) -> Orders:
|
|||
# return link
|
||||
|
||||
|
||||
async def create_shop_order_details(
|
||||
order_id: str, data: List[createOrderDetails]
|
||||
):
|
||||
async def create_shop_order_details(order_id: str, data: List[createOrderDetails]):
|
||||
for item in data:
|
||||
item_id = urlsafe_short_hash()
|
||||
await db.execute(
|
||||
|
@ -280,9 +266,7 @@ async def get_shop_order_details(order_id: str) -> List[OrderDetail]:
|
|||
|
||||
|
||||
async def get_shop_order(order_id: str) -> Optional[Orders]:
|
||||
row = await db.fetchone(
|
||||
"SELECT * FROM shop.orders WHERE id = ?", (order_id,)
|
||||
)
|
||||
row = await db.fetchone("SELECT * FROM shop.orders WHERE id = ?", (order_id,))
|
||||
return Orders(**row) if row else None
|
||||
|
||||
|
||||
|
@ -362,9 +346,7 @@ async def get_shop_markets(user: str) -> List[Market]:
|
|||
|
||||
|
||||
async def get_shop_market(market_id: str) -> Optional[Market]:
|
||||
row = await db.fetchone(
|
||||
"SELECT * FROM shop.markets WHERE id = ?", (market_id,)
|
||||
)
|
||||
row = await db.fetchone("SELECT * FROM shop.markets WHERE id = ?", (market_id,))
|
||||
return Market(**row) if row else None
|
||||
|
||||
|
||||
|
@ -397,9 +379,7 @@ async def create_shop_market(data: CreateMarket):
|
|||
return market
|
||||
|
||||
|
||||
async def create_shop_market_stalls(
|
||||
market_id: str, data: List[CreateMarketStalls]
|
||||
):
|
||||
async def create_shop_market_stalls(market_id: str, data: List[CreateMarketStalls]):
|
||||
for stallid in data:
|
||||
id = urlsafe_short_hash()
|
||||
|
||||
|
|
|
@ -14,17 +14,18 @@
|
|||
<li>Create Shipping Zones you're willing to ship to</li>
|
||||
<li>Create a Stall to list yiur products on</li>
|
||||
<li>Create products to put on the Stall</li>
|
||||
<li>
|
||||
Take orders
|
||||
</li>
|
||||
<li>
|
||||
Includes chat support!
|
||||
</li>
|
||||
<li>Take orders</li>
|
||||
<li>Includes chat support!</li>
|
||||
</ol>
|
||||
The first LNbits shop idea 'Diagon Alley' helped create Nostr, and soon this shop extension will have the option to work on Nostr 'Diagon Alley' mode, by the merchant, shop, and buyer all having keys, and data being routed through Nostr relays.
|
||||
The first LNbits shop idea 'Diagon Alley' helped create Nostr, and soon
|
||||
this shop extension will have the option to work on Nostr 'Diagon Alley'
|
||||
mode, by the merchant, shop, and buyer all having keys, and data being
|
||||
routed through Nostr relays.
|
||||
<br />
|
||||
<small>
|
||||
Created by, <a href="https://github.com/talvasconcelos">Tal Vasconcelos</a>, <a href="https://github.com/benarc">Ben Arc</a></small
|
||||
Created by,
|
||||
<a href="https://github.com/talvasconcelos">Tal Vasconcelos</a>,
|
||||
<a href="https://github.com/benarc">Ben Arc</a></small
|
||||
>
|
||||
<!-- </p> -->
|
||||
</q-card-section>
|
||||
|
|
|
@ -253,7 +253,7 @@
|
|||
>
|
||||
</q-select>
|
||||
<q-input
|
||||
v-if="diagonalley"
|
||||
v-if="diagonalley"
|
||||
v-if="keys"
|
||||
filled
|
||||
dense
|
||||
|
@ -261,8 +261,7 @@
|
|||
label="Public Key"
|
||||
></q-input>
|
||||
<q-input
|
||||
|
||||
v-if="diagonalley"
|
||||
v-if="diagonalley"
|
||||
v-if="keys"
|
||||
filled
|
||||
dense
|
||||
|
@ -291,7 +290,7 @@
|
|||
label="Shipping Zones"
|
||||
></q-select>
|
||||
<q-select
|
||||
v-if="diagonalley"
|
||||
v-if="diagonalley"
|
||||
:options="relayOptions"
|
||||
filled
|
||||
dense
|
||||
|
@ -300,14 +299,14 @@
|
|||
label="Relays"
|
||||
></q-select>
|
||||
<q-input
|
||||
v-if="diagonalley"
|
||||
v-if="diagonalley"
|
||||
filled
|
||||
dense
|
||||
v-model.trim="stallDialog.data.crelays"
|
||||
label="Custom relays (seperate by comma)"
|
||||
></q-input>
|
||||
<q-input
|
||||
v-if="diagonalley"
|
||||
v-if="diagonalley"
|
||||
filled
|
||||
dense
|
||||
v-model.trim="stallDialog.data.nostrShops"
|
||||
|
@ -399,9 +398,7 @@
|
|||
<q-separator inset></q-separator>
|
||||
<q-card-section>
|
||||
<div class="text-h6">Shop</div>
|
||||
<div class="text-subtitle2">
|
||||
Make a shop of multiple stalls.
|
||||
</div>
|
||||
<div class="text-subtitle2">Make a shop of multiple stalls.</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section>
|
||||
|
@ -929,7 +926,7 @@
|
|||
</q-card-section>
|
||||
</q-card>
|
||||
<!-- CHAT BOX -->
|
||||
<q-card style="max-height: 600px">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<h6 class="text-subtitle1 q-my-none">Messages</h6>
|
||||
</q-card-section>
|
||||
|
@ -946,45 +943,45 @@
|
|||
></q-select>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="chat-container q-pa-md" ref="chatCard">
|
||||
<div class="chat-box">
|
||||
<!-- <p v-if="Object.keys(messages).length === 0">No messages yet</p> -->
|
||||
<div class="chat-messages">
|
||||
<q-chat-message
|
||||
:key="index"
|
||||
v-for="(message, index) in orderMessages"
|
||||
:name="message.pubkey == keys.pubkey ? 'me' : 'customer'"
|
||||
:text="[message.msg]"
|
||||
:sent="message.pubkey == keys.pubkey ? true : false"
|
||||
:bg-color="message.pubkey == keys.pubkey ? 'white' : 'light-green-2'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<q-card-section>
|
||||
<q-form @submit="sendMessage" class="full-width chat-input">
|
||||
<q-input
|
||||
ref="newMessage"
|
||||
v-model="newMessage"
|
||||
placeholder="Message"
|
||||
class="full-width"
|
||||
dense
|
||||
outlined
|
||||
>
|
||||
<template>
|
||||
<q-btn
|
||||
round
|
||||
dense
|
||||
flat
|
||||
type="submit"
|
||||
icon="send"
|
||||
color="primary"
|
||||
<div class="chat-container" ref="chatCard">
|
||||
<div class="chat-box">
|
||||
<!-- <p v-if="Object.keys(messages).length === 0">No messages yet</p> -->
|
||||
<div class="chat-messages">
|
||||
<q-chat-message
|
||||
:key="index"
|
||||
v-for="(message, index) in orderMessages"
|
||||
:name="message.pubkey == keys.pubkey ? 'me' : 'customer'"
|
||||
:text="[message.msg]"
|
||||
:sent="message.pubkey == keys.pubkey ? true : false"
|
||||
:bg-color="message.pubkey == keys.pubkey ? 'white' : 'light-green-2'"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
</q-form>
|
||||
</div>
|
||||
</div>
|
||||
<q-card-section>
|
||||
<q-form @submit="sendMessage" class="full-width chat-input">
|
||||
<q-input
|
||||
ref="newMessage"
|
||||
v-model="newMessage"
|
||||
placeholder="Message"
|
||||
class="full-width"
|
||||
dense
|
||||
outlined
|
||||
>
|
||||
<template>
|
||||
<q-btn
|
||||
round
|
||||
dense
|
||||
flat
|
||||
type="submit"
|
||||
icon="send"
|
||||
color="primary"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
</q-form>
|
||||
</q-card-section>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<!-- <q-card>
|
||||
<q-card-section>
|
||||
|
@ -1112,12 +1109,15 @@
|
|||
</div>
|
||||
|
||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/pica@6.1.1/dist/pica.min.js"></script>
|
||||
<script src="https://github.com/paulmillr/noble-secp256k1/releases/download/1.7.0/noble-secp256k1.js"></script>
|
||||
|
||||
<script>
|
||||
Vue.component(VueQrcode.name, VueQrcode)
|
||||
|
||||
const pica = window.pica()
|
||||
const secp = window.nobleSecp256k1
|
||||
|
||||
function imgSizeFit(img, maxWidth = 1024, maxHeight = 768) {
|
||||
let ratio = Math.min(
|
||||
|
@ -1504,31 +1504,17 @@
|
|||
window.URL.revokeObjectURL(url)
|
||||
},
|
||||
generateKeys() {
|
||||
LNbits.api
|
||||
.request(
|
||||
'GET',
|
||||
'/shop/api/v1/keys',
|
||||
this.g.user.wallets[0].adminkey
|
||||
)
|
||||
.then(response => {
|
||||
if (response.data) {
|
||||
this.keys = response.data
|
||||
this.stallDialog.data.publickey = this.keys.pubkey
|
||||
this.stallDialog.data.privatekey = this.keys.privkey
|
||||
this.$q.localStorage.set(
|
||||
`lnbits.shop.${this.g.user.id}`,
|
||||
this.keys
|
||||
)
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
const privkey = secp.utils.bytesToHex(secp.utils.randomPrivateKey())
|
||||
const pubkey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(privKey))
|
||||
|
||||
this.keys = {privKey, pubKey}
|
||||
this.stallDialog.data.publickey = this.keys.pubkey
|
||||
this.stallDialog.data.privatekey = this.keys.privkey
|
||||
this.$q.localStorage.set(`lnbits.shop.${this.g.user.id}`, this.keys)
|
||||
console.log({privKey, pubKey})
|
||||
},
|
||||
restoreKeys() {
|
||||
let keys = this.$q.localStorage.getItem(
|
||||
`lnbits.shop.${this.g.user.id}`
|
||||
)
|
||||
let keys = this.$q.localStorage.getItem(`lnbits.shop.${this.g.user.id}`)
|
||||
if (keys) {
|
||||
this.keys = keys
|
||||
this.stallDialog.data.publickey = this.keys.pubkey
|
||||
|
@ -1858,11 +1844,7 @@
|
|||
var self = this
|
||||
|
||||
LNbits.api
|
||||
.request(
|
||||
'GET',
|
||||
'/shop/api/v1/zones',
|
||||
this.g.user.wallets[0].inkey
|
||||
)
|
||||
.request('GET', '/shop/api/v1/zones', this.g.user.wallets[0].inkey)
|
||||
.then(function (response) {
|
||||
if (response.data) {
|
||||
self.zones = response.data.map(mapZone)
|
||||
|
@ -1967,11 +1949,7 @@
|
|||
////////////////////////////////////////
|
||||
getMarkets() {
|
||||
LNbits.api
|
||||
.request(
|
||||
'GET',
|
||||
'/shop/api/v1/markets',
|
||||
this.g.user.wallets[0].inkey
|
||||
)
|
||||
.request('GET', '/shop/api/v1/markets', this.g.user.wallets[0].inkey)
|
||||
.then(response => {
|
||||
if (response.data) {
|
||||
this.markets = response.data.map(mapMarkets)
|
||||
|
@ -2171,17 +2149,17 @@
|
|||
this.g.user.wallets[0].adminkey
|
||||
)
|
||||
.then(res => {
|
||||
if (!res.data?.length) return
|
||||
this.messages = _.groupBy(res.data, 'id_conversation')
|
||||
this.checkUnreadMessages()
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error)
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
},
|
||||
updateLastSeenMsg(id) {
|
||||
let data = this.$q.localStorage.getItem(
|
||||
`lnbits.shop.${this.g.user.id}`
|
||||
)
|
||||
let data = this.$q.localStorage.getItem(`lnbits.shop.${this.g.user.id}`)
|
||||
let chat = {
|
||||
...data.chat,
|
||||
[`${id}`]: {
|
||||
|
@ -2256,9 +2234,7 @@
|
|||
} else {
|
||||
ws_scheme = 'ws://'
|
||||
}
|
||||
ws = new WebSocket(
|
||||
ws_scheme + location.host + '/shop/ws/' + room_name
|
||||
)
|
||||
ws = new WebSocket(ws_scheme + location.host + '/shop/ws/' + room_name)
|
||||
|
||||
function checkWebSocket(event) {
|
||||
if (ws.readyState === WebSocket.CLOSED) {
|
||||
|
@ -2296,9 +2272,7 @@
|
|||
await this.getOrders()
|
||||
this.getMarkets()
|
||||
await this.getAllMessages()
|
||||
let keys = this.$q.localStorage.getItem(
|
||||
`lnbits.shop.${this.g.user.id}`
|
||||
)
|
||||
let keys = this.$q.localStorage.getItem(`lnbits.shop.${this.g.user.id}`)
|
||||
if (keys) {
|
||||
this.keys = keys
|
||||
}
|
||||
|
@ -2319,7 +2293,8 @@
|
|||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr auto;
|
||||
height: calc(100vh - 140px);
|
||||
/*height: calc(100vh - 200px);*/
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.chat-box {
|
||||
|
@ -2327,6 +2302,7 @@
|
|||
flex-direction: column-reverse;
|
||||
padding: 1rem;
|
||||
overflow-y: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div class="row q-col-gutter-md flex">
|
||||
<div class="col-12 col-md-7 col-lg-6 q-gutter-y-md">
|
||||
<q-card>
|
||||
<div class="chat-container q-pa-md">
|
||||
<div class="chat-container">
|
||||
<div class="chat-box">
|
||||
<!-- <p v-if="Object.keys(messages).length === 0">No messages yet</p> -->
|
||||
<div class="chat-messages">
|
||||
|
@ -102,7 +102,11 @@
|
|||
<q-card-section>
|
||||
<q-separator></q-separator>
|
||||
<q-list>
|
||||
<q-expansion-item v-if="diagonalley" group="extras" icon="vpn_key" label="Keys"
|
||||
<q-expansion-item
|
||||
v-if="diagonalley"
|
||||
group="extras"
|
||||
icon="vpn_key"
|
||||
label="Keys"
|
||||
><p>
|
||||
Bellow are the keys needed to contact the merchant. They are
|
||||
stored in the browser!
|
||||
|
@ -197,7 +201,7 @@
|
|||
</div>
|
||||
<!-- RESTORE KEYS DIALOG -->
|
||||
<q-dialog
|
||||
v-if="diagonalley"
|
||||
v-if="diagonalley"
|
||||
v-model="keysDialog.show"
|
||||
position="top"
|
||||
@hide="clearRestoreKeyDialog"
|
||||
|
@ -398,9 +402,7 @@
|
|||
} else {
|
||||
ws_scheme = 'ws://'
|
||||
}
|
||||
ws = new WebSocket(
|
||||
ws_scheme + location.host + '/shop/ws/' + room_name
|
||||
)
|
||||
ws = new WebSocket(ws_scheme + location.host + '/shop/ws/' + room_name)
|
||||
|
||||
function checkWebSocket(event) {
|
||||
if (ws.readyState === WebSocket.CLOSED) {
|
||||
|
@ -448,8 +450,7 @@
|
|||
}
|
||||
})
|
||||
|
||||
let data =
|
||||
this.$q.localStorage.getItem(`lnbits.shop.data`) || false
|
||||
let data = this.$q.localStorage.getItem(`lnbits.shop.data`) || false
|
||||
|
||||
if (data) {
|
||||
this.user = data
|
||||
|
@ -486,7 +487,8 @@
|
|||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr auto;
|
||||
height: calc(100vh - 133px);
|
||||
/*height: calc(100vh - 200px);*/
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.chat-box {
|
||||
|
@ -494,6 +496,7 @@
|
|||
flex-direction: column-reverse;
|
||||
padding: 1rem;
|
||||
overflow-y: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
|
|
|
@ -163,7 +163,7 @@
|
|||
label="Name *optional"
|
||||
></q-input>
|
||||
<q-input
|
||||
v-if="diagonalley"
|
||||
v-if="diagonalley"
|
||||
filled
|
||||
dense
|
||||
v-model.trim="checkoutDialog.data.pubkey"
|
||||
|
@ -280,7 +280,7 @@
|
|||
stall: null,
|
||||
products: [],
|
||||
searchText: null,
|
||||
diagonalley:false,
|
||||
diagonalley: false,
|
||||
cart: {
|
||||
total: 0,
|
||||
size: 0,
|
||||
|
|
|
@ -78,9 +78,7 @@ async def display(request: Request, market_id):
|
|||
|
||||
stalls = await get_shop_market_stalls(market_id)
|
||||
stalls_ids = [stall.id for stall in stalls]
|
||||
products = [
|
||||
product.dict() for product in await get_shop_products(stalls_ids)
|
||||
]
|
||||
products = [product.dict() for product in await get_shop_products(stalls_ids)]
|
||||
|
||||
return shop_renderer().TemplateResponse(
|
||||
"shop/market.html",
|
||||
|
|
|
@ -346,9 +346,7 @@ async def api_shop_order_shipped(
|
|||
order_id,
|
||||
),
|
||||
)
|
||||
order = await db.fetchone(
|
||||
"SELECT * FROM shop.orders WHERE id = ?", (order_id,)
|
||||
)
|
||||
order = await db.fetchone("SELECT * FROM shop.orders WHERE id = ?", (order_id,))
|
||||
|
||||
return order
|
||||
|
||||
|
@ -361,15 +359,11 @@ async def api_shop_stall_products(
|
|||
stall_id, wallet: WalletTypeInfo = Depends(get_key_type)
|
||||
):
|
||||
|
||||
rows = await db.fetchone(
|
||||
"SELECT * FROM shop.stalls WHERE id = ?", (stall_id,)
|
||||
)
|
||||
rows = await db.fetchone("SELECT * FROM shop.stalls WHERE id = ?", (stall_id,))
|
||||
if not rows:
|
||||
return {"message": "Stall does not exist."}
|
||||
|
||||
products = db.fetchone(
|
||||
"SELECT * FROM shop.products WHERE wallet = ?", (rows[1],)
|
||||
)
|
||||
products = db.fetchone("SELECT * FROM shop.products WHERE wallet = ?", (rows[1],))
|
||||
if not products:
|
||||
return {"message": "No products"}
|
||||
|
||||
|
@ -441,10 +435,7 @@ async def api_shop_stall_checkshipped(
|
|||
async def api_shop_markets(wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||
# await get_shop_market_stalls(market_id="FzpWnMyHQMcRppiGVua4eY")
|
||||
try:
|
||||
return [
|
||||
market.dict()
|
||||
for market in await get_shop_markets(wallet.wallet.user)
|
||||
]
|
||||
return [market.dict() for market in await get_shop_markets(wallet.wallet.user)]
|
||||
except:
|
||||
return {"message": "We could not retrieve the markets."}
|
||||
|
||||
|
@ -498,10 +489,7 @@ async def api_shop_generate_keys():
|
|||
async def api_get_merchant_messages(
|
||||
orders: str = Query(...), wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||
):
|
||||
|
||||
return [
|
||||
msg.dict() for msg in await get_shop_chat_by_merchant(orders.split(","))
|
||||
]
|
||||
return [msg.dict() for msg in await get_shop_chat_by_merchant(orders.split(","))]
|
||||
|
||||
|
||||
@shop_ext.get("/api/v1/chat/messages/{room_name}")
|
||||
|
|
Loading…
Reference in New Issue
Block a user