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

- 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:
Padreug 2026-05-05 20:04:15 +02:00
commit b428b0dca8
11 changed files with 48 additions and 57 deletions

View file

@ -75,9 +75,7 @@ def events_start():
except Exception as exc:
logger.error(f"[EVENTS] Nostr sync task failed: {exc}")
task3 = create_permanent_unique_task(
"ext_events_nostr_sync", _sync_nostr_events
)
task3 = create_permanent_unique_task("ext_events_nostr_sync", _sync_nostr_events)
scheduled_tasks.append(task3)

12
crud.py
View file

@ -94,7 +94,7 @@ async def update_ticket(ticket: Ticket) -> Ticket:
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",
{"id": payment_hash},
)
@ -107,13 +107,15 @@ async def get_tickets(wallet_ids: str | list[str]) -> list[Ticket]:
if isinstance(wallet_ids, str):
wallet_ids = [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]
async def get_tickets_by_user_id(user_id: str) -> list[Ticket]:
"""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",
{"user_id": user_id},
)
@ -206,7 +208,7 @@ async def get_pending_events() -> list[Event]:
async def get_settings() -> EventsSettings:
"""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:
return EventsSettings(**dict(row))
return EventsSettings()
@ -225,7 +227,7 @@ async def delete_event(event_id: str) -> None:
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",
{"event": event_id},
)

View file

@ -232,14 +232,12 @@ async def m010_add_events_settings(db):
Create the extension settings singleton row used by the admin UI to
toggle e.g. auto_approve.
"""
await db.execute(
"""
await db.execute("""
CREATE TABLE IF NOT EXISTS events.settings (
id INTEGER PRIMARY KEY DEFAULT 1,
auto_approve BOOLEAN NOT NULL DEFAULT FALSE
)
"""
)
""")
await db.execute(
"INSERT INTO events.settings (id, auto_approve) "
"SELECT 1, FALSE WHERE NOT EXISTS "

View file

@ -118,9 +118,7 @@ class CreateTicket(BaseModel):
email = values.get("email")
user_id = values.get("user_id")
if not user_id and not (name and email):
raise ValueError(
"Either user_id or both name and email must be provided"
)
raise ValueError("Either user_id or both name and email must be provided")
if user_id and (name or email):
raise ValueError("Cannot provide both user_id and name/email")
return values

View file

@ -39,8 +39,7 @@ class NostrClient:
async def connect(self) -> WebSocketApp:
relay_endpoint = encrypt_internal_message("relay", urlsafe=True)
ws_url = (
f"ws://localhost:{settings.port}"
f"/nostrclient/api/v1/{relay_endpoint}"
f"ws://localhost:{settings.port}" f"/nostrclient/api/v1/{relay_endpoint}"
)
logger.info("[EVENTS] Connecting to nostrclient WebSocket...")
@ -58,12 +57,8 @@ class NostrClient:
logger.warning(f"[EVENTS] WebSocket error: {error}")
def on_close(_, status_code, message):
logger.warning(
f"[EVENTS] WebSocket closed: {status_code} {message}"
)
self.receive_event_queue.put_nowait(
ValueError("WebSocket closed")
)
logger.warning(f"[EVENTS] WebSocket closed: {status_code} {message}")
self.receive_event_queue.put_nowait(ValueError("WebSocket closed"))
ws = WebSocketApp(
ws_url,
@ -118,9 +113,7 @@ class NostrClient:
async def subscribe(self, filters: list[dict]):
"""Subscribe to events matching the given filters."""
self.subscription_id = "events-" + urlsafe_short_hash()[:32]
await self.send_req_queue.put(
["REQ", self.subscription_id, *filters]
)
await self.send_req_queue.put(["REQ", self.subscription_id, *filters])
logger.info(
f"[EVENTS] Subscribed to NIP-52 events "
f"(sub: {self.subscription_id[:20]}...)"

View file

@ -29,11 +29,15 @@ async def publish_or_delete_nostr_event(event: Event, *, delete: bool = False) -
if not wallet_obj:
return
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
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:
event.nostr_event_id = nostr_event.id

View file

@ -41,7 +41,7 @@ def build_nip52_event(event: Event, pubkey: str) -> NostrEvent:
tags.append(["image", event.banner])
if event.location:
tags.append(["location", event.location])
for cat in (event.categories or []):
for cat in event.categories or []:
tags.append(["t", cat])
nostr_event = NostrEvent(

View file

@ -50,7 +50,7 @@ async def _handle_calendar_event(nostr_client: NostrClient, event_data: dict):
return
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", []):
if len(t) >= 2:
tag_lists.setdefault(t[0], []).append(t[1])
@ -137,9 +137,11 @@ async def wait_for_nostr_events(nostr_client: NostrClient):
while True:
try:
# Subscribe to NIP-52 calendar events
await nostr_client.subscribe([
await nostr_client.subscribe(
[
{"kinds": [31922, 31923]},
])
]
)
# Process incoming events
while True:

View file

@ -187,12 +187,7 @@ window.PageEvents = {
},
saveSettings() {
LNbits.api
.request(
'PUT',
'/events/api/v1/events/settings',
null,
this.settings
)
.request('PUT', '/events/api/v1/events/settings', null, this.settings)
.then(() => {
Quasar.Notify.create({type: 'positive', message: 'Settings saved'})
})

View file

@ -192,7 +192,13 @@
<q-td v-for="col in props.cols" :key="col.name" :props="props">
<q-badge
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"
></q-badge>
<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-badge
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"
></q-badge>
<span v-else v-text="col.value"></span>

View file

@ -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
# start_date) but the field is typed Optional, so guard for the typechecker.
closing_date = (
event.closing_date or event.event_end_date or event.event_start_date
)
closing_date = event.closing_date or event.event_end_date or event.event_start_date
is_window_open = datetime.now(timezone.utc) < datetime.strptime(
closing_date, "%Y-%m-%d"
).replace(tzinfo=timezone.utc)
@ -181,10 +179,7 @@ async def api_event_create(
ext_settings = await get_settings()
user_id = wallet.wallet.user
is_admin = (
user_id == settings.super_user
or user_id in settings.lnbits_admin_users
)
is_admin = user_id == settings.super_user or user_id in settings.lnbits_admin_users
if not is_admin and not ext_settings.auto_approve:
data.status = "proposed"
@ -208,9 +203,7 @@ async def api_event_update(
status_code=HTTPStatus.NOT_FOUND, detail="Event does not exist."
)
if event.wallet != wallet.wallet.id:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not your event."
)
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your event.")
for k, v in data.dict().items():
setattr(event, k, v)
event = await update_event(event)
@ -351,9 +344,7 @@ async def api_get_ticket(ticket_id: str) -> Ticket:
@tickets_api_router.post("/{event_id}")
async def api_ticket_create(
event_id: str, data: CreateTicket
) -> TicketPaymentRequest:
async def api_ticket_create(event_id: str, data: CreateTicket) -> TicketPaymentRequest:
event = await get_event(event_id)
if not event:
raise HTTPException(
@ -423,9 +414,7 @@ async def _create_named_ticket(
)
async def _create_user_id_ticket(
event: Event, user_id: str
) -> TicketPaymentRequest:
async def _create_user_id_ticket(event: Event, user_id: str) -> TicketPaymentRequest:
price = event.price_per_ticket
extra: dict[str, Any] = {"tag": "events", "user_id": user_id}