feat: update to v1.0.0 (#30)
This commit is contained in:
parent
2bdbbb274d
commit
73054fd5ce
20 changed files with 2029 additions and 2132 deletions
|
|
@ -2,12 +2,17 @@
|
|||
"name": "Nostr Relay",
|
||||
"short_description": "One click launch your own relay!",
|
||||
"tile": "/nostrrelay/static/image/nostrrelay.png",
|
||||
"min_lnbits_version": "0.12.6",
|
||||
"min_lnbits_version": "1.0.0",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "motorina0",
|
||||
"uri": "https://github.com/motorina0",
|
||||
"role": "Contributor"
|
||||
},
|
||||
{
|
||||
"name": "dni",
|
||||
"uri": "https://github.com/dni",
|
||||
"role": "Contributor"
|
||||
}
|
||||
],
|
||||
"images": [
|
||||
|
|
|
|||
367
crud.py
367
crud.py
|
|
@ -1,115 +1,72 @@
|
|||
import json
|
||||
from typing import List, Optional, Tuple
|
||||
from typing import Optional
|
||||
|
||||
from lnbits.db import Database
|
||||
|
||||
from .models import NostrAccount
|
||||
from .models import NostrAccount, NostrEventTags
|
||||
from .relay.event import NostrEvent
|
||||
from .relay.filter import NostrFilter
|
||||
from .relay.relay import NostrRelay, RelayPublicSpec, RelaySpec
|
||||
from .relay.relay import NostrRelay, RelayPublicSpec
|
||||
|
||||
db = Database("ext_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, meta)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
user_id,
|
||||
r.id,
|
||||
r.name,
|
||||
r.description,
|
||||
r.pubkey,
|
||||
r.contact,
|
||||
json.dumps(dict(r.config)),
|
||||
),
|
||||
)
|
||||
relay = await get_relay(user_id, r.id)
|
||||
assert relay, "Created relay cannot be retrieved"
|
||||
async def create_relay(relay: NostrRelay) -> NostrRelay:
|
||||
await db.insert("nostrrelay.relays", relay)
|
||||
return relay
|
||||
|
||||
|
||||
async def update_relay(user_id: str, r: NostrRelay) -> NostrRelay:
|
||||
await db.execute(
|
||||
"""
|
||||
UPDATE nostrrelay.relays
|
||||
SET (name, description, pubkey, contact, active, meta) = (?, ?, ?, ?, ?, ?)
|
||||
WHERE user_id = ? AND id = ?
|
||||
""",
|
||||
(
|
||||
r.name,
|
||||
r.description,
|
||||
r.pubkey,
|
||||
r.contact,
|
||||
r.active,
|
||||
json.dumps(dict(r.config)),
|
||||
user_id,
|
||||
r.id,
|
||||
),
|
||||
)
|
||||
|
||||
return r
|
||||
async def update_relay(relay: NostrRelay) -> NostrRelay:
|
||||
await db.update("nostrrelay.relays", relay, "WHERE user_id = :user_id AND id = :id")
|
||||
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 await db.fetchone(
|
||||
"SELECT * FROM nostrrelay.relays WHERE user_id = :user_id AND id = :id",
|
||||
{"user_id": user_id, "id": relay_id},
|
||||
NostrRelay,
|
||||
)
|
||||
|
||||
return NostrRelay.from_row(row) if row else None
|
||||
|
||||
|
||||
async def get_relay_by_id(relay_id: str) -> Optional[NostrRelay]:
|
||||
"""Note: it does not require `user_id`. Can read any relay. Use it with care."""
|
||||
row = await db.fetchone(
|
||||
"""SELECT * FROM nostrrelay.relays WHERE id = ?""",
|
||||
(relay_id,),
|
||||
return await db.fetchone(
|
||||
"SELECT * FROM nostrrelay.relays WHERE id = :id",
|
||||
{"id": relay_id},
|
||||
NostrRelay,
|
||||
)
|
||||
|
||||
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 = ? ORDER BY id ASC""",
|
||||
(user_id,),
|
||||
async def get_relays(user_id: str) -> list[NostrRelay]:
|
||||
return await db.fetchall(
|
||||
"SELECT * FROM nostrrelay.relays WHERE user_id = :user_id ORDER BY id ASC",
|
||||
{"user_id": user_id},
|
||||
NostrRelay,
|
||||
)
|
||||
|
||||
return [NostrRelay.from_row(row) for row in rows]
|
||||
|
||||
|
||||
async def get_config_for_all_active_relays() -> dict:
|
||||
rows = await db.fetchall(
|
||||
relays = await db.fetchall(
|
||||
"SELECT id, meta FROM nostrrelay.relays WHERE active = true",
|
||||
model=NostrRelay,
|
||||
)
|
||||
active_relay_configs = {}
|
||||
for r in rows:
|
||||
active_relay_configs[r["id"]] = RelaySpec(
|
||||
**json.loads(r["meta"])
|
||||
) # todo: from_json
|
||||
for relay in relays:
|
||||
active_relay_configs[relay.id] = relay.meta.dict()
|
||||
|
||||
return active_relay_configs
|
||||
|
||||
|
||||
async def get_public_relay(relay_id: str) -> Optional[dict]:
|
||||
row = await db.fetchone(
|
||||
"""SELECT * FROM nostrrelay.relays WHERE id = ?""", (relay_id,)
|
||||
relay = await db.fetchone(
|
||||
"SELECT * FROM nostrrelay.relays WHERE id = :id",
|
||||
{"id": relay_id},
|
||||
NostrRelay,
|
||||
)
|
||||
|
||||
if not row:
|
||||
if not relay:
|
||||
return None
|
||||
|
||||
relay = NostrRelay.from_row(row)
|
||||
return {
|
||||
**NostrRelay.info(),
|
||||
"id": relay.id,
|
||||
|
|
@ -117,88 +74,66 @@ async def get_public_relay(relay_id: str) -> Optional[dict]:
|
|||
"description": relay.description,
|
||||
"pubkey": relay.pubkey,
|
||||
"contact": relay.contact,
|
||||
"config": RelayPublicSpec(**dict(relay.config)).dict(by_alias=True),
|
||||
"config": RelayPublicSpec(**relay.meta.dict()).dict(by_alias=True),
|
||||
}
|
||||
|
||||
|
||||
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,
|
||||
),
|
||||
"DELETE FROM nostrrelay.relays WHERE user_id = :user_id AND id = :id",
|
||||
{"user_id": user_id, "id": relay_id},
|
||||
)
|
||||
|
||||
|
||||
########################## EVENTS ####################
|
||||
async def create_event(relay_id: str, e: NostrEvent, publisher: Optional[str]):
|
||||
publisher = publisher if publisher else e.pubkey
|
||||
await db.execute(
|
||||
"""
|
||||
INSERT INTO nostrrelay.events (
|
||||
relay_id,
|
||||
publisher,
|
||||
id,
|
||||
pubkey,
|
||||
created_at,
|
||||
kind,
|
||||
content,
|
||||
sig,
|
||||
size
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (relay_id, id) DO NOTHING
|
||||
""",
|
||||
(
|
||||
relay_id,
|
||||
publisher,
|
||||
e.id,
|
||||
e.pubkey,
|
||||
e.created_at,
|
||||
e.kind,
|
||||
e.content,
|
||||
e.sig,
|
||||
e.size_bytes,
|
||||
),
|
||||
)
|
||||
async def create_event(event: NostrEvent):
|
||||
await db.update("nostrrelay.events", event)
|
||||
|
||||
# todo: optimize with bulk insert
|
||||
for tag in e.tags:
|
||||
for tag in event.tags:
|
||||
name, value, *rest = tag
|
||||
extra = json.dumps(rest) if rest else None
|
||||
await create_event_tags(relay_id, e.id, name, value, extra)
|
||||
_tag = NostrEventTags(
|
||||
event_id=event.id,
|
||||
name=name,
|
||||
value=value,
|
||||
extra=extra,
|
||||
)
|
||||
await create_event_tags(_tag)
|
||||
|
||||
|
||||
async def get_events(
|
||||
relay_id: str, nostr_filter: NostrFilter, include_tags=True
|
||||
) -> List[NostrEvent]:
|
||||
query, values = build_select_events_query(relay_id, nostr_filter)
|
||||
) -> list[NostrEvent]:
|
||||
|
||||
rows = await db.fetchall(query, tuple(values))
|
||||
inner_joins, where, values = nostr_filter.to_sql_components(relay_id)
|
||||
query = f"""
|
||||
SELECT * FROM nostrrelay.events
|
||||
{" ".join(inner_joins)}
|
||||
WHERE { " AND ".join(where)}
|
||||
ORDER BY created_at DESC
|
||||
"""
|
||||
|
||||
events = []
|
||||
for row in rows:
|
||||
event = NostrEvent.from_row(row)
|
||||
# todo: check & enforce range
|
||||
if nostr_filter.limit and nostr_filter.limit > 0:
|
||||
query += f" LIMIT {nostr_filter.limit}"
|
||||
|
||||
events = await db.fetchall(query, values, NostrEvent)
|
||||
|
||||
for event in events:
|
||||
if include_tags:
|
||||
event.tags = await get_event_tags(relay_id, event.id)
|
||||
events.append(event)
|
||||
|
||||
return events
|
||||
|
||||
|
||||
async def get_event(relay_id: str, event_id: str) -> Optional[NostrEvent]:
|
||||
row = await db.fetchone(
|
||||
"SELECT * FROM nostrrelay.events WHERE relay_id = ? AND id = ?",
|
||||
(
|
||||
relay_id,
|
||||
event_id,
|
||||
),
|
||||
event = await db.fetchone(
|
||||
"SELECT * FROM nostrrelay.events WHERE relay_id = :relay_id AND id = :id",
|
||||
{"relay_id": relay_id, "id": event_id},
|
||||
NostrEvent,
|
||||
)
|
||||
if not row:
|
||||
if not event:
|
||||
return None
|
||||
|
||||
event = NostrEvent.from_row(row)
|
||||
event.tags = await get_event_tags(relay_id, event_id)
|
||||
return event
|
||||
|
||||
|
|
@ -209,36 +144,36 @@ async def get_storage_for_public_key(relay_id: str, publisher_pubkey: str) -> in
|
|||
Deleted events are also counted
|
||||
"""
|
||||
|
||||
row = await db.fetchone(
|
||||
result = await db.execute(
|
||||
"""
|
||||
SELECT SUM(size) as sum FROM nostrrelay.events
|
||||
WHERE relay_id = ? AND publisher = ? GROUP BY publisher
|
||||
WHERE relay_id = :relay_id AND publisher = :publisher GROUP BY publisher
|
||||
""",
|
||||
(
|
||||
relay_id,
|
||||
publisher_pubkey,
|
||||
),
|
||||
{"relay_id": relay_id, "publisher": publisher_pubkey},
|
||||
)
|
||||
row = await result.mappings().first()
|
||||
if not row:
|
||||
return 0
|
||||
|
||||
return round(row["sum"])
|
||||
|
||||
|
||||
async def get_prunable_events(relay_id: str, pubkey: str) -> List[Tuple[str, int]]:
|
||||
async def get_prunable_events(relay_id: str, pubkey: str) -> list[tuple[str, int]]:
|
||||
"""
|
||||
Return the oldest 10 000 events. Only the `id` and the size are returned,
|
||||
so the data size should be small
|
||||
"""
|
||||
query = """
|
||||
SELECT id, size FROM nostrrelay.events
|
||||
WHERE relay_id = ? AND pubkey = ?
|
||||
ORDER BY created_at ASC LIMIT 10000
|
||||
events = await db.fetchall(
|
||||
"""
|
||||
SELECT * FROM nostrrelay.events
|
||||
WHERE relay_id = :relay_id AND pubkey = :pubkey
|
||||
ORDER BY created_at ASC LIMIT 10000
|
||||
""",
|
||||
{"relay_id": relay_id, "pubkey": pubkey},
|
||||
NostrEvent,
|
||||
)
|
||||
|
||||
rows = await db.fetchall(query, (relay_id, pubkey))
|
||||
|
||||
return [(r["id"], r["size"]) for r in rows]
|
||||
return [(event.id, event.size_bytes) for event in events]
|
||||
|
||||
|
||||
async def mark_events_deleted(relay_id: str, nostr_filter: NostrFilter):
|
||||
|
|
@ -247,8 +182,8 @@ async def mark_events_deleted(relay_id: str, nostr_filter: NostrFilter):
|
|||
_, where, values = nostr_filter.to_sql_components(relay_id)
|
||||
|
||||
await db.execute(
|
||||
f"""UPDATE nostrrelay.events SET deleted=true WHERE {" AND ".join(where)}""",
|
||||
tuple(values),
|
||||
f"UPDATE nostrrelay.events SET deleted=true WHERE {' AND '.join(where)}",
|
||||
values,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -257,11 +192,12 @@ async def delete_events(relay_id: str, nostr_filter: NostrFilter):
|
|||
return None
|
||||
_, where, values = nostr_filter.to_sql_components(relay_id)
|
||||
|
||||
query = f"""DELETE from nostrrelay.events WHERE {" AND ".join(where)}"""
|
||||
await db.execute(query, tuple(values))
|
||||
query = f"DELETE from nostrrelay.events WHERE {' AND '.join(where)}"
|
||||
await db.execute(query, values)
|
||||
# todo: delete tags
|
||||
|
||||
|
||||
# move to services
|
||||
async def prune_old_events(relay_id: str, pubkey: str, space_to_regain: int):
|
||||
prunable_events = await get_prunable_events(relay_id, pubkey)
|
||||
prunable_event_ids = []
|
||||
|
|
@ -278,113 +214,58 @@ async def prune_old_events(relay_id: str, pubkey: str, space_to_regain: int):
|
|||
|
||||
|
||||
async def delete_all_events(relay_id: str):
|
||||
query = "DELETE from nostrrelay.events WHERE relay_id = ?"
|
||||
await db.execute(query, (relay_id,))
|
||||
await db.execute(
|
||||
"DELETE from nostrrelay.events WHERE relay_id = :id",
|
||||
{"id": relay_id},
|
||||
)
|
||||
# todo: delete tags
|
||||
|
||||
|
||||
async def create_event_tags(
|
||||
relay_id: str,
|
||||
event_id: str,
|
||||
tag_name: str,
|
||||
tag_value: str,
|
||||
extra_values: Optional[str],
|
||||
):
|
||||
await db.execute(
|
||||
async def create_event_tags(tag: NostrEventTags):
|
||||
await db.insert("nostrrelay.event_tags", tag)
|
||||
|
||||
|
||||
async def get_event_tags(relay_id: str, event_id: str) -> list[list[str]]:
|
||||
_tags = await db.fetchall(
|
||||
"""
|
||||
INSERT INTO nostrrelay.event_tags (
|
||||
relay_id,
|
||||
event_id,
|
||||
name,
|
||||
value,
|
||||
extra
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
SELECT * FROM nostrrelay.event_tags
|
||||
WHERE relay_id = :relay_id and event_id = :event_id
|
||||
""",
|
||||
(relay_id, event_id, tag_name, tag_value, extra_values),
|
||||
{"relay_id": relay_id, "event_id": event_id},
|
||||
model=NostrEventTags,
|
||||
)
|
||||
|
||||
|
||||
async def get_event_tags(relay_id: str, event_id: str) -> List[List[str]]:
|
||||
rows = await db.fetchall(
|
||||
"SELECT * FROM nostrrelay.event_tags WHERE relay_id = ? and event_id = ?",
|
||||
(relay_id, event_id),
|
||||
)
|
||||
|
||||
tags: List[List[str]] = []
|
||||
for row in rows:
|
||||
tag = [row["name"], row["value"]]
|
||||
extra = row["extra"]
|
||||
if extra:
|
||||
tag += json.loads(extra)
|
||||
tags.append(tag)
|
||||
tags: list[list[str]] = []
|
||||
for tag in _tags:
|
||||
_tag = [tag.name, tag.value]
|
||||
if tag.extra:
|
||||
_tag += json.loads(tag.extra)
|
||||
tags.append(_tag)
|
||||
|
||||
return tags
|
||||
|
||||
|
||||
def build_select_events_query(relay_id: str, nostr_filter: NostrFilter):
|
||||
inner_joins, where, values = nostr_filter.to_sql_components(relay_id)
|
||||
|
||||
query = f"""
|
||||
SELECT id, pubkey, created_at, kind, content, sig
|
||||
FROM nostrrelay.events
|
||||
{" ".join(inner_joins)}
|
||||
WHERE { " AND ".join(where)}
|
||||
ORDER BY created_at DESC
|
||||
"""
|
||||
|
||||
# todo: check & enforce range
|
||||
if nostr_filter.limit and nostr_filter.limit > 0:
|
||||
query += f" LIMIT {nostr_filter.limit}"
|
||||
|
||||
return query, values
|
||||
|
||||
|
||||
########################## ACCOUNTS ####################
|
||||
|
||||
|
||||
async def create_account(relay_id: str, a: NostrAccount) -> NostrAccount:
|
||||
await db.execute(
|
||||
"""
|
||||
INSERT INTO nostrrelay.accounts
|
||||
(relay_id, pubkey, sats, storage, paid_to_join, allowed, blocked)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
relay_id,
|
||||
a.pubkey,
|
||||
a.sats,
|
||||
a.storage,
|
||||
a.paid_to_join,
|
||||
a.allowed,
|
||||
a.blocked,
|
||||
),
|
||||
)
|
||||
account = await get_account(relay_id, a.pubkey)
|
||||
assert account, "Created account cannot be retrieved"
|
||||
async def create_account(account: NostrAccount) -> NostrAccount:
|
||||
await db.insert("nostrrelay.accounts", account)
|
||||
return account
|
||||
|
||||
|
||||
async def update_account(relay_id: str, a: NostrAccount) -> NostrAccount:
|
||||
await db.execute(
|
||||
"""
|
||||
UPDATE nostrrelay.accounts
|
||||
SET (sats, storage, paid_to_join, allowed, blocked) = (?, ?, ?, ?, ?)
|
||||
WHERE relay_id = ? AND pubkey = ?
|
||||
""",
|
||||
(a.sats, a.storage, a.paid_to_join, a.allowed, a.blocked, relay_id, a.pubkey),
|
||||
async def update_account(account: NostrAccount) -> NostrAccount:
|
||||
await db.update(
|
||||
"nostrrelay.accounts",
|
||||
account,
|
||||
"WHERE relay_id = :relay_id AND pubkey = :pubkey",
|
||||
)
|
||||
|
||||
return a
|
||||
return account
|
||||
|
||||
|
||||
async def delete_account(relay_id: str, pubkey: str):
|
||||
await db.execute(
|
||||
"""
|
||||
DELETE FROM nostrrelay.accounts
|
||||
WHERE relay_id = ? AND pubkey = ?
|
||||
WHERE relay_id = :id AND pubkey = :pubkey
|
||||
""",
|
||||
(relay_id, pubkey),
|
||||
{"id": relay_id, "pubkey": pubkey},
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -392,28 +273,28 @@ async def get_account(
|
|||
relay_id: str,
|
||||
pubkey: str,
|
||||
) -> Optional[NostrAccount]:
|
||||
row = await db.fetchone(
|
||||
"SELECT * FROM nostrrelay.accounts WHERE relay_id = ? AND pubkey = ?",
|
||||
(relay_id, pubkey),
|
||||
return await db.fetchone(
|
||||
"""
|
||||
SELECT * FROM nostrrelay.accounts
|
||||
WHERE relay_id = :id AND pubkey = :pubkey
|
||||
""",
|
||||
{"id": relay_id, "pubkey": pubkey},
|
||||
NostrAccount,
|
||||
)
|
||||
|
||||
return NostrAccount.from_row(row) if row else None
|
||||
|
||||
|
||||
async def get_accounts(
|
||||
relay_id: str,
|
||||
allowed=True,
|
||||
blocked=False,
|
||||
) -> List[NostrAccount]:
|
||||
|
||||
) -> list[NostrAccount]:
|
||||
if not allowed and not blocked:
|
||||
return []
|
||||
|
||||
rows = await db.fetchall(
|
||||
return await db.fetchall(
|
||||
"""
|
||||
SELECT * FROM nostrrelay.accounts
|
||||
WHERE relay_id = ? AND allowed = ? OR blocked = ?"
|
||||
WHERE relay_id = :id AND allowed = :allowed OR blocked = :blocked
|
||||
""",
|
||||
(relay_id, allowed, blocked),
|
||||
{"id": relay_id, "allowed": allowed, "blocked": blocked},
|
||||
NostrAccount,
|
||||
)
|
||||
return [NostrAccount.from_row(row) for row in rows]
|
||||
|
|
|
|||
13
models.py
13
models.py
|
|
@ -1,4 +1,3 @@
|
|||
from sqlite3 import Row
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
|
@ -23,6 +22,7 @@ class NostrPartialAccount(BaseModel):
|
|||
|
||||
class NostrAccount(BaseModel):
|
||||
pubkey: str
|
||||
relay_id: str
|
||||
sats: int = 0
|
||||
storage: int = 0
|
||||
paid_to_join: bool = False
|
||||
|
|
@ -36,8 +36,11 @@ class NostrAccount(BaseModel):
|
|||
|
||||
@classmethod
|
||||
def null_account(cls) -> "NostrAccount":
|
||||
return NostrAccount(pubkey="")
|
||||
return NostrAccount(pubkey="", relay_id="")
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row) -> "NostrAccount":
|
||||
return cls(**dict(row))
|
||||
|
||||
class NostrEventTags(BaseModel):
|
||||
event_id: str
|
||||
name: str
|
||||
value: str
|
||||
extra: Optional[str] = None
|
||||
|
|
|
|||
2270
poetry.lock
generated
2270
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -6,7 +6,7 @@ authors = ["dni <dni@lnbits.com>"]
|
|||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10 | ^3.9"
|
||||
lnbits = "*"
|
||||
lnbits = {allow-prereleases = true, version = "*"}
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = "^24.3.0"
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ class NostrClientConnection:
|
|||
NostrFilter(kinds=[e.kind], authors=[e.pubkey], until=e.created_at),
|
||||
)
|
||||
if not e.is_ephemeral_event:
|
||||
await create_event(self.relay_id, e, self.auth_pubkey)
|
||||
await create_event(e)
|
||||
await self._broadcast_event(e)
|
||||
|
||||
if e.is_delete_event:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import hashlib
|
||||
import json
|
||||
from enum import Enum
|
||||
from sqlite3 import Row
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
from secp256k1 import PublicKey
|
||||
|
|
@ -20,11 +18,11 @@ class NostrEvent(BaseModel):
|
|||
pubkey: str
|
||||
created_at: int
|
||||
kind: int
|
||||
tags: List[List[str]] = []
|
||||
tags: list[list[str]] = []
|
||||
content: str = ""
|
||||
sig: str
|
||||
|
||||
def serialize(self) -> List:
|
||||
def serialize(self) -> list:
|
||||
return [0, self.pubkey, self.created_at, self.kind, self.tags, self.content]
|
||||
|
||||
def serialize_json(self) -> str:
|
||||
|
|
@ -87,7 +85,7 @@ class NostrEvent(BaseModel):
|
|||
def serialize_response(self, subscription_id):
|
||||
return [NostrEventType.EVENT, subscription_id, dict(self)]
|
||||
|
||||
def tag_values(self, tag_name: str) -> List[str]:
|
||||
def tag_values(self, tag_name: str) -> list[str]:
|
||||
return [t[1] for t in self.tags if t[0] == tag_name]
|
||||
|
||||
def has_tag_value(self, tag_name: str, tag_value: str) -> bool:
|
||||
|
|
@ -95,7 +93,3 @@ class NostrEvent(BaseModel):
|
|||
|
||||
def is_direct_message_for_pubkey(self, pubkey: str) -> bool:
|
||||
return self.is_direct_message and self.has_tag_value("p", pubkey)
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row) -> "NostrEvent":
|
||||
return cls(**dict(row))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Any, List, Optional, Tuple
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
|
@ -6,11 +6,11 @@ from .event import NostrEvent
|
|||
|
||||
|
||||
class NostrFilter(BaseModel):
|
||||
e: List[str] = Field(default=[], alias="#e")
|
||||
p: List[str] = Field(default=[], alias="#p")
|
||||
ids: List[str] = []
|
||||
authors: List[str] = []
|
||||
kinds: List[int] = []
|
||||
e: list[str] = Field(default=[], alias="#e")
|
||||
p: list[str] = Field(default=[], alias="#p")
|
||||
ids: list[str] = []
|
||||
authors: list[str] = []
|
||||
kinds: list[int] = []
|
||||
subscription_id: Optional[str] = None
|
||||
since: Optional[int] = None
|
||||
until: Optional[int] = None
|
||||
|
|
@ -66,16 +66,13 @@ class NostrFilter(BaseModel):
|
|||
if not self.limit or self.limit > limit:
|
||||
self.limit = limit
|
||||
|
||||
def to_sql_components(
|
||||
self, relay_id: str
|
||||
) -> Tuple[List[str], List[str], List[Any]]:
|
||||
inner_joins: List[str] = []
|
||||
where = ["deleted=false", "nostrrelay.events.relay_id = ?"]
|
||||
values: List[Any] = [relay_id]
|
||||
def to_sql_components(self, relay_id: str) -> tuple[list[str], list[str], dict]:
|
||||
inner_joins: list[str] = []
|
||||
where = ["deleted=false", "nostrrelay.events.relay_id = :relay_id"]
|
||||
values: dict = {"relay_id": relay_id}
|
||||
|
||||
if len(self.e):
|
||||
values += self.e
|
||||
e_s = ",".join(["?"] * len(self.e))
|
||||
e_s = ",".join([f"'{e}'" for e in self.e])
|
||||
inner_joins.append(
|
||||
"INNER JOIN nostrrelay.event_tags e_tags "
|
||||
"ON nostrrelay.events.id = e_tags.event_id"
|
||||
|
|
@ -83,8 +80,7 @@ class NostrFilter(BaseModel):
|
|||
where.append(f" (e_tags.value in ({e_s}) AND e_tags.name = 'e')")
|
||||
|
||||
if len(self.p):
|
||||
values += self.p
|
||||
p_s = ",".join(["?"] * len(self.p))
|
||||
p_s = ",".join([f"'{p}'" for p in self.p])
|
||||
inner_joins.append(
|
||||
"INNER JOIN nostrrelay.event_tags p_tags "
|
||||
"ON nostrrelay.events.id = p_tags.event_id"
|
||||
|
|
@ -92,26 +88,23 @@ class NostrFilter(BaseModel):
|
|||
where.append(f" p_tags.value in ({p_s}) AND p_tags.name = 'p'")
|
||||
|
||||
if len(self.ids) != 0:
|
||||
ids = ",".join(["?"] * len(self.ids))
|
||||
ids = ",".join([f"'{_id}'" for _id in self.ids])
|
||||
where.append(f"id IN ({ids})")
|
||||
values += self.ids
|
||||
|
||||
if len(self.authors) != 0:
|
||||
authors = ",".join(["?"] * len(self.authors))
|
||||
authors = ",".join([f"'{author}'" for author in self.authors])
|
||||
where.append(f"pubkey IN ({authors})")
|
||||
values += self.authors
|
||||
|
||||
if len(self.kinds) != 0:
|
||||
kinds = ",".join(["?"] * len(self.kinds))
|
||||
kinds = ",".join([f"'{kind}'" for kind in self.kinds])
|
||||
where.append(f"kind IN ({kinds})")
|
||||
values += self.kinds
|
||||
|
||||
if self.since:
|
||||
where.append("created_at >= ?")
|
||||
values += [self.since]
|
||||
where.append("created_at >= :since")
|
||||
values["since"] = self.since
|
||||
|
||||
if self.until:
|
||||
where.append("created_at < ?")
|
||||
values += [self.until]
|
||||
where.append("created_at < :until")
|
||||
values["until"] = self.until
|
||||
|
||||
return inner_joins, where, values
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import json
|
||||
from sqlite3 import Row
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
|
@ -102,23 +100,17 @@ class RelaySpec(RelayPublicSpec, WalletSpec, AuthSpec):
|
|||
|
||||
class NostrRelay(BaseModel):
|
||||
id: str
|
||||
user_id: Optional[str] = None
|
||||
name: str
|
||||
description: Optional[str]
|
||||
pubkey: Optional[str]
|
||||
contact: Optional[str]
|
||||
description: Optional[str] = None
|
||||
pubkey: Optional[str] = None
|
||||
contact: Optional[str] = None
|
||||
active: bool = False
|
||||
|
||||
config = RelaySpec()
|
||||
meta: RelaySpec = RelaySpec()
|
||||
|
||||
@property
|
||||
def is_free_to_join(self):
|
||||
return not self.config.is_paid_relay or self.config.cost_to_join == 0
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row) -> "NostrRelay":
|
||||
relay = cls(**dict(row))
|
||||
relay.config = RelaySpec(**json.loads(row["meta"]))
|
||||
return relay
|
||||
return not self.meta.is_paid_relay or self.meta.cost_to_join == 0
|
||||
|
||||
@classmethod
|
||||
def info(
|
||||
|
|
|
|||
290
static/components/relay-details.js
Normal file
290
static/components/relay-details.js
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
window.app.component('relay-details', {
|
||||
name: 'relay-details',
|
||||
template: '#relay-details',
|
||||
props: ['relay-id', 'adminkey', 'inkey', 'wallet-options'],
|
||||
data() {
|
||||
return {
|
||||
tab: 'info',
|
||||
relay: null,
|
||||
accounts: [],
|
||||
accountPubkey: '',
|
||||
formDialogItem: {
|
||||
show: false,
|
||||
data: {
|
||||
name: '',
|
||||
description: ''
|
||||
}
|
||||
},
|
||||
showBlockedAccounts: true,
|
||||
showAllowedAccounts: false,
|
||||
accountsTable: {
|
||||
columns: [
|
||||
{
|
||||
name: 'action',
|
||||
align: 'left',
|
||||
label: '',
|
||||
field: ''
|
||||
},
|
||||
{
|
||||
name: 'pubkey',
|
||||
align: 'left',
|
||||
label: 'Public Key',
|
||||
field: 'pubkey'
|
||||
},
|
||||
{
|
||||
name: 'allowed',
|
||||
align: 'left',
|
||||
label: 'Allowed',
|
||||
field: 'allowed'
|
||||
},
|
||||
{
|
||||
name: 'blocked',
|
||||
align: 'left',
|
||||
label: 'Blocked',
|
||||
field: 'blocked'
|
||||
},
|
||||
{
|
||||
name: 'paid_to_join',
|
||||
align: 'left',
|
||||
label: 'Paid to join',
|
||||
field: 'paid_to_join'
|
||||
},
|
||||
{
|
||||
name: 'sats',
|
||||
align: 'left',
|
||||
label: 'Spent Sats',
|
||||
field: 'sats'
|
||||
},
|
||||
{
|
||||
name: 'storage',
|
||||
align: 'left',
|
||||
label: 'Storage',
|
||||
field: 'storage'
|
||||
}
|
||||
],
|
||||
pagination: {
|
||||
rowsPerPage: 10
|
||||
}
|
||||
},
|
||||
skipEventKind: 0,
|
||||
forceEventKind: 0
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
hours() {
|
||||
const y = []
|
||||
for (let i = 0; i <= 24; i++) {
|
||||
y.push(i)
|
||||
}
|
||||
return y
|
||||
},
|
||||
range60() {
|
||||
const y = []
|
||||
for (let i = 0; i <= 60; i++) {
|
||||
y.push(i)
|
||||
}
|
||||
return y
|
||||
},
|
||||
storageUnits() {
|
||||
return ['KB', 'MB']
|
||||
},
|
||||
fullStorageActions() {
|
||||
return [
|
||||
{value: 'block', label: 'Block New Events'},
|
||||
{value: 'prune', label: 'Prune Old Events'}
|
||||
]
|
||||
},
|
||||
wssLink() {
|
||||
this.relay.meta.domain =
|
||||
this.relay.meta.domain || window.location.hostname
|
||||
return 'wss://' + this.relay.meta.domain + '/nostrrelay/' + this.relay.id
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
satBtc(val, showUnit = true) {
|
||||
return satOrBtc(val, showUnit, this.satsDenominated)
|
||||
},
|
||||
deleteRelay() {
|
||||
LNbits.utils
|
||||
.confirmDialog(
|
||||
'All data will be lost! Are you sure you want to delete this relay?'
|
||||
)
|
||||
.onOk(async () => {
|
||||
try {
|
||||
await LNbits.api.request(
|
||||
'DELETE',
|
||||
'/nostrrelay/api/v1/relay/' + this.relayId,
|
||||
this.adminkey
|
||||
)
|
||||
this.$emit('relay-deleted', this.relayId)
|
||||
Quasar.Notify.create({
|
||||
type: 'positive',
|
||||
message: 'Relay Deleted',
|
||||
timeout: 5000
|
||||
})
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
})
|
||||
},
|
||||
async getRelay() {
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'GET',
|
||||
'/nostrrelay/api/v1/relay/' + this.relayId,
|
||||
this.inkey
|
||||
)
|
||||
this.relay = data
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
async updateRelay() {
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'PATCH',
|
||||
'/nostrrelay/api/v1/relay/' + this.relayId,
|
||||
this.adminkey,
|
||||
this.relay
|
||||
)
|
||||
this.relay = data
|
||||
this.$emit('relay-updated', this.relay)
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: 'Relay Updated',
|
||||
timeout: 5000
|
||||
})
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
togglePaidRelay: async function () {
|
||||
this.relay.meta.wallet =
|
||||
this.relay.meta.wallet || this.walletOptions[0].value
|
||||
},
|
||||
getAccounts: async function () {
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'GET',
|
||||
`/nostrrelay/api/v1/account?relay_id=${this.relay.id}&allowed=${this.showAllowedAccounts}&blocked=${this.showBlockedAccounts}`,
|
||||
this.inkey
|
||||
)
|
||||
this.accounts = data
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
allowPublicKey: async function (pubkey, allowed) {
|
||||
await this.updatePublicKey({pubkey, allowed})
|
||||
},
|
||||
blockPublicKey: async function (pubkey, blocked = true) {
|
||||
await this.updatePublicKey({pubkey, blocked})
|
||||
},
|
||||
removePublicKey: async function (pubkey) {
|
||||
LNbits.utils
|
||||
.confirmDialog('This public key will be removed from relay!')
|
||||
.onOk(async () => {
|
||||
await this.deletePublicKey(pubkey)
|
||||
})
|
||||
},
|
||||
togglePublicKey: async function (account, action) {
|
||||
if (action === 'allow') {
|
||||
await this.updatePublicKey({
|
||||
pubkey: account.pubkey,
|
||||
allowed: account.allowed
|
||||
})
|
||||
}
|
||||
if (action === 'block') {
|
||||
await this.updatePublicKey({
|
||||
pubkey: account.pubkey,
|
||||
blocked: account.blocked
|
||||
})
|
||||
}
|
||||
},
|
||||
updatePublicKey: async function (ops) {
|
||||
try {
|
||||
await LNbits.api.request(
|
||||
'PUT',
|
||||
'/nostrrelay/api/v1/account',
|
||||
this.adminkey,
|
||||
{
|
||||
relay_id: this.relay.id,
|
||||
pubkey: ops.pubkey,
|
||||
allowed: ops.allowed,
|
||||
blocked: ops.blocked
|
||||
}
|
||||
)
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: 'Account Updated',
|
||||
timeout: 5000
|
||||
})
|
||||
this.accountPubkey = ''
|
||||
await this.getAccounts()
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
|
||||
deletePublicKey: async function (pubkey) {
|
||||
try {
|
||||
await LNbits.api.request(
|
||||
'DELETE',
|
||||
`/nostrrelay/api/v1/account/${this.relay.id}/${pubkey}`,
|
||||
this.adminkey,
|
||||
{}
|
||||
)
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: 'Account Deleted',
|
||||
timeout: 5000
|
||||
})
|
||||
await this.getAccounts()
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
|
||||
addSkipAuthForEvent: function () {
|
||||
value = +this.skipEventKind
|
||||
if (this.relay.meta.skipedAuthEvents.indexOf(value) != -1) {
|
||||
return
|
||||
}
|
||||
this.relay.meta.skipedAuthEvents.push(value)
|
||||
},
|
||||
removeSkipAuthForEvent: function (eventKind) {
|
||||
value = +eventKind
|
||||
this.relay.meta.skipedAuthEvents =
|
||||
this.relay.meta.skipedAuthEvents.filter(e => e !== value)
|
||||
},
|
||||
addForceAuthForEvent: function () {
|
||||
value = +this.forceEventKind
|
||||
if (this.relay.meta.forcedAuthEvents.indexOf(value) != -1) {
|
||||
return
|
||||
}
|
||||
this.relay.meta.forcedAuthEvents.push(value)
|
||||
},
|
||||
removeForceAuthForEvent: function (eventKind) {
|
||||
value = +eventKind
|
||||
this.relay.meta.forcedAuthEvents =
|
||||
this.relay.meta.forcedAuthEvents.filter(e => e !== value)
|
||||
},
|
||||
// todo: bad. base.js not present in custom components
|
||||
copyText: function (text, message, position) {
|
||||
Quasar.copyToClipboard(text).then(function () {
|
||||
Quasar.Notify.create({
|
||||
message: message || 'Copied to clipboard!',
|
||||
position: position || 'bottom'
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
async created() {
|
||||
await this.getRelay()
|
||||
await this.getAccounts()
|
||||
}
|
||||
})
|
||||
|
|
@ -1,298 +0,0 @@
|
|||
async function relayDetails(path) {
|
||||
const template = await loadTemplateAsync(path)
|
||||
Vue.component('relay-details', {
|
||||
name: 'relay-details',
|
||||
template,
|
||||
|
||||
props: ['relay-id', 'adminkey', 'inkey', 'wallet-options'],
|
||||
data: function () {
|
||||
return {
|
||||
tab: 'info',
|
||||
relay: null,
|
||||
accounts: [],
|
||||
accountPubkey: '',
|
||||
formDialogItem: {
|
||||
show: false,
|
||||
data: {
|
||||
name: '',
|
||||
description: ''
|
||||
}
|
||||
},
|
||||
showBlockedAccounts: true,
|
||||
showAllowedAccounts: false,
|
||||
accountsTable: {
|
||||
columns: [
|
||||
{
|
||||
name: 'action',
|
||||
align: 'left',
|
||||
label: '',
|
||||
field: ''
|
||||
},
|
||||
{
|
||||
name: 'pubkey',
|
||||
align: 'left',
|
||||
label: 'Public Key',
|
||||
field: 'pubkey'
|
||||
},
|
||||
{
|
||||
name: 'allowed',
|
||||
align: 'left',
|
||||
label: 'Allowed',
|
||||
field: 'allowed'
|
||||
},
|
||||
{
|
||||
name: 'blocked',
|
||||
align: 'left',
|
||||
label: 'Blocked',
|
||||
field: 'blocked'
|
||||
},
|
||||
{
|
||||
name: 'paid_to_join',
|
||||
align: 'left',
|
||||
label: 'Paid to join',
|
||||
field: 'paid_to_join'
|
||||
},
|
||||
{
|
||||
name: 'sats',
|
||||
align: 'left',
|
||||
label: 'Spent Sats',
|
||||
field: 'sats'
|
||||
},
|
||||
{
|
||||
name: 'storage',
|
||||
align: 'left',
|
||||
label: 'Storage',
|
||||
field: 'storage'
|
||||
}
|
||||
],
|
||||
pagination: {
|
||||
rowsPerPage: 10
|
||||
}
|
||||
},
|
||||
skipEventKind: 0,
|
||||
forceEventKind: 0
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
hours: function () {
|
||||
const y = []
|
||||
for (let i = 0; i <= 24; i++) {
|
||||
y.push(i)
|
||||
}
|
||||
return y
|
||||
},
|
||||
range60: function () {
|
||||
const y = []
|
||||
for (let i = 0; i <= 60; i++) {
|
||||
y.push(i)
|
||||
}
|
||||
return y
|
||||
},
|
||||
storageUnits: function () {
|
||||
return ['KB', 'MB']
|
||||
},
|
||||
fullStorageActions: function () {
|
||||
return [
|
||||
{value: 'block', label: 'Block New Events'},
|
||||
{value: 'prune', label: 'Prune Old Events'}
|
||||
]
|
||||
},
|
||||
wssLink: function () {
|
||||
this.relay.config.domain =
|
||||
this.relay.config.domain || window.location.hostname
|
||||
return (
|
||||
'wss://' + this.relay.config.domain + '/nostrrelay/' + this.relay.id
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
satBtc(val, showUnit = true) {
|
||||
return satOrBtc(val, showUnit, this.satsDenominated)
|
||||
},
|
||||
deleteRelay: function () {
|
||||
LNbits.utils
|
||||
.confirmDialog(
|
||||
'All data will be lost! Are you sure you want to delete this relay?'
|
||||
)
|
||||
.onOk(async () => {
|
||||
try {
|
||||
await LNbits.api.request(
|
||||
'DELETE',
|
||||
'/nostrrelay/api/v1/relay/' + this.relayId,
|
||||
this.adminkey
|
||||
)
|
||||
this.$emit('relay-deleted', this.relayId)
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: 'Relay Deleted',
|
||||
timeout: 5000
|
||||
})
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
})
|
||||
},
|
||||
getRelay: async function () {
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'GET',
|
||||
'/nostrrelay/api/v1/relay/' + this.relayId,
|
||||
this.inkey
|
||||
)
|
||||
this.relay = data
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
updateRelay: async function () {
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'PATCH',
|
||||
'/nostrrelay/api/v1/relay/' + this.relayId,
|
||||
this.adminkey,
|
||||
this.relay
|
||||
)
|
||||
this.relay = data
|
||||
this.$emit('relay-updated', this.relay)
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: 'Relay Updated',
|
||||
timeout: 5000
|
||||
})
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
togglePaidRelay: async function () {
|
||||
this.relay.config.wallet =
|
||||
this.relay.config.wallet || this.walletOptions[0].value
|
||||
},
|
||||
getAccounts: async function () {
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'GET',
|
||||
`/nostrrelay/api/v1/account?relay_id=${this.relay.id}&allowed=${this.showAllowedAccounts}&blocked=${this.showBlockedAccounts}`,
|
||||
this.inkey
|
||||
)
|
||||
this.accounts = data
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
allowPublicKey: async function (pubkey, allowed) {
|
||||
await this.updatePublicKey({pubkey, allowed})
|
||||
},
|
||||
blockPublicKey: async function (pubkey, blocked = true) {
|
||||
await this.updatePublicKey({pubkey, blocked})
|
||||
},
|
||||
removePublicKey: async function (pubkey) {
|
||||
LNbits.utils
|
||||
.confirmDialog('This public key will be removed from relay!')
|
||||
.onOk(async () => {
|
||||
await this.deletePublicKey(pubkey)
|
||||
})
|
||||
},
|
||||
togglePublicKey: async function (account, action) {
|
||||
if (action === 'allow') {
|
||||
await this.updatePublicKey({
|
||||
pubkey: account.pubkey,
|
||||
allowed: account.allowed
|
||||
})
|
||||
}
|
||||
if (action === 'block') {
|
||||
await this.updatePublicKey({
|
||||
pubkey: account.pubkey,
|
||||
blocked: account.blocked
|
||||
})
|
||||
}
|
||||
},
|
||||
updatePublicKey: async function (ops) {
|
||||
try {
|
||||
await LNbits.api.request(
|
||||
'PUT',
|
||||
'/nostrrelay/api/v1/account',
|
||||
this.adminkey,
|
||||
{
|
||||
relay_id: this.relay.id,
|
||||
pubkey: ops.pubkey,
|
||||
allowed: ops.allowed,
|
||||
blocked: ops.blocked
|
||||
}
|
||||
)
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: 'Account Updated',
|
||||
timeout: 5000
|
||||
})
|
||||
this.accountPubkey = ''
|
||||
await this.getAccounts()
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
|
||||
deletePublicKey: async function (pubkey) {
|
||||
try {
|
||||
await LNbits.api.request(
|
||||
'DELETE',
|
||||
`/nostrrelay/api/v1/account/${this.relay.id}/${pubkey}`,
|
||||
this.adminkey,
|
||||
{}
|
||||
)
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: 'Account Deleted',
|
||||
timeout: 5000
|
||||
})
|
||||
await this.getAccounts()
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
|
||||
addSkipAuthForEvent: function () {
|
||||
value = +this.skipEventKind
|
||||
if (this.relay.config.skipedAuthEvents.indexOf(value) != -1) {
|
||||
return
|
||||
}
|
||||
this.relay.config.skipedAuthEvents.push(value)
|
||||
},
|
||||
removeSkipAuthForEvent: function (eventKind) {
|
||||
value = +eventKind
|
||||
this.relay.config.skipedAuthEvents =
|
||||
this.relay.config.skipedAuthEvents.filter(e => e !== value)
|
||||
},
|
||||
addForceAuthForEvent: function () {
|
||||
value = +this.forceEventKind
|
||||
if (this.relay.config.forcedAuthEvents.indexOf(value) != -1) {
|
||||
return
|
||||
}
|
||||
this.relay.config.forcedAuthEvents.push(value)
|
||||
},
|
||||
removeForceAuthForEvent: function (eventKind) {
|
||||
value = +eventKind
|
||||
this.relay.config.forcedAuthEvents =
|
||||
this.relay.config.forcedAuthEvents.filter(e => e !== value)
|
||||
},
|
||||
// todo: bad. base.js not present in custom components
|
||||
copyText: function (text, message, position) {
|
||||
var notify = this.$q.notify
|
||||
Quasar.utils.copyToClipboard(text).then(function () {
|
||||
notify({
|
||||
message: message || 'Copied to clipboard!',
|
||||
position: position || 'bottom'
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
created: async function () {
|
||||
await this.getRelay()
|
||||
await this.getAccounts()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -1,80 +1,13 @@
|
|||
const relays = async () => {
|
||||
Vue.component(VueQrcode.name, VueQrcode)
|
||||
|
||||
await relayDetails('static/components/relay-details/relay-details.html')
|
||||
|
||||
new Vue({
|
||||
el: '#vue',
|
||||
mixins: [windowMixin],
|
||||
data: function () {
|
||||
return {
|
||||
filter: '',
|
||||
relayLinks: [],
|
||||
formDialogRelay: {
|
||||
show: false,
|
||||
data: {
|
||||
id: '',
|
||||
name: '',
|
||||
description: '',
|
||||
pubkey: '',
|
||||
contact: ''
|
||||
}
|
||||
},
|
||||
|
||||
relaysTable: {
|
||||
columns: [
|
||||
{
|
||||
name: '',
|
||||
align: 'left',
|
||||
label: '',
|
||||
field: ''
|
||||
},
|
||||
{
|
||||
name: 'id',
|
||||
align: 'left',
|
||||
label: 'ID',
|
||||
field: 'id'
|
||||
},
|
||||
{
|
||||
name: 'toggle',
|
||||
align: 'left',
|
||||
label: 'Active',
|
||||
field: ''
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
align: 'left',
|
||||
label: 'Name',
|
||||
field: 'name'
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
align: 'left',
|
||||
label: 'Description',
|
||||
field: 'description'
|
||||
},
|
||||
{
|
||||
name: 'pubkey',
|
||||
align: 'left',
|
||||
label: 'Public Key',
|
||||
field: 'pubkey'
|
||||
},
|
||||
{
|
||||
name: 'contact',
|
||||
align: 'left',
|
||||
label: 'Contact',
|
||||
field: 'contact'
|
||||
}
|
||||
],
|
||||
pagination: {
|
||||
rowsPerPage: 10
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDefaultRelayData: function () {
|
||||
return {
|
||||
window.app = Vue.createApp({
|
||||
el: '#vue',
|
||||
mixins: [windowMixin],
|
||||
data() {
|
||||
return {
|
||||
filter: '',
|
||||
relayLinks: [],
|
||||
formDialogRelay: {
|
||||
show: false,
|
||||
data: {
|
||||
id: '',
|
||||
name: '',
|
||||
description: '',
|
||||
|
|
@ -83,99 +16,158 @@ const relays = async () => {
|
|||
}
|
||||
},
|
||||
|
||||
openCreateRelayDialog: function () {
|
||||
this.formDialogRelay.data = this.getDefaultRelayData()
|
||||
this.formDialogRelay.show = true
|
||||
},
|
||||
getRelays: async function () {
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'GET',
|
||||
'/nostrrelay/api/v1/relay',
|
||||
this.g.user.wallets[0].inkey
|
||||
)
|
||||
this.relayLinks = data.map(c =>
|
||||
mapRelay(
|
||||
c,
|
||||
this.relayLinks.find(old => old.id === c.id)
|
||||
)
|
||||
)
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
relaysTable: {
|
||||
columns: [
|
||||
{
|
||||
name: '',
|
||||
align: 'left',
|
||||
label: '',
|
||||
field: ''
|
||||
},
|
||||
{
|
||||
name: 'id',
|
||||
align: 'left',
|
||||
label: 'ID',
|
||||
field: 'id'
|
||||
},
|
||||
{
|
||||
name: 'toggle',
|
||||
align: 'left',
|
||||
label: 'Active',
|
||||
field: ''
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
align: 'left',
|
||||
label: 'Name',
|
||||
field: 'name'
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
align: 'left',
|
||||
label: 'Description',
|
||||
field: 'description'
|
||||
},
|
||||
{
|
||||
name: 'pubkey',
|
||||
align: 'left',
|
||||
label: 'Public Key',
|
||||
field: 'pubkey'
|
||||
},
|
||||
{
|
||||
name: 'contact',
|
||||
align: 'left',
|
||||
label: 'Contact',
|
||||
field: 'contact'
|
||||
}
|
||||
],
|
||||
pagination: {
|
||||
rowsPerPage: 10
|
||||
}
|
||||
},
|
||||
|
||||
createRelay: async function (data) {
|
||||
try {
|
||||
const resp = await LNbits.api.request(
|
||||
'POST',
|
||||
'/nostrrelay/api/v1/relay',
|
||||
this.g.user.wallets[0].adminkey,
|
||||
data
|
||||
)
|
||||
|
||||
this.relayLinks.unshift(mapRelay(resp.data))
|
||||
this.formDialogRelay.show = false
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
showToggleRelayDialog: function (relay) {
|
||||
if (relay.active) {
|
||||
this.toggleRelay(relay)
|
||||
return
|
||||
}
|
||||
LNbits.utils
|
||||
.confirmDialog('Are you sure you want to deactivate this relay?')
|
||||
.onOk(async () => {
|
||||
this.toggleRelay(relay)
|
||||
})
|
||||
.onCancel(async () => {
|
||||
relay.active = !relay.active
|
||||
})
|
||||
},
|
||||
toggleRelay: async function (relay) {
|
||||
try {
|
||||
await LNbits.api.request(
|
||||
'PUT',
|
||||
'/nostrrelay/api/v1/relay/' + relay.id,
|
||||
this.g.user.wallets[0].adminkey,
|
||||
{}
|
||||
)
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
|
||||
sendFormDataRelay: async function () {
|
||||
this.createRelay(this.formDialogRelay.data)
|
||||
},
|
||||
|
||||
handleRelayDeleted: function (relayId) {
|
||||
this.relayLinks = _.reject(this.relayLinks, function (obj) {
|
||||
return obj.id === relayId
|
||||
})
|
||||
},
|
||||
handleRelayUpdated: function (relay) {
|
||||
const index = this.relayLinks.findIndex(r => r.id === relay.id)
|
||||
if (index !== -1) {
|
||||
relay.expanded = true
|
||||
this.relayLinks.splice(index, 1, relay)
|
||||
}
|
||||
},
|
||||
|
||||
exportrelayCSV: function () {
|
||||
LNbits.utils.exportCSV(
|
||||
this.relaysTable.columns,
|
||||
this.relayLinks,
|
||||
'relays'
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDefaultRelayData: function () {
|
||||
return {
|
||||
id: '',
|
||||
name: '',
|
||||
description: '',
|
||||
pubkey: '',
|
||||
contact: ''
|
||||
}
|
||||
},
|
||||
created: async function () {
|
||||
await this.getRelays()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
relays()
|
||||
openCreateRelayDialog: function () {
|
||||
this.formDialogRelay.data = this.getDefaultRelayData()
|
||||
this.formDialogRelay.show = true
|
||||
},
|
||||
getRelays: async function () {
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'GET',
|
||||
'/nostrrelay/api/v1/relay',
|
||||
this.g.user.wallets[0].inkey
|
||||
)
|
||||
this.relayLinks = data.map(c =>
|
||||
mapRelay(
|
||||
c,
|
||||
this.relayLinks.find(old => old.id === c.id)
|
||||
)
|
||||
)
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
|
||||
createRelay: async function (data) {
|
||||
try {
|
||||
const resp = await LNbits.api.request(
|
||||
'POST',
|
||||
'/nostrrelay/api/v1/relay',
|
||||
this.g.user.wallets[0].adminkey,
|
||||
data
|
||||
)
|
||||
|
||||
this.relayLinks.unshift(mapRelay(resp.data))
|
||||
this.formDialogRelay.show = false
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
showToggleRelayDialog: function (relay) {
|
||||
if (relay.active) {
|
||||
this.toggleRelay(relay)
|
||||
return
|
||||
}
|
||||
LNbits.utils
|
||||
.confirmDialog('Are you sure you want to deactivate this relay?')
|
||||
.onOk(async () => {
|
||||
this.toggleRelay(relay)
|
||||
})
|
||||
.onCancel(async () => {
|
||||
relay.active = !relay.active
|
||||
})
|
||||
},
|
||||
toggleRelay: async function (relay) {
|
||||
try {
|
||||
await LNbits.api.request(
|
||||
'PUT',
|
||||
'/nostrrelay/api/v1/relay/' + relay.id,
|
||||
this.g.user.wallets[0].adminkey,
|
||||
{}
|
||||
)
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
|
||||
sendFormDataRelay: async function () {
|
||||
this.createRelay(this.formDialogRelay.data)
|
||||
},
|
||||
|
||||
handleRelayDeleted: function (relayId) {
|
||||
this.relayLinks = _.reject(this.relayLinks, function (obj) {
|
||||
return obj.id === relayId
|
||||
})
|
||||
},
|
||||
handleRelayUpdated: function (relay) {
|
||||
const index = this.relayLinks.findIndex(r => r.id === relay.id)
|
||||
if (index !== -1) {
|
||||
relay.expanded = true
|
||||
this.relayLinks.splice(index, 1, relay)
|
||||
}
|
||||
},
|
||||
|
||||
exportrelayCSV: function () {
|
||||
LNbits.utils.exportCSV(
|
||||
this.relaysTable.columns,
|
||||
this.relayLinks,
|
||||
'relays'
|
||||
)
|
||||
}
|
||||
},
|
||||
created: async function () {
|
||||
await this.getRelays()
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,22 +5,3 @@ const mapRelay = (obj, oldObj = {}) => {
|
|||
|
||||
return relay
|
||||
}
|
||||
|
||||
function loadTemplateAsync(path) {
|
||||
const result = new Promise(resolve => {
|
||||
const xhttp = new XMLHttpRequest()
|
||||
|
||||
xhttp.onreadystatechange = function () {
|
||||
if (this.readyState == 4) {
|
||||
if (this.status == 200) resolve(this.responseText)
|
||||
|
||||
if (this.status == 404) resolve(`<div>Page not found: ${path}</div>`)
|
||||
}
|
||||
}
|
||||
|
||||
xhttp.open('GET', path, true)
|
||||
xhttp.send()
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
|
|||
58
tasks.py
58
tasks.py
|
|
@ -3,7 +3,6 @@ import json
|
|||
|
||||
from lnbits.core.models import Payment
|
||||
from lnbits.core.services import websocket_updater
|
||||
from lnbits.helpers import get_current_extension_name
|
||||
from lnbits.tasks import register_invoice_listener
|
||||
from loguru import logger
|
||||
|
||||
|
|
@ -13,7 +12,7 @@ from .models import NostrAccount
|
|||
|
||||
async def wait_for_paid_invoices():
|
||||
invoice_queue = asyncio.Queue()
|
||||
register_invoice_listener(invoice_queue, get_current_extension_name())
|
||||
register_invoice_listener(invoice_queue, "ext_nostrrelay")
|
||||
|
||||
while True:
|
||||
payment = await invoice_queue.get()
|
||||
|
|
@ -65,43 +64,36 @@ async def on_invoice_paid(payment: Payment):
|
|||
|
||||
|
||||
async def invoice_paid_to_join(relay_id: str, pubkey: str, amount: int):
|
||||
try:
|
||||
account = await get_account(relay_id, pubkey)
|
||||
if not account:
|
||||
await create_account(
|
||||
relay_id, NostrAccount(pubkey=pubkey, paid_to_join=True, sats=amount)
|
||||
)
|
||||
return
|
||||
account = await get_account(relay_id, pubkey)
|
||||
if not account:
|
||||
account = NostrAccount(
|
||||
relay_id=relay_id, pubkey=pubkey, paid_to_join=True, sats=amount
|
||||
)
|
||||
await create_account(account)
|
||||
return
|
||||
|
||||
if account.blocked or account.paid_to_join:
|
||||
return
|
||||
if account.blocked or account.paid_to_join:
|
||||
return
|
||||
|
||||
account.paid_to_join = True
|
||||
account.sats += amount
|
||||
await update_account(relay_id, account)
|
||||
|
||||
except Exception as ex:
|
||||
logger.warning(ex)
|
||||
account.paid_to_join = True
|
||||
account.sats += amount
|
||||
await update_account(account)
|
||||
|
||||
|
||||
async def invoice_paid_for_storage(
|
||||
relay_id: str, pubkey: str, storage_to_buy: int, amount: int
|
||||
):
|
||||
try:
|
||||
account = await get_account(relay_id, pubkey)
|
||||
if not account:
|
||||
await create_account(
|
||||
relay_id,
|
||||
NostrAccount(pubkey=pubkey, storage=storage_to_buy, sats=amount),
|
||||
)
|
||||
return
|
||||
account = await get_account(relay_id, pubkey)
|
||||
if not account:
|
||||
account = NostrAccount(
|
||||
relay_id=relay_id, pubkey=pubkey, storage=storage_to_buy, sats=amount
|
||||
)
|
||||
await create_account(account)
|
||||
return
|
||||
|
||||
if account.blocked:
|
||||
return
|
||||
if account.blocked:
|
||||
return
|
||||
|
||||
account.storage = storage_to_buy
|
||||
account.sats += amount
|
||||
await update_account(relay_id, account)
|
||||
|
||||
except Exception as ex:
|
||||
logger.warning(ex)
|
||||
account.storage = storage_to_buy
|
||||
account.sats += amount
|
||||
await update_account(account)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
||||
%} {% block page %}
|
||||
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-7 q-gutter-y-md">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
{% raw %}
|
||||
<q-btn unelevated color="primary" @click="openCreateRelayDialog()"
|
||||
>New relay
|
||||
</q-btn>
|
||||
|
|
@ -49,10 +49,10 @@
|
|||
<q-table
|
||||
flat
|
||||
dense
|
||||
:data="relayLinks"
|
||||
:rows="relayLinks"
|
||||
row-key="id"
|
||||
:columns="relaysTable.columns"
|
||||
:pagination.sync="relaysTable.pagination"
|
||||
v-model:pagination="relaysTable.pagination"
|
||||
:filter="filter"
|
||||
>
|
||||
<template v-slot:body="props">
|
||||
|
|
@ -69,28 +69,37 @@
|
|||
</q-td>
|
||||
|
||||
<q-td key="id" :props="props">
|
||||
<a style="color: unset" :href="props.row.id" target="_blank">
|
||||
{{props.row.id}}</a
|
||||
>
|
||||
<a
|
||||
style="color: unset"
|
||||
:href="props.row.id"
|
||||
target="_blank"
|
||||
v-text="props.row.id"
|
||||
></a>
|
||||
</q-td>
|
||||
<q-td key="toggle" :props="props">
|
||||
<q-toggle
|
||||
size="sm"
|
||||
color="secodary"
|
||||
v-model="props.row.active"
|
||||
@input="showToggleRelayDialog(props.row)"
|
||||
@update:model-value="showToggleRelayDialog(props.row)"
|
||||
></q-toggle>
|
||||
</q-td>
|
||||
<q-td auto-width> {{props.row.name}} </q-td>
|
||||
<q-td key="description" :props="props">
|
||||
{{props.row.description}}
|
||||
</q-td>
|
||||
<q-td key="pubkey" :props="props">
|
||||
<div>{{props.row.pubkey}}</div>
|
||||
</q-td>
|
||||
<q-td key="contact" :props="props">
|
||||
<div>{{props.row.contact}}</div>
|
||||
</q-td>
|
||||
<q-td auto-width v-text="props.row.name"></q-td>
|
||||
<q-td
|
||||
key="description"
|
||||
:props="props"
|
||||
v-text="props.row.description"
|
||||
></q-td>
|
||||
<q-td
|
||||
key="pubkey"
|
||||
:props="props"
|
||||
v-text="props.row.pubkey"
|
||||
></q-td>
|
||||
<q-td
|
||||
key="contact"
|
||||
:props="props"
|
||||
v-text="props.row.contact"
|
||||
></q-td>
|
||||
</q-tr>
|
||||
<q-tr v-if="props.row.expanded" :props="props">
|
||||
<q-td colspan="100%">
|
||||
|
|
@ -109,7 +118,6 @@
|
|||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
{% endraw %}
|
||||
</q-table>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
|
@ -188,9 +196,14 @@
|
|||
</q-card>
|
||||
</q-dialog>
|
||||
</div>
|
||||
{% endblock %} {% block vue_templates %}
|
||||
<template id="relay-details">
|
||||
{% include("nostrrelay/relay-details.html") %}
|
||||
</template>
|
||||
|
||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||
<script src="{{ url_for('nostrrelay_static', path='js/utils.js') }}"></script>
|
||||
<script src="{{ url_for('nostrrelay_static', path='components/relay-details/relay-details.js') }}"></script>
|
||||
<script src="{{ url_for('nostrrelay_static', path='js/index.js') }}"></script>
|
||||
<script src="{{ static_url_for('nostrrelay/static', path='js/utils.js') }}"></script>
|
||||
<script src="{{ static_url_for('nostrrelay/static', path='js/index.js') }}"></script>
|
||||
<script src="{{ static_url_for('nostrrelay/static', path='components/relay-details.js') }}"></script>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section v-if="relay.config.isPaidRelay">
|
||||
<q-card-section v-if="relay.meta.isPaidRelay">
|
||||
<div class="row">
|
||||
<div class="col-2 q-pt-sm">
|
||||
<span class="text-bold">Public Key:</span>
|
||||
|
|
@ -60,19 +60,19 @@
|
|||
<div class="col-2"></div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section v-if="relay.config.isPaidRelay">
|
||||
<q-card-section v-if="relay.meta.isPaidRelay">
|
||||
<div class="row">
|
||||
<div class="col-2">
|
||||
<span class="text-bold">Cost to join: </span>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div>
|
||||
<span v-text="relay.config.costToJoin"></span>
|
||||
<span v-text="relay.meta.costToJoin"></span>
|
||||
<span class="text-bold q-ml-sm">sats</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div v-if="relay.config.costToJoin">
|
||||
<div v-if="relay.meta.costToJoin">
|
||||
<q-btn
|
||||
@click="createInvoice('join')"
|
||||
unelevated
|
||||
|
|
@ -89,37 +89,37 @@
|
|||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section v-if="relay.config.isPaidRelay">
|
||||
<q-card-section v-if="relay.meta.isPaidRelay">
|
||||
<div class="row q-mt-md q-mb-md">
|
||||
<div class="col-2 q-pt-sm">
|
||||
<span class="text-bold">Storage cost: </span>
|
||||
</div>
|
||||
<div class="col-3 q-pt-sm">
|
||||
<span v-text="relay.config.storageCostValue"></span>
|
||||
<span v-text="relay.meta.storageCostValue"></span>
|
||||
<span class="text-bold q-ml-sm"> sats per</span>
|
||||
<q-badge color="orange">
|
||||
<span v-text="relay.config.storageCostUnit"></span>
|
||||
<span v-text="relay.meta.storageCostUnit"></span>
|
||||
</q-badge>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<q-input
|
||||
v-if="relay.config.storageCostValue"
|
||||
v-if="relay.meta.storageCostValue"
|
||||
filled
|
||||
dense
|
||||
v-model="unitsToBuy"
|
||||
type="number"
|
||||
min="0"
|
||||
:label="relay.config.storageCostUnit"
|
||||
:label="relay.meta.storageCostUnit"
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-2 q-pt-sm">
|
||||
<div v-if="relay.config.storageCostValue">
|
||||
<div v-if="relay.meta.storageCostValue">
|
||||
<span class="text-bold q-ml-md" v-text="storageCost"></span>
|
||||
<span>sats</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<div v-if="relay.config.storageCostValue">
|
||||
<div v-if="relay.meta.storageCostValue">
|
||||
<q-btn
|
||||
@click="createInvoice('storage')"
|
||||
unelevated
|
||||
|
|
@ -197,12 +197,10 @@
|
|||
</q-page-container>
|
||||
{% endblock %} {% block scripts %}
|
||||
<script>
|
||||
Vue.component(VueQrcode.name, VueQrcode)
|
||||
|
||||
new Vue({
|
||||
window.app = Vue.createApp({
|
||||
el: '#vue',
|
||||
mixins: [windowMixin],
|
||||
data: function () {
|
||||
data() {
|
||||
return {
|
||||
relay: JSON.parse('{{relay | tojson | safe}}'),
|
||||
pubkey: '',
|
||||
|
|
@ -213,14 +211,14 @@
|
|||
},
|
||||
computed: {
|
||||
storageCost: function () {
|
||||
if (!this.relay || !this.relay.config.storageCostValue) return 0
|
||||
return this.unitsToBuy * this.relay.config.storageCostValue
|
||||
if (!this.relay || !this.relay.meta.storageCostValue) return 0
|
||||
return this.unitsToBuy * this.relay.meta.storageCostValue
|
||||
},
|
||||
wssLink: function () {
|
||||
this.relay.config.domain =
|
||||
this.relay.config.domain || window.location.hostname
|
||||
this.relay.meta.domain =
|
||||
this.relay.meta.domain || window.location.hostname
|
||||
return (
|
||||
'wss://' + this.relay.config.domain + '/nostrrelay/' + this.relay.id
|
||||
'wss://' + this.relay.meta.domain + '/nostrrelay/' + this.relay.id
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -272,7 +270,7 @@
|
|||
wsConnection.close()
|
||||
}
|
||||
} catch (error) {
|
||||
this.$q.notify({
|
||||
Quasar.Notify.create({
|
||||
timeout: 5000,
|
||||
type: 'warning',
|
||||
message: 'Failed to get invoice status',
|
||||
|
|
@ -280,8 +278,7 @@
|
|||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created: function () {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@
|
|||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model.trim="relay.config.domain"
|
||||
v-model.trim="relay.meta.domain"
|
||||
type="text"
|
||||
></q-input>
|
||||
</div>
|
||||
|
|
@ -93,7 +93,7 @@
|
|||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model.trim="relay.config.freeStorageValue"
|
||||
v-model.trim="relay.meta.freeStorageValue"
|
||||
type="number"
|
||||
hint="Value"
|
||||
min="0"
|
||||
|
|
@ -103,7 +103,7 @@
|
|||
<q-select
|
||||
filled
|
||||
dense
|
||||
v-model="relay.config.freeStorageUnit"
|
||||
v-model="relay.meta.freeStorageUnit"
|
||||
type="text"
|
||||
hint="Unit"
|
||||
:options="storageUnits"
|
||||
|
|
@ -119,7 +119,7 @@
|
|||
</div>
|
||||
<div class="col-md-4 col-sm-2">
|
||||
<q-badge
|
||||
v-if="relay.config.freeStorageValue == 0"
|
||||
v-if="relay.meta.freeStorageValue == 0"
|
||||
color="orange"
|
||||
class="float-right q-mb-md"
|
||||
><span>No free storage</span>
|
||||
|
|
@ -132,13 +132,13 @@
|
|||
<div class="col-md-3 q-pr-lg">
|
||||
<q-toggle
|
||||
color="secodary"
|
||||
v-model="relay.config.isPaidRelay"
|
||||
@input="togglePaidRelay"
|
||||
v-model="relay.meta.isPaidRelay"
|
||||
@update:model-value="togglePaidRelay"
|
||||
></q-toggle>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-badge
|
||||
v-if="!relay.config.isPaidRelay && relay.config.freeStorageValue == 0"
|
||||
v-if="!relay.meta.isPaidRelay && relay.meta.freeStorageValue == 0"
|
||||
color="orange"
|
||||
class="float-right q-mb-md"
|
||||
><span>No data will be stored. Read-only relay.</span>
|
||||
|
|
@ -146,7 +146,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="relay.config.isPaidRelay && relay.config.wallet">
|
||||
<div v-if="relay.meta.isPaidRelay && relay.meta.wallet">
|
||||
<div class="row items-center no-wrap q-mb-md">
|
||||
<div class="col-3 q-pr-lg">Wallet:</div>
|
||||
<div class="col-md-6 col-sm-8 q-pr-lg">
|
||||
|
|
@ -154,7 +154,7 @@
|
|||
filled
|
||||
dense
|
||||
emit-value
|
||||
v-model="relay.config.wallet"
|
||||
v-model="relay.meta.wallet"
|
||||
:options="walletOptions"
|
||||
label="Wallet *"
|
||||
>
|
||||
|
|
@ -174,7 +174,7 @@
|
|||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model.trim="relay.config.costToJoin"
|
||||
v-model.trim="relay.meta.costToJoin"
|
||||
type="number"
|
||||
hint="sats"
|
||||
min="0"
|
||||
|
|
@ -189,7 +189,7 @@
|
|||
</div>
|
||||
<div class="col-md-6 col-sm-4">
|
||||
<q-badge
|
||||
v-if="relay.config.costToJoin == 0"
|
||||
v-if="relay.meta.costToJoin == 0"
|
||||
color="green"
|
||||
class="float-right"
|
||||
><span>Free to join</span>
|
||||
|
|
@ -202,7 +202,7 @@
|
|||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model.trim="relay.config.storageCostValue"
|
||||
v-model.trim="relay.meta.storageCostValue"
|
||||
type="number"
|
||||
hint="sats"
|
||||
min="0"
|
||||
|
|
@ -212,7 +212,7 @@
|
|||
<q-select
|
||||
filled
|
||||
dense
|
||||
v-model="relay.config.storageCostUnit"
|
||||
v-model="relay.meta.storageCostUnit"
|
||||
type="text"
|
||||
hint="Unit"
|
||||
:options="storageUnits"
|
||||
|
|
@ -227,7 +227,7 @@
|
|||
</div>
|
||||
<div class="col-md-4 col-sm-0">
|
||||
<q-badge
|
||||
v-if="relay.config.storageCostValue == 0"
|
||||
v-if="relay.meta.storageCostValue == 0"
|
||||
color="green"
|
||||
class="float-right"
|
||||
><span>Unlimited storage</span>
|
||||
|
|
@ -245,7 +245,7 @@
|
|||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model.trim="relay.config.createdAtDaysPast"
|
||||
v-model.trim="relay.meta.createdAtDaysPast"
|
||||
type="number"
|
||||
min="0"
|
||||
hint="Days"
|
||||
|
|
@ -255,7 +255,7 @@
|
|||
<q-select
|
||||
filled
|
||||
dense
|
||||
v-model="relay.config.createdAtHoursPast"
|
||||
v-model="relay.meta.createdAtHoursPast"
|
||||
type="number"
|
||||
hint="Hours"
|
||||
:options="hours"
|
||||
|
|
@ -265,7 +265,7 @@
|
|||
<q-select
|
||||
filled
|
||||
dense
|
||||
v-model="relay.config.createdAtMinutesPast"
|
||||
v-model="relay.meta.createdAtMinutesPast"
|
||||
type="number"
|
||||
hint="Minutes"
|
||||
:options="range60"
|
||||
|
|
@ -275,7 +275,7 @@
|
|||
<q-select
|
||||
filled
|
||||
dense
|
||||
v-model="relay.config.createdAtSecondsPast"
|
||||
v-model="relay.meta.createdAtSecondsPast"
|
||||
type="number"
|
||||
hint="Seconds"
|
||||
:options="range60"
|
||||
|
|
@ -296,7 +296,7 @@
|
|||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model.trim="relay.config.createdAtDaysFuture"
|
||||
v-model.trim="relay.meta.createdAtDaysFuture"
|
||||
type="number"
|
||||
min="0"
|
||||
hint="Days"
|
||||
|
|
@ -306,7 +306,7 @@
|
|||
<q-select
|
||||
filled
|
||||
dense
|
||||
v-model="relay.config.createdAtHoursFuture"
|
||||
v-model="relay.meta.createdAtHoursFuture"
|
||||
type="number"
|
||||
hint="Hours"
|
||||
:options="hours"
|
||||
|
|
@ -316,7 +316,7 @@
|
|||
<q-select
|
||||
filled
|
||||
dense
|
||||
v-model="relay.config.createdAtMinutesFuture"
|
||||
v-model="relay.meta.createdAtMinutesFuture"
|
||||
type="number"
|
||||
hint="Minutes"
|
||||
:options="range60"
|
||||
|
|
@ -326,7 +326,7 @@
|
|||
<q-select
|
||||
filled
|
||||
dense
|
||||
v-model="relay.config.createdAtSecondsFuture"
|
||||
v-model="relay.meta.createdAtSecondsFuture"
|
||||
type="number"
|
||||
hint="Seconds"
|
||||
:options="range60"
|
||||
|
|
@ -348,7 +348,7 @@
|
|||
<q-toggle
|
||||
color="secodary"
|
||||
class="q-ml-md q-mr-md"
|
||||
v-model="relay.config.requireAuthFilter"
|
||||
v-model="relay.meta.requireAuthFilter"
|
||||
>For Filters</q-toggle
|
||||
>
|
||||
</div>
|
||||
|
|
@ -356,7 +356,7 @@
|
|||
<q-toggle
|
||||
color="secodary"
|
||||
class="q-ml-md q-mr-md"
|
||||
v-model="relay.config.requireAuthEvents"
|
||||
v-model="relay.meta.requireAuthEvents"
|
||||
>For All Events</q-toggle
|
||||
>
|
||||
</div>
|
||||
|
|
@ -370,7 +370,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="relay.config.requireAuthEvents"
|
||||
v-if="relay.meta.requireAuthEvents"
|
||||
class="row items-center no-wrap q-mb-md q-mt-md"
|
||||
>
|
||||
<div class="col-3 q-pr-lg">Skip Auth For Events:</div>
|
||||
|
|
@ -393,14 +393,14 @@
|
|||
</div>
|
||||
<div class="col-7">
|
||||
<q-chip
|
||||
v-for="e in relay.config.skipedAuthEvents"
|
||||
v-for="e in relay.meta.skipedAuthEvents"
|
||||
:key="e"
|
||||
removable
|
||||
@remove="removeSkipAuthForEvent(e)"
|
||||
color="primary"
|
||||
text-color="white"
|
||||
>
|
||||
{{ e }}
|
||||
<span v-text="e"></span>
|
||||
</q-chip>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -425,14 +425,14 @@
|
|||
</div>
|
||||
<div class="col-7">
|
||||
<q-chip
|
||||
v-for="e in relay.config.forcedAuthEvents"
|
||||
v-for="e in relay.meta.forcedAuthEvents"
|
||||
:key="e"
|
||||
removable
|
||||
@remove="removeForceAuthForEvent(e)"
|
||||
color="primary"
|
||||
text-color="white"
|
||||
>
|
||||
{{ e }}
|
||||
<span v-text="e"></span>
|
||||
</q-chip>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -444,7 +444,7 @@
|
|||
filled
|
||||
dense
|
||||
emit-value
|
||||
v-model="relay.config.fullStorageAction"
|
||||
v-model="relay.meta.fullStorageAction"
|
||||
type="text"
|
||||
:options="fullStorageActions"
|
||||
></q-select>
|
||||
|
|
@ -464,7 +464,7 @@
|
|||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model.trim="relay.config.limitPerFilter"
|
||||
v-model.trim="relay.meta.limitPerFilter"
|
||||
type="number"
|
||||
min="0"
|
||||
></q-input>
|
||||
|
|
@ -477,7 +477,7 @@
|
|||
</q-tooltip></q-icon
|
||||
>
|
||||
<q-badge
|
||||
v-if="relay.config.limitPerFilter == 0"
|
||||
v-if="relay.meta.limitPerFilter == 0"
|
||||
color="green"
|
||||
class="float-right"
|
||||
><span>No Limit</span>
|
||||
|
|
@ -490,7 +490,7 @@
|
|||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model.trim="relay.config.maxClientFilters"
|
||||
v-model.trim="relay.meta.maxClientFilters"
|
||||
type="number"
|
||||
min="0"
|
||||
></q-input>
|
||||
|
|
@ -504,7 +504,7 @@
|
|||
</q-tooltip></q-icon
|
||||
>
|
||||
<q-badge
|
||||
v-if="relay.config.maxClientFilters == 0"
|
||||
v-if="relay.meta.maxClientFilters == 0"
|
||||
color="green"
|
||||
class="float-right"
|
||||
><span>Unlimited Filters</span>
|
||||
|
|
@ -517,7 +517,7 @@
|
|||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model.trim="relay.config.maxEventsPerHour"
|
||||
v-model.trim="relay.meta.maxEventsPerHour"
|
||||
type="number"
|
||||
min="0"
|
||||
></q-input>
|
||||
|
|
@ -530,7 +530,7 @@
|
|||
</q-tooltip></q-icon
|
||||
>
|
||||
<q-badge
|
||||
v-if="relay.config.maxEventsPerHour == 0"
|
||||
v-if="relay.meta.maxEventsPerHour == 0"
|
||||
color="green"
|
||||
class="float-right"
|
||||
><span>No Limit</span>
|
||||
|
|
@ -584,7 +584,7 @@
|
|||
color="secodary"
|
||||
class="q-mr-lg"
|
||||
v-model="showAllowedAccounts"
|
||||
@input="getAccounts()"
|
||||
@update:model-value="getAccounts()"
|
||||
>Show Allowed Account</q-toggle
|
||||
>
|
||||
<q-toggle
|
||||
|
|
@ -592,7 +592,7 @@
|
|||
color="secodary"
|
||||
class="q-mr-lg"
|
||||
v-model="showBlockedAccounts"
|
||||
@input="getAccounts()"
|
||||
@update:model-value="getAccounts()"
|
||||
>
|
||||
Show Blocked Accounts</q-toggle
|
||||
>
|
||||
|
|
@ -605,7 +605,7 @@
|
|||
<q-table
|
||||
flat
|
||||
dense
|
||||
:data="accounts"
|
||||
:rows="accounts"
|
||||
row-key="pubkey"
|
||||
:columns="accountsTable.columns"
|
||||
:pagination.sync="accountsTable.pagination"
|
||||
|
|
@ -623,14 +623,14 @@
|
|||
>
|
||||
</q-td>
|
||||
<q-td key="pubkey" :props="props">
|
||||
{{props.row.pubkey}}
|
||||
<span v-text="props.row.pubkey"></span>
|
||||
</q-td>
|
||||
<q-td key="allowed" :props="props">
|
||||
<q-toggle
|
||||
size="sm"
|
||||
color="secodary"
|
||||
v-model="props.row.allowed"
|
||||
@input="togglePublicKey(props.row, 'allow')"
|
||||
@update:model-value="togglePublicKey(props.row, 'allow')"
|
||||
></q-toggle>
|
||||
</q-td>
|
||||
<q-td key="blocked" :props="props">
|
||||
|
|
@ -638,12 +638,17 @@
|
|||
size="sm"
|
||||
color="secodary"
|
||||
v-model="props.row.blocked"
|
||||
@input="togglePublicKey(props.row, 'block')"
|
||||
@update:model-value="togglePublicKey(props.row, 'block')"
|
||||
></q-toggle>
|
||||
</q-td>
|
||||
<q-td auto-width> {{props.row.paid_to_join}} </q-td>
|
||||
<q-td auto-width> {{props.row.sats}} </q-td>
|
||||
<q-td auto-width> {{props.row.storage}} </q-td>
|
||||
|
||||
<q-td auto-width
|
||||
><span v-text="props.row.paid_to_join"></span>
|
||||
</q-td>
|
||||
<q-td auto-width> <span v-text="props.row.sats"></span></q-td>
|
||||
<q-td auto-width
|
||||
><span v-text="props.row.storage"></span>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
|
|
@ -61,7 +61,7 @@ async def test_valid_event_crud(valid_events: List[EventFixture]):
|
|||
|
||||
# insert all events in DB before doing an query
|
||||
for e in all_events:
|
||||
await create_event(RELAY_ID, e, None)
|
||||
await create_event(e)
|
||||
|
||||
for f in valid_events:
|
||||
await get_by_id(f.data, f.name)
|
||||
|
|
|
|||
10
views.py
10
views.py
|
|
@ -1,18 +1,14 @@
|
|||
from http import HTTPStatus
|
||||
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from lnbits.core.models import User
|
||||
from lnbits.decorators import check_user_exists
|
||||
from lnbits.helpers import template_renderer
|
||||
from starlette.responses import HTMLResponse
|
||||
|
||||
from .crud import get_public_relay
|
||||
from .helpers import relay_info_response
|
||||
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
nostrrelay_generic_router: APIRouter = APIRouter()
|
||||
|
||||
|
||||
|
|
@ -23,7 +19,7 @@ def nostrrelay_renderer():
|
|||
@nostrrelay_generic_router.get("/", response_class=HTMLResponse)
|
||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
||||
return nostrrelay_renderer().TemplateResponse(
|
||||
"nostrrelay/index.html", {"request": request, "user": user.dict()}
|
||||
"nostrrelay/index.html", {"request": request, "user": user.json()}
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
223
views_api.py
223
views_api.py
|
|
@ -1,8 +1,7 @@
|
|||
from http import HTTPStatus
|
||||
from typing import List, Optional
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Request, WebSocket
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, WebSocket
|
||||
from lnbits.core.crud import get_user
|
||||
from lnbits.core.models import WalletTypeInfo
|
||||
from lnbits.core.services import create_invoice
|
||||
|
|
@ -38,6 +37,7 @@ nostrrelay_api_router = APIRouter()
|
|||
|
||||
|
||||
@nostrrelay_api_router.websocket("/{relay_id}")
|
||||
@nostrrelay_api_router.websocket("/{relay_id}/")
|
||||
async def websocket_endpoint(relay_id: str, websocket: WebSocket):
|
||||
client = NostrClientConnection(relay_id=relay_id, websocket=websocket)
|
||||
client_accepted = await client_manager.add_client(client)
|
||||
|
|
@ -55,26 +55,19 @@ async def websocket_endpoint(relay_id: str, websocket: WebSocket):
|
|||
async def api_create_relay(
|
||||
data: NostrRelay,
|
||||
request: Request,
|
||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||
key_info: WalletTypeInfo = Depends(require_admin_key),
|
||||
) -> NostrRelay:
|
||||
data.user_id = key_info.wallet.user
|
||||
if len(data.id):
|
||||
user = await get_user(wallet.wallet.user)
|
||||
user = await get_user(data.user_id)
|
||||
assert user, "User not found."
|
||||
assert user.admin, "Only admin users can set the relay ID"
|
||||
else:
|
||||
data.id = urlsafe_short_hash()[:8]
|
||||
|
||||
try:
|
||||
data.config.domain = extract_domain(str(request.url))
|
||||
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",
|
||||
) from ex
|
||||
data.meta.domain = extract_domain(str(request.url))
|
||||
relay = await create_relay(data)
|
||||
return relay
|
||||
|
||||
|
||||
@nostrrelay_api_router.patch("/api/v1/relay/{relay_id}")
|
||||
|
|
@ -87,79 +80,54 @@ async def api_update_relay(
|
|||
detail="Cannot change the relay id",
|
||||
)
|
||||
|
||||
try:
|
||||
relay = await get_relay(wallet.wallet.user, data.id)
|
||||
if not relay:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
detail="Relay not found",
|
||||
)
|
||||
updated_relay = NostrRelay.parse_obj({**dict(relay), **dict(data)})
|
||||
updated_relay = await update_relay(wallet.wallet.user, updated_relay)
|
||||
# activate & deactivate have their own endpoint
|
||||
updated_relay.active = relay.active
|
||||
|
||||
if updated_relay.active:
|
||||
await client_manager.enable_relay(relay_id, updated_relay.config)
|
||||
else:
|
||||
await client_manager.disable_relay(relay_id)
|
||||
|
||||
return updated_relay
|
||||
|
||||
except HTTPException as ex:
|
||||
raise ex
|
||||
except Exception as ex:
|
||||
logger.warning(ex)
|
||||
relay = await get_relay(wallet.wallet.user, data.id)
|
||||
if not relay:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
detail="Cannot update relay",
|
||||
) from ex
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
detail="Relay not found",
|
||||
)
|
||||
|
||||
updated_relay = NostrRelay.parse_obj({**dict(relay), **dict(data)})
|
||||
updated_relay.user_id = wallet.wallet.user
|
||||
updated_relay = await update_relay(updated_relay)
|
||||
|
||||
# activate & deactivate have their own endpoint
|
||||
updated_relay.active = relay.active
|
||||
|
||||
if updated_relay.active:
|
||||
await client_manager.enable_relay(relay_id, updated_relay.meta)
|
||||
else:
|
||||
await client_manager.disable_relay(relay_id)
|
||||
|
||||
return updated_relay
|
||||
|
||||
|
||||
@nostrrelay_api_router.put("/api/v1/relay/{relay_id}")
|
||||
async def api_toggle_relay(
|
||||
relay_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||
) -> NostrRelay:
|
||||
|
||||
try:
|
||||
relay = await get_relay(wallet.wallet.user, relay_id)
|
||||
if not relay:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
detail="Relay not found",
|
||||
)
|
||||
relay.active = not relay.active
|
||||
updated_relay = await update_relay(wallet.wallet.user, relay)
|
||||
|
||||
if relay.active:
|
||||
await client_manager.enable_relay(relay_id, relay.config)
|
||||
else:
|
||||
await client_manager.disable_relay(relay_id)
|
||||
|
||||
return updated_relay
|
||||
|
||||
except HTTPException as ex:
|
||||
raise ex
|
||||
except Exception as ex:
|
||||
logger.warning(ex)
|
||||
relay = await get_relay(wallet.wallet.user, relay_id)
|
||||
if not relay:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
detail="Cannot update relay",
|
||||
) from ex
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
detail="Relay not found",
|
||||
)
|
||||
relay.active = not relay.active
|
||||
await update_relay(relay)
|
||||
|
||||
if relay.active:
|
||||
await client_manager.enable_relay(relay_id, relay.meta)
|
||||
else:
|
||||
await client_manager.disable_relay(relay_id)
|
||||
|
||||
return relay
|
||||
|
||||
|
||||
@nostrrelay_api_router.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",
|
||||
) from ex
|
||||
) -> list[NostrRelay]:
|
||||
return await get_relays(wallet.wallet.user)
|
||||
|
||||
|
||||
@nostrrelay_api_router.get("/api/v1/relay-info")
|
||||
|
|
@ -171,14 +139,7 @@ async def api_get_relay_info() -> JSONResponse:
|
|||
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",
|
||||
) from ex
|
||||
relay = await get_relay(wallet.wallet.user, relay_id)
|
||||
if not relay:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
|
|
@ -191,39 +152,23 @@ async def api_get_relay(
|
|||
async def api_create_or_update_account(
|
||||
data: NostrPartialAccount,
|
||||
) -> NostrAccount:
|
||||
data.pubkey = normalize_public_key(data.pubkey)
|
||||
account = await get_account(data.relay_id, data.pubkey)
|
||||
if not account:
|
||||
account = NostrAccount(
|
||||
pubkey=data.pubkey,
|
||||
relay_id=data.relay_id,
|
||||
blocked=data.blocked or False,
|
||||
allowed=data.allowed or False,
|
||||
)
|
||||
return await create_account(account)
|
||||
|
||||
try:
|
||||
data.pubkey = normalize_public_key(data.pubkey)
|
||||
if data.blocked is not None:
|
||||
account.blocked = data.blocked
|
||||
if data.allowed is not None:
|
||||
account.allowed = data.allowed
|
||||
|
||||
account = await get_account(data.relay_id, data.pubkey)
|
||||
if not account:
|
||||
account = NostrAccount(
|
||||
pubkey=data.pubkey,
|
||||
blocked=data.blocked or False,
|
||||
allowed=data.allowed or False,
|
||||
)
|
||||
return await create_account(data.relay_id, account)
|
||||
|
||||
if data.blocked is not None:
|
||||
account.blocked = data.blocked
|
||||
if data.allowed is not None:
|
||||
account.allowed = data.allowed
|
||||
|
||||
return await update_account(data.relay_id, account)
|
||||
|
||||
except ValueError as ex:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.BAD_REQUEST,
|
||||
detail=str(ex),
|
||||
) from ex
|
||||
except HTTPException as ex:
|
||||
raise ex
|
||||
except Exception as ex:
|
||||
logger.warning(ex)
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
detail="Cannot create account",
|
||||
) from ex
|
||||
return await update_account(account)
|
||||
|
||||
|
||||
@nostrrelay_api_router.delete(
|
||||
|
|
@ -249,30 +194,16 @@ async def api_get_accounts(
|
|||
allowed: bool = False,
|
||||
blocked: bool = True,
|
||||
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
||||
) -> List[NostrAccount]:
|
||||
try:
|
||||
# make sure the user has access to the relay
|
||||
relay = await get_relay(wallet.wallet.user, relay_id)
|
||||
if not relay:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
detail="Relay not found",
|
||||
)
|
||||
accounts = await get_accounts(relay.id, allowed, blocked)
|
||||
return accounts
|
||||
except ValueError as ex:
|
||||
) -> list[NostrAccount]:
|
||||
# make sure the user has access to the relay
|
||||
relay = await get_relay(wallet.wallet.user, relay_id)
|
||||
if not relay:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.BAD_REQUEST,
|
||||
detail=str(ex),
|
||||
) from ex
|
||||
except HTTPException as ex:
|
||||
raise ex
|
||||
except Exception as ex:
|
||||
logger.warning(ex)
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
detail="Cannot fetch accounts",
|
||||
) from ex
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
detail="Relay not found",
|
||||
)
|
||||
accounts = await get_accounts(relay.id, allowed, blocked)
|
||||
return accounts
|
||||
|
||||
|
||||
@nostrrelay_api_router.delete("/api/v1/relay/{relay_id}")
|
||||
|
|
@ -305,9 +236,9 @@ async def api_pay_to_join(data: BuyOrder):
|
|||
if data.action == "join":
|
||||
if relay.is_free_to_join:
|
||||
raise ValueError("Relay is free to join")
|
||||
amount = int(relay.config.cost_to_join)
|
||||
amount = int(relay.meta.cost_to_join)
|
||||
elif data.action == "storage":
|
||||
if relay.config.storage_cost_value == 0:
|
||||
if relay.meta.storage_cost_value == 0:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.BAD_REQUEST,
|
||||
detail="Relay storage cost is zero. Cannot buy!",
|
||||
|
|
@ -317,18 +248,18 @@ async def api_pay_to_join(data: BuyOrder):
|
|||
status_code=HTTPStatus.BAD_REQUEST,
|
||||
detail="Must specify how much storage to buy!",
|
||||
)
|
||||
storage_to_buy = data.units_to_buy * relay.config.storage_cost_value * 1024
|
||||
if relay.config.storage_cost_unit == "MB":
|
||||
storage_to_buy = data.units_to_buy * relay.meta.storage_cost_value * 1024
|
||||
if relay.meta.storage_cost_unit == "MB":
|
||||
storage_to_buy *= 1024
|
||||
amount = data.units_to_buy * relay.config.storage_cost_value
|
||||
amount = data.units_to_buy * relay.meta.storage_cost_value
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.BAD_REQUEST,
|
||||
detail=f"Unknown action: '{data.action}'",
|
||||
)
|
||||
|
||||
_, payment_request = await create_invoice(
|
||||
wallet_id=relay.config.wallet,
|
||||
payment = await create_invoice(
|
||||
wallet_id=relay.meta.wallet,
|
||||
amount=amount,
|
||||
memo=f"Pubkey '{data.pubkey}' wants to join {relay.id}",
|
||||
extra={
|
||||
|
|
@ -339,4 +270,4 @@ async def api_pay_to_join(data: BuyOrder):
|
|||
"storage_to_buy": storage_to_buy,
|
||||
},
|
||||
)
|
||||
return {"invoice": payment_request}
|
||||
return {"invoice": payment.bolt11}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue