feat: add UI for basic relay operations
This commit is contained in:
parent
0db0d9c351
commit
a849dea99f
6 changed files with 152 additions and 62 deletions
53
crud.py
53
crud.py
|
|
@ -1,10 +1,59 @@
|
||||||
import json
|
import json
|
||||||
from typing import Any, List, Optional
|
from typing import Any, List, Optional
|
||||||
|
|
||||||
|
from lnbits.helpers import urlsafe_short_hash
|
||||||
|
|
||||||
from . import db
|
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):
|
async def create_event(relay_id: str, e: NostrEvent):
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"""
|
||||||
|
|
@ -28,7 +77,6 @@ async def create_event(relay_id: str, e: NostrEvent):
|
||||||
extra = json.dumps(rest) if rest else None
|
extra = json.dumps(rest) if rest else None
|
||||||
await create_event_tags(relay_id, e.id, name, value, extra)
|
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]:
|
async def get_events(relay_id: str, filter: NostrFilter, include_tags = True) -> List[NostrEvent]:
|
||||||
values, query = build_select_events_query(relay_id, filter)
|
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
|
return events
|
||||||
|
|
||||||
|
|
||||||
async def get_event(relay_id: str, id: str) -> Optional[NostrEvent]:
|
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,))
|
row = await db.fetchone("SELECT * FROM nostrrelay.events WHERE relay_id = ? AND id = ?", (relay_id, id,))
|
||||||
if not row:
|
if not row:
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,17 @@ async def m001_initial(db):
|
||||||
"""
|
"""
|
||||||
Initial nostrrelays tables.
|
Initial nostrrelays tables.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"""
|
||||||
CREATE TABLE nostrrelay.relays (
|
CREATE TABLE nostrrelay.relays (
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
pubkey TEXT,
|
pubkey TEXT,
|
||||||
contact TEXT,
|
contact TEXT,
|
||||||
supported_nips TEXT,
|
active BOOLEAN DEFAULT false,
|
||||||
software TEXT,
|
|
||||||
version TEXT,
|
|
||||||
meta TEXT NOT NULL DEFAULT '{}'
|
meta TEXT NOT NULL DEFAULT '{}'
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
18
models.py
18
models.py
|
|
@ -10,25 +10,19 @@ from secp256k1 import PublicKey
|
||||||
|
|
||||||
class NostrRelay(BaseModel):
|
class NostrRelay(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
wallet: str
|
|
||||||
name: 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]
|
description: Optional[str]
|
||||||
pubkey: Optional[str]
|
pubkey: Optional[str]
|
||||||
contact: Optional[str] = "https://t.me/lnbits"
|
contact: Optional[str] = "https://t.me/lnbits"
|
||||||
supported_nips: List[str] = ["NIP01", "NIP09", "NIP11", "NIP15", "NIP20"]
|
supported_nips: List[str] = ["NIP01", "NIP09", "NIP11", "NIP15", "NIP20"]
|
||||||
software: Optional[str] = "LNbist"
|
software: Optional[str] = "LNbist"
|
||||||
version: Optional[str]
|
version: Optional[str]
|
||||||
|
# meta: Optional[str]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_row(cls, row: Row) -> "NostrRelay":
|
||||||
|
return cls(**dict(row))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class NostrEventType(str, Enum):
|
class NostrEventType(str, Enum):
|
||||||
|
|
|
||||||
|
|
@ -22,29 +22,22 @@ const relays = async () => {
|
||||||
wallet: ''
|
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: {
|
relaysTable: {
|
||||||
columns: [
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
align: 'left',
|
||||||
|
label: 'ID',
|
||||||
|
field: 'id'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: '',
|
name: '',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
label: '',
|
label: '',
|
||||||
field: ''
|
field: ''
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'name',
|
name: 'name',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
|
@ -58,16 +51,16 @@ const relays = async () => {
|
||||||
field: 'description'
|
field: 'description'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'type',
|
name: 'pubkey',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
label: 'Type',
|
label: 'Public Key',
|
||||||
field: 'type'
|
field: 'pubkey'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'amount',
|
name: 'contact',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
label: 'Amount',
|
label: 'Contact',
|
||||||
field: 'amount'
|
field: 'contact'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
pagination: {
|
pagination: {
|
||||||
|
|
@ -79,17 +72,14 @@ const relays = async () => {
|
||||||
methods: {
|
methods: {
|
||||||
getDefaultRelayData: function () {
|
getDefaultRelayData: function () {
|
||||||
return {
|
return {
|
||||||
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
type: this.relayTypes[0],
|
pubkey: '',
|
||||||
amount: '100',
|
contact: ''
|
||||||
wallet: ''
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getRelayTypeLabel: function (relayType) {
|
|
||||||
const type = this.relayTypes.find(s => (s.id = relayType))
|
|
||||||
return type ? type.label : '?'
|
|
||||||
},
|
|
||||||
openCreateRelayDialog: function () {
|
openCreateRelayDialog: function () {
|
||||||
this.formDialogRelay.data = this.getDefaultRelayData()
|
this.formDialogRelay.data = this.getDefaultRelayData()
|
||||||
this.formDialogRelay.show = true
|
this.formDialogRelay.show = true
|
||||||
|
|
@ -98,7 +88,7 @@ const relays = async () => {
|
||||||
try {
|
try {
|
||||||
const {data} = await LNbits.api.request(
|
const {data} = await LNbits.api.request(
|
||||||
'GET',
|
'GET',
|
||||||
'/reviews/api/v1/survey',
|
'/nostrrelay/api/v1/relay',
|
||||||
this.g.user.wallets[0].inkey
|
this.g.user.wallets[0].inkey
|
||||||
)
|
)
|
||||||
this.relayLinks = data.map(c =>
|
this.relayLinks = data.map(c =>
|
||||||
|
|
@ -116,10 +106,10 @@ const relays = async () => {
|
||||||
|
|
||||||
createRelay: async function (data) {
|
createRelay: async function (data) {
|
||||||
try {
|
try {
|
||||||
data.type = data.type.id
|
console.log('### createRelay', data)
|
||||||
const resp = await LNbits.api.request(
|
const resp = await LNbits.api.request(
|
||||||
'POST',
|
'POST',
|
||||||
'/reviews/api/v1/survey',
|
'/nostrrelay/api/v1/relay',
|
||||||
this.g.user.wallets[0].adminkey,
|
this.g.user.wallets[0].adminkey,
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
|
|
@ -138,7 +128,7 @@ const relays = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await LNbits.api.request(
|
const response = await LNbits.api.request(
|
||||||
'DELETE',
|
'DELETE',
|
||||||
'/reviews/api/v1/survey/' + relayId,
|
'/nostrrelay/api/v1/relay/' + relayId,
|
||||||
this.g.user.wallets[0].adminkey
|
this.g.user.wallets[0].adminkey
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,14 +69,15 @@
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
||||||
<q-td auto-width> {{props.row.name}} </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}}
|
{{props.row.description}}
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td key="type" :props="props" :class="">
|
<q-td key="contact" :props="props">
|
||||||
<div>{{getRelayTypeLabel(props.row.type)}}</div>
|
<div>{{props.row.contact}}</div>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td key="amount" :props="props" :class="">
|
<q-td key="pubkey" :props="props">
|
||||||
<div>{{props.row.amount}}</div>
|
<div>{{props.row.pubkey}}</div>
|
||||||
</q-td>
|
</q-td>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
<q-tr v-if="props.row.expanded" :props="props">
|
<q-tr v-if="props.row.expanded" :props="props">
|
||||||
|
|
|
||||||
74
views_api.py
74
views_api.py
|
|
@ -1,12 +1,23 @@
|
||||||
from http import HTTPStatus
|
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 fastapi.responses import JSONResponse
|
||||||
from loguru import logger
|
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 . import nostrrelay_ext
|
||||||
from .client_manager import NostrClientConnection, NostrClientManager
|
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()
|
client_manager = NostrClientManager()
|
||||||
|
|
||||||
|
|
@ -29,14 +40,61 @@ async def api_nostrrelay_info():
|
||||||
"Access-Control-Allow-Headers": "*",
|
"Access-Control-Allow-Headers": "*",
|
||||||
"Access-Control-Allow-Methods": "GET"
|
"Access-Control-Allow-Methods": "GET"
|
||||||
}
|
}
|
||||||
info = NostrRelayInfo()
|
info = NostrRelay()
|
||||||
return JSONResponse(content=dict(info), headers=headers)
|
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)):
|
@nostrrelay_ext.post("/api/v1/relay")
|
||||||
return await enable_relay(enable)
|
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):
|
@nostrrelay_ext.get("/api/v1/relay")
|
||||||
return enable
|
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",
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue