diff --git a/crud.py b/crud.py index ed5c619..7882943 100644 --- a/crud.py +++ b/crud.py @@ -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: diff --git a/migrations.py b/migrations.py index 7d170ea..bcf9074 100644 --- a/migrations.py +++ b/migrations.py @@ -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 '{}' ); """ diff --git a/models.py b/models.py index bb1efe5..5c01495 100644 --- a/models.py +++ b/models.py @@ -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): diff --git a/static/js/index.js b/static/js/index.js index 633b701..3185beb 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -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 ) diff --git a/templates/nostrrelay/index.html b/templates/nostrrelay/index.html index a06605c..9e62c7a 100644 --- a/templates/nostrrelay/index.html +++ b/templates/nostrrelay/index.html @@ -69,14 +69,15 @@ {{props.row.name}} - + {{props.row.id}} + {{props.row.description}} - -
{{getRelayTypeLabel(props.row.type)}}
+ +
{{props.row.contact}}
- -
{{props.row.amount}}
+ +
{{props.row.pubkey}}
diff --git a/views_api.py b/views_api.py index 60c268c..0e204ba 100644 --- a/views_api.py +++ b/views_api.py @@ -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", + )