chore(activities): reroute CreateActivityDialog through TicketApiService.createEvent #83

Merged
padreug merged 1 commit from chore/delete-activities-nostr-service-publish into dev 2026-05-30 15:26:07 +00:00
Owner

Summary

The aiolabs/events extension on its signer-abstraction branch (commit 66076d6) constructs and publishes kind-31922 NIP-52 calendar events server-side via NostrSignerPOST /events/api/v1/events accepts a CreateEventRequest payload, signs through the operator's signer, and broadcasts to configured relays. The webapp no longer needs to sign calendar events client-side.

This is webapp's second bucket-A leg per aiolabs/lnbits#9 phase-1.

Changes

src/modules/activities/services/ActivitiesNostrService.ts

  • Delete publishCalendarEvent() method and its helper imports (finalizeEvent, EventTemplate, buildCalendarTimeEventTags, the local hexToUint8Array function).
  • Keep the subscribe / query paths — the service still reads NIP-52 events off relays for the activity feed (useActivities, useActivityDetail consumers).
  • Update the class docstring to reflect the read-only role and point at the events extension for the publish path.

src/modules/activities/components/CreateActivityDialog.vue

Swap the publish flow:

  • Drop ActivitiesNostrService injection + currentUser.value.prvkey read.
  • Inject TicketApiService instead; pull invoiceKey from currentUser.value.wallets[0].inkey (same pattern as EventsPage.vue:71 handleCreateEvent).
  • Build CreateEventRequest with amount_tickets: 0, price_per_ticket: 0. The events extension treats 0 as "unlimited / not ticketed" per models.py:45-46 (verified in lnbits's log:2026-05-28T22:30Z audit).
  • Fold summary + description into the events extension's info field since CreateEventRequest has no separate summary slot.
  • Update toast on success to "Activity created!" (server publishes to relays via the signer; webapp doesn't sign).

Approval-workflow caveat (documented inline + in this PR description)

Non-admin users on instances where the events extension has auto_approve=false (the default) land in the proposal queue when they submit a new activity. The event isn't published to relays until an admin approves it. Admins (and instances configured with auto_approve=true) publish immediately.

This is the intended new behavior — operators can flip auto_approve=true on the events extension config per-instance if they want the legacy direct-publish moderation posture. The webapp's useApprovalState.ts flow already surfaces proposed events to their owners (fetchMyEvents with all_wallets=true), so a non-admin who submits to the proposal queue still sees their event in the "My events" tab, just labeled appropriately.

Diff stat

 src/modules/activities/components/CreateActivityDialog.vue | 80 ++++++++++++----------
 src/modules/activities/services/ActivitiesNostrService.ts  | 48 +++----------
 2 files changed, 54 insertions(+), 74 deletions(-)

What's NOT in this PR

  • User.prvkey field removal — atomic phase-1 final PR per design doc Q1.2 Option (b). Other webapp sites (chat, forum, nostr-feed, etc.) still read currentUser.value.prvkey; those bucket-B sites intentionally break when the field is removed, which is the design-intended pressure mechanism that forces phase-2 to start.
  • Operator-side approval UI changes — out of scope. If/when we want to expose auto_approve toggle to operators in the webapp settings, that's a separate UX initiative.

Server-side compensation (confirmed live on aio-demo)

Endpoint What it does (signer-abstraction branch)
POST /events/api/v1/events Accepts CreateEventRequest; creates DB row + (if approved status) calls nostr_publisher.publish_event_to_nostr(event, signer) → kind-31922 published via the operator's NostrSigner
Behavior on amount_tickets: 0 First-class semantics (models.py:45-46); nostr_publisher.py correctly omits the tickets_available tag (distinguishes "unlimited" from "sold out")
Approval workflow views_api.py:283-285 — non-admin + auto_approve=falseproposed, no relay publish until admin approves

Test plan

  • vue-tsc --noEmit clean post-change
  • Manual smoke on aio-demo after dev deploy:
    • Open the activities standalone (sortir), click "Create Activity"
    • Fill in title / description / dates / location / categories; leave image blank
    • Submit
    • Admin user: confirm activity appears in public feed immediately AND check relay logs for a kind-31922 from the operator's signed identity
    • Non-admin user: confirm activity goes to proposal queue (visible in My events with proposed status); no relay publish yet; once admin approves, kind-31922 lands on relays
  • Confirm existing activity-feed read paths still work (subscribe + query in useActivities / useActivityDetail are unchanged)
  • Confirm the existing ticketed-event flow via CreateEventDialog is unaffected (different consumer)

Refs

  • log:2026-05-28T22:30Z — lnbits Q2.1 audit verifying ticket-less acceptance + approval-workflow caveat
  • ~/dev/coordination/webapp-design-questions.md Q2.1 (full decision context)
  • aiolabs/events signer-abstraction commit 66076d6 (server-side publish via signer)
  • aiolabs/lnbits cascade tip 861f427c deployed to aio-demo (server-deploy:e2eed9c)
  • Parent initiative: aiolabs/lnbits#9 (signer abstraction / bunker integration)
  • Sibling open / merged PRs:
    • #80 ReactionService dedup — merged
    • #81 ScheduledEventService dedup — merged
    • #82 nostr-metadata-service deletion — open

🤖 Generated with Claude Code

## Summary The `aiolabs/events` extension on its `signer-abstraction` branch (commit `66076d6`) constructs and publishes kind-31922 NIP-52 calendar events server-side via `NostrSigner` — `POST /events/api/v1/events` accepts a `CreateEventRequest` payload, signs through the operator's signer, and broadcasts to configured relays. The webapp no longer needs to sign calendar events client-side. This is webapp's second bucket-A leg per `aiolabs/lnbits#9` phase-1. ## Changes ### `src/modules/activities/services/ActivitiesNostrService.ts` - **Delete** `publishCalendarEvent()` method and its helper imports (`finalizeEvent`, `EventTemplate`, `buildCalendarTimeEventTags`, the local `hexToUint8Array` function). - **Keep** the subscribe / query paths — the service still reads NIP-52 events off relays for the activity feed (`useActivities`, `useActivityDetail` consumers). - **Update** the class docstring to reflect the read-only role and point at the events extension for the publish path. ### `src/modules/activities/components/CreateActivityDialog.vue` Swap the publish flow: - Drop `ActivitiesNostrService` injection + `currentUser.value.prvkey` read. - Inject `TicketApiService` instead; pull `invoiceKey` from `currentUser.value.wallets[0].inkey` (same pattern as `EventsPage.vue:71` `handleCreateEvent`). - Build `CreateEventRequest` with `amount_tickets: 0, price_per_ticket: 0`. The events extension treats 0 as "unlimited / not ticketed" per `models.py:45-46` (verified in lnbits's `log:2026-05-28T22:30Z` audit). - Fold `summary + description` into the events extension's `info` field since `CreateEventRequest` has no separate summary slot. - Update toast on success to `"Activity created!"` (server publishes to relays via the signer; webapp doesn't sign). ## Approval-workflow caveat (documented inline + in this PR description) Non-admin users on instances where the events extension has `auto_approve=false` (the default) land in the proposal queue when they submit a new activity. The event isn't published to relays until an admin approves it. Admins (and instances configured with `auto_approve=true`) publish immediately. This is the **intended new behavior** — operators can flip `auto_approve=true` on the events extension config per-instance if they want the legacy direct-publish moderation posture. The webapp's `useApprovalState.ts` flow already surfaces `proposed` events to their owners (`fetchMyEvents` with `all_wallets=true`), so a non-admin who submits to the proposal queue still sees their event in the "My events" tab, just labeled appropriately. ## Diff stat ``` src/modules/activities/components/CreateActivityDialog.vue | 80 ++++++++++++---------- src/modules/activities/services/ActivitiesNostrService.ts | 48 +++---------- 2 files changed, 54 insertions(+), 74 deletions(-) ``` ## What's NOT in this PR - **`User.prvkey` field removal** — atomic phase-1 final PR per design doc Q1.2 Option (b). Other webapp sites (chat, forum, nostr-feed, etc.) still read `currentUser.value.prvkey`; those bucket-B sites intentionally break when the field is removed, which is the design-intended pressure mechanism that forces phase-2 to start. - **Operator-side approval UI changes** — out of scope. If/when we want to expose `auto_approve` toggle to operators in the webapp settings, that's a separate UX initiative. ## Server-side compensation (confirmed live on `aio-demo`) | Endpoint | What it does (signer-abstraction branch) | |---|---| | `POST /events/api/v1/events` | Accepts `CreateEventRequest`; creates DB row + (if approved status) calls `nostr_publisher.publish_event_to_nostr(event, signer)` → kind-31922 published via the operator's `NostrSigner` | | Behavior on `amount_tickets: 0` | First-class semantics (`models.py:45-46`); `nostr_publisher.py` correctly omits the `tickets_available` tag (distinguishes "unlimited" from "sold out") | | Approval workflow | `views_api.py:283-285` — non-admin + `auto_approve=false` → `proposed`, no relay publish until admin approves | ## Test plan - [x] `vue-tsc --noEmit` clean post-change - [ ] Manual smoke on `aio-demo` after `dev` deploy: - Open the activities standalone (sortir), click "Create Activity" - Fill in title / description / dates / location / categories; leave image blank - Submit - **Admin user**: confirm activity appears in public feed immediately AND check relay logs for a kind-31922 from the operator's signed identity - **Non-admin user**: confirm activity goes to proposal queue (visible in My events with `proposed` status); no relay publish yet; once admin approves, kind-31922 lands on relays - [ ] Confirm existing activity-feed read paths still work (subscribe + query in `useActivities` / `useActivityDetail` are unchanged) - [ ] Confirm the existing ticketed-event flow via `CreateEventDialog` is unaffected (different consumer) ## Refs - `log:2026-05-28T22:30Z` — lnbits Q2.1 audit verifying ticket-less acceptance + approval-workflow caveat - `~/dev/coordination/webapp-design-questions.md` Q2.1 (full decision context) - `aiolabs/events` `signer-abstraction` commit `66076d6` (server-side publish via signer) - `aiolabs/lnbits` cascade tip `861f427c` deployed to `aio-demo` (`server-deploy:e2eed9c`) - Parent initiative: `aiolabs/lnbits#9` (signer abstraction / bunker integration) - Sibling open / merged PRs: - `#80` ReactionService dedup — **merged** - `#81` ScheduledEventService dedup — **merged** - `#82` `nostr-metadata-service` deletion — open 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The aiolabs/events extension on its signer-abstraction branch (commit
66076d6) constructs and publishes kind-31922 NIP-52 calendar events
server-side via NostrSigner — POST /events/api/v1/events accepts a
CreateEventRequest payload, signs through the operator's signer, and
broadcasts to configured relays. The webapp no longer needs to sign
calendar events client-side.

Changes:
- ActivitiesNostrService.ts: delete publishCalendarEvent() and its
  helper imports (finalizeEvent, EventTemplate, buildCalendarTimeEventTags,
  the local hexToUint8Array). The subscribe / query paths stay — the
  service still reads NIP-52 events off relays for the activity feed.
  Docstring updated to reflect the read-only role and point at the
  events extension for the publish path.
- CreateActivityDialog.vue: swap the publish flow.
  - Drop ActivitiesNostrService injection + currentUser.value.prvkey read.
  - Inject TicketApiService instead; pull invoiceKey from
    currentUser.value.wallets[0].inkey (same pattern as EventsPage.vue
    handleCreateEvent).
  - Build CreateEventRequest with amount_tickets: 0, price_per_ticket: 0
    (events extension treats 0 as unlimited/not-ticketed per
    models.py:45-46 per lnbits 22:30Z audit).
  - Fold summary + description into the events extension's `info`
    field since CreateEventRequest has no separate summary slot.
  - Update toast on success to "Activity created!" (server publishes
    to relays via the signer, not the webapp).

Approval-workflow caveat documented inline in the submit handler:
non-admin users on instances with auto_approve=false (the default)
land in the proposal queue and don't publish to relays until an admin
approves. Admins / auto_approve=true instances publish immediately.
This is the intended new behavior — operators can flip auto_approve
on the events extension config per-instance if they want the legacy
direct-publish moderation posture.

This is webapp's second bucket-A leg per aiolabs/lnbits#9 phase-1.
The remaining `currentUser.value.prvkey` reads stay until the
atomic User.prvkey field-removal PR (Q1.2 Option (b)).

Refs:
- log:2026-05-28T22:30Z (lnbits Q2.1 audit verifying ticket-less
  acceptance + approval-workflow caveat)
- ~/dev/coordination/webapp-design-questions.md Q2.1
- aiolabs/events signer-abstraction commit 66076d6 (the server-side
  publish path)
- aiolabs/lnbits cascade tip 861f427c deployed to aio-demo

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
padreug force-pushed chore/delete-activities-nostr-service-publish from 26a661891a to 531de18ad7 2026-05-29 19:51:00 +00:00 Compare
padreug changed target branch from dev to chore/delete-nostr-metadata-service 2026-05-29 19:51:11 +00:00
padreug force-pushed chore/delete-activities-nostr-service-publish from 531de18ad7 to ba916a4c37 2026-05-30 05:50:13 +00:00 Compare
padreug force-pushed chore/delete-activities-nostr-service-publish from ba916a4c37 to 141e59da82 2026-05-30 06:04:42 +00:00 Compare
padreug changed target branch from chore/delete-nostr-metadata-service to dev 2026-05-30 15:25:48 +00:00
padreug deleted branch chore/delete-activities-nostr-service-publish 2026-05-30 15:26:07 +00:00
Sign in to join this conversation.
No description provided.