feat: add UI for basic relay operations

This commit is contained in:
Vlad Stan 2023-02-06 15:30:05 +02:00
parent 0db0d9c351
commit a849dea99f
6 changed files with 152 additions and 62 deletions

53
crud.py
View file

@ -1,10 +1,59 @@
import json
from typing import Any, List, Optional
from lnbits.helpers import urlsafe_short_hash
from . import db
from .models import NostrEvent, NostrFilter
from .models import NostrEvent, NostrFilter, NostrRelay
########################## RELAYS ####################
async def create_relay(user_id: str, r: NostrRelay) -> NostrRelay:
await db.execute(
"""
INSERT INTO nostrrelay.relays (user_id, id, name, description, pubkey, contact)
VALUES (?, ?, ?, ?, ?, ?)
""",
(user_id, r.id, r.name, r.description, r.pubkey, r.contact,),
)
relay = await get_relay(user_id, r.id)
assert relay, "Created relay cannot be retrieved"
return relay
async def get_relay(user_id: str, relay_id: str) -> Optional[NostrRelay]:
row = await db.fetchone("""SELECT * FROM nostrrelay.relays WHERE user_id = ? AND id = ?""", (user_id, relay_id,))
return NostrRelay.from_row(row) if row else None
async def get_relays(user_id: str) -> List[NostrRelay]:
rows = await db.fetchall("""SELECT * FROM nostrrelay.relays WHERE user_id = ?""", (user_id,))
return [NostrRelay.from_row(row) for row in rows]
async def get_public_relay(relay_id: str) -> Optional[dict]:
row = await db.fetchone("""SELECT * FROM nostrrelay.relays WHERE id = ?""", (relay_id,))
if row:
relay = NostrRelay.parse_obj({"id": row["id"], **json.loads(row["meta"])})
return {
"id": relay.id,
"name": relay.name,
"description":relay.description,
"pubkey":relay.pubkey,
"contact":relay.contact,
"supported_nips":relay.supported_nips,
}
return None
async def delete_relay(user_id: str, relay_id: str):
await db.execute("""DELETE FROM nostrrelay.relays WHERE user_id = ? AND id = ?""", (user_id, relay_id,))
########################## EVENTS ####################
async def create_event(relay_id: str, e: NostrEvent):
await db.execute(
"""
@ -28,7 +77,6 @@ async def create_event(relay_id: str, e: NostrEvent):
extra = json.dumps(rest) if rest else None
await create_event_tags(relay_id, e.id, name, value, extra)
async def get_events(relay_id: str, filter: NostrFilter, include_tags = True) -> List[NostrEvent]:
values, query = build_select_events_query(relay_id, filter)
@ -43,7 +91,6 @@ async def get_events(relay_id: str, filter: NostrFilter, include_tags = True) ->
return events
async def get_event(relay_id: str, id: str) -> Optional[NostrEvent]:
row = await db.fetchone("SELECT * FROM nostrrelay.events WHERE relay_id = ? AND id = ?", (relay_id, id,))
if not row:

View file

@ -2,17 +2,17 @@ async def m001_initial(db):
"""
Initial nostrrelays tables.
"""
await db.execute(
"""
CREATE TABLE nostrrelay.relays (
user_id TEXT NOT NULL,
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
pubkey TEXT,
contact TEXT,
supported_nips TEXT,
software TEXT,
version TEXT,
active BOOLEAN DEFAULT false,
meta TEXT NOT NULL DEFAULT '{}'
);
"""

View file

@ -10,25 +10,19 @@ from secp256k1 import PublicKey
class NostrRelay(BaseModel):
id: str
wallet: str
name: str
currency: str
tip_options: Optional[str]
tip_wallet: Optional[str]
@classmethod
def from_row(cls, row: Row) -> "NostrRelay":
return cls(**dict(row))
class NostrRelayInfo(BaseModel):
name: Optional[str]
description: Optional[str]
pubkey: Optional[str]
contact: Optional[str] = "https://t.me/lnbits"
supported_nips: List[str] = ["NIP01", "NIP09", "NIP11", "NIP15", "NIP20"]
software: Optional[str] = "LNbist"
version: Optional[str]
# meta: Optional[str]
@classmethod
def from_row(cls, row: Row) -> "NostrRelay":
return cls(**dict(row))
class NostrEventType(str, Enum):

View file

@ -22,29 +22,22 @@ const relays = async () => {
wallet: ''
}
},
relayTypes: [
{
id: 'rating',
label: 'Rating (rate one item from a list)'
},
{
id: 'poll',
label: 'Poll (choose one item from a list)'
},
{
id: 'likes',
label: 'Likes (like or dislike an item)'
}
],
relaysTable: {
columns: [
{
name: 'id',
align: 'left',
label: 'ID',
field: 'id'
},
{
name: '',
align: 'left',
label: '',
field: ''
},
{
name: 'name',
align: 'left',
@ -58,16 +51,16 @@ const relays = async () => {
field: 'description'
},
{
name: 'type',
name: 'pubkey',
align: 'left',
label: 'Type',
field: 'type'
label: 'Public Key',
field: 'pubkey'
},
{
name: 'amount',
name: 'contact',
align: 'left',
label: 'Amount',
field: 'amount'
label: 'Contact',
field: 'contact'
}
],
pagination: {
@ -79,17 +72,14 @@ const relays = async () => {
methods: {
getDefaultRelayData: function () {
return {
id: '',
name: '',
description: '',
type: this.relayTypes[0],
amount: '100',
wallet: ''
pubkey: '',
contact: ''
}
},
getRelayTypeLabel: function (relayType) {
const type = this.relayTypes.find(s => (s.id = relayType))
return type ? type.label : '?'
},
openCreateRelayDialog: function () {
this.formDialogRelay.data = this.getDefaultRelayData()
this.formDialogRelay.show = true
@ -98,7 +88,7 @@ const relays = async () => {
try {
const {data} = await LNbits.api.request(
'GET',
'/reviews/api/v1/survey',
'/nostrrelay/api/v1/relay',
this.g.user.wallets[0].inkey
)
this.relayLinks = data.map(c =>
@ -116,10 +106,10 @@ const relays = async () => {
createRelay: async function (data) {
try {
data.type = data.type.id
console.log('### createRelay', data)
const resp = await LNbits.api.request(
'POST',
'/reviews/api/v1/survey',
'/nostrrelay/api/v1/relay',
this.g.user.wallets[0].adminkey,
data
)
@ -138,7 +128,7 @@ const relays = async () => {
try {
const response = await LNbits.api.request(
'DELETE',
'/reviews/api/v1/survey/' + relayId,
'/nostrrelay/api/v1/relay/' + relayId,
this.g.user.wallets[0].adminkey
)

View file

@ -69,14 +69,15 @@
</q-td>
<q-td auto-width> {{props.row.name}} </q-td>
<q-td key="description" :props="props" :class="">
<q-td key="id" :props="props"> {{props.row.id}} </q-td>
<q-td key="description" :props="props">
{{props.row.description}}
</q-td>
<q-td key="type" :props="props" :class="">
<div>{{getRelayTypeLabel(props.row.type)}}</div>
<q-td key="contact" :props="props">
<div>{{props.row.contact}}</div>
</q-td>
<q-td key="amount" :props="props" :class="">
<div>{{props.row.amount}}</div>
<q-td key="pubkey" :props="props">
<div>{{props.row.pubkey}}</div>
</q-td>
</q-tr>
<q-tr v-if="props.row.expanded" :props="props">

View file

@ -1,12 +1,23 @@
from http import HTTPStatus
from typing import List, Optional
from fastapi import Query, WebSocket
from fastapi import Depends, Query, WebSocket
from fastapi.exceptions import HTTPException
from fastapi.responses import JSONResponse
from loguru import logger
from lnbits.decorators import (
WalletTypeInfo,
check_admin,
require_admin_key,
require_invoice_key,
)
from lnbits.helpers import urlsafe_short_hash
from . import nostrrelay_ext
from .client_manager import NostrClientConnection, NostrClientManager
from .models import NostrRelayInfo
from .crud import create_relay, delete_relay, get_relay, get_relays
from .models import NostrRelay
client_manager = NostrClientManager()
@ -29,14 +40,61 @@ async def api_nostrrelay_info():
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Methods": "GET"
}
info = NostrRelayInfo()
info = NostrRelay()
return JSONResponse(content=dict(info), headers=headers)
@nostrrelay_ext.get("/api/v1/enable", status_code=HTTPStatus.OK)
async def api_nostrrelay(enable: bool = Query(True)):
return await enable_relay(enable)
@nostrrelay_ext.post("/api/v1/relay")
async def api_create_survey(data: NostrRelay, wallet: WalletTypeInfo = Depends(require_admin_key)) -> NostrRelay:
try:
relay = await create_relay(wallet.wallet.user, data)
return relay
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot create relay",
)
async def enable_relay(enable: bool):
return enable
@nostrrelay_ext.get("/api/v1/relay")
async def api_get_relays(wallet: WalletTypeInfo = Depends(require_invoice_key)) -> List[NostrRelay]:
try:
return await get_relays(wallet.wallet.user)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot fetch relays",
)
@nostrrelay_ext.get("/api/v1/relay/{relay_id}")
async def api_get_relay(relay_id: str, wallet: WalletTypeInfo = Depends(require_invoice_key)) -> Optional[NostrRelay]:
try:
relay = await get_relay(wallet.wallet.user, relay_id)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot fetch relay",
)
if not relay:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Cannot find relay",
)
return relay
@nostrrelay_ext.delete("/api/v1/relay/{relay_id}")
async def api_delete_relay(relay_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)):
try:
await delete_relay(wallet.wallet.user, relay_id)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot delete relay",
)