chore: satisfy upstream lint (black, mypy, prettier, ruff)
Some checks failed
lint.yml / chore: satisfy upstream lint (black, mypy, prettier, ruff) (push) Failing after 0s
Some checks failed
lint.yml / chore: satisfy upstream lint (black, mypy, prettier, ruff) (push) Failing after 0s
- black/prettier reformatting across new aio code - type annotations on db.fetchone/fetchall callsites in crud.py - explicit dict[str, list[str]] for tag_lists in nostr_sync.py - type:ignore[attr-defined] on Account.prvkey access — the field is added by the aio-fork lnbits.core.models.Account; upstream lnbits does not yet have it, so consumers without the fork must add a prvkey column to accounts before the Nostr publisher can sign. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
42a373bff1
commit
b428b0dca8
11 changed files with 48 additions and 57 deletions
|
|
@ -75,9 +75,7 @@ def events_start():
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"[EVENTS] Nostr sync task failed: {exc}")
|
logger.error(f"[EVENTS] Nostr sync task failed: {exc}")
|
||||||
|
|
||||||
task3 = create_permanent_unique_task(
|
task3 = create_permanent_unique_task("ext_events_nostr_sync", _sync_nostr_events)
|
||||||
"ext_events_nostr_sync", _sync_nostr_events
|
|
||||||
)
|
|
||||||
scheduled_tasks.append(task3)
|
scheduled_tasks.append(task3)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
12
crud.py
12
crud.py
|
|
@ -94,7 +94,7 @@ async def update_ticket(ticket: Ticket) -> Ticket:
|
||||||
|
|
||||||
|
|
||||||
async def get_ticket(payment_hash: str) -> Ticket | None:
|
async def get_ticket(payment_hash: str) -> Ticket | None:
|
||||||
row = await db.fetchone(
|
row: dict | None = await db.fetchone(
|
||||||
"SELECT * FROM events.ticket WHERE id = :id",
|
"SELECT * FROM events.ticket WHERE id = :id",
|
||||||
{"id": payment_hash},
|
{"id": payment_hash},
|
||||||
)
|
)
|
||||||
|
|
@ -107,13 +107,15 @@ async def get_tickets(wallet_ids: str | list[str]) -> list[Ticket]:
|
||||||
if isinstance(wallet_ids, str):
|
if isinstance(wallet_ids, str):
|
||||||
wallet_ids = [wallet_ids]
|
wallet_ids = [wallet_ids]
|
||||||
q = ",".join([f"'{wallet_id}'" for wallet_id in wallet_ids])
|
q = ",".join([f"'{wallet_id}'" for wallet_id in wallet_ids])
|
||||||
rows = await db.fetchall(f"SELECT * FROM events.ticket WHERE wallet IN ({q})")
|
rows: list[dict] = await db.fetchall(
|
||||||
|
f"SELECT * FROM events.ticket WHERE wallet IN ({q})"
|
||||||
|
)
|
||||||
return [Ticket(**_parse_ticket_row(row)) for row in rows]
|
return [Ticket(**_parse_ticket_row(row)) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
async def get_tickets_by_user_id(user_id: str) -> list[Ticket]:
|
async def get_tickets_by_user_id(user_id: str) -> list[Ticket]:
|
||||||
"""All tickets owned by the given LNbits user_id."""
|
"""All tickets owned by the given LNbits user_id."""
|
||||||
rows = await db.fetchall(
|
rows: list[dict] = await db.fetchall(
|
||||||
"SELECT * FROM events.ticket WHERE user_id = :user_id ORDER BY time DESC",
|
"SELECT * FROM events.ticket WHERE user_id = :user_id ORDER BY time DESC",
|
||||||
{"user_id": user_id},
|
{"user_id": user_id},
|
||||||
)
|
)
|
||||||
|
|
@ -206,7 +208,7 @@ async def get_pending_events() -> list[Event]:
|
||||||
|
|
||||||
async def get_settings() -> EventsSettings:
|
async def get_settings() -> EventsSettings:
|
||||||
"""Singleton settings row, seeded by m010."""
|
"""Singleton settings row, seeded by m010."""
|
||||||
row = await db.fetchone("SELECT * FROM events.settings WHERE id = 1")
|
row: dict | None = await db.fetchone("SELECT * FROM events.settings WHERE id = 1")
|
||||||
if row:
|
if row:
|
||||||
return EventsSettings(**dict(row))
|
return EventsSettings(**dict(row))
|
||||||
return EventsSettings()
|
return EventsSettings()
|
||||||
|
|
@ -225,7 +227,7 @@ async def delete_event(event_id: str) -> None:
|
||||||
|
|
||||||
|
|
||||||
async def get_event_tickets(event_id: str) -> list[Ticket]:
|
async def get_event_tickets(event_id: str) -> list[Ticket]:
|
||||||
rows = await db.fetchall(
|
rows: list[dict] = await db.fetchall(
|
||||||
"SELECT * FROM events.ticket WHERE event = :event",
|
"SELECT * FROM events.ticket WHERE event = :event",
|
||||||
{"event": event_id},
|
{"event": event_id},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -232,14 +232,12 @@ async def m010_add_events_settings(db):
|
||||||
Create the extension settings singleton row used by the admin UI to
|
Create the extension settings singleton row used by the admin UI to
|
||||||
toggle e.g. auto_approve.
|
toggle e.g. auto_approve.
|
||||||
"""
|
"""
|
||||||
await db.execute(
|
await db.execute("""
|
||||||
"""
|
|
||||||
CREATE TABLE IF NOT EXISTS events.settings (
|
CREATE TABLE IF NOT EXISTS events.settings (
|
||||||
id INTEGER PRIMARY KEY DEFAULT 1,
|
id INTEGER PRIMARY KEY DEFAULT 1,
|
||||||
auto_approve BOOLEAN NOT NULL DEFAULT FALSE
|
auto_approve BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
)
|
)
|
||||||
"""
|
""")
|
||||||
)
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"INSERT INTO events.settings (id, auto_approve) "
|
"INSERT INTO events.settings (id, auto_approve) "
|
||||||
"SELECT 1, FALSE WHERE NOT EXISTS "
|
"SELECT 1, FALSE WHERE NOT EXISTS "
|
||||||
|
|
|
||||||
|
|
@ -118,9 +118,7 @@ class CreateTicket(BaseModel):
|
||||||
email = values.get("email")
|
email = values.get("email")
|
||||||
user_id = values.get("user_id")
|
user_id = values.get("user_id")
|
||||||
if not user_id and not (name and email):
|
if not user_id and not (name and email):
|
||||||
raise ValueError(
|
raise ValueError("Either user_id or both name and email must be provided")
|
||||||
"Either user_id or both name and email must be provided"
|
|
||||||
)
|
|
||||||
if user_id and (name or email):
|
if user_id and (name or email):
|
||||||
raise ValueError("Cannot provide both user_id and name/email")
|
raise ValueError("Cannot provide both user_id and name/email")
|
||||||
return values
|
return values
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,7 @@ class NostrClient:
|
||||||
async def connect(self) -> WebSocketApp:
|
async def connect(self) -> WebSocketApp:
|
||||||
relay_endpoint = encrypt_internal_message("relay", urlsafe=True)
|
relay_endpoint = encrypt_internal_message("relay", urlsafe=True)
|
||||||
ws_url = (
|
ws_url = (
|
||||||
f"ws://localhost:{settings.port}"
|
f"ws://localhost:{settings.port}" f"/nostrclient/api/v1/{relay_endpoint}"
|
||||||
f"/nostrclient/api/v1/{relay_endpoint}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info("[EVENTS] Connecting to nostrclient WebSocket...")
|
logger.info("[EVENTS] Connecting to nostrclient WebSocket...")
|
||||||
|
|
@ -58,12 +57,8 @@ class NostrClient:
|
||||||
logger.warning(f"[EVENTS] WebSocket error: {error}")
|
logger.warning(f"[EVENTS] WebSocket error: {error}")
|
||||||
|
|
||||||
def on_close(_, status_code, message):
|
def on_close(_, status_code, message):
|
||||||
logger.warning(
|
logger.warning(f"[EVENTS] WebSocket closed: {status_code} {message}")
|
||||||
f"[EVENTS] WebSocket closed: {status_code} {message}"
|
self.receive_event_queue.put_nowait(ValueError("WebSocket closed"))
|
||||||
)
|
|
||||||
self.receive_event_queue.put_nowait(
|
|
||||||
ValueError("WebSocket closed")
|
|
||||||
)
|
|
||||||
|
|
||||||
ws = WebSocketApp(
|
ws = WebSocketApp(
|
||||||
ws_url,
|
ws_url,
|
||||||
|
|
@ -118,9 +113,7 @@ class NostrClient:
|
||||||
async def subscribe(self, filters: list[dict]):
|
async def subscribe(self, filters: list[dict]):
|
||||||
"""Subscribe to events matching the given filters."""
|
"""Subscribe to events matching the given filters."""
|
||||||
self.subscription_id = "events-" + urlsafe_short_hash()[:32]
|
self.subscription_id = "events-" + urlsafe_short_hash()[:32]
|
||||||
await self.send_req_queue.put(
|
await self.send_req_queue.put(["REQ", self.subscription_id, *filters])
|
||||||
["REQ", self.subscription_id, *filters]
|
|
||||||
)
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"[EVENTS] Subscribed to NIP-52 events "
|
f"[EVENTS] Subscribed to NIP-52 events "
|
||||||
f"(sub: {self.subscription_id[:20]}...)"
|
f"(sub: {self.subscription_id[:20]}...)"
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,15 @@ async def publish_or_delete_nostr_event(event: Event, *, delete: bool = False) -
|
||||||
if not wallet_obj:
|
if not wallet_obj:
|
||||||
return
|
return
|
||||||
account = await get_account(wallet_obj.user)
|
account = await get_account(wallet_obj.user)
|
||||||
if not account or not account.pubkey or not account.prvkey:
|
if not account or not account.pubkey or not account.prvkey: # type: ignore[attr-defined]
|
||||||
return
|
return
|
||||||
|
|
||||||
nostr_event = await publish_event_to_nostr(
|
nostr_event = await publish_event_to_nostr(
|
||||||
nostr_client, event, account.pubkey, account.prvkey, delete=delete
|
nostr_client,
|
||||||
|
event,
|
||||||
|
account.pubkey,
|
||||||
|
account.prvkey, # type: ignore[attr-defined]
|
||||||
|
delete=delete,
|
||||||
)
|
)
|
||||||
if nostr_event and not delete:
|
if nostr_event and not delete:
|
||||||
event.nostr_event_id = nostr_event.id
|
event.nostr_event_id = nostr_event.id
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ def build_nip52_event(event: Event, pubkey: str) -> NostrEvent:
|
||||||
tags.append(["image", event.banner])
|
tags.append(["image", event.banner])
|
||||||
if event.location:
|
if event.location:
|
||||||
tags.append(["location", event.location])
|
tags.append(["location", event.location])
|
||||||
for cat in (event.categories or []):
|
for cat in event.categories or []:
|
||||||
tags.append(["t", cat])
|
tags.append(["t", cat])
|
||||||
|
|
||||||
nostr_event = NostrEvent(
|
nostr_event = NostrEvent(
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ async def _handle_calendar_event(nostr_client: NostrClient, event_data: dict):
|
||||||
return
|
return
|
||||||
|
|
||||||
tags = {t[0]: t[1] for t in event_data.get("tags", []) if len(t) >= 2}
|
tags = {t[0]: t[1] for t in event_data.get("tags", []) if len(t) >= 2}
|
||||||
tag_lists = {}
|
tag_lists: dict[str, list[str]] = {}
|
||||||
for t in event_data.get("tags", []):
|
for t in event_data.get("tags", []):
|
||||||
if len(t) >= 2:
|
if len(t) >= 2:
|
||||||
tag_lists.setdefault(t[0], []).append(t[1])
|
tag_lists.setdefault(t[0], []).append(t[1])
|
||||||
|
|
@ -137,9 +137,11 @@ async def wait_for_nostr_events(nostr_client: NostrClient):
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
# Subscribe to NIP-52 calendar events
|
# Subscribe to NIP-52 calendar events
|
||||||
await nostr_client.subscribe([
|
await nostr_client.subscribe(
|
||||||
{"kinds": [31922, 31923]},
|
[
|
||||||
])
|
{"kinds": [31922, 31923]},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
# Process incoming events
|
# Process incoming events
|
||||||
while True:
|
while True:
|
||||||
|
|
|
||||||
|
|
@ -187,12 +187,7 @@ window.PageEvents = {
|
||||||
},
|
},
|
||||||
saveSettings() {
|
saveSettings() {
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.request(
|
.request('PUT', '/events/api/v1/events/settings', null, this.settings)
|
||||||
'PUT',
|
|
||||||
'/events/api/v1/events/settings',
|
|
||||||
null,
|
|
||||||
this.settings
|
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
Quasar.Notify.create({type: 'positive', message: 'Settings saved'})
|
Quasar.Notify.create({type: 'positive', message: 'Settings saved'})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,13 @@
|
||||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
<q-badge
|
<q-badge
|
||||||
v-if="col.name === 'status'"
|
v-if="col.name === 'status'"
|
||||||
:color="col.value === 'approved' ? 'green' : col.value === 'proposed' ? 'orange' : 'red'"
|
:color="
|
||||||
|
col.value === 'approved'
|
||||||
|
? 'green'
|
||||||
|
: col.value === 'proposed'
|
||||||
|
? 'orange'
|
||||||
|
: 'red'
|
||||||
|
"
|
||||||
:label="col.value"
|
:label="col.value"
|
||||||
></q-badge>
|
></q-badge>
|
||||||
<span v-else v-text="col.value"></span>
|
<span v-else v-text="col.value"></span>
|
||||||
|
|
@ -284,7 +290,13 @@
|
||||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
<q-badge
|
<q-badge
|
||||||
v-if="col.name === 'status'"
|
v-if="col.name === 'status'"
|
||||||
:color="col.value === 'approved' ? 'green' : col.value === 'proposed' ? 'orange' : 'red'"
|
:color="
|
||||||
|
col.value === 'approved'
|
||||||
|
? 'green'
|
||||||
|
: col.value === 'proposed'
|
||||||
|
? 'orange'
|
||||||
|
: 'red'
|
||||||
|
"
|
||||||
:label="col.value"
|
:label="col.value"
|
||||||
></q-badge>
|
></q-badge>
|
||||||
<span v-else v-text="col.value"></span>
|
<span v-else v-text="col.value"></span>
|
||||||
|
|
|
||||||
21
views_api.py
21
views_api.py
|
|
@ -139,9 +139,7 @@ async def api_get_event(event_id: str) -> Event:
|
||||||
|
|
||||||
# closing_date is filled in by create_event (defaults to end_date or
|
# closing_date is filled in by create_event (defaults to end_date or
|
||||||
# start_date) but the field is typed Optional, so guard for the typechecker.
|
# start_date) but the field is typed Optional, so guard for the typechecker.
|
||||||
closing_date = (
|
closing_date = event.closing_date or event.event_end_date or event.event_start_date
|
||||||
event.closing_date or event.event_end_date or event.event_start_date
|
|
||||||
)
|
|
||||||
is_window_open = datetime.now(timezone.utc) < datetime.strptime(
|
is_window_open = datetime.now(timezone.utc) < datetime.strptime(
|
||||||
closing_date, "%Y-%m-%d"
|
closing_date, "%Y-%m-%d"
|
||||||
).replace(tzinfo=timezone.utc)
|
).replace(tzinfo=timezone.utc)
|
||||||
|
|
@ -181,10 +179,7 @@ async def api_event_create(
|
||||||
|
|
||||||
ext_settings = await get_settings()
|
ext_settings = await get_settings()
|
||||||
user_id = wallet.wallet.user
|
user_id = wallet.wallet.user
|
||||||
is_admin = (
|
is_admin = user_id == settings.super_user or user_id in settings.lnbits_admin_users
|
||||||
user_id == settings.super_user
|
|
||||||
or user_id in settings.lnbits_admin_users
|
|
||||||
)
|
|
||||||
if not is_admin and not ext_settings.auto_approve:
|
if not is_admin and not ext_settings.auto_approve:
|
||||||
data.status = "proposed"
|
data.status = "proposed"
|
||||||
|
|
||||||
|
|
@ -208,9 +203,7 @@ async def api_event_update(
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Event does not exist."
|
status_code=HTTPStatus.NOT_FOUND, detail="Event does not exist."
|
||||||
)
|
)
|
||||||
if event.wallet != wallet.wallet.id:
|
if event.wallet != wallet.wallet.id:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your event.")
|
||||||
status_code=HTTPStatus.FORBIDDEN, detail="Not your event."
|
|
||||||
)
|
|
||||||
for k, v in data.dict().items():
|
for k, v in data.dict().items():
|
||||||
setattr(event, k, v)
|
setattr(event, k, v)
|
||||||
event = await update_event(event)
|
event = await update_event(event)
|
||||||
|
|
@ -351,9 +344,7 @@ async def api_get_ticket(ticket_id: str) -> Ticket:
|
||||||
|
|
||||||
|
|
||||||
@tickets_api_router.post("/{event_id}")
|
@tickets_api_router.post("/{event_id}")
|
||||||
async def api_ticket_create(
|
async def api_ticket_create(event_id: str, data: CreateTicket) -> TicketPaymentRequest:
|
||||||
event_id: str, data: CreateTicket
|
|
||||||
) -> TicketPaymentRequest:
|
|
||||||
event = await get_event(event_id)
|
event = await get_event(event_id)
|
||||||
if not event:
|
if not event:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
@ -423,9 +414,7 @@ async def _create_named_ticket(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _create_user_id_ticket(
|
async def _create_user_id_ticket(event: Event, user_id: str) -> TicketPaymentRequest:
|
||||||
event: Event, user_id: str
|
|
||||||
) -> TicketPaymentRequest:
|
|
||||||
price = event.price_per_ticket
|
price = event.price_per_ticket
|
||||||
extra: dict[str, Any] = {"tag": "events", "user_id": user_id}
|
extra: dict[str, Any] = {"tag": "events", "user_id": user_id}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue